diff options
| author | Chris Dent <chdent@redhat.com> | 2015-03-12 13:31:06 +0000 |
|---|---|---|
| committer | Chris Dent <chdent@redhat.com> | 2015-03-13 12:01:04 +0000 |
| commit | d9d8cc614eccaffefdfd4aa4f536745f0aabc5e3 (patch) | |
| tree | 8ab56748455532a93e06fe98edddad60a423db15 /ceilometerclient/tests/unit | |
| parent | e727d22bf1fc4ff613699560717e4aa460f60b73 (diff) | |
| download | python-ceilometerclient-d9d8cc614eccaffefdfd4aa4f536745f0aabc5e3.tar.gz | |
Move unit tests into their own subdirectory
This is in preparation of moving functional tests currently in
tempest in-tree.
No surprises in this change:
* move files
* update the local imports
Same number of tests run, all still pass.
Change-Id: Ibfeac44421c5f36740b1377868f5ec469b2893f5
Diffstat (limited to 'ceilometerclient/tests/unit')
27 files changed, 4523 insertions, 0 deletions
diff --git a/ceilometerclient/tests/unit/__init__.py b/ceilometerclient/tests/unit/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/ceilometerclient/tests/unit/__init__.py diff --git a/ceilometerclient/tests/unit/fakes.py b/ceilometerclient/tests/unit/fakes.py new file mode 100644 index 0000000..10d8d8c --- /dev/null +++ b/ceilometerclient/tests/unit/fakes.py @@ -0,0 +1,64 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from keystoneclient.v2_0 import client as ksclient + + +def script_keystone_client(): + ksclient.Client(auth_url='http://no.where', + insecure=False, + password='password', + tenant_id='', + tenant_name='tenant_name', + username='username').AndReturn(FakeKeystone('abcd1234')) + + +def fake_headers(): + return {'X-Auth-Token': 'abcd1234', + 'Content-Type': 'application/json', + 'Accept': 'application/json', + 'User-Agent': 'python-ceilometerclient'} + + +class FakeServiceCatalog(object): + @staticmethod + def url_for(endpoint_type, service_type): + return 'http://192.168.1.5:8004/v1/f14b41234' + + +class FakeKeystone(object): + service_catalog = FakeServiceCatalog() + + def __init__(self, auth_token): + self.auth_token = auth_token + + +class FakeHTTPResponse(object): + + version = 1.1 + + def __init__(self, status, reason, headers, body): + self.headers = headers + self.body = body + self.status = status + self.reason = reason + + def getheader(self, name, default=None): + return self.headers.get(name, default) + + def getheaders(self): + return self.headers.items() + + def read(self, amt=None): + b = self.body + self.body = None + return b diff --git a/ceilometerclient/tests/unit/test_client.py b/ceilometerclient/tests/unit/test_client.py new file mode 100644 index 0000000..d582d93 --- /dev/null +++ b/ceilometerclient/tests/unit/test_client.py @@ -0,0 +1,158 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import types + +import mock + +from ceilometerclient import client +from ceilometerclient.tests.unit import fakes +from ceilometerclient.tests.unit import utils +from ceilometerclient.v1 import client as v1client +from ceilometerclient.v2 import client as v2client + +FAKE_ENV = { + 'username': 'username', + 'password': 'password', + 'tenant_name': 'tenant_name', + 'auth_url': 'http://no.where', + 'ceilometer_url': 'http://no.where', + 'auth_plugin': 'fake_auth', + 'token': '1234', + 'user_domain_name': 'default', + 'project_domain_name': 'default', +} + + +class ClientTest(utils.BaseTestCase): + + @staticmethod + def create_client(env, api_version=2, endpoint=None, exclude=[]): + env = dict((k, v) for k, v in env.items() + if k not in exclude) + + return client.get_client(api_version, **env) + + def test_client_version(self): + c1 = self.create_client(env=FAKE_ENV, api_version=1) + self.assertIsInstance(c1, v1client.Client) + + c2 = self.create_client(env=FAKE_ENV, api_version=2) + self.assertIsInstance(c2, v2client.Client) + + def test_client_auth_lambda(self): + env = FAKE_ENV.copy() + env['token'] = lambda: env['token'] + self.assertIsInstance(env['token'], + types.FunctionType) + c2 = self.create_client(env) + self.assertIsInstance(c2, v2client.Client) + + def test_client_auth_non_lambda(self): + env = FAKE_ENV.copy() + env['token'] = "1234" + self.assertIsInstance(env['token'], str) + c2 = self.create_client(env) + self.assertIsInstance(c2, v2client.Client) + + @mock.patch('keystoneclient.v2_0.client', fakes.FakeKeystone) + def test_client_without_auth_plugin(self): + env = FAKE_ENV.copy() + del env['auth_plugin'] + c = self.create_client(env, api_version=2, endpoint='fake_endpoint') + self.assertIsInstance(c.auth_plugin, client.AuthPlugin) + + def test_client_without_auth_plugin_keystone_v3(self): + env = FAKE_ENV.copy() + del env['auth_plugin'] + expected = { + 'username': 'username', + 'endpoint': 'http://no.where', + 'tenant_name': 'tenant_name', + 'service_type': None, + 'token': '1234', + 'endpoint_type': None, + 'auth_url': 'http://no.where', + 'tenant_id': None, + 'cacert': None, + 'password': 'password', + 'user_domain_name': 'default', + 'user_domain_id': None, + 'project_domain_name': 'default', + 'project_domain_id': None, + } + with mock.patch('ceilometerclient.client.AuthPlugin') as auth_plugin: + self.create_client(env, api_version=2, endpoint='http://no.where') + auth_plugin.assert_called_with(**expected) + + def test_client_with_auth_plugin(self): + c = self.create_client(FAKE_ENV, api_version=2) + self.assertIsInstance(c.auth_plugin, str) + + def test_v2_client_timeout_invalid_value(self): + env = FAKE_ENV.copy() + env['timeout'] = 'abc' + self.assertRaises(ValueError, self.create_client, env) + env['timeout'] = '1.5' + self.assertRaises(ValueError, self.create_client, env) + + def _test_v2_client_timeout_integer(self, timeout, expected_value): + env = FAKE_ENV.copy() + env['timeout'] = timeout + expected = { + 'auth_plugin': 'fake_auth', + 'timeout': expected_value, + 'original_ip': None, + 'http': None, + 'region_name': None, + 'verify': True, + 'timings': None, + 'keyring_saver': None, + 'cert': None, + 'endpoint_type': None, + 'user_agent': None, + 'debug': None, + } + cls = 'ceilometerclient.openstack.common.apiclient.client.HTTPClient' + with mock.patch(cls) as mocked: + self.create_client(env) + mocked.assert_called_with(**expected) + + def test_v2_client_timeout_zero(self): + self._test_v2_client_timeout_integer(0, None) + + def test_v2_client_timeout_valid_value(self): + self._test_v2_client_timeout_integer(30, 30) + + def test_v2_client_cacert_in_verify(self): + env = FAKE_ENV.copy() + env['cacert'] = '/path/to/cacert' + client = self.create_client(env) + self.assertEqual('/path/to/cacert', client.client.verify) + + def test_v2_client_certfile_and_keyfile(self): + env = FAKE_ENV.copy() + env['cert_file'] = '/path/to/cert' + env['key_file'] = '/path/to/keycert' + client = self.create_client(env) + self.assertEqual(('/path/to/cert', '/path/to/keycert'), + client.client.cert) + + +class ClientTest2(ClientTest): + @staticmethod + def create_client(env, api_version=2, endpoint=None, exclude=[]): + env = dict((k, v) for k, v in env.items() + if k not in exclude) + + # Run the same tests with direct instantiation of the Client + return client.Client(api_version, endpoint, **env) diff --git a/ceilometerclient/tests/unit/test_exc.py b/ceilometerclient/tests/unit/test_exc.py new file mode 100644 index 0000000..db8df32 --- /dev/null +++ b/ceilometerclient/tests/unit/test_exc.py @@ -0,0 +1,71 @@ +# Copyright 2013 eNovance +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import json + +from ceilometerclient import exc +from ceilometerclient.tests.unit import utils + +HTTPEXCEPTIONS = {'HTTPBadRequest': exc.HTTPBadRequest, + 'HTTPUnauthorized': exc.HTTPUnauthorized, + 'HTTPForbidden': exc.HTTPForbidden, + 'HTTPNotFound': exc.HTTPNotFound, + 'HTTPMethodNotAllowed': exc.HTTPMethodNotAllowed, + 'HTTPConflict': exc.HTTPConflict, + 'HTTPOverLimit': exc.HTTPOverLimit, + 'HTTPInternalServerError': exc.HTTPInternalServerError, + 'HTTPNotImplemented': exc.HTTPNotImplemented, + 'HTTPBadGateway': exc.HTTPBadGateway, + 'HTTPServiceUnavailable': exc.HTTPServiceUnavailable} + + +class HTTPExceptionsTest(utils.BaseTestCase): + def test_str_no_details(self): + for k, v in HTTPEXCEPTIONS.items(): + exception = v() + ret_str = k + " (HTTP " + str(exception.code) + ")" + self.assertEqual(ret_str, str(exception)) + + def test_str_no_json(self): + for k, v in HTTPEXCEPTIONS.items(): + exception = v(details="foo") + ret_str = k + " (HTTP " + str(exception.code) + ")" + self.assertEqual(ret_str, str(exception)) + + def test_str_no_error_message(self): + for k, v in HTTPEXCEPTIONS.items(): + exception = v(details=json.dumps({})) + ret_str = k + " (HTTP " + str(exception.code) + ")" + self.assertEqual(ret_str, str(exception)) + + def test_str_no_faultstring(self): + for k, v in HTTPEXCEPTIONS.items(): + exception = v( + details=json.dumps({"error_message": {"foo": "bar"}})) + ret_str = k + " (HTTP " + str(exception.code) + ")" + self.assertEqual(ret_str, str(exception)) + + def test_str_error_message_unknown_format(self): + for k, v in HTTPEXCEPTIONS.items(): + exception = v(details=json.dumps({"error_message": "oops"})) + ret_str = k + " (HTTP " + str(exception.code) + ")" + self.assertEqual(ret_str, str(exception)) + + def test_str_faultstring(self): + for k, v in HTTPEXCEPTIONS.items(): + exception = v(details=json.dumps( + {"error_message": {"faultstring": "oops"}})) + ret_str = k + " (HTTP " + str(exception.code) + ") ERROR oops" + self.assertEqual(ret_str, str(exception)) diff --git a/ceilometerclient/tests/unit/test_shell.py b/ceilometerclient/tests/unit/test_shell.py new file mode 100644 index 0000000..7d06ab7 --- /dev/null +++ b/ceilometerclient/tests/unit/test_shell.py @@ -0,0 +1,225 @@ +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import re +import sys + +import fixtures +from keystoneclient import session as ks_session +import mock +import six +from testtools import matchers + +from ceilometerclient import exc +from ceilometerclient.openstack.common.apiclient import client as api_client +from ceilometerclient import shell as ceilometer_shell +from ceilometerclient.tests.unit import utils +from ceilometerclient.v2 import client as v2client + +FAKE_V2_ENV = {'OS_USERNAME': 'username', + 'OS_PASSWORD': 'password', + 'OS_TENANT_NAME': 'tenant_name', + 'OS_AUTH_URL': 'http://localhost:5000/v2.0'} + +FAKE_V3_ENV = {'OS_USERNAME': 'username', + 'OS_PASSWORD': 'password', + 'OS_USER_DOMAIN_NAME': 'domain_name', + 'OS_PROJECT_ID': '1234567890', + 'OS_AUTH_URL': 'http://localhost:5000/v3'} + + +class ShellTestBase(utils.BaseTestCase): + + # Patch os.environ to avoid required auth info. + def make_env(self, env_version, exclude=None): + env = dict((k, v) for k, v in env_version.items() if k != exclude) + self.useFixture(fixtures.MonkeyPatch('os.environ', env)) + + +class ShellHelpTest(ShellTestBase): + RE_OPTIONS = re.DOTALL | re.MULTILINE + + @mock.patch('sys.stdout', new=six.StringIO()) + @mock.patch.object(ks_session, 'Session', mock.MagicMock()) + @mock.patch.object(v2client.client.HTTPClient, + 'client_request', mock.MagicMock()) + def shell(self, argstr): + try: + _shell = ceilometer_shell.CeilometerShell() + _shell.main(argstr.split()) + except SystemExit: + exc_type, exc_value, exc_traceback = sys.exc_info() + self.assertEqual(exc_value.code, 0) + + return sys.stdout.getvalue() + + def test_help_unknown_command(self): + self.assertRaises(exc.CommandError, self.shell, 'help foofoo') + + def test_help(self): + required = [ + '.*?^usage: ceilometer', + '.*?^See "ceilometer help COMMAND" ' + 'for help on a specific command', + ] + for argstr in ['--help', 'help']: + help_text = self.shell(argstr) + for r in required: + self.assertThat(help_text, + matchers.MatchesRegex(r, + self.RE_OPTIONS)) + + def test_help_on_subcommand(self): + required = [ + '.*?^usage: ceilometer meter-list', + ".*?^List the user's meter", + ] + argstrings = [ + 'help meter-list', + ] + for argstr in argstrings: + help_text = self.shell(argstr) + for r in required: + self.assertThat(help_text, + matchers.MatchesRegex(r, self.RE_OPTIONS)) + + +class ShellKeystoneV2Test(ShellTestBase): + + @mock.patch.object(ks_session, 'Session') + def test_debug_switch_raises_error(self, mock_ksclient): + mock_ksclient.side_effect = exc.HTTPUnauthorized + self.make_env(FAKE_V2_ENV) + args = ['--debug', 'event-list'] + self.assertRaises(exc.CommandError, ceilometer_shell.main, args) + + @mock.patch.object(ks_session, 'Session') + def test_dash_d_switch_raises_error(self, mock_ksclient): + mock_ksclient.side_effect = exc.CommandError("FAIL") + self.make_env(FAKE_V2_ENV) + args = ['-d', 'event-list'] + self.assertRaises(exc.CommandError, ceilometer_shell.main, args) + + @mock.patch('sys.stderr') + @mock.patch.object(ks_session, 'Session') + def test_no_debug_switch_no_raises_errors(self, mock_ksclient, __): + mock_ksclient.side_effect = exc.HTTPUnauthorized("FAIL") + self.make_env(FAKE_V2_ENV) + args = ['event-list'] + self.assertRaises(SystemExit, ceilometer_shell.main, args) + + +class ShellKeystoneV3Test(ShellTestBase): + + @mock.patch.object(ks_session, 'Session') + def test_debug_switch_raises_error(self, mock_ksclient): + mock_ksclient.side_effect = exc.HTTPUnauthorized + self.make_env(FAKE_V3_ENV) + args = ['--debug', 'event-list'] + self.assertRaises(exc.CommandError, ceilometer_shell.main, args) + + @mock.patch.object(ks_session, 'Session') + def test_dash_d_switch_raises_error(self, mock_ksclient): + mock_ksclient.side_effect = exc.CommandError("FAIL") + self.make_env(FAKE_V3_ENV) + args = ['-d', 'event-list'] + self.assertRaises(exc.CommandError, ceilometer_shell.main, args) + + @mock.patch('sys.stderr') + @mock.patch.object(ks_session, 'Session') + def test_no_debug_switch_no_raises_errors(self, mock_ksclient, __): + mock_ksclient.side_effect = exc.HTTPUnauthorized("FAIL") + self.make_env(FAKE_V3_ENV) + args = ['event-list'] + self.assertRaises(SystemExit, ceilometer_shell.main, args) + + +class ShellTimeoutTest(ShellTestBase): + + @mock.patch('sys.stderr', new=six.StringIO()) + def _test_timeout(self, timeout, expected_msg): + args = ['--timeout', timeout, 'alarm-list'] + self.assertRaises(SystemExit, ceilometer_shell.main, args) + self.assertEqual(expected_msg, sys.stderr.getvalue().splitlines()[-1]) + + def test_timeout_invalid_value(self): + expected_msg = ('ceilometer: error: argument --timeout: ' + 'abc must be an integer') + self._test_timeout('abc', expected_msg) + + def test_timeout_negative_value(self): + expected_msg = ('ceilometer: error: argument --timeout: ' + '-1 must be greater than 0') + self._test_timeout('-1', expected_msg) + + def test_timeout_float_value(self): + expected_msg = ('ceilometer: error: argument --timeout: ' + '1.5 must be an integer') + self._test_timeout('1.5', expected_msg) + + def test_timeout_zero(self): + expected_msg = ('ceilometer: error: argument --timeout: ' + '0 must be greater than 0') + self._test_timeout('0', expected_msg) + + +class ShellInsecureTest(ShellTestBase): + + @mock.patch.object(api_client, 'HTTPClient') + def test_insecure_true_ceilometer(self, mocked_client): + self.make_env(FAKE_V2_ENV) + args = ['--debug', '--os-insecure', 'true', 'alarm-list'] + self.assertIsNone(ceilometer_shell.main(args)) + args, kwargs = mocked_client.call_args + self.assertEqual(False, kwargs.get('verify')) + + @mock.patch.object(ks_session, 'Session') + def test_insecure_true_keystone(self, mocked_session): + mocked_session.side_effect = exc.HTTPUnauthorized("FAIL") + self.make_env(FAKE_V2_ENV) + args = ['--debug', '--os-insecure', 'true', 'alarm-list'] + self.assertRaises(exc.CommandError, ceilometer_shell.main, args) + mocked_session.assert_called_with(verify=False, cert='') + + @mock.patch.object(api_client, 'HTTPClient') + def test_insecure_false_ceilometer(self, mocked_client): + self.make_env(FAKE_V2_ENV) + args = ['--debug', '--os-insecure', 'false', 'alarm-list'] + self.assertIsNone(ceilometer_shell.main(args)) + args, kwargs = mocked_client.call_args + self.assertEqual(True, kwargs.get('verify')) + + @mock.patch.object(ks_session, 'Session') + def test_insecure_false_keystone(self, mocked_session): + mocked_session.side_effect = exc.HTTPUnauthorized("FAIL") + self.make_env(FAKE_V2_ENV) + args = ['--debug', '--os-insecure', 'false', 'alarm-list'] + self.assertRaises(exc.CommandError, ceilometer_shell.main, args) + mocked_session.assert_called_with(verify=True, cert='') + + +class ShellEndpointTest(ShellTestBase): + + @mock.patch('ceilometerclient.v2.client.Client') + def _test_endpoint_and_token(self, token_name, endpoint_name, mocked): + args = ['--debug', token_name, 'fake-token', + endpoint_name, 'http://fake-url', 'alarm-list'] + self.assertEqual(None, ceilometer_shell.main(args)) + args, kwargs = mocked.call_args + self.assertEqual('http://fake-url', kwargs.get('endpoint')) + self.assertEqual('fake-token', kwargs.get('token')) + + def test_endpoint_and_token(self): + self._test_endpoint_and_token('--os-auth-token', '--ceilometer-url') + self._test_endpoint_and_token('--os-auth-token', '--os-endpoint') + self._test_endpoint_and_token('--os-token', '--ceilometer-url') + self._test_endpoint_and_token('--os-token', '--os-endpoint') diff --git a/ceilometerclient/tests/unit/test_utils.py b/ceilometerclient/tests/unit/test_utils.py new file mode 100644 index 0000000..00bd416 --- /dev/null +++ b/ceilometerclient/tests/unit/test_utils.py @@ -0,0 +1,265 @@ +# Copyright 2013 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import itertools + +import mock +import six + +from ceilometerclient.common import utils +from ceilometerclient.tests.unit import utils as test_utils + + +class UtilsTest(test_utils.BaseTestCase): + + def test_prettytable(self): + class Struct(object): + def __init__(self, **entries): + self.__dict__.update(entries) + + # test that the prettytable output is wellformatted (left-aligned) + with mock.patch('sys.stdout', new=six.StringIO()) as stdout: + utils.print_dict({'K': 'k', 'Key': 'Value'}) + self.assertEqual('''\ ++----------+-------+ +| Property | Value | ++----------+-------+ +| K | k | +| Key | Value | ++----------+-------+ +''', stdout.getvalue()) + + with mock.patch('sys.stdout', new=six.StringIO()) as stdout: + utils.print_dict({'alarm_id': '262567fd-d79a-4bbb-a9d0-59d879b6', + 'name': u'\u6d4b\u8bd5', + 'description': u'\u6d4b\u8bd5', + 'state': 'insufficient data', + 'repeat_actions': 'False', + 'type': 'threshold', + 'threshold': '1.0', + 'statistic': 'avg', + 'time_constraints': '[{name: c1,' + '\\n description: test,' + '\\n start: 0 18 * * *,' + '\\n duration: 1,' + '\\n timezone: US}]'}, + wrap=72) + expected = u'''\ ++------------------+----------------------------------+ +| Property | Value | ++------------------+----------------------------------+ +| alarm_id | 262567fd-d79a-4bbb-a9d0-59d879b6 | +| description | \u6d4b\u8bd5 | +| name | \u6d4b\u8bd5 | +| repeat_actions | False | +| state | insufficient data | +| statistic | avg | +| threshold | 1.0 | +| time_constraints | [{name: c1, | +| | description: test, | +| | start: 0 18 * * *, | +| | duration: 1, | +| | timezone: US}] | +| type | threshold | ++------------------+----------------------------------+ +''' + # py2 prints str type, py3 prints unicode type + if six.PY2: + expected = expected.encode('utf-8') + self.assertEqual(expected, stdout.getvalue()) + + def test_print_list(self): + class Foo(object): + def __init__(self, one, two, three): + self.one = one + self.two = two + self.three = three + + foo_list = [ + Foo(10, 'a', 'B'), + Foo(8, 'c', 'c'), + Foo(12, '0', 'Z')] + + def do_print_list(sortby): + with mock.patch('sys.stdout', new=six.StringIO()) as stdout: + utils.print_list(foo_list, + ['one', 'two', 'three'], + ['1st', '2nd', '3rd'], + {'one': lambda o: o.one * 10}, + sortby) + return stdout.getvalue() + + printed = do_print_list(None) + self.assertEqual(printed, '''\ ++-----+-----+-----+ +| 1st | 2nd | 3rd | ++-----+-----+-----+ +| 100 | a | B | +| 80 | c | c | +| 120 | 0 | Z | ++-----+-----+-----+ +''') + + printed = do_print_list(0) + self.assertEqual(printed, '''\ ++-----+-----+-----+ +| 1st | 2nd | 3rd | ++-----+-----+-----+ +| 80 | c | c | +| 100 | a | B | +| 120 | 0 | Z | ++-----+-----+-----+ +''') + + printed = do_print_list(1) + self.assertEqual(printed, '''\ ++-----+-----+-----+ +| 1st | 2nd | 3rd | ++-----+-----+-----+ +| 120 | 0 | Z | +| 100 | a | B | +| 80 | c | c | ++-----+-----+-----+ +''') + + def test_args_array_to_dict(self): + my_args = { + 'matching_metadata': ['metadata.key=metadata_value'], + 'other': 'value' + } + cleaned_dict = utils.args_array_to_dict(my_args, + "matching_metadata") + self.assertEqual(cleaned_dict, { + 'matching_metadata': {'metadata.key': 'metadata_value'}, + 'other': 'value' + }) + + def test_args_array_to_list_of_dicts(self): + starts = ['0 11 * * *', '"0 11 * * *"', '\'0 11 * * *\''] + timezones = [None, 'US/Eastern', '"US/Eastern"', '\'US/Eastern\''] + descs = [None, 'de sc', '"de sc"', '\'de sc\''] + for start, tz, desc in itertools.product(starts, timezones, descs): + my_args = { + 'time_constraints': ['name=const1;start=%s;duration=1' + % start], + 'other': 'value' + } + expected = { + 'time_constraints': [dict(name='const1', + start='0 11 * * *', + duration='1')], + 'other': 'value' + } + if tz: + my_args['time_constraints'][0] += ';timezone=%s' % tz + expected['time_constraints'][0]['timezone'] = 'US/Eastern' + if desc: + my_args['time_constraints'][0] += ';description=%s' % desc + expected['time_constraints'][0]['description'] = 'de sc' + + cleaned = utils.args_array_to_list_of_dicts(my_args, + 'time_constraints') + self.assertEqual(expected, cleaned) + + def test_key_with_slash_to_nested_dict(self): + my_args = { + 'combination_rule/alarm_ids': ['id1', 'id2'], + 'combination_rule/operator': 'and', + 'threshold_rule/threshold': 400, + 'threshold_rule/statictic': 'avg', + 'threshold_rule/comparison_operator': 'or', + } + nested_dict = utils.key_with_slash_to_nested_dict(my_args) + self.assertEqual(nested_dict, { + 'combination_rule': {'alarm_ids': ['id1', 'id2'], + 'operator': 'and'}, + 'threshold_rule': {'threshold': 400, + 'statictic': 'avg', + 'comparison_operator': 'or'}, + }) + + def test_arg(self): + @utils.arg(help="not_required_no_default.") + def not_required_no_default(): + pass + _, args = not_required_no_default.__dict__['arguments'][0] + self.assertEqual(args['help'], "not_required_no_default.") + + @utils.arg(required=True, help="required_no_default.") + def required_no_default(): + pass + _, args = required_no_default.__dict__['arguments'][0] + self.assertEqual(args['help'], "required_no_default. Required.") + + @utils.arg(default=42, help="not_required_default.") + def not_required_default(): + pass + _, args = not_required_default.__dict__['arguments'][0] + self.assertEqual(args['help'], "not_required_default. Defaults to 42.") + + def test_merge_nested_dict(self): + dest = {'key': 'value', + 'nested': {'key2': 'value2', + 'key3': 'value3', + 'nested2': {'key': 'value', + 'some': 'thing'}}} + source = {'key': 'modified', + 'nested': {'key3': 'modified3', + 'nested2': {'key5': 'value5'}}} + utils.merge_nested_dict(dest, source, depth=1) + + self.assertEqual(dest, {'key': 'modified', + 'nested': {'key2': 'value2', + 'key3': 'modified3', + 'nested2': {'key5': 'value5'}}}) + + def test_merge_nested_dict_no_depth(self): + dest = {'key': 'value', + 'nested': {'key2': 'value2', + 'key3': 'value3', + 'nested2': {'key': 'value', + 'some': 'thing'}}} + source = {'key': 'modified', + 'nested': {'key3': 'modified3', + 'nested2': {'key5': 'value5'}}} + utils.merge_nested_dict(dest, source) + + self.assertEqual(dest, {'key': 'modified', + 'nested': {'key3': 'modified3', + 'nested2': {'key5': 'value5'}}}) + + @mock.patch('prettytable.PrettyTable') + def test_format_nested_list_of_dict(self, pt_mock): + actual_rows = [] + + def mock_add_row(row): + actual_rows.append(row) + + table = mock.Mock() + table.add_row = mock_add_row + table.get_string.return_value = "the table" + + test_data = [ + {'column_1': 'value_11', 'column_2': 'value_21'}, + {'column_1': 'value_12', 'column_2': 'value_22'} + + ] + columns = ['column_1', 'column_2'] + pt_mock.return_value = table + + rval = utils.format_nested_list_of_dict(test_data, columns) + self.assertEqual("the table", rval) + self.assertEqual([['value_11', 'value_21'], ['value_12', 'value_22']], + actual_rows) diff --git a/ceilometerclient/tests/unit/utils.py b/ceilometerclient/tests/unit/utils.py new file mode 100644 index 0000000..57bc276 --- /dev/null +++ b/ceilometerclient/tests/unit/utils.py @@ -0,0 +1,24 @@ +# Copyright 2012 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import fixtures +import testtools + + +class BaseTestCase(testtools.TestCase): + + def setUp(self): + super(BaseTestCase, self).setUp() + self.useFixture(fixtures.FakeLogger()) diff --git a/ceilometerclient/tests/unit/v1/__init__.py b/ceilometerclient/tests/unit/v1/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/ceilometerclient/tests/unit/v1/__init__.py diff --git a/ceilometerclient/tests/unit/v1/test_meters.py b/ceilometerclient/tests/unit/v1/test_meters.py new file mode 100644 index 0000000..afd9a1b --- /dev/null +++ b/ceilometerclient/tests/unit/v1/test_meters.py @@ -0,0 +1,162 @@ +# Copyright 2012 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from ceilometerclient.openstack.common.apiclient import client +from ceilometerclient.openstack.common.apiclient import fake_client +from ceilometerclient.tests.unit import utils +import ceilometerclient.v1.meters + + +fixtures = { + '/v1/meters': { + 'GET': ( + {}, + {'meters': [ + { + 'resource_id': 'a', + 'project_id': 'dig_the_ditch', + 'user_id': 'freddy', + 'name': 'this', + 'type': 'counter', + }, + { + 'resource_id': 'b', + 'project_id': 'dig_the_ditch', + 'user_id': 'joey', + 'name': 'this', + 'type': 'counter', + }, + ]}, + ), + }, + '/v1/users/joey/meters': { + 'GET': ( + {}, + {'meters': [ + { + 'resource_id': 'b', + 'project_id': 'dig_the_ditch', + 'user_id': 'joey', + 'name': 'this', + 'type': 'counter', + }, + ]}, + ), + }, + '/v1/projects/dig_the_ditch/meters': { + 'GET': ( + {}, + {'meters': [ + { + 'resource_id': 'b', + 'project_id': 'dig_the_ditch', + 'user_id': 'joey', + 'name': 'this', + 'type': 'counter', + }, + ]}, + ), + }, + '/v1/sources/openstack/meters': { + 'GET': ( + {}, + {'meters': [ + { + 'resource_id': 'b', + 'project_id': 'dig_the_ditch', + 'user_id': 'joey', + 'name': 'this', + 'type': 'counter', + }, + { + 'resource_id': 'q', + 'project_id': 'dig_the_trench', + 'user_id': 'joey', + 'name': 'this', + 'type': 'counter', + }, + ]}, + ), + }, + '/v1/meters?metadata.zxc_id=foo': { + 'GET': ( + {}, + {'meters': [ + { + 'resource_id': 'b', + 'project_id': 'dig_the_ditch', + 'user_id': 'joey', + 'name': 'this', + 'type': 'counter', + }, + ]}, + ), + }, +} + + +class MeterManagerTest(utils.BaseTestCase): + + def setUp(self): + super(MeterManagerTest, self).setUp() + self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) + self.api = client.BaseClient(self.http_client) + self.mgr = ceilometerclient.v1.meters.MeterManager(self.api) + + def test_list_all(self): + resources = list(self.mgr.list()) + expect = [ + 'GET', '/v1/meters' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(resources), 2) + self.assertEqual(resources[0].resource_id, 'a') + self.assertEqual(resources[1].resource_id, 'b') + + def test_list_by_source(self): + resources = list(self.mgr.list(source='openstack')) + expect = [ + 'GET', '/v1/sources/openstack/meters' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(resources), 2) + self.assertEqual(resources[0].resource_id, 'b') + self.assertEqual(resources[1].resource_id, 'q') + + def test_list_by_user(self): + resources = list(self.mgr.list(user_id='joey')) + expect = [ + 'GET', '/v1/users/joey/meters' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(resources), 1) + self.assertEqual(resources[0].resource_id, 'b') + + def test_list_by_project(self): + resources = list(self.mgr.list(project_id='dig_the_ditch')) + expect = [ + 'GET', '/v1/projects/dig_the_ditch/meters' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(resources), 1) + self.assertEqual(resources[0].resource_id, 'b') + + def test_list_by_metaquery(self): + resources = list(self.mgr.list(metaquery='metadata.zxc_id=foo')) + expect = [ + 'GET', '/v1/meters?metadata.zxc_id=foo' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(resources), 1) + self.assertEqual(resources[0].resource_id, 'b') diff --git a/ceilometerclient/tests/unit/v1/test_projects.py b/ceilometerclient/tests/unit/v1/test_projects.py new file mode 100644 index 0000000..7b31bce --- /dev/null +++ b/ceilometerclient/tests/unit/v1/test_projects.py @@ -0,0 +1,65 @@ +# Copyright 2012 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from ceilometerclient.openstack.common.apiclient import client +from ceilometerclient.openstack.common.apiclient import fake_client +from ceilometerclient.tests.unit import utils +import ceilometerclient.v1.meters + + +fixtures = { + '/v1/projects': { + 'GET': ( + {}, + {'projects': [ + 'a', + 'b', + ]}, + ), + }, + '/v1/sources/source_b/projects': { + 'GET': ( + {}, + {'projects': ['b']}, + ), + }, +} + + +class ProjectManagerTest(utils.BaseTestCase): + + def setUp(self): + super(ProjectManagerTest, self).setUp() + self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) + self.api = client.BaseClient(self.http_client) + self.mgr = ceilometerclient.v1.meters.ProjectManager(self.api) + + def test_list_all(self): + projects = list(self.mgr.list()) + expect = [ + 'GET', '/v1/projects' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(projects), 2) + self.assertEqual(projects[0].project_id, 'a') + self.assertEqual(projects[1].project_id, 'b') + + def test_list_by_source(self): + projects = list(self.mgr.list(source='source_b')) + expect = [ + 'GET', '/v1/sources/source_b/projects' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(projects), 1) + self.assertEqual(projects[0].project_id, 'b') diff --git a/ceilometerclient/tests/unit/v1/test_resources.py b/ceilometerclient/tests/unit/v1/test_resources.py new file mode 100644 index 0000000..cfc4f6c --- /dev/null +++ b/ceilometerclient/tests/unit/v1/test_resources.py @@ -0,0 +1,161 @@ +# Copyright 2012 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from ceilometerclient.openstack.common.apiclient import client +from ceilometerclient.openstack.common.apiclient import fake_client +from ceilometerclient.tests.unit import utils +import ceilometerclient.v1.meters + + +fixtures = { + '/v1/resources': { + 'GET': ( + {}, + {'resources': [ + { + 'resource_id': 'a', + 'project_id': 'project_bla', + 'user_id': 'freddy', + 'timestamp': 'now', + 'meter': ['this', 'that'], + 'metadata': {'zxc_id': 'bla'}, + }, + { + 'resource_id': 'b', + 'project_id': 'dig_the_ditch', + 'user_id': 'joey', + 'timestamp': 'now', + 'meter': ['this', 'that'], + 'metadata': {'zxc_id': 'foo'}, + }, + ]}, + ), + }, + '/v1/users/joey/resources': { + 'GET': ( + {}, + {'resources': [ + { + 'resource_id': 'b', + 'project_id': 'dig_the_ditch', + 'user_id': 'joey', + 'timestamp': 'now', + 'meter': ['this', 'that'], + 'metadata': {'zxc_id': 'foo'}, + }, + ]}, + ), + }, + '/v1/resources?metadata.zxc_id=foo': { + 'GET': ( + {}, + {'resources': [ + { + 'resource_id': 'b', + 'project_id': 'dig_the_ditch', + 'user_id': 'joey', + 'timestamp': 'now', + 'meter': ['this', 'that'], + 'metadata': {'zxc_id': 'foo'}, + }, + ]}, + ), + }, + '/v1/projects/project_bla/resources': { + 'GET': ( + {}, + {'resources': [ + { + 'resource_id': 'a', + 'project_id': 'project_bla', + 'user_id': 'freddy', + 'timestamp': 'now', + 'meter': ['this', 'that'], + 'metadata': {'zxc_id': 'bla'}, + }, + ]}, + ), + }, + '/v1/resources?start_timestamp=now&end_timestamp=now': { + 'GET': ( + {}, + {'resources': [ + { + 'resource_id': 'b', + 'project_id': 'dig_the_ditch', + 'user_id': 'joey', + 'timestamp': 'now', + 'meter': ['this', 'that'], + 'metadata': {'zxc_id': 'foo'}, + }, + ]}, + ), + }, +} + + +class ResourceManagerTest(utils.BaseTestCase): + + def setUp(self): + super(ResourceManagerTest, self).setUp() + self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) + self.api = client.BaseClient(self.http_client) + self.mgr = ceilometerclient.v1.meters.ResourceManager(self.api) + + def test_list_all(self): + resources = list(self.mgr.list()) + expect = [ + 'GET', '/v1/resources' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(resources), 2) + self.assertEqual(resources[0].resource_id, 'a') + self.assertEqual(resources[1].resource_id, 'b') + + def test_list_by_user(self): + resources = list(self.mgr.list(user_id='joey')) + expect = [ + 'GET', '/v1/users/joey/resources' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(resources), 1) + self.assertEqual(resources[0].resource_id, 'b') + + def test_list_by_metaquery(self): + resources = list(self.mgr.list(metaquery='metadata.zxc_id=foo')) + expect = [ + 'GET', '/v1/resources?metadata.zxc_id=foo' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(resources), 1) + self.assertEqual(resources[0].resource_id, 'b') + + def test_list_by_project(self): + resources = list(self.mgr.list(project_id='project_bla')) + expect = [ + 'GET', '/v1/projects/project_bla/resources' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(resources), 1) + self.assertEqual(resources[0].resource_id, 'a') + + def test_list_by_timestamp(self): + resources = list(self.mgr.list(start_timestamp='now', + end_timestamp='now')) + expect = [ + 'GET', '/v1/resources?start_timestamp=now&end_timestamp=now' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(resources), 1) + self.assertEqual(resources[0].resource_id, 'b') diff --git a/ceilometerclient/tests/unit/v1/test_samples.py b/ceilometerclient/tests/unit/v1/test_samples.py new file mode 100644 index 0000000..0f1a029 --- /dev/null +++ b/ceilometerclient/tests/unit/v1/test_samples.py @@ -0,0 +1,196 @@ +# Copyright 2012 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from ceilometerclient.openstack.common.apiclient import client +from ceilometerclient.openstack.common.apiclient import fake_client +from ceilometerclient.tests.unit import utils +import ceilometerclient.v1.meters + + +fixtures = { + '/v1/users/freddy/meters/balls': { + 'GET': ( + {}, + {'events': [ + { + 'resource_id': 'inst-0045', + 'project_id': 'melbourne_open', + 'user_id': 'freddy', + 'name': 'tennis', + 'type': 'counter', + 'unit': 'balls', + 'volume': 3, + 'timestamp': None, + 'resource_metadata': None, + }, + ]}, + ), + }, + '/v1/sources/openstack/meters/this': { + 'GET': ( + {}, + {'events': [ + { + 'resource_id': 'b', + 'project_id': 'dig_the_ditch', + 'user_id': 'joey', + 'name': 'this', + 'type': 'counter', + 'unit': 'b', + 'volume': 45, + 'timestamp': None, + 'resource_metadata': None, + }, + ]}, + ), + }, + '/v1/projects/dig_the_ditch/meters/meters': { + 'GET': ( + {}, + {'events': [ + { + 'resource_id': 'b', + 'project_id': 'dig_the_ditch', + 'user_id': 'joey', + 'name': 'meters', + 'type': 'counter', + 'unit': 'meters', + 'volume': 345, + 'timestamp': None, + 'resource_metadata': None, + }, + ]}, + ), + }, + '/v1/meters?metadata.zxc_id=foo': { + 'GET': ( + {}, + {'events': [ + { + 'resource_id': 'b', + 'project_id': 'dig_the_ditch', + 'user_id': 'joey', + 'name': 'this', + 'type': 'counter', + 'unit': 'meters', + 'volume': 98, + 'timestamp': None, + 'resource_metadata': {'zxc_id': 'foo'}, + }, + ]}, + ), + }, + '/v1/users/freddy/meters/balls?start_timestamp=now&end_timestamp=now': { + 'GET': ( + {}, + {'events': [ + { + 'resource_id': 'inst-0045', + 'project_id': 'melbourne_open', + 'user_id': 'freddy', + 'name': 'tennis', + 'type': 'counter', + 'unit': 'balls', + 'volume': 3, + 'timestamp': 'now', + 'resource_metadata': None, + }, + + ]}, + ), + }, + '/v1/meters': { + 'GET': ( + {}, + {'meters': []}, + ), + }, +} + + +class SampleManagerTest(utils.BaseTestCase): + + def setUp(self): + super(SampleManagerTest, self).setUp() + self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) + self.api = client.BaseClient(self.http_client) + self.mgr = ceilometerclient.v1.meters.SampleManager(self.api) + + def test_list_all(self): + samples = list(self.mgr.list(counter_name=None)) + expect = [ + 'GET', '/v1/meters' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(samples), 0) + + def test_list_by_source(self): + samples = list(self.mgr.list(source='openstack', + counter_name='this')) + expect = [ + 'GET', '/v1/sources/openstack/meters/this' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(samples), 1) + self.assertEqual(samples[0].resource_id, 'b') + + def test_list_by_user(self): + samples = list(self.mgr.list(user_id='freddy', + counter_name='balls')) + expect = [ + 'GET', '/v1/users/freddy/meters/balls' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(samples), 1) + self.assertEqual(samples[0].project_id, 'melbourne_open') + self.assertEqual(samples[0].user_id, 'freddy') + self.assertEqual(samples[0].volume, 3) + + def test_list_by_project(self): + samples = list(self.mgr.list(project_id='dig_the_ditch', + counter_name='meters')) + expect = [ + 'GET', '/v1/projects/dig_the_ditch/meters/meters' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(samples), 1) + self.assertEqual(samples[0].project_id, 'dig_the_ditch') + self.assertEqual(samples[0].volume, 345) + self.assertEqual(samples[0].unit, 'meters') + + def test_list_by_metaquery(self): + samples = list(self.mgr.list(metaquery='metadata.zxc_id=foo', + counter_name='this')) + expect = [ + 'GET', '/v1/meters?metadata.zxc_id=foo' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(samples), 1) + self.assertEqual(samples[0].resource_metadata['zxc_id'], 'foo') + + def test_list_by_timestamp(self): + samples = list(self.mgr.list(user_id='freddy', + counter_name='balls', + start_timestamp='now', + end_timestamp='now')) + expect = [ + 'GET', + '/v1/users/freddy/meters/balls?' + + 'start_timestamp=now&end_timestamp=now' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(samples), 1) + self.assertEqual(samples[0].project_id, 'melbourne_open') + self.assertEqual(samples[0].user_id, 'freddy') + self.assertEqual(samples[0].volume, 3) diff --git a/ceilometerclient/tests/unit/v1/test_users.py b/ceilometerclient/tests/unit/v1/test_users.py new file mode 100644 index 0000000..e8e98a1 --- /dev/null +++ b/ceilometerclient/tests/unit/v1/test_users.py @@ -0,0 +1,65 @@ +# Copyright 2012 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from ceilometerclient.openstack.common.apiclient import client +from ceilometerclient.openstack.common.apiclient import fake_client +from ceilometerclient.tests.unit import utils +import ceilometerclient.v1.meters + + +fixtures = { + '/v1/users': { + 'GET': ( + {}, + {'users': [ + 'a', + 'b', + ]}, + ), + }, + '/v1/sources/source_b/users': { + 'GET': ( + {}, + {'users': ['b']}, + ), + }, +} + + +class UserManagerTest(utils.BaseTestCase): + + def setUp(self): + super(UserManagerTest, self).setUp() + self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) + self.api = client.BaseClient(self.http_client) + self.mgr = ceilometerclient.v1.meters.UserManager(self.api) + + def test_list_all(self): + users = list(self.mgr.list()) + expect = [ + 'GET', '/v1/users' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(users), 2) + self.assertEqual(users[0].user_id, 'a') + self.assertEqual(users[1].user_id, 'b') + + def test_list_by_source(self): + users = list(self.mgr.list(source='source_b')) + expect = [ + 'GET', '/v1/sources/source_b/users' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(users), 1) + self.assertEqual(users[0].user_id, 'b') diff --git a/ceilometerclient/tests/unit/v2/__init__.py b/ceilometerclient/tests/unit/v2/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/ceilometerclient/tests/unit/v2/__init__.py diff --git a/ceilometerclient/tests/unit/v2/test_alarms.py b/ceilometerclient/tests/unit/v2/test_alarms.py new file mode 100644 index 0000000..4239d2f --- /dev/null +++ b/ceilometerclient/tests/unit/v2/test_alarms.py @@ -0,0 +1,540 @@ +# +# Copyright 2013 Red Hat, Inc +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import copy + +import six +from six.moves import xrange # noqa +import testtools + +from ceilometerclient import exc +from ceilometerclient.openstack.common.apiclient import client +from ceilometerclient.openstack.common.apiclient import fake_client +from ceilometerclient.v2 import alarms + +AN_ALARM = {u'alarm_actions': [u'http://site:8000/alarm'], + u'ok_actions': [u'http://site:8000/ok'], + u'description': u'An alarm', + u'type': u'threshold', + u'severity': 'low', + u'threshold_rule': { + u'meter_name': u'storage.objects', + u'query': [{u'field': u'key_name', + u'op': u'eq', + u'value': u'key_value'}], + u'evaluation_periods': 2, + u'period': 240.0, + u'statistic': u'avg', + u'threshold': 200.0, + u'comparison_operator': 'gt'}, + u'time_constraints': [ + { + u'name': u'cons1', + u'description': u'desc1', + u'start': u'0 11 * * *', + u'duration': 300, + u'timezone': u''}, + { + u'name': u'cons2', + u'description': u'desc2', + u'start': u'0 23 * * *', + u'duration': 600, + u'timezone': ''}], + u'timestamp': u'2013-05-09T13:41:23.085000', + u'enabled': True, + u'alarm_id': u'alarm-id', + u'state': u'ok', + u'insufficient_data_actions': [u'http://site:8000/nodata'], + u'user_id': u'user-id', + u'project_id': u'project-id', + u'state_timestamp': u'2013-05-09T13:41:23.085000', + u'repeat_actions': False, + u'name': 'SwiftObjectAlarm'} +CREATE_ALARM = copy.deepcopy(AN_ALARM) +del CREATE_ALARM['timestamp'] +del CREATE_ALARM['state_timestamp'] +del CREATE_ALARM['alarm_id'] +CREATE_ALARM_WITHOUT_TC = copy.deepcopy(CREATE_ALARM) +del CREATE_ALARM_WITHOUT_TC['time_constraints'] +DELTA_ALARM = {u'alarm_actions': ['url1', 'url2']} +DELTA_ALARM_RULE = {u'comparison_operator': u'lt', + u'threshold': 42.1, + u'meter_name': u'foobar', + u'query': [{u'field': u'key_name', + u'op': u'eq', + u'value': u'key_value'}]} +DELTA_ALARM_TC = [{u'name': u'cons1', + u'duration': 500}] +DELTA_ALARM['time_constraints'] = DELTA_ALARM_TC +UPDATED_ALARM = copy.deepcopy(AN_ALARM) +UPDATED_ALARM.update(DELTA_ALARM) +UPDATED_ALARM['threshold_rule'].update(DELTA_ALARM_RULE) +DELTA_ALARM['remove_time_constraints'] = 'cons2' +UPDATED_ALARM['time_constraints'] = [{u'name': u'cons1', + u'description': u'desc1', + u'start': u'0 11 * * *', + u'duration': 500, + u'timezone': u''}] +DELTA_ALARM['threshold_rule'] = DELTA_ALARM_RULE +UPDATE_ALARM = copy.deepcopy(UPDATED_ALARM) +UPDATE_ALARM['remove_time_constraints'] = 'cons2' +del UPDATE_ALARM['user_id'] +del UPDATE_ALARM['project_id'] +del UPDATE_ALARM['name'] +del UPDATE_ALARM['alarm_id'] +del UPDATE_ALARM['timestamp'] +del UPDATE_ALARM['state_timestamp'] + +AN_LEGACY_ALARM = {u'alarm_actions': [u'http://site:8000/alarm'], + u'ok_actions': [u'http://site:8000/ok'], + u'description': u'An alarm', + u'matching_metadata': {u'key_name': u'key_value'}, + u'evaluation_periods': 2, + u'timestamp': u'2013-05-09T13:41:23.085000', + u'enabled': True, + u'meter_name': u'storage.objects', + u'period': 240.0, + u'alarm_id': u'alarm-id', + u'state': u'ok', + u'severity': u'low', + u'insufficient_data_actions': [u'http://site:8000/nodata'], + u'statistic': u'avg', + u'threshold': 200.0, + u'user_id': u'user-id', + u'project_id': u'project-id', + u'state_timestamp': u'2013-05-09T13:41:23.085000', + u'comparison_operator': 'gt', + u'repeat_actions': False, + u'name': 'SwiftObjectAlarm'} +CREATE_LEGACY_ALARM = copy.deepcopy(AN_LEGACY_ALARM) +del CREATE_LEGACY_ALARM['timestamp'] +del CREATE_LEGACY_ALARM['state_timestamp'] +del CREATE_LEGACY_ALARM['alarm_id'] +DELTA_LEGACY_ALARM = {u'alarm_actions': ['url1', 'url2'], + u'comparison_operator': u'lt', + u'meter_name': u'foobar', + u'threshold': 42.1} +DELTA_LEGACY_ALARM['time_constraints'] = [{u'name': u'cons1', + u'duration': 500}] +DELTA_LEGACY_ALARM['remove_time_constraints'] = 'cons2' +UPDATED_LEGACY_ALARM = copy.deepcopy(AN_LEGACY_ALARM) +UPDATED_LEGACY_ALARM.update(DELTA_LEGACY_ALARM) +UPDATE_LEGACY_ALARM = copy.deepcopy(UPDATED_LEGACY_ALARM) +del UPDATE_LEGACY_ALARM['user_id'] +del UPDATE_LEGACY_ALARM['project_id'] +del UPDATE_LEGACY_ALARM['name'] +del UPDATE_LEGACY_ALARM['alarm_id'] +del UPDATE_LEGACY_ALARM['timestamp'] +del UPDATE_LEGACY_ALARM['state_timestamp'] + +FULL_DETAIL = ('{"alarm_actions": [], ' + '"user_id": "8185aa72421a4fd396d4122cba50e1b5", ' + '"name": "scombo", ' + '"timestamp": "2013-10-03T08:58:33.647912", ' + '"enabled": true, ' + '"state_timestamp": "2013-10-03T08:58:33.647912", ' + '"rule": {"operator": "or", "alarm_ids": ' + '["062cc907-3a9f-4867-ab3b-fa83212b39f7"]}, ' + '"alarm_id": "alarm-id, ' + '"state": "insufficient data", ' + '"insufficient_data_actions": [], ' + '"repeat_actions": false, ' + '"ok_actions": [], ' + '"project_id": "57d04f24d0824b78b1ea9bcecedbda8f", ' + '"type": "combination", ' + '"description": "Combined state of alarms ' + '062cc907-3a9f-4867-ab3b-fa83212b39f7"}') +ALARM_HISTORY = [{'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', + 'user_id': '8185aa72421a4fd396d4122cba50e1b5', + 'event_id': 'c74a8611-6553-4764-a860-c15a6aabb5d0', + 'timestamp': '2013-10-03T08:59:28.326000', + 'detail': '{"state": "alarm"}', + 'alarm_id': 'alarm-id', + 'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', + 'type': 'state transition'}, + {'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', + 'user_id': '8185aa72421a4fd396d4122cba50e1b5', + 'event_id': 'c74a8611-6553-4764-a860-c15a6aabb5d0', + 'timestamp': '2013-10-03T08:59:28.326000', + 'detail': '{"description": "combination of one"}', + 'alarm_id': 'alarm-id', + 'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', + 'type': 'rule change'}, + {'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', + 'user_id': '8185aa72421a4fd396d4122cba50e1b5', + 'event_id': '4fd7df9e-190d-4471-8884-dc5a33d5d4bb', + 'timestamp': '2013-10-03T08:58:33.647000', + 'detail': FULL_DETAIL, + 'alarm_id': 'alarm-id', + 'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', + 'type': 'creation'}] + +fixtures = { + '/v2/alarms': + { + 'GET': ( + {}, + [AN_ALARM], + ), + 'POST': ( + {}, + CREATE_ALARM, + ), + }, + '/v2/alarms/alarm-id': + { + 'GET': ( + {}, + AN_ALARM, + ), + 'PUT': ( + {}, + UPDATED_ALARM, + ), + 'DELETE': ( + {}, + None, + ), + }, + '/v2/alarms/unk-alarm-id': + { + 'GET': ( + {}, + None, + ), + 'PUT': ( + {}, + None, + ), + }, + '/v2/alarms/alarm-id/state': + { + 'PUT': ( + {}, + {'alarm': 'alarm'} + ), + 'GET': ( + {}, + {'alarm': 'alarm'} + ), + + }, + '/v2/alarms?q.field=project_id&q.field=name&q.op=&q.op=' + '&q.type=&q.type=&q.value=project-id&q.value=SwiftObjectAlarm': + { + 'GET': ( + {}, + [AN_ALARM], + ), + }, + '/v2/alarms/victim-id': + { + 'DELETE': ( + {}, + None, + ), + }, + '/v2/alarms/alarm-id/history': + { + 'GET': ( + {}, + ALARM_HISTORY, + ), + }, + '/v2/alarms/alarm-id/history?q.field=timestamp&q.op=&q.type=&q.value=NOW': + { + 'GET': ( + {}, + ALARM_HISTORY, + ), + }, +} + + +class AlarmManagerTest(testtools.TestCase): + + def setUp(self): + super(AlarmManagerTest, self).setUp() + self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) + self.api = client.BaseClient(self.http_client) + self.mgr = alarms.AlarmManager(self.api) + + def test_list_all(self): + alarms = list(self.mgr.list()) + expect = [ + 'GET', '/v2/alarms' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(alarms), 1) + self.assertEqual(alarms[0].alarm_id, 'alarm-id') + + def test_list_with_query(self): + alarms = list(self.mgr.list(q=[{"field": "project_id", + "value": "project-id"}, + {"field": "name", + "value": "SwiftObjectAlarm"}])) + expect = [ + 'GET', + '/v2/alarms?q.field=project_id&q.field=name&q.op=&q.op=' + '&q.type=&q.type=&q.value=project-id&q.value=SwiftObjectAlarm', + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(alarms), 1) + self.assertEqual(alarms[0].alarm_id, 'alarm-id') + + def test_get(self): + alarm = self.mgr.get(alarm_id='alarm-id') + expect = [ + 'GET', '/v2/alarms/alarm-id' + ] + self.http_client.assert_called(*expect) + self.assertIsNotNone(alarm) + self.assertEqual(alarm.alarm_id, 'alarm-id') + self.assertEqual(alarm.rule, alarm.threshold_rule) + + def test_create(self): + alarm = self.mgr.create(**CREATE_ALARM) + expect = [ + 'POST', '/v2/alarms' + ] + self.http_client.assert_called(*expect, body=CREATE_ALARM) + self.assertIsNotNone(alarm) + + def test_update(self): + alarm = self.mgr.update(alarm_id='alarm-id', **UPDATE_ALARM) + expect_get = [ + 'GET', '/v2/alarms/alarm-id' + ] + expect_put = [ + 'PUT', '/v2/alarms/alarm-id', UPDATED_ALARM + ] + self.http_client.assert_called(*expect_get, pos=0) + self.http_client.assert_called(*expect_put, pos=1) + self.assertIsNotNone(alarm) + self.assertEqual(alarm.alarm_id, 'alarm-id') + for (key, value) in six.iteritems(UPDATED_ALARM): + self.assertEqual(getattr(alarm, key), value) + + def test_update_delta(self): + alarm = self.mgr.update(alarm_id='alarm-id', **DELTA_ALARM) + expect_get = [ + 'GET', '/v2/alarms/alarm-id' + ] + expect_put = [ + 'PUT', '/v2/alarms/alarm-id', UPDATED_ALARM + ] + self.http_client.assert_called(*expect_get, pos=0) + self.http_client.assert_called(*expect_put, pos=1) + self.assertIsNotNone(alarm) + self.assertEqual(alarm.alarm_id, 'alarm-id') + for (key, value) in six.iteritems(UPDATED_ALARM): + self.assertEqual(getattr(alarm, key), value) + + def test_set_state(self): + state = self.mgr.set_state(alarm_id='alarm-id', state='alarm') + expect = [ + 'PUT', '/v2/alarms/alarm-id/state' + ] + self.http_client.assert_called(*expect, body='alarm') + self.assertEqual(state, {'alarm': 'alarm'}) + + def test_get_state(self): + state = self.mgr.get_state(alarm_id='alarm-id') + expect = [ + 'GET', '/v2/alarms/alarm-id/state' + ] + self.http_client.assert_called(*expect) + self.assertEqual(state, {'alarm': 'alarm'}) + + def test_delete(self): + deleted = self.mgr.delete(alarm_id='victim-id') + expect = [ + 'DELETE', '/v2/alarms/victim-id' + ] + self.http_client.assert_called(*expect) + self.assertIsNone(deleted) + + def test_get_from_alarm_class(self): + alarm = self.mgr.get(alarm_id='alarm-id') + self.assertIsNotNone(alarm) + alarm.get() + expect = [ + 'GET', '/v2/alarms/alarm-id' + ] + self.http_client.assert_called(*expect, pos=0) + self.http_client.assert_called(*expect, pos=1) + self.assertEqual('alarm-id', alarm.alarm_id) + self.assertEqual(alarm.threshold_rule, alarm.rule) + + def test_get_state_from_alarm_class(self): + alarm = self.mgr.get(alarm_id='alarm-id') + self.assertIsNotNone(alarm) + state = alarm.get_state() + expect_get_1 = [ + 'GET', '/v2/alarms/alarm-id' + ] + expect_get_2 = [ + 'GET', '/v2/alarms/alarm-id/state' + ] + self.http_client.assert_called(*expect_get_1, pos=0) + self.http_client.assert_called(*expect_get_2, pos=1) + self.assertEqual('alarm', state) + + def test_update_missing(self): + alarm = None + try: + alarm = self.mgr.update(alarm_id='unk-alarm-id', **UPDATE_ALARM) + except exc.CommandError: + pass + self.assertEqual(alarm, None) + + def test_delete_from_alarm_class(self): + alarm = self.mgr.get(alarm_id='alarm-id') + self.assertIsNotNone(alarm) + deleted = alarm.delete() + expect_get = [ + 'GET', '/v2/alarms/alarm-id' + ] + expect_delete = [ + 'DELETE', '/v2/alarms/alarm-id' + ] + self.http_client.assert_called(*expect_get, pos=0) + self.http_client.assert_called(*expect_delete, pos=1) + self.assertIsNone(deleted) + + def _do_test_get_history(self, q, url): + history = self.mgr.get_history(q=q, alarm_id='alarm-id') + expect = ['GET', url] + self.http_client.assert_called(*expect) + for i in xrange(len(history)): + change = history[i] + self.assertIsInstance(change, alarms.AlarmChange) + for k, v in six.iteritems(ALARM_HISTORY[i]): + self.assertEqual(getattr(change, k), v) + + def test_get_all_history(self): + url = '/v2/alarms/alarm-id/history' + self._do_test_get_history(None, url) + + def test_get_constrained_history(self): + q = [dict(field='timestamp', value='NOW')] + url = ('/v2/alarms/alarm-id/history?q.field=timestamp' + '&q.op=&q.type=&q.value=NOW') + self._do_test_get_history(q, url) + + +class AlarmLegacyManagerTest(testtools.TestCase): + + def setUp(self): + super(AlarmLegacyManagerTest, self).setUp() + self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) + self.api = client.BaseClient(self.http_client) + self.mgr = alarms.AlarmManager(self.api) + + def test_create(self): + alarm = self.mgr.create(**CREATE_LEGACY_ALARM) + expect = [ + 'POST', '/v2/alarms', CREATE_ALARM_WITHOUT_TC, + ] + self.http_client.assert_called(*expect) + self.assertIsNotNone(alarm) + + def test_create_counter_name(self): + create = {} + create.update(CREATE_LEGACY_ALARM) + create['counter_name'] = CREATE_LEGACY_ALARM['meter_name'] + del create['meter_name'] + alarm = self.mgr.create(**create) + expect = [ + 'POST', '/v2/alarms', CREATE_ALARM_WITHOUT_TC, + ] + self.http_client.assert_called(*expect) + self.assertIsNotNone(alarm) + + def test_update(self): + alarm = self.mgr.update(alarm_id='alarm-id', **DELTA_LEGACY_ALARM) + expect_put = [ + 'PUT', '/v2/alarms/alarm-id', UPDATED_ALARM + ] + self.http_client.assert_called(*expect_put) + self.assertIsNotNone(alarm) + self.assertEqual(alarm.alarm_id, 'alarm-id') + for (key, value) in six.iteritems(UPDATED_ALARM): + self.assertEqual(getattr(alarm, key), value) + + def test_update_counter_name(self): + updated = {} + updated.update(UPDATE_LEGACY_ALARM) + updated['counter_name'] = UPDATED_LEGACY_ALARM['meter_name'] + del updated['meter_name'] + alarm = self.mgr.update(alarm_id='alarm-id', **updated) + expect_put = [ + 'PUT', '/v2/alarms/alarm-id', UPDATED_ALARM + ] + self.http_client.assert_called(*expect_put) + self.assertIsNotNone(alarm) + self.assertEqual(alarm.alarm_id, 'alarm-id') + for (key, value) in six.iteritems(UPDATED_ALARM): + self.assertEqual(getattr(alarm, key), value) + + +class AlarmTimeConstraintTest(testtools.TestCase): + + def setUp(self): + super(AlarmTimeConstraintTest, self).setUp() + self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) + self.api = client.BaseClient(self.http_client) + self.mgr = alarms.AlarmManager(self.api) + + def test_add_new(self): + new_constraint = dict(name='cons3', + start='0 0 * * *', + duration=500) + kwargs = dict(time_constraints=[new_constraint]) + self.mgr.update(alarm_id='alarm-id', **kwargs) + body = copy.deepcopy(AN_ALARM) + body[u'time_constraints'] = \ + AN_ALARM[u'time_constraints'] + [new_constraint] + expect = [ + 'PUT', '/v2/alarms/alarm-id', body + ] + self.http_client.assert_called(*expect) + + def test_update_existing(self): + updated_constraint = dict(name='cons2', + duration=500) + kwargs = dict(time_constraints=[updated_constraint]) + self.mgr.update(alarm_id='alarm-id', **kwargs) + body = copy.deepcopy(AN_ALARM) + body[u'time_constraints'][1] = dict(name='cons2', + description='desc2', + start='0 23 * * *', + duration=500, + timezone='') + + expect = [ + 'PUT', '/v2/alarms/alarm-id', body + ] + self.http_client.assert_called(*expect) + + def test_remove(self): + kwargs = dict(remove_time_constraints=['cons2']) + self.mgr.update(alarm_id='alarm-id', **kwargs) + body = copy.deepcopy(AN_ALARM) + body[u'time_constraints'] = AN_ALARM[u'time_constraints'][:1] + expect = [ + 'PUT', '/v2/alarms/alarm-id', body + ] + self.http_client.assert_called(*expect) diff --git a/ceilometerclient/tests/unit/v2/test_event_types.py b/ceilometerclient/tests/unit/v2/test_event_types.py new file mode 100644 index 0000000..7dc1508 --- /dev/null +++ b/ceilometerclient/tests/unit/v2/test_event_types.py @@ -0,0 +1,50 @@ +# Copyright 2014 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + + +from ceilometerclient.openstack.common.apiclient import client +from ceilometerclient.openstack.common.apiclient import fake_client +from ceilometerclient.tests.unit import utils +import ceilometerclient.v2.event_types + + +fixtures = { + '/v2/event_types/': { + 'GET': ( + {}, + ['Foo', 'Bar', 'Sna', 'Fu'] + ), + } +} + + +class EventTypesManagerTest(utils.BaseTestCase): + + def setUp(self): + super(EventTypesManagerTest, self).setUp() + self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) + self.api = client.BaseClient(self.http_client) + self.mgr = ceilometerclient.v2.event_types.EventTypeManager(self.api) + + def test_list(self): + event_types = list(self.mgr.list()) + expect = [ + 'GET', '/v2/event_types/' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(event_types), 4) + self.assertEqual(event_types[0].event_type, "Foo") + self.assertEqual(event_types[1].event_type, "Bar") + self.assertEqual(event_types[2].event_type, "Sna") + self.assertEqual(event_types[3].event_type, "Fu") diff --git a/ceilometerclient/tests/unit/v2/test_events.py b/ceilometerclient/tests/unit/v2/test_events.py new file mode 100644 index 0000000..6cb1c1e --- /dev/null +++ b/ceilometerclient/tests/unit/v2/test_events.py @@ -0,0 +1,198 @@ +# Copyright 2014 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from ceilometerclient.openstack.common.apiclient import client +from ceilometerclient.openstack.common.apiclient import fake_client +from ceilometerclient.tests.unit import utils +import ceilometerclient.v2.events + + +fixtures = { + '/v2/events': { + 'GET': ( + {}, + [ + { + 'message_id': '1', + 'event_type': 'Foo', + 'generated': '1970-01-01T00:00:00', + 'traits': {'trait_A': 'abc'}, + }, + { + 'message_id': '2', + 'event_type': 'Foo', + 'generated': '1970-01-01T00:00:00', + 'traits': {'trait_A': 'def'}, + }, + { + 'message_id': '3', + 'event_type': 'Bar', + 'generated': '1970-01-01T00:00:00', + 'traits': {'trait_B': 'bartrait'}, + }, + ] + ), + }, + '/v2/events?q.field=hostname&q.op=&q.type=string&q.value=localhost': + { + 'GET': ( + {}, + [ + { + 'message_id': '1', + 'event_type': 'Foo', + 'generated': '1970-01-01T00:00:00', + 'traits': {'trait_A': 'abc', + 'hostname': 'localhost'}, + }, + { + 'message_id': '2', + 'event_type': 'Foo', + 'generated': '1970-01-01T00:00:00', + 'traits': {'trait_A': 'def', + 'hostname': 'localhost'}, + } + ] + ), + }, + '/v2/events?q.field=hostname&q.op=&q.type=&q.value=foreignhost': + { + 'GET': ( + {}, + [ + { + 'message_id': '1', + 'event_type': 'Foo', + 'generated': '1970-01-01T00:00:00', + 'traits': {'trait_A': 'abc', + 'hostname': 'foreignhost'}, + }, + { + 'message_id': '2', + 'event_type': 'Foo', + 'generated': '1970-01-01T00:00:00', + 'traits': {'trait_A': 'def', + 'hostname': 'foreignhost'}, + } + ] + ), + }, + '/v2/events?q.field=hostname&q.field=num_cpus&q.op=&q.op=' + '&q.type=&q.type=integer&q.value=localhost&q.value=5': + { + 'GET': ( + {}, + [ + { + 'message_id': '1', + 'event_type': 'Bar', + 'generated': '1970-01-01T00:00:00', + 'traits': {'trait_A': 'abc', + 'hostname': 'localhost', + 'num_cpus': '5'}, + }, + ] + ), + }, + + '/v2/events/2': + { + 'GET': ( + {}, + { + 'message_id': '2', + 'event_type': 'Foo', + 'generated': '1970-01-01T00:00:00', + 'traits': {'trait_A': 'def', + 'intTrait': '42'}, + } + ), + }, +} + + +class EventManagerTest(utils.BaseTestCase): + + def setUp(self): + super(EventManagerTest, self).setUp() + self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) + self.api = client.BaseClient(self.http_client) + self.mgr = ceilometerclient.v2.events.EventManager(self.api) + + def test_list_all(self): + events = list(self.mgr.list()) + expect = [ + 'GET', '/v2/events' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(events), 3) + self.assertEqual(events[0].event_type, 'Foo') + self.assertEqual(events[1].event_type, 'Foo') + self.assertEqual(events[2].event_type, 'Bar') + + def test_list_one(self): + event = self.mgr.get(2) + expect = [ + 'GET', '/v2/events/2' + ] + self.http_client.assert_called(*expect) + self.assertIsNotNone(event) + self.assertEqual(event.event_type, 'Foo') + + def test_list_with_query(self): + events = list(self.mgr.list(q=[{"field": "hostname", + "value": "localhost", + "type": "string"}])) + expect = [ + 'GET', '/v2/events?q.field=hostname&q.op=&q.type=string' + '&q.value=localhost' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(events), 2) + self.assertEqual(events[0].event_type, 'Foo') + + def test_list_with_query_no_type(self): + events = list(self.mgr.list(q=[{"field": "hostname", + "value": "foreignhost"}])) + expect = [ + 'GET', '/v2/events?q.field=hostname&q.op=' + '&q.type=&q.value=foreignhost' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(events), 2) + self.assertEqual(events[0].event_type, 'Foo') + + def test_list_with_multiple_filters(self): + events = list(self.mgr.list(q=[{"field": "hostname", + "value": "localhost"}, + {"field": "num_cpus", + "value": "5", + "type": "integer"}])) + + expect = [ + 'GET', '/v2/events?q.field=hostname&q.field=num_cpus&q.op=&q.op=' + '&q.type=&q.type=integer&q.value=localhost&q.value=5' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(events), 1) + + def test_get_from_event_class(self): + event = self.mgr.get(2) + self.assertIsNotNone(event) + event.get() + expect = [ + 'GET', '/v2/events/2' + ] + self.http_client.assert_called(*expect, pos=0) + self.http_client.assert_called(*expect, pos=1) + self.assertEqual('Foo', event.event_type) diff --git a/ceilometerclient/tests/unit/v2/test_options.py b/ceilometerclient/tests/unit/v2/test_options.py new file mode 100644 index 0000000..1a9ec24 --- /dev/null +++ b/ceilometerclient/tests/unit/v2/test_options.py @@ -0,0 +1,239 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from ceilometerclient.tests.unit import utils +from ceilometerclient.v2 import options + + +class BuildUrlTest(utils.BaseTestCase): + + def test_one(self): + url = options.build_url('/', [{'field': 'this', + 'op': 'gt', + 'value': 43}]) + self.assertEqual(url, '/?q.field=this&q.op=gt&q.type=&q.value=43') + + def test_two(self): + url = options.build_url('/', [{'field': 'this', + 'op': 'gt', + 'value': 43}, + {'field': 'that', + 'op': 'lt', + 'value': 88}]) + ops = 'q.op=gt&q.op=lt' + vals = 'q.value=43&q.value=88' + types = 'q.type=&q.type=' + fields = 'q.field=this&q.field=that' + self.assertEqual(url, '/?%s&%s&%s&%s' % (fields, ops, types, vals)) + + def test_default_op(self): + url = options.build_url('/', [{'field': 'this', + 'value': 43}]) + self.assertEqual(url, '/?q.field=this&q.op=&q.type=&q.value=43') + + def test_one_param(self): + url = options.build_url('/', None, ['period=60']) + self.assertEqual(url, '/?period=60') + + def test_two_params(self): + url = options.build_url('/', None, ['period=60', + 'others=value']) + self.assertEqual(url, '/?period=60&others=value') + + def test_with_data_type(self): + url = options.build_url('/', [{'field': 'f1', + 'value': '10', + 'type': 'integer'}]) + + self.assertEqual('/?q.field=f1&q.op=&q.type=integer&q.value=10', url) + + +class CliTest(utils.BaseTestCase): + + def test_one(self): + ar = options.cli_to_array('this<=34') + self.assertEqual(ar, [{'field': 'this', 'op': 'le', + 'value': '34', 'type': ''}]) + + def test_two(self): + ar = options.cli_to_array('this<=34;that!=foo') + self.assertEqual(ar, [{'field': 'this', 'op': 'le', + 'value': '34', 'type': ''}, + {'field': 'that', 'op': 'ne', + 'value': 'foo', 'type': ''}]) + + def test_negative(self): + ar = options.cli_to_array('this>=-783') + self.assertEqual(ar, [{'field': 'this', 'op': 'ge', + 'value': '-783', 'type': ''}]) + + def test_float(self): + ar = options.cli_to_array('this<=283.347') + self.assertEqual(ar, [{'field': 'this', + 'op': 'le', 'value': '283.347', + 'type': ''}]) + + def test_comma(self): + ar = options.cli_to_array('this=2.4,fooo=doof') + self.assertEqual([{'field': 'this', + 'op': 'eq', + 'value': '2.4,fooo=doof', + 'type': ''}], + ar) + + def test_special_character(self): + ar = options.cli_to_array('key~123=value!123') + self.assertEqual([{'field': 'key~123', + 'op': 'eq', + 'value': 'value!123', + 'type': ''}], + ar) + + def _do_test_typed_float_op(self, op, op_str): + ar = options.cli_to_array('that%sfloat::283.347' % op) + self.assertEqual([{'field': 'that', + 'type': 'float', + 'value': '283.347', + 'op': op_str}], + ar) + + def test_typed_float_eq(self): + self._do_test_typed_float_op('<', 'lt') + + def test_typed_float_le(self): + self._do_test_typed_float_op('<=', 'le') + + def test_typed_string_whitespace(self): + ar = options.cli_to_array('state=string::insufficient data') + self.assertEqual([{'field': 'state', + 'op': 'eq', + 'type': 'string', + 'value': 'insufficient data'}], + ar) + + def test_typed_string_whitespace_complex(self): + ar = options.cli_to_array( + 'that>=float::99.9999;state=string::insufficient data' + ) + self.assertEqual([{'field': 'that', + 'op': 'ge', + 'type': 'float', + 'value': '99.9999'}, + {'field': 'state', + 'op': 'eq', + 'type': 'string', + 'value': 'insufficient data'}], + ar) + + def test_invalid_operator(self): + self.assertRaises(ValueError, options.cli_to_array, + 'this=2.4;fooo-doof') + + def test_with_dot(self): + ar = options.cli_to_array('metadata.this<=34') + self.assertEqual(ar, [{'field': 'metadata.this', + 'op': 'le', 'value': '34', + 'type': ''}]) + + def test_single_char_field_or_value(self): + ar = options.cli_to_array('m<=34;large.thing>s;x!=y') + self.assertEqual([{'field': 'm', + 'op': 'le', + 'value': '34', + 'type': ''}, + {'field': 'large.thing', + 'op': 'gt', + 'value': 's', + 'type': ''}, + {'field': 'x', + 'op': 'ne', + 'value': 'y', + 'type': ''}], + ar) + + def test_without_data_type(self): + ar = options.cli_to_array('hostname=localhost') + self.assertEqual(ar, [{'field': 'hostname', + 'op': 'eq', + 'value': 'localhost', + 'type': ''}]) + + def test_with_string_data_type(self): + ar = options.cli_to_array('hostname=string::localhost') + self.assertEqual(ar, [{'field': 'hostname', + 'op': 'eq', + 'type': 'string', + 'value': 'localhost'}]) + + def test_with_int_data_type(self): + ar = options.cli_to_array('port=integer::1234') + self.assertEqual(ar, [{'field': 'port', + 'op': 'eq', + 'type': 'integer', + 'value': '1234'}]) + + def test_with_bool_data_type(self): + ar = options.cli_to_array('port=boolean::true') + self.assertEqual(ar, [{'field': 'port', + 'op': 'eq', + 'type': 'boolean', + 'value': 'true'}]) + + def test_with_float_data_type(self): + ar = options.cli_to_array('average=float::1234.5678') + self.assertEqual(ar, [{'field': 'average', + 'op': 'eq', + 'type': 'float', + 'value': '1234.5678'}]) + + def test_with_datetime_data_type(self): + ar = options.cli_to_array('timestamp=datetime::sometimestamp') + self.assertEqual(ar, [{'field': 'timestamp', + 'op': 'eq', + 'type': 'datetime', + 'value': 'sometimestamp'}]) + + def test_with_incorrect_type(self): + ar = options.cli_to_array('timestamp=invalid::sometimestamp') + self.assertEqual(ar, [{'field': 'timestamp', + 'op': 'eq', + 'type': '', + 'value': 'invalid::sometimestamp'}]) + + def test_with_single_colon(self): + ar = options.cli_to_array('timestamp=datetime:sometimestamp') + self.assertEqual(ar, [{'field': 'timestamp', + 'op': 'eq', + 'type': '', + 'value': 'datetime:sometimestamp'}]) + + def test_missing_key(self): + self.assertRaises(ValueError, options.cli_to_array, + 'average=float::1234.0;>=string::hello') + + def test_missing_value(self): + self.assertRaises(ValueError, options.cli_to_array, + 'average=float::1234.0;house>=') + + def test_timestamp_value(self): + ar = options.cli_to_array( + 'project=cow;timestamp>=datetime::2014-03-11T16:02:58' + ) + self.assertEqual([{'field': 'project', + 'op': 'eq', + 'type': '', + 'value': 'cow'}, + {'field': 'timestamp', + 'op': 'ge', + 'type': 'datetime', + 'value': '2014-03-11T16:02:58'}], + ar) diff --git a/ceilometerclient/tests/unit/v2/test_query_alarm_history.py b/ceilometerclient/tests/unit/v2/test_query_alarm_history.py new file mode 100644 index 0000000..014b894 --- /dev/null +++ b/ceilometerclient/tests/unit/v2/test_query_alarm_history.py @@ -0,0 +1,66 @@ +# Copyright Ericsson AB 2014. All rights reserved +# +# Authors: Ildiko Vancsa <ildiko.vancsa@ericsson.com> +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from ceilometerclient.openstack.common.apiclient import client +from ceilometerclient.openstack.common.apiclient import fake_client +from ceilometerclient.tests.unit import utils +from ceilometerclient.v2 import query + + +ALARMCHANGE = {"alarm_id": "e8ff32f772a44a478182c3fe1f7cad6a", + "event_id": "c74a8611-6553-4764-a860-c15a6aabb5d0", + "detail": "{\"threshold\": 42.0, \"evaluation_periods\": 4}", + "on_behalf_of": "92159030020611e3b26dde429e99ee8c", + "project_id": "b6f16144010811e387e4de429e99ee8c", + "timestamp": "2014-03-11T16:02:58.376261", + "type": "rule change", + "user_id": "3e5d11fda79448ac99ccefb20be187ca" + } + +QUERY = {"filter": {"and": [{">": {"timestamp": "2014-03-11T16:02:58"}}, + {"=": {"type": "rule change"}}]}, + "orderby": [{"timestamp": "desc"}], + "limit": 10} + +base_url = '/v2/query/alarms/history' +fixtures = { + base_url: + { + 'POST': ( + {}, + [ALARMCHANGE], + ), + }, +} + + +class QueryAlarmsManagerTest(utils.BaseTestCase): + + def setUp(self): + super(QueryAlarmsManagerTest, self).setUp() + self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) + self.api = client.BaseClient(self.http_client) + self.mgr = query.QueryAlarmHistoryManager(self.api) + + def test_query(self): + alarm_history = self.mgr.query(**QUERY) + expect = [ + + 'POST', '/v2/query/alarms/history', QUERY, + + ] + self.http_client.assert_called(*expect) + self.assertEqual(1, len(alarm_history)) diff --git a/ceilometerclient/tests/unit/v2/test_query_alarms.py b/ceilometerclient/tests/unit/v2/test_query_alarms.py new file mode 100644 index 0000000..e633938 --- /dev/null +++ b/ceilometerclient/tests/unit/v2/test_query_alarms.py @@ -0,0 +1,74 @@ +# Copyright Ericsson AB 2014. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from ceilometerclient.openstack.common.apiclient import client +from ceilometerclient.openstack.common.apiclient import fake_client +from ceilometerclient.tests.unit import utils +from ceilometerclient.v2 import query + + +ALARM = {"alarm_actions": ["http://site:8000/alarm"], + "alarm_id": None, + "combination_rule": { + "alarm_ids": [ + "739e99cb-c2ec-4718-b900-332502355f38", + "153462d0-a9b8-4b5b-8175-9e4b05e9b856"], + "operator": "or"}, + "description": "An alarm", + "enabled": True, + "insufficient_data_actions": ["http://site:8000/nodata"], + "name": "SwiftObjectAlarm", + "ok_actions": ["http://site:8000/ok"], + "project_id": "c96c887c216949acbdfbd8b494863567", + "repeat_actions": False, + "state": "ok", + "state_timestamp": "2014-02-20T10:37:15.589860", + "threshold_rule": None, + "timestamp": "2014-02-20T10:37:15.589856", + "type": "combination", + "user_id": "c96c887c216949acbdfbd8b494863567"} + +QUERY = {"filter": {"and": [{"!=": {"state": "ok"}}, + {"=": {"type": "combination"}}]}, + "orderby": [{"state_timestamp": "desc"}], + "limit": 10} + +base_url = '/v2/query/alarms' +fixtures = { + base_url: + { + 'POST': ( + {}, + [ALARM], + ), + }, +} + + +class QueryAlarmsManagerTest(utils.BaseTestCase): + + def setUp(self): + super(QueryAlarmsManagerTest, self).setUp() + self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) + self.api = client.BaseClient(self.http_client) + self.mgr = query.QueryAlarmsManager(self.api) + + def test_query(self): + alarms = self.mgr.query(**QUERY) + expect = [ + 'POST', '/v2/query/alarms', QUERY, + ] + + self.http_client.assert_called(*expect) + self.assertEqual(1, len(alarms)) diff --git a/ceilometerclient/tests/unit/v2/test_query_samples.py b/ceilometerclient/tests/unit/v2/test_query_samples.py new file mode 100644 index 0000000..16828fe --- /dev/null +++ b/ceilometerclient/tests/unit/v2/test_query_samples.py @@ -0,0 +1,67 @@ +# Copyright Ericsson AB 2014. All rights reserved +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from ceilometerclient.openstack.common.apiclient import client +from ceilometerclient.openstack.common.apiclient import fake_client +from ceilometerclient.tests.unit import utils +from ceilometerclient.v2 import query + + +SAMPLE = {u'id': u'b55d1526-9929-11e3-a3f6-02163e5df1e6', + u'metadata': { + u'name1': u'value1', + u'name2': u'value2'}, + u'meter': 'instance', + u'project_id': u'35b17138-b364-4e6a-a131-8f3099c5be68', + u'resource_id': u'bd9431c1-8d69-4ad3-803a-8d4a6b89fd36', + u'source': u'openstack', + u'timestamp': u'2014-02-19T05:50:16.673604', + u'type': u'gauge', + u'unit': u'instance', + u'volume': 1, + u'user_id': 'efd87807-12d2-4b38-9c70-5f5c2ac427ff'} + +QUERY = {"filter": {"and": [{"=": {"source": "openstack"}}, + {">": {"timestamp": "2014-02-19T05:50:16"}}]}, + "orderby": [{"timestamp": "desc"}, {"volume": "asc"}], + "limit": 10} + +base_url = '/v2/query/samples' +fixtures = { + base_url: + { + 'POST': ( + {}, + [SAMPLE], + ), + }, +} + + +class QuerySamplesManagerTest(utils.BaseTestCase): + + def setUp(self): + super(QuerySamplesManagerTest, self).setUp() + self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) + self.api = client.BaseClient(self.http_client) + self.mgr = query.QuerySamplesManager(self.api) + + def test_query(self): + samples = self.mgr.query(**QUERY) + expect = [ + + 'POST', '/v2/query/samples', QUERY, + ] + self.http_client.assert_called(*expect) + self.assertEqual(1, len(samples)) diff --git a/ceilometerclient/tests/unit/v2/test_resources.py b/ceilometerclient/tests/unit/v2/test_resources.py new file mode 100644 index 0000000..4aa9131 --- /dev/null +++ b/ceilometerclient/tests/unit/v2/test_resources.py @@ -0,0 +1,118 @@ +# Copyright 2012 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from ceilometerclient.openstack.common.apiclient import client +from ceilometerclient.openstack.common.apiclient import fake_client +from ceilometerclient.tests.unit import utils +import ceilometerclient.v2.resources + + +fixtures = { + '/v2/resources': { + 'GET': ( + {}, + [ + { + 'resource_id': 'a', + 'project_id': 'project_bla', + 'user_id': 'freddy', + 'metadata': {'zxc_id': 'bla'}, + }, + { + 'resource_id': 'b', + 'project_id': 'dig_the_ditch', + 'user_id': 'joey', + 'metadata': {'zxc_id': 'foo'}, + }, + ] + ), + }, + '/v2/resources?q.field=resource_id&q.op=&q.type=&q.value=a': + { + 'GET': ( + {}, + [ + { + 'resource_id': 'a', + 'project_id': 'project_bla', + 'user_id': 'freddy', + 'metadata': {'zxc_id': 'bla'}, + }, + ] + ), + }, + '/v2/resources/a': + { + 'GET': ( + {}, + { + 'resource_id': 'a', + 'project_id': 'project_bla', + 'user_id': 'freddy', + 'metadata': {'zxc_id': 'bla'}, + }, + ), + }, +} + + +class ResourceManagerTest(utils.BaseTestCase): + + def setUp(self): + super(ResourceManagerTest, self).setUp() + self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) + self.api = client.BaseClient(self.http_client) + self.mgr = ceilometerclient.v2.resources.ResourceManager(self.api) + + def test_list_all(self): + resources = list(self.mgr.list()) + expect = [ + 'GET', '/v2/resources' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(resources), 2) + self.assertEqual(resources[0].resource_id, 'a') + self.assertEqual(resources[1].resource_id, 'b') + + def test_list_one(self): + resource = self.mgr.get(resource_id='a') + expect = [ + 'GET', '/v2/resources/a' + ] + self.http_client.assert_called(*expect) + self.assertIsNotNone(resource) + self.assertEqual(resource.resource_id, 'a') + + def test_list_by_query(self): + resources = list(self.mgr.list(q=[{"field": "resource_id", + "value": "a"}, + ])) + expect = [ + 'GET', '/v2/resources?q.field=resource_id&q.op=' + '&q.type=&q.value=a' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(resources), 1) + self.assertEqual(resources[0].resource_id, 'a') + + def test_get_from_resource_class(self): + resource = self.mgr.get(resource_id='a') + self.assertIsNotNone(resource) + resource.get() + expect = [ + 'GET', '/v2/resources/a' + ] + self.http_client.assert_called(*expect, pos=0) + self.http_client.assert_called(*expect, pos=1) + self.assertEqual('a', resource.resource_id) diff --git a/ceilometerclient/tests/unit/v2/test_samples.py b/ceilometerclient/tests/unit/v2/test_samples.py new file mode 100644 index 0000000..1d42fb4 --- /dev/null +++ b/ceilometerclient/tests/unit/v2/test_samples.py @@ -0,0 +1,198 @@ +# Copyright 2012 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import copy + +from ceilometerclient.openstack.common.apiclient import client +from ceilometerclient.openstack.common.apiclient import fake_client +from ceilometerclient.tests.unit import utils +import ceilometerclient.v2.samples + +GET_OLD_SAMPLE = {u'counter_name': u'instance', + u'user_id': u'user-id', + u'resource_id': u'resource-id', + u'timestamp': u'2012-07-02T10:40:00', + u'source': u'test_source', + u'message_id': u'54558a1c-6ef3-11e2-9875-5453ed1bbb5f', + u'counter_unit': u'', + u'counter_volume': 1.0, + u'project_id': u'project1', + u'resource_metadata': {u'tag': u'self.counter', + u'display_name': u'test-server'}, + u'counter_type': u'cumulative'} +CREATE_SAMPLE = copy.deepcopy(GET_OLD_SAMPLE) +del CREATE_SAMPLE['message_id'] +del CREATE_SAMPLE['source'] + +GET_SAMPLE = { + "user_id": None, + "resource_id": "9b651dfd-7d30-402b-972e-212b2c4bfb05", + "timestamp": "2014-11-03T13:37:46", + "meter": "image", + "volume": 1.0, + "source": "openstack", + "recorded_at": "2014-11-03T13:37:46.994458", + "project_id": "2cc3a7bb859b4bacbeab0aa9ca673033", + "type": "gauge", + "id": "98b5f258-635e-11e4-8bdd-0025647390c1", + "unit": "image", + "resource_metadata": {}, +} + +METER_URL = '/v2/meters/instance' +SAMPLE_URL = '/v2/samples' +QUERIES = ('q.field=resource_id&q.field=source&q.op=&q.op=' + '&q.type=&q.type=&q.value=foo&q.value=bar') +LIMIT = 'limit=1' + +OLD_SAMPLE_FIXTURES = { + METER_URL: { + 'GET': ( + {}, + [GET_OLD_SAMPLE] + ), + 'POST': ( + {}, + [CREATE_SAMPLE], + ), + }, + '%s?%s' % (METER_URL, QUERIES): { + 'GET': ( + {}, + [], + ), + }, + '%s?%s' % (METER_URL, LIMIT): { + 'GET': ( + {}, + [GET_OLD_SAMPLE] + ), + } +} +SAMPLE_FIXTURES = { + SAMPLE_URL: { + 'GET': ( + (), + [GET_SAMPLE] + ), + }, + '%s?%s' % (SAMPLE_URL, QUERIES): { + 'GET': ( + {}, + [], + ), + }, + '%s?%s' % (SAMPLE_URL, LIMIT): { + 'GET': ( + {}, + [GET_SAMPLE], + ), + }, + '%s/%s' % (SAMPLE_URL, GET_SAMPLE['id']): { + 'GET': ( + {}, + GET_SAMPLE, + ), + }, +} + + +class OldSampleManagerTest(utils.BaseTestCase): + + def setUp(self): + super(OldSampleManagerTest, self).setUp() + self.http_client = fake_client.FakeHTTPClient( + fixtures=OLD_SAMPLE_FIXTURES) + self.api = client.BaseClient(self.http_client) + self.mgr = ceilometerclient.v2.samples.OldSampleManager(self.api) + + def test_list_by_meter_name(self): + samples = list(self.mgr.list(meter_name='instance')) + expect = [ + 'GET', '/v2/meters/instance' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(samples), 1) + self.assertEqual(samples[0].resource_id, 'resource-id') + + def test_list_by_meter_name_extended(self): + samples = list(self.mgr.list(meter_name='instance', + q=[ + {"field": "resource_id", + "value": "foo"}, + {"field": "source", + "value": "bar"}, + ])) + expect = ['GET', '%s?%s' % (METER_URL, QUERIES)] + self.http_client.assert_called(*expect) + self.assertEqual(len(samples), 0) + + def test_create(self): + sample = self.mgr.create(**CREATE_SAMPLE) + expect = [ + 'POST', '/v2/meters/instance' + ] + self.http_client.assert_called(*expect, body=[CREATE_SAMPLE]) + self.assertIsNotNone(sample) + + def test_limit(self): + samples = list(self.mgr.list(meter_name='instance', limit=1)) + expect = ['GET', '/v2/meters/instance?limit=1'] + self.http_client.assert_called(*expect) + self.assertEqual(len(samples), 1) + + +class SampleManagerTest(utils.BaseTestCase): + + def setUp(self): + super(SampleManagerTest, self).setUp() + self.http_client = fake_client.FakeHTTPClient( + fixtures=SAMPLE_FIXTURES) + self.api = client.BaseClient(self.http_client) + self.mgr = ceilometerclient.v2.samples.SampleManager(self.api) + + def test_sample_list(self): + samples = list(self.mgr.list()) + expect = [ + 'GET', '/v2/samples' + ] + self.http_client.assert_called(*expect) + self.assertEqual(1, len(samples)) + self.assertEqual('9b651dfd-7d30-402b-972e-212b2c4bfb05', + samples[0].resource_id) + + def test_sample_list_with_queries(self): + queries = [ + {"field": "resource_id", + "value": "foo"}, + {"field": "source", + "value": "bar"}, + ] + samples = list(self.mgr.list(q=queries)) + expect = ['GET', '%s?%s' % (SAMPLE_URL, QUERIES)] + self.http_client.assert_called(*expect) + self.assertEqual(0, len(samples)) + + def test_sample_list_with_limit(self): + samples = list(self.mgr.list(limit=1)) + expect = ['GET', '/v2/samples?limit=1'] + self.http_client.assert_called(*expect) + self.assertEqual(1, len(samples)) + + def test_sample_get(self): + sample = self.mgr.get(GET_SAMPLE['id']) + expect = ['GET', '/v2/samples/' + GET_SAMPLE['id']] + self.http_client.assert_called(*expect) + self.assertEqual(GET_SAMPLE, sample.to_dict()) diff --git a/ceilometerclient/tests/unit/v2/test_shell.py b/ceilometerclient/tests/unit/v2/test_shell.py new file mode 100644 index 0000000..a4b9f33 --- /dev/null +++ b/ceilometerclient/tests/unit/v2/test_shell.py @@ -0,0 +1,1175 @@ +# Copyright Ericsson AB 2014. All rights reserved +# +# Authors: Balazs Gibizer <balazs.gibizer@ericsson.com> +# Ildiko Vancsa <ildiko.vancsa@ericsson.com> +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import re +import sys + +import mock +import six +from testtools import matchers + +from ceilometerclient import exc +from ceilometerclient import shell as base_shell +from ceilometerclient.tests.unit import test_shell +from ceilometerclient.tests.unit import utils +from ceilometerclient.v2 import alarms +from ceilometerclient.v2 import events +from ceilometerclient.v2 import samples +from ceilometerclient.v2 import shell as ceilometer_shell +from ceilometerclient.v2 import statistics + + +class ShellAlarmStateCommandsTest(utils.BaseTestCase): + + ALARM_ID = 'foobar' + + def setUp(self): + super(ShellAlarmStateCommandsTest, self).setUp() + self.cc = mock.Mock() + self.cc.alarms = mock.Mock() + self.args = mock.Mock() + self.args.alarm_id = self.ALARM_ID + + def test_alarm_state_get(self): + ceilometer_shell.do_alarm_state_get(self.cc, self.args) + self.cc.alarms.get_state.assert_called_once_with(self.ALARM_ID) + self.assertFalse(self.cc.alarms.set_state.called) + + def test_alarm_state_set(self): + self.args.state = 'ok' + ceilometer_shell.do_alarm_state_set(self.cc, self.args) + self.cc.alarms.set_state.assert_called_once_with(self.ALARM_ID, 'ok') + self.assertFalse(self.cc.alarms.get_state.called) + + +class ShellAlarmHistoryCommandTest(utils.BaseTestCase): + + ALARM_ID = '768ff714-8cfb-4db9-9753-d484cb33a1cc' + FULL_DETAIL = ('{"alarm_actions": [], ' + '"user_id": "8185aa72421a4fd396d4122cba50e1b5", ' + '"name": "scombo", ' + '"timestamp": "2013-10-03T08:58:33.647912", ' + '"enabled": true, ' + '"state_timestamp": "2013-10-03T08:58:33.647912", ' + '"rule": {"operator": "or", "alarm_ids": ' + '["062cc907-3a9f-4867-ab3b-fa83212b39f7"]}, ' + '"alarm_id": "768ff714-8cfb-4db9-9753-d484cb33a1cc", ' + '"state": "insufficient data", ' + '"insufficient_data_actions": [], ' + '"repeat_actions": false, ' + '"ok_actions": [], ' + '"project_id": "57d04f24d0824b78b1ea9bcecedbda8f", ' + '"type": "combination", ' + '"description": "Combined state of alarms ' + '062cc907-3a9f-4867-ab3b-fa83212b39f7"}') + ALARM_HISTORY = [{'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', + 'user_id': '8185aa72421a4fd396d4122cba50e1b5', + 'event_id': 'c74a8611-6553-4764-a860-c15a6aabb5d0', + 'timestamp': '2013-10-03T08:59:28.326000', + 'detail': '{"state": "alarm"}', + 'alarm_id': '768ff714-8cfb-4db9-9753-d484cb33a1cc', + 'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', + 'type': 'state transition'}, + {'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', + 'user_id': '8185aa72421a4fd396d4122cba50e1b5', + 'event_id': 'c74a8611-6553-4764-a860-c15a6aabb5d0', + 'timestamp': '2013-10-03T08:59:28.326000', + 'detail': '{"description": "combination of one"}', + 'alarm_id': '768ff714-8cfb-4db9-9753-d484cb33a1cc', + 'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', + 'type': 'rule change'}, + {'on_behalf_of': '57d04f24d0824b78b1ea9bcecedbda8f', + 'user_id': '8185aa72421a4fd396d4122cba50e1b5', + 'event_id': '4fd7df9e-190d-4471-8884-dc5a33d5d4bb', + 'timestamp': '2013-10-03T08:58:33.647000', + 'detail': FULL_DETAIL, + 'alarm_id': '768ff714-8cfb-4db9-9753-d484cb33a1cc', + 'project_id': '57d04f24d0824b78b1ea9bcecedbda8f', + 'type': 'creation'}] + TIMESTAMP_RE = (' +\| (\d{4})-(\d{2})-(\d{2})T' + '(\d{2})\:(\d{2})\:(\d{2})\.(\d{6}) \| +') + + def setUp(self): + super(ShellAlarmHistoryCommandTest, self).setUp() + self.cc = mock.Mock() + self.cc.alarms = mock.Mock() + self.args = mock.Mock() + self.args.alarm_id = self.ALARM_ID + + @mock.patch('sys.stdout', new=six.StringIO()) + def _do_test_alarm_history(self, raw_query=None, parsed_query=None): + self.args.query = raw_query + history = [alarms.AlarmChange(mock.Mock(), change) + for change in self.ALARM_HISTORY] + self.cc.alarms.get_history.return_value = history + + ceilometer_shell.do_alarm_history(self.cc, self.args) + self.cc.alarms.get_history.assert_called_once_with( + q=parsed_query, + alarm_id=self.ALARM_ID + ) + out = sys.stdout.getvalue() + required = [ + '.*creation%sname: scombo.*' % self.TIMESTAMP_RE, + '.*rule change%sdescription: combination of one.*' % + self.TIMESTAMP_RE, + '.*state transition%sstate: alarm.*' % self.TIMESTAMP_RE, + ] + for r in required: + self.assertThat(out, matchers.MatchesRegex(r, re.DOTALL)) + + def test_alarm_all_history(self): + self._do_test_alarm_history() + + def test_alarm_constrained_history(self): + parsed_query = [dict(field='timestamp', + value='2013-10-03T08:59:28', + op='gt', + type='')] + self._do_test_alarm_history(raw_query='timestamp>2013-10-03T08:59:28', + parsed_query=parsed_query) + + +class ShellAlarmCommandTest(utils.BaseTestCase): + + ALARM_ID = '768ff714-8cfb-4db9-9753-d484cb33a1cc' + ALARM = {"alarm_actions": ["log://"], + "ok_actions": [], + "description": "instance running hot", + "timestamp": "2013-11-20T10:38:42.206952", + "enabled": True, + "state_timestamp": "2013-11-19T17:20:44", + "threshold_rule": {"meter_name": "cpu_util", + "evaluation_periods": 3, + "period": 600, + "statistic": "avg", + "threshold": 99.0, + "query": [{"field": "resource_id", + "value": "INSTANCE_ID", + "op": "eq"}], + "comparison_operator": "gt"}, + "time_constraints": [{"name": "cons1", + "description": "desc1", + "start": "0 11 * * *", + "duration": 300, + "timezone": ""}, + {"name": "cons2", + "description": "desc2", + "start": "0 23 * * *", + "duration": 600, + "timezone": ""}], + "alarm_id": ALARM_ID, + "state": "insufficient data", + "severity": "low", + "insufficient_data_actions": [], + "repeat_actions": True, + "user_id": "528d9b68fa774689834b5c04b4564f8a", + "project_id": "ed9d4e2be2a748bc80108053cf4598f5", + "type": "threshold", + "name": "cpu_high"} + + THRESHOLD_ALARM_CLI_ARGS = [ + '--name', 'cpu_high', + '--description', 'instance running hot', + '--meter-name', 'cpu_util', + '--threshold', '70.0', + '--comparison-operator', 'gt', + '--statistic', 'avg', + '--period', '600', + '--evaluation-periods', '3', + '--alarm-action', 'log://', + '--alarm-action', 'http://example.com/alarm/state', + '--query', 'resource_id=INSTANCE_ID' + ] + + def setUp(self): + super(ShellAlarmCommandTest, self).setUp() + self.cc = mock.Mock() + self.cc.alarms = mock.Mock() + self.args = mock.Mock() + self.args.alarm_id = self.ALARM_ID + + @mock.patch('sys.stdout', new=six.StringIO()) + def _do_test_alarm_update_repeat_actions(self, method, repeat_actions): + self.args.threshold = 42.0 + if repeat_actions is not None: + self.args.repeat_actions = repeat_actions + alarm = [alarms.Alarm(mock.Mock(), self.ALARM)] + self.cc.alarms.get.return_value = alarm + self.cc.alarms.update.return_value = alarm[0] + + method(self.cc, self.args) + args, kwargs = self.cc.alarms.update.call_args + self.assertEqual(self.ALARM_ID, args[0]) + self.assertEqual(42.0, kwargs.get('threshold')) + if repeat_actions is not None: + self.assertEqual(repeat_actions, kwargs.get('repeat_actions')) + else: + self.assertNotIn('repeat_actions', kwargs) + + def test_alarm_update_repeat_actions_untouched(self): + method = ceilometer_shell.do_alarm_update + self._do_test_alarm_update_repeat_actions(method, None) + + def test_alarm_update_repeat_actions_set(self): + method = ceilometer_shell.do_alarm_update + self._do_test_alarm_update_repeat_actions(method, True) + + def test_alarm_update_repeat_actions_clear(self): + method = ceilometer_shell.do_alarm_update + self._do_test_alarm_update_repeat_actions(method, False) + + def test_alarm_combination_update_repeat_actions_untouched(self): + method = ceilometer_shell.do_alarm_combination_update + self._do_test_alarm_update_repeat_actions(method, None) + + def test_alarm_combination_update_repeat_actions_set(self): + method = ceilometer_shell.do_alarm_combination_update + self._do_test_alarm_update_repeat_actions(method, True) + + def test_alarm_combination_update_repeat_actions_clear(self): + method = ceilometer_shell.do_alarm_combination_update + self._do_test_alarm_update_repeat_actions(method, False) + + def test_alarm_threshold_update_repeat_actions_untouched(self): + method = ceilometer_shell.do_alarm_threshold_update + self._do_test_alarm_update_repeat_actions(method, None) + + def test_alarm_threshold_update_repeat_actions_set(self): + method = ceilometer_shell.do_alarm_threshold_update + self._do_test_alarm_update_repeat_actions(method, True) + + def test_alarm_threshold_update_repeat_actions_clear(self): + method = ceilometer_shell.do_alarm_threshold_update + self._do_test_alarm_update_repeat_actions(method, False) + + @mock.patch('sys.stdout', new=six.StringIO()) + def test_alarm_threshold_create_args(self): + argv = ['alarm-threshold-create'] + self.THRESHOLD_ALARM_CLI_ARGS + self._test_alarm_threshold_action_args('create', argv) + + def test_alarm_threshold_update_args(self): + argv = ['alarm-threshold-update', 'x'] + self.THRESHOLD_ALARM_CLI_ARGS + self._test_alarm_threshold_action_args('update', argv) + + @mock.patch('sys.stdout', new=six.StringIO()) + def _test_alarm_threshold_action_args(self, action, argv): + shell = base_shell.CeilometerShell() + _, args = shell.parse_args(argv) + + alarm = alarms.Alarm(mock.Mock(), self.ALARM) + getattr(self.cc.alarms, action).return_value = alarm + + func = getattr(ceilometer_shell, 'do_alarm_threshold_' + action) + func(self.cc, args) + _, kwargs = getattr(self.cc.alarms, action).call_args + self._check_alarm_threshold_args(kwargs) + + def _check_alarm_threshold_args(self, kwargs): + self.assertEqual('cpu_high', kwargs.get('name')) + self.assertEqual('instance running hot', kwargs.get('description')) + actions = ['log://', 'http://example.com/alarm/state'] + self.assertEqual(actions, kwargs.get('alarm_actions')) + self.assertIn('threshold_rule', kwargs) + rule = kwargs['threshold_rule'] + self.assertEqual('cpu_util', rule.get('meter_name')) + self.assertEqual(70.0, rule.get('threshold')) + self.assertEqual('gt', rule.get('comparison_operator')) + self.assertEqual('avg', rule.get('statistic')) + self.assertEqual(600, rule.get('period')) + self.assertEqual(3, rule.get('evaluation_periods')) + query = dict(field='resource_id', type='', + value='INSTANCE_ID', op='eq') + self.assertEqual([query], rule['query']) + + @mock.patch('sys.stdout', new=six.StringIO()) + def test_alarm_create_time_constraints(self): + shell = base_shell.CeilometerShell() + argv = ['alarm-threshold-create', + '--name', 'cpu_high', + '--meter-name', 'cpu_util', + '--threshold', '70.0', + '--time-constraint', + 'name=cons1;start="0 11 * * *";duration=300', + '--time-constraint', + 'name=cons2;start="0 23 * * *";duration=600', + ] + _, args = shell.parse_args(argv) + + alarm = alarms.Alarm(mock.Mock(), self.ALARM) + self.cc.alarms.create.return_value = alarm + + ceilometer_shell.do_alarm_threshold_create(self.cc, args) + _, kwargs = self.cc.alarms.create.call_args + time_constraints = [dict(name='cons1', start='0 11 * * *', + duration='300'), + dict(name='cons2', start='0 23 * * *', + duration='600')] + self.assertEqual(time_constraints, kwargs['time_constraints']) + + +class ShellSampleListCommandTest(utils.BaseTestCase): + + METER = 'cpu_util' + SAMPLE_VALUES = ( + ("cpu_util", + "5dcf5537-3161-4e25-9235-407e1385bd35", + "2013-10-15T05:50:30", + "%", + 0.261666666667, + "gauge", + "86536501-b2c9-48f6-9c6a-7a5b14ba7482"), + ("cpu_util", + "87d197e9-9cf6-4c25-bc66-1b1f4cedb52f", + "2013-10-15T05:50:29", + "%", + 0.261666666667, + "gauge", + "fe2a91ec-602b-4b55-8cba-5302ce3b916e",), + ("cpu_util", + "5dcf5537-3161-4e25-9235-407e1385bd35", + "2013-10-15T05:40:30", + "%", + 0.251247920133, + "gauge", + "52768bcb-b4e9-4db9-a30c-738c758b6f43"), + ("cpu_util", + "87d197e9-9cf6-4c25-bc66-1b1f4cedb52f", + "2013-10-15T05:40:29", + "%", + 0.26, + "gauge", + "31ae614a-ac6b-4fb9-b106-4667bae03308"), + ) + + OLD_SAMPLES = [ + dict(counter_name=s[0], + resource_id=s[1], + timestamp=s[2], + counter_unit=s[3], + counter_volume=s[4], + counter_type=s[5]) + for s in SAMPLE_VALUES + ] + + SAMPLES = [ + dict(meter=s[0], + resource_id=s[1], + timestamp=s[2], + unit=s[3], + volume=s[4], + type=s[5], + id=s[6]) + for s in SAMPLE_VALUES + ] + + def setUp(self): + super(ShellSampleListCommandTest, self).setUp() + self.cc = mock.Mock() + self.cc.samples = mock.Mock() + self.cc.new_samples = mock.Mock() + self.args = mock.Mock() + self.args.query = None + self.args.limit = None + + @mock.patch('sys.stdout', new=six.StringIO()) + def test_old_sample_list(self): + self.args.meter = self.METER + sample_list = [samples.OldSample(mock.Mock(), sample) + for sample in self.OLD_SAMPLES] + self.cc.samples.list.return_value = sample_list + + ceilometer_shell.do_sample_list(self.cc, self.args) + self.cc.samples.list.assert_called_once_with( + meter_name=self.METER, + q=None, + limit=None) + + self.assertEqual('''\ ++--------------------------------------+----------+-------+----------------\ ++------+---------------------+ +| Resource ID | Name | Type | Volume \ +| Unit | Timestamp | ++--------------------------------------+----------+-------+----------------\ ++------+---------------------+ +| 5dcf5537-3161-4e25-9235-407e1385bd35 | cpu_util | gauge | 0.261666666667 \ +| % | 2013-10-15T05:50:30 | +| 87d197e9-9cf6-4c25-bc66-1b1f4cedb52f | cpu_util | gauge | 0.261666666667 \ +| % | 2013-10-15T05:50:29 | +| 5dcf5537-3161-4e25-9235-407e1385bd35 | cpu_util | gauge | 0.251247920133 \ +| % | 2013-10-15T05:40:30 | +| 87d197e9-9cf6-4c25-bc66-1b1f4cedb52f | cpu_util | gauge | 0.26 \ +| % | 2013-10-15T05:40:29 | ++--------------------------------------+----------+-------+----------------\ ++------+---------------------+ +''', sys.stdout.getvalue()) + + @mock.patch('sys.stdout', new=six.StringIO()) + def test_sample_list(self): + self.args.meter = None + sample_list = [samples.Sample(mock.Mock(), sample) + for sample in self.SAMPLES] + self.cc.new_samples.list.return_value = sample_list + + ceilometer_shell.do_sample_list(self.cc, self.args) + self.cc.new_samples.list.assert_called_once_with( + q=None, + limit=None) + + self.assertEqual('''\ ++--------------------------------------+--------------------------------------\ ++----------+-------+----------------+------+---------------------+ +| ID | Resource ID \ +| Name | Type | Volume | Unit | Timestamp | ++--------------------------------------+--------------------------------------\ ++----------+-------+----------------+------+---------------------+ +| 86536501-b2c9-48f6-9c6a-7a5b14ba7482 | 5dcf5537-3161-4e25-9235-407e1385bd35 \ +| cpu_util | gauge | 0.261666666667 | % | 2013-10-15T05:50:30 | +| fe2a91ec-602b-4b55-8cba-5302ce3b916e | 87d197e9-9cf6-4c25-bc66-1b1f4cedb52f \ +| cpu_util | gauge | 0.261666666667 | % | 2013-10-15T05:50:29 | +| 52768bcb-b4e9-4db9-a30c-738c758b6f43 | 5dcf5537-3161-4e25-9235-407e1385bd35 \ +| cpu_util | gauge | 0.251247920133 | % | 2013-10-15T05:40:30 | +| 31ae614a-ac6b-4fb9-b106-4667bae03308 | 87d197e9-9cf6-4c25-bc66-1b1f4cedb52f \ +| cpu_util | gauge | 0.26 | % | 2013-10-15T05:40:29 | ++--------------------------------------+--------------------------------------\ ++----------+-------+----------------+------+---------------------+ +''', sys.stdout.getvalue()) + + +class ShellSampleShowCommandTest(utils.BaseTestCase): + + SAMPLE = { + "user_id": None, + "resource_id": "9b651dfd-7d30-402b-972e-212b2c4bfb05", + "timestamp": "2014-11-03T13:37:46", + "meter": "image", + "volume": 1.0, + "source": "openstack", + "recorded_at": "2014-11-03T13:37:46.994458", + "project_id": "2cc3a7bb859b4bacbeab0aa9ca673033", + "type": "gauge", + "id": "98b5f258-635e-11e4-8bdd-0025647390c1", + "unit": "image", + "metadata": { + "name": "cirros-0.3.2-x86_64-uec", + } + } + + def setUp(self): + super(ShellSampleShowCommandTest, self).setUp() + self.cc = mock.Mock() + self.cc.new_samples = mock.Mock() + self.args = mock.Mock() + self.args.sample_id = "98b5f258-635e-11e4-8bdd-0025647390c1" + + @mock.patch('sys.stdout', new=six.StringIO()) + def test_sample_show(self): + sample = samples.Sample(mock.Mock(), self.SAMPLE) + self.cc.new_samples.get.return_value = sample + + ceilometer_shell.do_sample_show(self.cc, self.args) + self.cc.new_samples.get.assert_called_once_with( + "98b5f258-635e-11e4-8bdd-0025647390c1") + + self.assertEqual('''\ ++-------------+--------------------------------------+ +| Property | Value | ++-------------+--------------------------------------+ +| id | 98b5f258-635e-11e4-8bdd-0025647390c1 | +| metadata | {"name": "cirros-0.3.2-x86_64-uec"} | +| meter | image | +| project_id | 2cc3a7bb859b4bacbeab0aa9ca673033 | +| recorded_at | 2014-11-03T13:37:46.994458 | +| resource_id | 9b651dfd-7d30-402b-972e-212b2c4bfb05 | +| source | openstack | +| timestamp | 2014-11-03T13:37:46 | +| type | gauge | +| unit | image | +| user_id | None | +| volume | 1.0 | ++-------------+--------------------------------------+ +''', sys.stdout.getvalue()) + + +class ShellSampleCreateCommandTest(utils.BaseTestCase): + + METER = 'instance' + METER_TYPE = 'gauge' + RESOURCE_ID = '0564c64c-3545-4e34-abfb-9d18e5f2f2f9' + SAMPLE_VOLUME = '1' + METER_UNIT = 'instance' + SAMPLE = [{ + u'counter_name': u'instance', + u'user_id': u'21b442b8101d407d8242b6610e0ed0eb', + u'resource_id': u'0564c64c-3545-4e34-abfb-9d18e5f2f2f9', + u'timestamp': u'2014-01-10T03: 05: 33.951170', + u'message_id': u'1247cbe6-79a4-11e3-a296-000c294c58e2', + u'source': u'384260c6987b451d8290e66e1f108082: openstack', + u'counter_unit': u'instance', + u'counter_volume': 1.0, + u'project_id': u'384260c6987b451d8290e66e1f108082', + u'resource_metadata': {}, + u'counter_type': u'gauge' + }] + + def setUp(self): + super(ShellSampleCreateCommandTest, self).setUp() + self.cc = mock.Mock() + self.cc.samples = mock.Mock() + self.args = mock.Mock() + self.args.meter_name = self.METER + self.args.meter_type = self.METER_TYPE + self.args.meter_unit = self.METER_UNIT + self.args.resource_id = self.RESOURCE_ID + self.args.sample_volume = self.SAMPLE_VOLUME + + @mock.patch('sys.stdout', new=six.StringIO()) + def test_sample_create(self): + ret_sample = [samples.OldSample(mock.Mock(), sample) + for sample in self.SAMPLE] + self.cc.samples.create.return_value = ret_sample + + ceilometer_shell.do_sample_create(self.cc, self.args) + + self.assertEqual('''\ ++-------------------+---------------------------------------------+ +| Property | Value | ++-------------------+---------------------------------------------+ +| message_id | 1247cbe6-79a4-11e3-a296-000c294c58e2 | +| name | instance | +| project_id | 384260c6987b451d8290e66e1f108082 | +| resource_id | 0564c64c-3545-4e34-abfb-9d18e5f2f2f9 | +| resource_metadata | {} | +| source | 384260c6987b451d8290e66e1f108082: openstack | +| timestamp | 2014-01-10T03: 05: 33.951170 | +| type | gauge | +| unit | instance | +| user_id | 21b442b8101d407d8242b6610e0ed0eb | +| volume | 1.0 | ++-------------------+---------------------------------------------+ +''', sys.stdout.getvalue()) + + +class ShellQuerySamplesCommandTest(utils.BaseTestCase): + + SAMPLE = [{u'id': u'b55d1526-9929-11e3-a3f6-02163e5df1e6', + u'metadata': { + u'name1': u'value1', + u'name2': u'value2'}, + u'meter': 'instance', + u'project_id': u'35b17138-b364-4e6a-a131-8f3099c5be68', + u'resource_id': u'bd9431c1-8d69-4ad3-803a-8d4a6b89fd36', + u'source': u'openstack', + u'timestamp': u'2014-02-19T05:50:16.673604', + u'type': u'gauge', + u'unit': u'instance', + u'volume': 1, + u'user_id': 'efd87807-12d2-4b38-9c70-5f5c2ac427ff'}] + + QUERY = {"filter": {"and": [{"=": {"source": "openstack"}}, + {">": {"timestamp": "2014-02-19T05:50:16"}}]}, + "orderby": [{"timestamp": "desc"}, {"volume": "asc"}], + "limit": 10} + + def setUp(self): + super(ShellQuerySamplesCommandTest, self).setUp() + self.cc = mock.Mock() + self.args = mock.Mock() + self.args.filter = self.QUERY["filter"] + self.args.orderby = self.QUERY["orderby"] + self.args.limit = self.QUERY["limit"] + + @mock.patch('sys.stdout', new=six.StringIO()) + def test_query(self): + ret_sample = [samples.Sample(mock.Mock(), sample) + for sample in self.SAMPLE] + self.cc.query_samples.query.return_value = ret_sample + + ceilometer_shell.do_query_samples(self.cc, self.args) + + self.assertEqual('''\ ++--------------------------------------+----------+-------+--------+---------\ +-+----------------------------+ +| Resource ID | Meter | Type | Volume | Unit \ + | Timestamp | ++--------------------------------------+----------+-------+--------+---------\ +-+----------------------------+ +| bd9431c1-8d69-4ad3-803a-8d4a6b89fd36 | instance | gauge | 1 | instance\ + | 2014-02-19T05:50:16.673604 | ++--------------------------------------+----------+-------+--------+---------\ +-+----------------------------+ +''', sys.stdout.getvalue()) + + +class ShellQueryAlarmsCommandTest(utils.BaseTestCase): + + ALARM = [{"alarm_actions": ["http://site:8000/alarm"], + "alarm_id": "768ff714-8cfb-4db9-9753-d484cb33a1cc", + "combination_rule": { + "alarm_ids": [ + "739e99cb-c2ec-4718-b900-332502355f38", + "153462d0-a9b8-4b5b-8175-9e4b05e9b856"], + "operator": "or"}, + "description": "An alarm", + "enabled": True, + "insufficient_data_actions": ["http://site:8000/nodata"], + "name": "SwiftObjectAlarm", + "ok_actions": ["http://site:8000/ok"], + "project_id": "c96c887c216949acbdfbd8b494863567", + "repeat_actions": False, + "state": "ok", + "severity": "critical", + "state_timestamp": "2014-02-20T10:37:15.589860", + "threshold_rule": None, + "timestamp": "2014-02-20T10:37:15.589856", + "time_constraints": [{"name": "test", "start": "0 23 * * *", + "duration": 10800}], + "type": "combination", + "user_id": "c96c887c216949acbdfbd8b494863567"}] + + QUERY = {"filter": {"and": [{"!=": {"state": "ok"}}, + {"=": {"type": "combination"}}]}, + "orderby": [{"state_timestamp": "desc"}], + "limit": 10} + + def setUp(self): + super(ShellQueryAlarmsCommandTest, self).setUp() + self.cc = mock.Mock() + self.args = mock.Mock() + self.args.filter = self.QUERY["filter"] + self.args.orderby = self.QUERY["orderby"] + self.args.limit = self.QUERY["limit"] + + @mock.patch('sys.stdout', new=six.StringIO()) + def test_query(self): + ret_alarm = [alarms.Alarm(mock.Mock(), alarm) + for alarm in self.ALARM] + self.cc.query_alarms.query.return_value = ret_alarm + + ceilometer_shell.do_query_alarms(self.cc, self.args) + + self.assertEqual('''\ ++--------------------------------------+------------------+-------+----------+\ +---------+------------+-------------------------------------------------------\ +-----------------------------------------------+-------------------------------\ +-+ +| Alarm ID | Name | State | Severity \ +| Enabled | Continuous | Alarm condition \ + | Time constraints \ + | ++--------------------------------------+------------------+-------+----------+\ +---------+------------+-------------------------------------------------------\ +-----------------------------------------------+--------------------------------+ +| 768ff714-8cfb-4db9-9753-d484cb33a1cc | SwiftObjectAlarm | ok | critical \ +| True | False | combinated states (OR) of \ +739e99cb-c2ec-4718-b900-332502355f38, 153462d0-a9b8-4b5b-8175-9e4b05e9b856 |\ + test at 0 23 * * * for 10800s | ++--------------------------------------+------------------+-------+----------+\ +---------+------------+-------------------------------------------------------\ +-----------------------------------------------+------------------------------\ +--+ +''', sys.stdout.getvalue()) + + @mock.patch('sys.stdout', new=six.StringIO()) + def test_time_constraints_compatibility(self): + # client should be backwards compatible + alarm_without_tc = dict(self.ALARM[0]) + del alarm_without_tc['time_constraints'] + + # NOTE(nsaje): Since we're accessing a nonexisting key in the resource, + # the resource is looking it up with the manager (which is a mock). + manager_mock = mock.Mock() + del manager_mock.get + ret_alarm = [alarms.Alarm(manager_mock, alarm_without_tc)] + self.cc.query_alarms.query.return_value = ret_alarm + + ceilometer_shell.do_query_alarms(self.cc, self.args) + + self.assertEqual('''\ ++--------------------------------------+------------------+-------+----------+\ +---------+------------+-------------------------------------------------------\ +-----------------------------------------------+------------------+ +| Alarm ID | Name | State | Severity \ +| Enabled | Continuous | Alarm condition \ + | Time constraints | ++--------------------------------------+------------------+-------+----------+\ +---------+------------+-------------------------------------------------------\ +-----------------------------------------------+------------------+ +| 768ff714-8cfb-4db9-9753-d484cb33a1cc | SwiftObjectAlarm | ok | critical \ +| True | False | combinated states (OR) of \ +739e99cb-c2ec-4718-b900-332502355f38, 153462d0-a9b8-4b5b-8175-9e4b05e9b856 \ +| None | ++--------------------------------------+------------------+-------+----------+\ +---------+------------+-------------------------------------------------------\ +-----------------------------------------------+------------------+ +''', sys.stdout.getvalue()) + + +class ShellQueryAlarmHistoryCommandTest(utils.BaseTestCase): + + ALARM_HISTORY = [{"alarm_id": "e8ff32f772a44a478182c3fe1f7cad6a", + "event_id": "c74a8611-6553-4764-a860-c15a6aabb5d0", + "detail": + "{\"threshold\": 42.0, \"evaluation_periods\": 4}", + "on_behalf_of": "92159030020611e3b26dde429e99ee8c", + "project_id": "b6f16144010811e387e4de429e99ee8c", + "timestamp": "2014-03-11T16:02:58.376261", + "type": "rule change", + "user_id": "3e5d11fda79448ac99ccefb20be187ca" + }] + + QUERY = {"filter": {"and": [{">": {"timestamp": "2014-03-11T16:02:58"}}, + {"=": {"type": "rule change"}}]}, + "orderby": [{"timestamp": "desc"}], + "limit": 10} + + def setUp(self): + super(ShellQueryAlarmHistoryCommandTest, self).setUp() + self.cc = mock.Mock() + self.args = mock.Mock() + self.args.filter = self.QUERY["filter"] + self.args.orderby = self.QUERY["orderby"] + self.args.limit = self.QUERY["limit"] + + @mock.patch('sys.stdout', new=six.StringIO()) + def test_query(self): + ret_alarm_history = [alarms.AlarmChange(mock.Mock(), alarm_history) + for alarm_history in self.ALARM_HISTORY] + self.cc.query_alarm_history.query.return_value = ret_alarm_history + + ceilometer_shell.do_query_alarm_history(self.cc, self.args) + + self.assertEqual('''\ ++----------------------------------+--------------------------------------+-\ +------------+----------------------------------------------+----------------\ +------------+ +| Alarm ID | Event ID | \ +Type | Detail | Timestamp \ + | ++----------------------------------+--------------------------------------+-\ +------------+----------------------------------------------+----------------\ +------------+ +| e8ff32f772a44a478182c3fe1f7cad6a | c74a8611-6553-4764-a860-c15a6aabb5d0 | \ +rule change | {"threshold": 42.0, "evaluation_periods": 4} | 2014-03-11T16:0\ +2:58.376261 | ++----------------------------------+--------------------------------------+-\ +------------+----------------------------------------------+----------------\ +------------+ +''', sys.stdout.getvalue()) + + +class ShellStatisticsTest(utils.BaseTestCase): + def setUp(self): + super(ShellStatisticsTest, self).setUp() + self.cc = mock.Mock() + self.displays = { + 'duration': 'Duration', + 'duration_end': 'Duration End', + 'duration_start': 'Duration Start', + 'period': 'Period', + 'period_end': 'Period End', + 'period_start': 'Period Start', + 'groupby': 'Group By', + 'avg': 'Avg', + 'count': 'Count', + 'max': 'Max', + 'min': 'Min', + 'sum': 'Sum', + 'stddev': 'Standard deviation', + 'cardinality': 'Cardinality' + } + self.args = mock.Mock() + self.args.meter_name = 'instance' + self.args.aggregate = [] + self.args.groupby = None + self.args.query = None + + def test_statistics_list_simple(self): + samples = [ + {u'count': 135, + u'duration_start': u'2013-02-04T10:51:42', + u'min': 1.0, + u'max': 1.0, + u'duration_end': + u'2013-02-05T15:46:09', + u'duration': 1734.0, + u'avg': 1.0, + u'sum': 135.0}, + ] + fields = [ + 'period', + 'period_start', + 'period_end', + 'max', + 'min', + 'avg', + 'sum', + 'count', + 'duration', + 'duration_start', + 'duration_end', + ] + statistics_ret = [ + statistics.Statistics(mock.Mock(), sample) for sample in samples + ] + self.cc.statistics.list.return_value = statistics_ret + with mock.patch('ceilometerclient.v2.shell.utils.print_list') as pmock: + ceilometer_shell.do_statistics(self.cc, self.args) + pmock.assert_called_with( + statistics_ret, + fields, + [self.displays[f] for f in fields] + ) + + def test_statistics_list_groupby(self): + samples = [ + {u'count': 135, + u'duration_start': u'2013-02-04T10:51:42', + u'min': 1.0, + u'max': 1.0, + u'duration_end': + u'2013-02-05T15:46:09', + u'duration': 1734.0, + u'avg': 1.0, + u'sum': 135.0, + u'groupby': {u'resource_id': u'foo'} + }, + {u'count': 12, + u'duration_start': u'2013-02-04T10:51:42', + u'min': 1.0, + u'max': 1.0, + u'duration_end': + u'2013-02-05T15:46:09', + u'duration': 1734.0, + u'avg': 1.0, + u'sum': 12.0, + u'groupby': {u'resource_id': u'bar'} + }, + ] + fields = [ + 'period', + 'period_start', + 'period_end', + 'groupby', + 'max', + 'min', + 'avg', + 'sum', + 'count', + 'duration', + 'duration_start', + 'duration_end', + ] + self.args.groupby = 'resource_id' + statistics_ret = [ + statistics.Statistics(mock.Mock(), sample) for sample in samples + ] + self.cc.statistics.list.return_value = statistics_ret + with mock.patch('ceilometerclient.v2.shell.utils.print_list') as pmock: + ceilometer_shell.do_statistics(self.cc, self.args) + pmock.assert_called_with( + statistics_ret, + fields, + [self.displays[f] for f in fields], + ) + + def test_statistics_list_aggregates(self): + samples = [ + {u'aggregate': {u'cardinality/resource_id': 4.0, u'count': 2.0}, + u'count': 2, + u'duration': 0.442451, + u'duration_end': u'2014-03-12T14:00:21.774154', + u'duration_start': u'2014-03-12T14:00:21.331703', + u'groupby': None, + u'period': 0, + u'period_end': u'2014-03-12T14:00:21.774154', + u'period_start': u'2014-03-12T14:00:21.331703', + u'unit': u'instance', + }, + ] + fields = [ + 'period', + 'period_start', + 'period_end', + 'count', + 'cardinality/resource_id', + 'duration', + 'duration_start', + 'duration_end', + ] + self.args.aggregate = ['count', 'cardinality<-resource_id'] + statistics_ret = [ + statistics.Statistics(mock.Mock(), sample) for sample in samples + ] + self.cc.statistics.list.return_value = statistics_ret + with mock.patch('ceilometerclient.v2.shell.utils.print_list') as pmock: + ceilometer_shell.do_statistics(self.cc, self.args) + pmock.assert_called_with( + statistics_ret, + fields, + [self.displays.get(f, f) for f in fields], + ) + + +class ShellEmptyIdTest(utils.BaseTestCase): + """Test empty field which will cause calling incorrect rest uri.""" + + def _test_entity_action_with_empty_values(self, entity, + *args, **kwargs): + positional = kwargs.pop('positional', False) + for value in ('', ' ', ' ', '\t'): + self._test_entity_action_with_empty_value(entity, value, + positional, *args) + + def _test_entity_action_with_empty_value(self, entity, value, + positional, *args): + new_args = [value] if positional else ['--%s' % entity, value] + argv = list(args) + new_args + shell = base_shell.CeilometerShell() + with mock.patch('ceilometerclient.exc.CommandError') as e: + e.return_value = exc.BaseException() + self.assertRaises(exc.BaseException, shell.parse_args, argv) + entity = entity.replace('-', '_') + e.assert_called_with('%s should not be empty' % entity) + + def _test_alarm_action_with_empty_ids(self, method, *args): + args = [method] + list(args) + self._test_entity_action_with_empty_values('alarm_id', + positional=True, *args) + + def test_alarm_show_with_empty_id(self): + self._test_alarm_action_with_empty_ids('alarm-show') + + def test_alarm_update_with_empty_id(self): + self._test_alarm_action_with_empty_ids('alarm-update') + + def test_alarm_threshold_update_with_empty_id(self): + self._test_alarm_action_with_empty_ids('alarm-threshold-update') + + def test_alarm_combination_update_with_empty_id(self): + self._test_alarm_action_with_empty_ids('alarm-combination-update') + + def test_alarm_delete_with_empty_id(self): + self._test_alarm_action_with_empty_ids('alarm-delete') + + def test_alarm_state_get_with_empty_id(self): + self._test_alarm_action_with_empty_ids('alarm-state-get') + + def test_alarm_state_set_with_empty_id(self): + args = ['alarm-state-set', '--state', 'ok'] + self._test_alarm_action_with_empty_ids(*args) + + def test_alarm_history_with_empty_id(self): + self._test_alarm_action_with_empty_ids('alarm-history') + + def test_event_show_with_empty_message_id(self): + args = ['event-show'] + self._test_entity_action_with_empty_values('message_id', *args) + + def test_resource_show_with_empty_id(self): + args = ['resource-show'] + self._test_entity_action_with_empty_values('resource_id', *args) + + def test_sample_list_with_empty_meter(self): + args = ['sample-list'] + self._test_entity_action_with_empty_values('meter', *args) + + def test_sample_create_with_empty_meter(self): + args = ['sample-create', '-r', 'x', '--meter-type', 'gauge', + '--meter-unit', 'B', '--sample-volume', '1'] + self._test_entity_action_with_empty_values('meter-name', *args) + + def test_statistics_with_empty_meter(self): + args = ['statistics'] + self._test_entity_action_with_empty_values('meter', *args) + + def test_trait_description_list_with_empty_event_type(self): + args = ['trait-description-list'] + self._test_entity_action_with_empty_values('event_type', *args) + + def test_trait_list_with_empty_event_type(self): + args = ['trait-list', '--trait_name', 'x'] + self._test_entity_action_with_empty_values('event_type', *args) + + def test_trait_list_with_empty_trait_name(self): + args = ['trait-list', '--event_type', 'x'] + self._test_entity_action_with_empty_values('trait_name', *args) + + +class ShellObsoletedArgsTest(utils.BaseTestCase): + """Test arguments that have been obsoleted.""" + + def _test_entity_obsoleted(self, entity, value, positional, *args): + new_args = [value] if positional else ['--%s' % entity, value] + argv = list(args) + new_args + shell = base_shell.CeilometerShell() + with mock.patch('sys.stdout', new_callable=six.StringIO) as stdout: + shell.parse_args(argv) + self.assertIn('obsolete', stdout.getvalue()) + + def test_obsolete_alarm_id(self): + for method in ['alarm-show', 'alarm-update', 'alarm-threshold-update', + 'alarm-combination-update', 'alarm-delete', + 'alarm-state-get', 'alarm-history']: + self._test_entity_obsoleted('alarm_id', 'abcde', False, method) + + +class ShellEventListCommandTest(utils.BaseTestCase): + + EVENTS = [ + { + "traits": [], + "generated": "2015-01-12T04:03:25.741471", + "message_id": "fb2bef58-88af-4380-8698-e0f18fcf452d", + "event_type": "compute.instance.create.start", + "traits": [{ + "name": "state", + "type": "string", + "value": "building", + }], + }, + { + "traits": [], + "generated": "2015-01-12T04:03:28.452495", + "message_id": "9b20509a-576b-4995-acfa-1a24ee5cf49f", + "event_type": "compute.instance.create.end", + "traits": [{ + "name": "state", + "type": "string", + "value": "active", + }], + }, + ] + + def setUp(self): + super(ShellEventListCommandTest, self).setUp() + self.cc = mock.Mock() + self.args = mock.Mock() + self.args.query = None + self.args.no_traits = None + + @mock.patch('sys.stdout', new=six.StringIO()) + def test_event_list(self): + ret_events = [events.Event(mock.Mock(), event) + for event in self.EVENTS] + self.cc.events.list.return_value = ret_events + ceilometer_shell.do_event_list(self.cc, self.args) + self.assertEqual('''\ ++--------------------------------------+-------------------------------+\ +----------------------------+-------------------------------+ +| Message ID | Event Type |\ + Generated | Traits | ++--------------------------------------+-------------------------------+\ +----------------------------+-------------------------------+ +| fb2bef58-88af-4380-8698-e0f18fcf452d | compute.instance.create.start |\ + 2015-01-12T04:03:25.741471 | +-------+--------+----------+ | +| | |\ + | | name | type | value | | +| | |\ + | +-------+--------+----------+ | +| | |\ + | | state | string | building | | +| | |\ + | +-------+--------+----------+ | +| 9b20509a-576b-4995-acfa-1a24ee5cf49f | compute.instance.create.end |\ + 2015-01-12T04:03:28.452495 | +-------+--------+--------+ | +| | |\ + | | name | type | value | | +| | |\ + | +-------+--------+--------+ | +| | |\ + | | state | string | active | | +| | |\ + | +-------+--------+--------+ | ++--------------------------------------+-------------------------------+\ +----------------------------+-------------------------------+ +''', sys.stdout.getvalue()) + + @mock.patch('sys.stdout', new=six.StringIO()) + def test_event_list_no_traits(self): + self.args.no_traits = True + ret_events = [events.Event(mock.Mock(), event) + for event in self.EVENTS] + self.cc.events.list.return_value = ret_events + ceilometer_shell.do_event_list(self.cc, self.args) + self.assertEqual('''\ ++--------------------------------------+-------------------------------\ ++----------------------------+ +| Message ID | Event Type \ +| Generated | ++--------------------------------------+-------------------------------\ ++----------------------------+ +| fb2bef58-88af-4380-8698-e0f18fcf452d | compute.instance.create.start \ +| 2015-01-12T04:03:25.741471 | +| 9b20509a-576b-4995-acfa-1a24ee5cf49f | compute.instance.create.end \ +| 2015-01-12T04:03:28.452495 | ++--------------------------------------+-------------------------------\ ++----------------------------+ +''', sys.stdout.getvalue()) + + +class ShellShadowedArgsTest(test_shell.ShellTestBase): + + def _test_project_id_alarm(self, command, args, method): + self.make_env(test_shell.FAKE_V2_ENV) + cli_args = [ + '--os-project-id', '0ba30185ddf44834914a0b859d244c56', + '--debug', command, + '--project-id', 'the-project-id-i-want-to-set', + '--name', 'project-id-test'] + args + with mock.patch.object(alarms.AlarmManager, method) as mocked: + base_shell.main(cli_args) + args, kwargs = mocked.call_args + self.assertEqual('the-project-id-i-want-to-set', + kwargs.get('project_id')) + + def test_project_id_threshold_alarm(self): + cli_args = ['--meter-name', 'cpu', '--threshold', '90'] + self._test_project_id_alarm('alarm-create', cli_args, 'create') + self._test_project_id_alarm('alarm-threshold-create', + cli_args, 'create') + cli_args += ['--alarm_id', '437b7ed0-3733-4054-a877-e9a297b8be85'] + self._test_project_id_alarm('alarm-update', cli_args, 'update') + self._test_project_id_alarm('alarm-threshold-update', + cli_args, 'update') + + def test_project_id_combination_alarm(self): + cli_args = ['--alarm_ids', 'fb16a05a-669d-414e-8bbe-93aa381df6a8', + '--alarm_ids', 'b189bcca-0a7b-49a9-a244-a927ac291881'] + self._test_project_id_alarm('alarm-combination-create', + cli_args, 'create') + cli_args += ['--alarm_id', '437b7ed0-3733-4054-a877-e9a297b8be85'] + self._test_project_id_alarm('alarm-combination-update', + cli_args, 'update') + + @mock.patch.object(samples.OldSampleManager, 'create') + def test_project_id_sample_create(self, mocked): + self.make_env(test_shell.FAKE_V2_ENV) + cli_args = [ + '--os-project-id', '0ba30185ddf44834914a0b859d244c56', + '--debug', 'sample-create', + '--project-id', 'the-project-id-i-want-to-set', + '--resource-id', 'b666633d-9bb6-4e05-89c0-ee5a8752fb0b', + '--meter-name', 'cpu', + '--meter-type', 'cumulative', + '--meter-unit', 'ns', + '--sample-volume', '10086', + ] + base_shell.main(cli_args) + args, kwargs = mocked.call_args + self.assertEqual('the-project-id-i-want-to-set', + kwargs.get('project_id')) diff --git a/ceilometerclient/tests/unit/v2/test_statistics.py b/ceilometerclient/tests/unit/v2/test_statistics.py new file mode 100644 index 0000000..d2f7aee --- /dev/null +++ b/ceilometerclient/tests/unit/v2/test_statistics.py @@ -0,0 +1,224 @@ +# Copyright 2012 OpenStack Foundation +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from ceilometerclient.openstack.common.apiclient import client +from ceilometerclient.openstack.common.apiclient import fake_client +from ceilometerclient.tests.unit import utils +import ceilometerclient.v2.statistics + +base_url = '/v2/meters/instance/statistics' +qry = ('q.field=resource_id&q.field=source&q.op=&q.op=' + '&q.type=&q.type=&q.value=foo&q.value=bar') +period = '&period=60' +groupby = '&groupby=resource_id' +aggregate_query = ("aggregate.func=cardinality&aggregate.param=resource_id" + "&aggregate.func=count") +samples = [ + {u'count': 135, + u'duration_start': u'2013-02-04T10:51:42', + u'min': 1.0, + u'max': 1.0, + u'duration_end': + u'2013-02-05T15:46:09', + u'duration': 1734.0, + u'avg': 1.0, + u'sum': 135.0}, +] +groupby_samples = [ + {u'count': 135, + u'duration_start': u'2013-02-04T10:51:42', + u'min': 1.0, + u'max': 1.0, + u'duration_end': + u'2013-02-05T15:46:09', + u'duration': 1734.0, + u'avg': 1.0, + u'sum': 135.0, + u'groupby': {u'resource_id': u'foo'} + }, + {u'count': 12, + u'duration_start': u'2013-02-04T10:51:42', + u'min': 1.0, + u'max': 1.0, + u'duration_end': + u'2013-02-05T15:46:09', + u'duration': 1734.0, + u'avg': 1.0, + u'sum': 12.0, + u'groupby': {u'resource_id': u'bar'} + }, +] +aggregate_samples = [ + {u'aggregate': {u'cardinality/resource_id': 4.0, u'count': 2.0}, + u'count': 2, + u'duration': 0.442451, + u'duration_end': u'2014-03-12T14:00:21.774154', + u'duration_start': u'2014-03-12T14:00:21.331703', + u'groupby': None, + u'period': 0, + u'period_end': u'2014-03-12T14:00:21.774154', + u'period_start': u'2014-03-12T14:00:21.331703', + u'unit': u'instance', + }, +] +fixtures = { + base_url: + { + 'GET': ( + {}, + samples + ), + }, + '%s?%s' % (base_url, qry): + { + 'GET': ( + {}, + samples + ), + }, + '%s?%s%s' % (base_url, qry, period): + { + 'GET': ( + {}, + samples + ), + }, + '%s?%s%s' % (base_url, qry, groupby): + { + 'GET': ( + {}, + groupby_samples + ), + }, + '%s?%s' % (base_url, aggregate_query): + { + 'GET': ( + {}, + aggregate_samples + ), + } +} + + +class StatisticsManagerTest(utils.BaseTestCase): + + def setUp(self): + super(StatisticsManagerTest, self).setUp() + self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) + self.api = client.BaseClient(self.http_client) + self.mgr = ceilometerclient.v2.statistics.StatisticsManager(self.api) + + def test_list_by_meter_name(self): + stats = list(self.mgr.list(meter_name='instance')) + expect = [ + 'GET', '/v2/meters/instance/statistics' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(stats), 1) + self.assertEqual(stats[0].count, 135) + + def test_list_by_meter_name_extended(self): + stats = list(self.mgr.list(meter_name='instance', + q=[ + {"field": "resource_id", + "value": "foo"}, + {"field": "source", + "value": "bar"}, + ])) + expect = [ + 'GET', '%s?%s' % (base_url, qry) + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(stats), 1) + self.assertEqual(stats[0].count, 135) + + def test_list_by_meter_name_with_period(self): + stats = list(self.mgr.list(meter_name='instance', + q=[ + {"field": "resource_id", + "value": "foo"}, + {"field": "source", + "value": "bar"}, + ], + period=60)) + expect = [ + 'GET', '%s?%s%s' % (base_url, qry, period) + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(stats), 1) + self.assertEqual(stats[0].count, 135) + + def test_list_by_meter_name_with_groupby(self): + stats = list(self.mgr.list(meter_name='instance', + q=[ + {"field": "resource_id", + "value": "foo"}, + {"field": "source", + "value": "bar"}, + ], + groupby=['resource_id'])) + expect = [ + 'GET', + '%s?%s%s' % (base_url, qry, groupby) + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(stats), 2) + self.assertEqual(stats[0].count, 135) + self.assertEqual(stats[1].count, 12) + self.assertEqual(stats[0].groupby.get('resource_id'), 'foo') + self.assertEqual(stats[1].groupby.get('resource_id'), 'bar') + + def test_list_by_meter_name_with_groupby_as_str(self): + stats = list(self.mgr.list(meter_name='instance', + q=[ + {"field": "resource_id", + "value": "foo"}, + {"field": "source", + "value": "bar"}, + ], + groupby='resource_id')) + expect = [ + 'GET', + '%s?%s%s' % (base_url, qry, groupby) + ] + self.http_client.assert_called(*expect) + self.assertEqual(2, len(stats)) + self.assertEqual(135, stats[0].count) + self.assertEqual(12, stats[1].count) + self.assertEqual('foo', stats[0].groupby.get('resource_id')) + self.assertEqual('bar', stats[1].groupby.get('resource_id')) + + def test_list_by_meter_name_with_aggregates(self): + aggregates = [ + { + 'func': 'count', + }, + { + 'func': 'cardinality', + 'param': 'resource_id', + }, + ] + stats = list(self.mgr.list(meter_name='instance', + aggregates=aggregates)) + expect = [ + 'GET', + '%s?%s' % (base_url, aggregate_query) + ] + self.http_client.assert_called(*expect) + self.assertEqual(1, len(stats)) + self.assertEqual(2, stats[0].count) + self.assertEqual(2.0, stats[0].aggregate.get('count')) + self.assertEqual(4.0, stats[0].aggregate.get( + 'cardinality/resource_id', + )) diff --git a/ceilometerclient/tests/unit/v2/test_trait_descriptions.py b/ceilometerclient/tests/unit/v2/test_trait_descriptions.py new file mode 100644 index 0000000..abe8288 --- /dev/null +++ b/ceilometerclient/tests/unit/v2/test_trait_descriptions.py @@ -0,0 +1,56 @@ +# Copyright 2014 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from ceilometerclient.openstack.common.apiclient import client +from ceilometerclient.openstack.common.apiclient import fake_client +from ceilometerclient.tests.unit import utils +import ceilometerclient.v2.trait_descriptions + + +fixtures = { + '/v2/event_types/Foo/traits': { + 'GET': ( + {}, + [ + {'name': 'trait_1', 'type': 'string'}, + {'name': 'trait_2', 'type': 'integer'}, + {'name': 'trait_3', 'type': 'datetime'} + ] + ), + } +} + + +class TraitDescriptionManagerTest(utils.BaseTestCase): + + def setUp(self): + super(TraitDescriptionManagerTest, self).setUp() + self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) + self.api = client.BaseClient(self.http_client) + self.mgr = (ceilometerclient.v2.trait_descriptions. + TraitDescriptionManager(self.api)) + + def test_list(self): + trait_descriptions = list(self.mgr.list('Foo')) + expect = [ + 'GET', '/v2/event_types/Foo/traits' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(trait_descriptions), 3) + for i, vals in enumerate([('trait_1', 'string'), + ('trait_2', 'integer'), + ('trait_3', 'datetime')]): + + name, type = vals + self.assertEqual(trait_descriptions[i].name, name) + self.assertEqual(trait_descriptions[i].type, type) diff --git a/ceilometerclient/tests/unit/v2/test_traits.py b/ceilometerclient/tests/unit/v2/test_traits.py new file mode 100644 index 0000000..7b44a69 --- /dev/null +++ b/ceilometerclient/tests/unit/v2/test_traits.py @@ -0,0 +1,62 @@ +# Copyright 2014 Hewlett-Packard Development Company, L.P. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. +from ceilometerclient.openstack.common.apiclient import client +from ceilometerclient.openstack.common.apiclient import fake_client +from ceilometerclient.tests.unit import utils +import ceilometerclient.v2.traits + + +fixtures = { + '/v2/event_types/Foo/traits/trait_1': { + 'GET': ( + {}, + [ + {'name': 'trait_1', + 'type': 'datetime', + 'value': '2014-01-07T17:22:10.925553'}, + {'name': 'trait_1', + 'type': 'datetime', + 'value': '2014-01-07T17:23:10.925553'} + ] + ), + } +} + + +class TraitManagerTest(utils.BaseTestCase): + + def setUp(self): + super(TraitManagerTest, self).setUp() + self.http_client = fake_client.FakeHTTPClient(fixtures=fixtures) + self.api = client.BaseClient(self.http_client) + self.mgr = ceilometerclient.v2.traits.TraitManager(self.api) + + def test_list(self): + traits = list(self.mgr.list('Foo', 'trait_1')) + expect = [ + 'GET', '/v2/event_types/Foo/traits/trait_1' + ] + self.http_client.assert_called(*expect) + self.assertEqual(len(traits), 2) + for i, vals in enumerate([('trait_1', + 'datetime', + '2014-01-07T17:22:10.925553'), + ('trait_1', + 'datetime', + '2014-01-07T17:23:10.925553')]): + + name, type, value = vals + self.assertEqual(traits[i].name, name) + self.assertEqual(traits[i].type, type) + self.assertEqual(traits[i].value, value) |
