diff --git a/doc/v3/api_samples/os-instance-usage-audit-log/inst-usage-audit-log-index-get-resp.json b/doc/v3/api_samples/os-instance-usage-audit-log/inst-usage-audit-log-index-get-resp.json new file mode 100644 index 000000000000..1d308d4ae803 --- /dev/null +++ b/doc/v3/api_samples/os-instance-usage-audit-log/inst-usage-audit-log-index-get-resp.json @@ -0,0 +1,17 @@ +{ + "instance_usage_audit_logs": { + "hosts_not_run": [ + "f4eb7cfd155f4574967f8b55a7faed75" + ], + "log": {}, + "num_hosts": 1, + "num_hosts_done": 0, + "num_hosts_not_run": 1, + "num_hosts_running": 0, + "overall_status": "0 of 1 hosts done. 0 errors.", + "period_beginning": "2012-12-01 00:00:00", + "period_ending": "2013-01-01 00:00:00", + "total_errors": 0, + "total_instances": 0 + } +} \ No newline at end of file diff --git a/doc/v3/api_samples/os-instance-usage-audit-log/inst-usage-audit-log-show-get-resp.json b/doc/v3/api_samples/os-instance-usage-audit-log/inst-usage-audit-log-show-get-resp.json new file mode 100644 index 000000000000..2b5fe54c184b --- /dev/null +++ b/doc/v3/api_samples/os-instance-usage-audit-log/inst-usage-audit-log-show-get-resp.json @@ -0,0 +1,17 @@ +{ + "instance_usage_audit_log": { + "hosts_not_run": [ + "8e33da2b48684ef3ab165444d6a7384c" + ], + "log": {}, + "num_hosts": 1, + "num_hosts_done": 0, + "num_hosts_not_run": 1, + "num_hosts_running": 0, + "overall_status": "0 of 1 hosts done. 0 errors.", + "period_beginning": "2012-06-01 00:00:00", + "period_ending": "2012-07-01 00:00:00", + "total_errors": 0, + "total_instances": 0 + } +} \ No newline at end of file diff --git a/etc/nova/policy.json b/etc/nova/policy.json index 2a8b6e8bcff8..5d38b654a4b1 100644 --- a/etc/nova/policy.json +++ b/etc/nova/policy.json @@ -190,6 +190,8 @@ "compute_extension:instance_actions:events": "rule:admin_api", "compute_extension:v3:os-instance-actions:events": "rule:admin_api", "compute_extension:instance_usage_audit_log": "rule:admin_api", + "compute_extension:v3:os-instance-usage-audit-log": "rule:admin_api", + "compute_extension:v3:os-instance-usage-audit-log:discoverable": "", "compute_extension:v3:ips:discoverable": "", "compute_extension:keypairs": "", "compute_extension:keypairs:index": "", diff --git a/nova/api/openstack/compute/plugins/v3/instance_usage_audit_log.py b/nova/api/openstack/compute/plugins/v3/instance_usage_audit_log.py new file mode 100644 index 000000000000..4d872f1f2f9b --- /dev/null +++ b/nova/api/openstack/compute/plugins/v3/instance_usage_audit_log.py @@ -0,0 +1,141 @@ +# 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 datetime + +from oslo.config import cfg +import webob.exc + +from nova.api.openstack import extensions +from nova.api.openstack import wsgi +from nova import compute +from nova.i18n import _ +from nova import utils + +CONF = cfg.CONF +CONF.import_opt('compute_topic', 'nova.compute.rpcapi') + + +ALIAS = 'os-instance-usage-audit-log' +authorize = extensions.extension_authorizer('compute', 'v3:' + ALIAS) + + +class InstanceUsageAuditLogController(wsgi.Controller): + def __init__(self): + self.host_api = compute.HostAPI() + + @extensions.expected_errors(()) + def index(self, req): + context = req.environ['nova.context'] + authorize(context) + task_log = self._get_audit_task_logs(context) + return {'instance_usage_audit_logs': task_log} + + @extensions.expected_errors(400) + def show(self, req, id): + context = req.environ['nova.context'] + authorize(context) + try: + if '.' in id: + before_date = datetime.datetime.strptime(str(id), + "%Y-%m-%d %H:%M:%S.%f") + else: + before_date = datetime.datetime.strptime(str(id), + "%Y-%m-%d %H:%M:%S") + except ValueError: + msg = _("Invalid timestamp for date %s") % id + raise webob.exc.HTTPBadRequest(explanation=msg) + task_log = self._get_audit_task_logs(context, + before=before_date) + return {'instance_usage_audit_log': task_log} + + def _get_audit_task_logs(self, context, begin=None, end=None, + before=None): + """Returns a full log for all instance usage audit tasks on all + computes. + + :param begin: datetime beginning of audit period to get logs for, + Defaults to the beginning of the most recently completed + audit period prior to the 'before' date. + :param end: datetime ending of audit period to get logs for, + Defaults to the ending of the most recently completed + audit period prior to the 'before' date. + :param before: By default we look for the audit period most recently + completed before this datetime. Has no effect if both begin and end + are specified. + """ + defbegin, defend = utils.last_completed_audit_period(before=before) + if begin is None: + begin = defbegin + if end is None: + end = defend + task_logs = self.host_api.task_log_get_all(context, + "instance_usage_audit", + begin, end) + # We do this this way to include disabled compute services, + # which can have instances on them. (mdragon) + filters = {'topic': CONF.compute_topic} + services = self.host_api.service_get_all(context, filters=filters) + hosts = set(serv['host'] for serv in services) + seen_hosts = set() + done_hosts = set() + running_hosts = set() + total_errors = 0 + total_items = 0 + for tlog in task_logs: + seen_hosts.add(tlog['host']) + if tlog['state'] == "DONE": + done_hosts.add(tlog['host']) + if tlog['state'] == "RUNNING": + running_hosts.add(tlog['host']) + total_errors += tlog['errors'] + total_items += tlog['task_items'] + log = dict((tl['host'], dict(state=tl['state'], + instances=tl['task_items'], + errors=tl['errors'], + message=tl['message'])) + for tl in task_logs) + missing_hosts = hosts - seen_hosts + overall_status = "%s hosts done. %s errors." % ( + 'ALL' if len(done_hosts) == len(hosts) + else "%s of %s" % (len(done_hosts), len(hosts)), + total_errors) + return dict(period_beginning=str(begin), + period_ending=str(end), + num_hosts=len(hosts), + num_hosts_done=len(done_hosts), + num_hosts_running=len(running_hosts), + num_hosts_not_run=len(missing_hosts), + hosts_not_run=list(missing_hosts), + total_instances=total_items, + total_errors=total_errors, + overall_status=overall_status, + log=log) + + +class InstanceUsageAuditLog(extensions.V3APIExtensionBase): + """Admin-only Task Log Monitoring.""" + name = "InstanceUsageAuditLog" + alias = ALIAS + version = 1 + + def get_resources(self): + ext = extensions.ResourceExtension('os-instance_usage_audit_log', + InstanceUsageAuditLogController()) + return [ext] + + def get_controller_extensions(self): + return [] diff --git a/nova/tests/unit/api/openstack/compute/contrib/test_instance_usage_audit_log.py b/nova/tests/unit/api/openstack/compute/contrib/test_instance_usage_audit_log.py index 1ae85c8625ed..1a9c0a0816ac 100644 --- a/nova/tests/unit/api/openstack/compute/contrib/test_instance_usage_audit_log.py +++ b/nova/tests/unit/api/openstack/compute/contrib/test_instance_usage_audit_log.py @@ -18,6 +18,8 @@ import datetime from oslo.utils import timeutils from nova.api.openstack.compute.contrib import instance_usage_audit_log as ial +from nova.api.openstack.compute.plugins.v3 import instance_usage_audit_log as \ + v21_ial from nova import context from nova import db from nova import exception @@ -109,12 +111,12 @@ def fake_last_completed_audit_period(unit=None, before=None): return begin1, end1 -class InstanceUsageAuditLogTest(test.NoDBTestCase): +class InstanceUsageAuditLogTestV21(test.NoDBTestCase): def setUp(self): - super(InstanceUsageAuditLogTest, self).setUp() + super(InstanceUsageAuditLogTestV21, self).setUp() self.context = context.get_admin_context() timeutils.set_time_override(datetime.datetime(2012, 7, 5, 10, 0, 0)) - self.controller = ial.InstanceUsageAuditLogController() + self._set_up_controller() self.host_api = self.controller.host_api def fake_service_get_all(context, disabled): @@ -128,8 +130,11 @@ class InstanceUsageAuditLogTest(test.NoDBTestCase): self.stubs.Set(db, 'task_log_get_all', fake_task_log_get_all) + def _set_up_controller(self): + self.controller = v21_ial.InstanceUsageAuditLogController() + def tearDown(self): - super(InstanceUsageAuditLogTest, self).tearDown() + super(InstanceUsageAuditLogTestV21, self).tearDown() timeutils.clear_time_override() def test_index(self): @@ -208,3 +213,8 @@ class InstanceUsageAuditLogTest(test.NoDBTestCase): self.assertEqual(0, logs['num_hosts_not_run']) self.assertEqual("ALL hosts done. 3 errors.", logs['overall_status']) + + +class InstanceUsageAuditLogTest(InstanceUsageAuditLogTestV21): + def _set_up_controller(self): + self.controller = ial.InstanceUsageAuditLogController() diff --git a/nova/tests/unit/fake_policy.py b/nova/tests/unit/fake_policy.py index 8344af475d55..1c1d5156f00b 100644 --- a/nova/tests/unit/fake_policy.py +++ b/nova/tests/unit/fake_policy.py @@ -237,6 +237,7 @@ policy_data = """ "compute_extension:instance_actions:events": "is_admin:True", "compute_extension:v3:os-instance-actions:events": "is_admin:True", "compute_extension:instance_usage_audit_log": "rule:admin_api", + "compute_extension:v3:os-instance-usage-audit-log": "rule:admin_api", "compute_extension:keypairs": "", "compute_extension:keypairs:index": "", "compute_extension:keypairs:show": "", diff --git a/nova/tests/unit/integrated/v3/api_samples/os-instance-usage-audit-log/inst-usage-audit-log-index-get-resp.json.tpl b/nova/tests/unit/integrated/v3/api_samples/os-instance-usage-audit-log/inst-usage-audit-log-index-get-resp.json.tpl new file mode 100644 index 000000000000..81b0d6c3412f --- /dev/null +++ b/nova/tests/unit/integrated/v3/api_samples/os-instance-usage-audit-log/inst-usage-audit-log-index-get-resp.json.tpl @@ -0,0 +1,17 @@ +{ + "instance_usage_audit_logs": { + "hosts_not_run": [ + "%(hostid)s" + ], + "log": {}, + "num_hosts": 1, + "num_hosts_done": 0, + "num_hosts_not_run": 1, + "num_hosts_running": 0, + "overall_status": "0 of 1 hosts done. 0 errors.", + "period_beginning": "%(xmltime)s", + "period_ending": "%(xmltime)s", + "total_errors": 0, + "total_instances": 0 + } +} diff --git a/nova/tests/unit/integrated/v3/api_samples/os-instance-usage-audit-log/inst-usage-audit-log-show-get-resp.json.tpl b/nova/tests/unit/integrated/v3/api_samples/os-instance-usage-audit-log/inst-usage-audit-log-show-get-resp.json.tpl new file mode 100644 index 000000000000..71549c156bc5 --- /dev/null +++ b/nova/tests/unit/integrated/v3/api_samples/os-instance-usage-audit-log/inst-usage-audit-log-show-get-resp.json.tpl @@ -0,0 +1,17 @@ +{ + "instance_usage_audit_log": { + "hosts_not_run": [ + "%(hostid)s" + ], + "log": {}, + "num_hosts": 1, + "num_hosts_done": 0, + "num_hosts_not_run": 1, + "num_hosts_running": 0, + "overall_status": "0 of 1 hosts done. 0 errors.", + "period_beginning": "%(xmltime)s", + "period_ending": "%(xmltime)s", + "total_errors": 0, + "total_instances": 0 + } +} diff --git a/nova/tests/unit/integrated/v3/test_instance_usage_audit_log.py b/nova/tests/unit/integrated/v3/test_instance_usage_audit_log.py new file mode 100644 index 000000000000..dccd280db4de --- /dev/null +++ b/nova/tests/unit/integrated/v3/test_instance_usage_audit_log.py @@ -0,0 +1,37 @@ +# Copyright 2012 Nebula, Inc. +# Copyright 2013 IBM Corp. +# +# 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 urllib + +from nova.tests.unit.integrated.v3 import api_sample_base + + +class InstanceUsageAuditLogJsonTest(api_sample_base.ApiSampleTestBaseV3): + extension_name = "os-instance-usage-audit-log" + + def test_show_instance_usage_audit_log(self): + response = self._do_get('os-instance_usage_audit_log/%s' % + urllib.quote('2012-07-05 10:00:00')) + subs = self._get_regexes() + subs['hostid'] = '[a-f0-9]+' + self._verify_response('inst-usage-audit-log-show-get-resp', + subs, response, 200) + + def test_index_instance_usage_audit_log(self): + response = self._do_get('os-instance_usage_audit_log') + subs = self._get_regexes() + subs['hostid'] = '[a-f0-9]+' + self._verify_response('inst-usage-audit-log-index-get-resp', + subs, response, 200) diff --git a/setup.cfg b/setup.cfg index ccc350c3dc0f..c883ec6bb3c0 100644 --- a/setup.cfg +++ b/setup.cfg @@ -98,6 +98,7 @@ nova.api.v3.extensions = image_metadata = nova.api.openstack.compute.plugins.v3.image_metadata:ImageMetadata image_size = nova.api.openstack.compute.plugins.v3.image_size:ImageSize instance_actions = nova.api.openstack.compute.plugins.v3.instance_actions:InstanceActions + instance_usage_audit_log = nova.api.openstack.compute.plugins.v3.instance_usage_audit_log:InstanceUsageAuditLog ips = nova.api.openstack.compute.plugins.v3.ips:IPs keypairs = nova.api.openstack.compute.plugins.v3.keypairs:Keypairs limits = nova.api.openstack.compute.plugins.v3.limits:Limits