From d418b5f245f4cef4d35b8795aa6af8b98cd60141 Mon Sep 17 00:00:00 2001 From: int32bit Date: Sat, 10 Dec 2016 12:12:41 +0800 Subject: [PATCH] Add CLI to show instance usage audit logs Currently we can get instance usage audit logs via Nova API, and the docs also update for it. It is necessary to add that to our client and CLI. This patch adds the following command. nova instance-usage-audit-log [--before ] Co-Authored-by: Takashi Natsume Change-Id: I4ef8e40c322f1768ee1b5e01e9681cab0e2804bd --- .../v2/test_instance_usage_audit_log.py | 87 +++++++++++++++++++ novaclient/tests/unit/v2/fakes.py | 82 +++++++++++++++++ .../unit/v2/test_instance_usage_audit_log.py | 37 ++++++++ novaclient/tests/unit/v2/test_shell.py | 11 +++ novaclient/v2/client.py | 3 + novaclient/v2/instance_usage_audit_log.py | 40 +++++++++ novaclient/v2/shell.py | 17 ++++ ...nce-usage-audit-logs-7826b411fac1283b.yaml | 8 ++ 8 files changed, 285 insertions(+) create mode 100644 novaclient/tests/functional/v2/test_instance_usage_audit_log.py create mode 100644 novaclient/tests/unit/v2/test_instance_usage_audit_log.py create mode 100644 novaclient/v2/instance_usage_audit_log.py create mode 100644 releasenotes/notes/show-instance-usage-audit-logs-7826b411fac1283b.yaml 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.