diff --git a/novaclient/tests/functional/v2/test_instance_usage_audit_log.py b/novaclient/tests/functional/v2/test_instance_usage_audit_log.py new file mode 100644 index 000000000..5e7ce8892 --- /dev/null +++ b/novaclient/tests/functional/v2/test_instance_usage_audit_log.py @@ -0,0 +1,87 @@ +# 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 datetime + +from oslo_utils import timeutils + +from novaclient.tests.functional import base + + +class TestInstanceUsageAuditLogCLI(base.ClientTestBase): + COMPUTE_API_VERSION = '2.1' + + # NOTE(takashin): By default, 'instance_usage_audit' is False in nova. + # So the instance usage audit log is not recoreded. + # Therefore an empty result can be got. + # But it is tested here to call APIs and get responses normally. + + @staticmethod + def _get_begin_end_time(): + current = timeutils.utcnow() + + end = datetime.datetime(day=1, month=current.month, year=current.year) + year = end.year + + if current.month == 1: + year -= 1 + month = 12 + else: + month = current.month - 1 + + begin = datetime.datetime(day=1, month=month, year=year) + + return (begin, end) + + def test_get_os_instance_usage_audit_log(self): + (begin, end) = self._get_begin_end_time() + expected = { + 'hosts_not_run': '[]', + 'log': '{}', + 'num_hosts': '0', + 'num_hosts_done': '0', + 'num_hosts_not_run': '0', + 'num_hosts_running': '0', + 'overall_status': 'ALL hosts done. 0 errors.', + 'total_errors': '0', + 'total_instances': '0', + 'period_beginning': str(begin), + 'period_ending': str(end) + } + + output = self.nova('instance-usage-audit-log') + + for key in expected.keys(): + self.assertEqual(expected[key], + self._get_value_from_the_table(output, key)) + + def test_get_os_instance_usage_audit_log_with_before(self): + expected = { + 'hosts_not_run': '[]', + 'log': '{}', + 'num_hosts': '0', + 'num_hosts_done': '0', + 'num_hosts_not_run': '0', + 'num_hosts_running': '0', + 'overall_status': 'ALL hosts done. 0 errors.', + 'total_errors': '0', + 'total_instances': '0', + 'period_beginning': '2016-11-01 00:00:00', + 'period_ending': '2016-12-01 00:00:00' + } + + output = self.nova( + 'instance-usage-audit-log --before "2016-12-10 13:59:59.999999"') + + for key in expected.keys(): + self.assertEqual(expected[key], + self._get_value_from_the_table(output, key)) diff --git a/novaclient/tests/unit/v2/fakes.py b/novaclient/tests/unit/v2/fakes.py index 4647d8055..24d1784d5 100644 --- a/novaclient/tests/unit/v2/fakes.py +++ b/novaclient/tests/unit/v2/fakes.py @@ -124,6 +124,8 @@ class FakeSessionClient(base_client.SessionClient): munged_url = munged_url.replace(' ', '_') munged_url = munged_url.replace('!', '_') munged_url = munged_url.replace('@', '_') + munged_url = munged_url.replace('%20', '_') + munged_url = munged_url.replace('%3A', '_') callback = "%s_%s" % (method.lower(), munged_url) if url is None or callback == "get_http:__nova_api:8774": @@ -1974,6 +1976,86 @@ class FakeSessionClient(base_client.SessionClient): return (200, FAKE_RESPONSE_HEADERS, { "instanceAction": action}) + def get_os_instance_usage_audit_log(self, **kw): + return (200, FAKE_RESPONSE_HEADERS, { + "instance_usage_audit_logs": { + "hosts_not_run": ["samplehost3"], + "log": { + "samplehost0": { + "errors": 1, + "instances": 1, + "message": ("Instance usage audit ran for host " + "samplehost0, 1 instances in 0.01 " + "seconds."), + "state": "DONE" + }, + "samplehost1": { + "errors": 1, + "instances": 2, + "message": ("Instance usage audit ran for host " + "samplehost1, 2 instances in 0.01 " + "seconds."), + "state": "DONE" + }, + "samplehost2": { + "errors": 1, + "instances": 3, + "message": ("Instance usage audit ran for host " + "samplehost2, 3 instances in 0.01 " + "seconds."), + "state": "DONE" + }, + }, + "num_hosts": 4, + "num_hosts_done": 3, + "num_hosts_not_run": 1, + "num_hosts_running": 0, + "overall_status": "3 of 4 hosts done. 3 errors.", + "period_beginning": "2012-06-01 00:00:00", + "period_ending": "2012-07-01 00:00:00", + "total_errors": 3, + "total_instances": 6}}) + + def get_os_instance_usage_audit_log_2016_12_10_13_59_59_999999(self, **kw): + return (200, FAKE_RESPONSE_HEADERS, { + "instance_usage_audit_log": { + "hosts_not_run": ["samplehost3"], + "log": { + "samplehost0": { + "errors": 1, + "instances": 1, + "message": ("Instance usage audit ran for host " + "samplehost0, 1 instances in 0.01 " + "seconds."), + "state": "DONE" + }, + "samplehost1": { + "errors": 1, + "instances": 2, + "message": ("Instance usage audit ran for host " + "samplehost1, 2 instances in 0.01 " + "seconds."), + "state": "DONE" + }, + "samplehost2": { + "errors": 1, + "instances": 3, + "message": ("Instance usage audit ran for host " + "samplehost2, 3 instances in 0.01 " + "seconds."), + "state": "DONE" + }, + }, + "num_hosts": 4, + "num_hosts_done": 3, + "num_hosts_not_run": 1, + "num_hosts_running": 0, + "overall_status": "3 of 4 hosts done. 3 errors.", + "period_beginning": "2012-06-01 00:00:00", + "period_ending": "2012-07-01 00:00:00", + "total_errors": 3, + "total_instances": 6}}) + def post_servers_uuid1_action(self, **kw): return 202, {}, {} diff --git a/novaclient/tests/unit/v2/test_instance_usage_audit_log.py b/novaclient/tests/unit/v2/test_instance_usage_audit_log.py new file mode 100644 index 000000000..148ebbda2 --- /dev/null +++ b/novaclient/tests/unit/v2/test_instance_usage_audit_log.py @@ -0,0 +1,37 @@ +# Copyright 2013 Rackspace Hosting +# 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 novaclient import api_versions +from novaclient.tests.unit import utils +from novaclient.tests.unit.v2 import fakes + + +class InstanceUsageAuditLogTests(utils.TestCase): + def setUp(self): + super(InstanceUsageAuditLogTests, self).setUp() + self.cs = fakes.FakeClient(api_versions.APIVersion("2.1")) + + def test_instance_usage_audit_log(self): + audit_log = self.cs.instance_usage_audit_log.get() + self.assert_request_id(audit_log, fakes.FAKE_REQUEST_ID_LIST) + self.cs.assert_called('GET', '/os-instance_usage_audit_log') + + def test_instance_usage_audit_log_with_before(self): + audit_log = self.cs.instance_usage_audit_log.get( + before='2016-12-10 13:59:59.999999') + self.assert_request_id(audit_log, fakes.FAKE_REQUEST_ID_LIST) + self.cs.assert_called( + 'GET', + '/os-instance_usage_audit_log/2016-12-10%2013%3A59%3A59.999999') diff --git a/novaclient/tests/unit/v2/test_shell.py b/novaclient/tests/unit/v2/test_shell.py index 34c6717b5..d971e6b47 100644 --- a/novaclient/tests/unit/v2/test_shell.py +++ b/novaclient/tests/unit/v2/test_shell.py @@ -3019,6 +3019,17 @@ class ShellTest(utils.TestCase): api_version='2.58') self.assertIn('Invalid changes-since value', six.text_type(ex)) + def test_instance_usage_audit_log(self): + self.run_command('instance-usage-audit-log') + self.assert_called('GET', '/os-instance_usage_audit_log') + + def test_instance_usage_audit_log_with_before(self): + self.run_command( + ["instance-usage-audit-log", "--before", + "2016-12-10 13:59:59.999999"]) + self.assert_called('GET', '/os-instance_usage_audit_log' + '/2016-12-10%2013%3A59%3A59.999999') + def test_cell_show(self): self.run_command('cell-show child_cell') self.assert_called('GET', '/os-cells/child_cell') diff --git a/novaclient/v2/client.py b/novaclient/v2/client.py index 1f543a820..bc66575f4 100644 --- a/novaclient/v2/client.py +++ b/novaclient/v2/client.py @@ -29,6 +29,7 @@ from novaclient.v2 import flavors from novaclient.v2 import hypervisors from novaclient.v2 import images from novaclient.v2 import instance_action +from novaclient.v2 import instance_usage_audit_log from novaclient.v2 import keypairs from novaclient.v2 import limits from novaclient.v2 import list_extensions @@ -169,6 +170,8 @@ class Client(object): assisted_volume_snapshots.AssistedSnapshotManager(self) self.cells = cells.CellsManager(self) self.instance_action = instance_action.InstanceActionManager(self) + self.instance_usage_audit_log = \ + instance_usage_audit_log.InstanceUsageAuditLogManager(self) self.list_extensions = list_extensions.ListExtManager(self) self.migrations = migrations.MigrationManager(self) self.server_external_events = \ diff --git a/novaclient/v2/instance_usage_audit_log.py b/novaclient/v2/instance_usage_audit_log.py new file mode 100644 index 000000000..19b588e9a --- /dev/null +++ b/novaclient/v2/instance_usage_audit_log.py @@ -0,0 +1,40 @@ +# Copyright 2013 Rackspace Hosting +# 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 six.moves.urllib import parse + +from novaclient import base + + +class InstanceUsageAuditLog(base.Resource): + pass + + +class InstanceUsageAuditLogManager(base.Manager): + resource_class = InstanceUsageAuditLog + + def get(self, before=None): + """Get server usage audits. + + :param before: Filters the response by the date and time + before which to list usage audits. + """ + if before: + return self._get('/os-instance_usage_audit_log/%s' % + parse.quote(before, safe=''), + 'instance_usage_audit_log') + else: + return self._get('/os-instance_usage_audit_log', + 'instance_usage_audit_logs') diff --git a/novaclient/v2/shell.py b/novaclient/v2/shell.py index 7c05d1691..e75ebb564 100644 --- a/novaclient/v2/shell.py +++ b/novaclient/v2/shell.py @@ -5084,3 +5084,20 @@ def do_migration_list(cs, args): changes_since=args.changes_since) # TODO(yikun): Output a "Marker" column if there is a next link? _print_migrations(cs, migrations) + + +@utils.arg( + '--before', + dest='before', + metavar='', + default=None, + help=_("Filters the response by the date and time before which to list " + "usage audits. The date and time stamp format is as follows: " + "CCYY-MM-DD hh:mm:ss.NNNNNN ex 2015-08-27 09:49:58 or " + "2015-08-27 09:49:58.123456.")) +def do_instance_usage_audit_log(cs, args): + """List/Get server usage audits.""" + audit_log = cs.instance_usage_audit_log.get(before=args.before).to_dict() + if 'hosts_not_run' in audit_log: + audit_log['hosts_not_run'] = pprint.pformat(audit_log['hosts_not_run']) + utils.print_dict(audit_log) diff --git a/releasenotes/notes/show-instance-usage-audit-logs-7826b411fac1283b.yaml b/releasenotes/notes/show-instance-usage-audit-logs-7826b411fac1283b.yaml new file mode 100644 index 000000000..57abbd307 --- /dev/null +++ b/releasenotes/notes/show-instance-usage-audit-logs-7826b411fac1283b.yaml @@ -0,0 +1,8 @@ +--- +features: + - Added new client API and CLI (``nova instance-usage-audit-log``) + to get server usage audit logs. + By default, it lists usage audits for all servers on all + compute hosts where usage auditing is configured. + If you specify the ``--before`` option, the result is filtered + by the date and time before which to list server usage audits.