From ffa0f0f58fa3ec900ae8d9f93a4b7344db5282a3 Mon Sep 17 00:00:00 2001 From: Steven Hardy Date: Wed, 29 Apr 2015 09:50:54 -0400 Subject: [PATCH] Refactor common event code into event_utils module The recently added event-mangling code for event-list --nested-depth will also be needed for hook-poll functionality, so refactor to move the code and tests into seperate files. Change-Id: I6d349e1e2056f6a6ad5b683d8d40072b61dbf35c --- heatclient/common/event_utils.py | 88 ++++++++++++++++++ heatclient/tests/test_event_utils.py | 130 +++++++++++++++++++++++++++ heatclient/tests/test_shell.py | 113 ----------------------- heatclient/v1/shell.py | 73 ++------------- 4 files changed, 224 insertions(+), 180 deletions(-) create mode 100644 heatclient/common/event_utils.py create mode 100644 heatclient/tests/test_event_utils.py diff --git a/heatclient/common/event_utils.py b/heatclient/common/event_utils.py new file mode 100644 index 00000000..511a2af7 --- /dev/null +++ b/heatclient/common/event_utils.py @@ -0,0 +1,88 @@ +# Copyright 2015 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. + +from heatclient.common import utils +import heatclient.exc as exc + +from heatclient.openstack.common._i18n import _ + + +def get_events(hc, stack_id, event_args, nested_depth=0, + marker=None, limit=None): + events = _get_stack_events(hc, stack_id, event_args) + if nested_depth > 0: + events.extend(_get_nested_events(hc, nested_depth, + stack_id, event_args)) + # Because there have been multiple stacks events mangled into + # one list, we need to sort before passing to print_list + # Note we can't use the prettytable sortby_index here, because + # the "start" option doesn't allow post-sort slicing, which + # will be needed to make "--marker" work for nested_depth lists + events.sort(key=lambda x: x.event_time) + + # Slice the list if marker is specified + if marker: + marker_index = [e.id for e in events].index(marker) + events = events[marker_index:] + + # Slice the list if limit is specified + if limit: + limit_index = min(int(limit), len(events)) + events = events[:limit_index] + return events + + +def _get_nested_ids(hc, stack_id): + nested_ids = [] + try: + resources = hc.resources.list(stack_id=stack_id) + except exc.HTTPNotFound: + raise exc.CommandError(_('Stack not found: %s') % stack_id) + for r in resources: + nested_id = utils.resource_nested_identifier(r) + if nested_id: + nested_ids.append(nested_id) + return nested_ids + + +def _get_nested_events(hc, nested_depth, stack_id, event_args): + # FIXME(shardy): this is very inefficient, we should add nested_depth to + # the event_list API in a future heat version, but this will be required + # until kilo heat is EOL. + nested_ids = _get_nested_ids(hc, stack_id) + nested_events = [] + for n_id in nested_ids: + stack_events = _get_stack_events(hc, n_id, event_args) + if stack_events: + nested_events.extend(stack_events) + if nested_depth > 1: + next_depth = nested_depth - 1 + nested_events.extend(_get_nested_events( + hc, next_depth, n_id, event_args)) + return nested_events + + +def _get_stack_events(hc, stack_id, event_args): + event_args['stack_id'] = stack_id + try: + events = hc.events.list(**event_args) + except exc.HTTPNotFound as ex: + # it could be the stack or resource that is not found + # just use the message that the server sent us. + raise exc.CommandError(str(ex)) + else: + # Show which stack the event comes from (for nested events) + for e in events: + e.stack_name = stack_id.split("/")[0] + return events diff --git a/heatclient/tests/test_event_utils.py b/heatclient/tests/test_event_utils.py new file mode 100644 index 00000000..bba77b4a --- /dev/null +++ b/heatclient/tests/test_event_utils.py @@ -0,0 +1,130 @@ +# 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 mock +import testtools + +from heatclient.common import event_utils +from heatclient.v1 import events as hc_ev +from heatclient.v1 import resources as hc_res + + +class ShellTestEventUtils(testtools.TestCase): + @staticmethod + def _mock_resource(resource_id, nested_id=None): + res_info = {"links": [{"href": "http://heat/foo", "rel": "self"}, + {"href": "http://heat/foo2", "rel": "resource"}], + "logical_resource_id": resource_id, + "physical_resource_id": resource_id, + "resource_status": "CREATE_COMPLETE", + "resource_status_reason": "state changed", + "resource_type": "OS::Nested::Server", + "updated_time": "2014-01-06T16:14:26Z"} + if nested_id: + nested_link = {"href": "http://heat/%s" % nested_id, + "rel": "nested"} + res_info["links"].append(nested_link) + return hc_res.Resource(manager=None, info=res_info) + + @staticmethod + def _mock_event(event_id, resource_id): + ev_info = {"links": [{"href": "http://heat/foo", "rel": "self"}], + "logical_resource_id": resource_id, + "physical_resource_id": resource_id, + "resource_status": "CREATE_COMPLETE", + "resource_status_reason": "state changed", + "event_time": "2014-12-05T14:14:30Z", + "id": event_id} + return hc_ev.Event(manager=None, info=ev_info) + + def test_get_nested_ids(self): + def list_stub(stack_id): + return [self._mock_resource('aresource', 'foo3/3id')] + mock_client = mock.MagicMock() + mock_client.resources.list.side_effect = list_stub + ids = event_utils._get_nested_ids(hc=mock_client, + stack_id='astack/123') + mock_client.resources.list.assert_called_once_with( + stack_id='astack/123') + self.assertEqual(['foo3/3id'], ids) + + def test_get_stack_events(self): + def event_stub(stack_id, argfoo): + return [self._mock_event('event1', 'aresource')] + mock_client = mock.MagicMock() + mock_client.events.list.side_effect = event_stub + ev_args = {'argfoo': 123} + evs = event_utils._get_stack_events(hc=mock_client, + stack_id='astack/123', + event_args=ev_args) + mock_client.events.list.assert_called_once_with( + stack_id='astack/123', argfoo=123) + self.assertEqual(1, len(evs)) + self.assertEqual('event1', evs[0].id) + self.assertEqual('astack', evs[0].stack_name) + + def test_get_nested_events(self): + resources = {'parent': self._mock_resource('resource1', 'foo/child1'), + 'foo/child1': self._mock_resource('res_child1', + 'foo/child2'), + 'foo/child2': self._mock_resource('res_child2', + 'foo/child3'), + 'foo/child3': self._mock_resource('res_child3', + 'foo/END')} + + def resource_list_stub(stack_id): + return [resources[stack_id]] + mock_client = mock.MagicMock() + mock_client.resources.list.side_effect = resource_list_stub + + events = {'foo/child1': self._mock_event('event1', 'res_child1'), + 'foo/child2': self._mock_event('event2', 'res_child2'), + 'foo/child3': self._mock_event('event3', 'res_child3')} + + def event_list_stub(stack_id, argfoo): + return [events[stack_id]] + mock_client.events.list.side_effect = event_list_stub + + ev_args = {'argfoo': 123} + # Check nested_depth=1 (non recursive).. + evs = event_utils._get_nested_events(hc=mock_client, + nested_depth=1, + stack_id='parent', + event_args=ev_args) + + rsrc_calls = [mock.call(stack_id='parent')] + mock_client.resources.list.assert_has_calls(rsrc_calls) + ev_calls = [mock.call(stack_id='foo/child1', argfoo=123)] + mock_client.events.list.assert_has_calls(ev_calls) + self.assertEqual(1, len(evs)) + self.assertEqual('event1', evs[0].id) + + # ..and the recursive case via nested_depth=3 + mock_client.resources.list.reset_mock() + mock_client.events.list.reset_mock() + evs = event_utils._get_nested_events(hc=mock_client, + nested_depth=3, + stack_id='parent', + event_args=ev_args) + + rsrc_calls = [mock.call(stack_id='parent'), + mock.call(stack_id='foo/child1'), + mock.call(stack_id='foo/child2')] + mock_client.resources.list.assert_has_calls(rsrc_calls) + ev_calls = [mock.call(stack_id='foo/child1', argfoo=123), + mock.call(stack_id='foo/child2', argfoo=123), + mock.call(stack_id='foo/child3', argfoo=123)] + mock_client.events.list.assert_has_calls(ev_calls) + self.assertEqual(3, len(evs)) + self.assertEqual('event1', evs[0].id) + self.assertEqual('event2', evs[1].id) + self.assertEqual('event3', evs[2].id) diff --git a/heatclient/tests/test_shell.py b/heatclient/tests/test_shell.py index 04f47ae5..87c0dea6 100644 --- a/heatclient/tests/test_shell.py +++ b/heatclient/tests/test_shell.py @@ -12,7 +12,6 @@ # limitations under the License. import fixtures -import mock import os from oslotest import mockpatch import re @@ -40,8 +39,6 @@ from heatclient.common import utils from heatclient import exc import heatclient.shell from heatclient.tests import fakes -from heatclient.v1 import events as hc_ev -from heatclient.v1 import resources as hc_res import heatclient.v1.shell load_tests = testscenarios.load_tests_apply_scenarios @@ -2273,116 +2270,6 @@ class ShellTestEventsNested(ShellBase): super(ShellTestEventsNested, self).setUp() self.set_fake_env(FAKE_ENV_KEYSTONE_V2) - @staticmethod - def _mock_resource(resource_id, nested_id=None): - res_info = {"links": [{"href": "http://heat/foo", "rel": "self"}, - {"href": "http://heat/foo2", "rel": "resource"}], - "logical_resource_id": resource_id, - "physical_resource_id": resource_id, - "resource_status": "CREATE_COMPLETE", - "resource_status_reason": "state changed", - "resource_type": "OS::Nested::Server", - "updated_time": "2014-01-06T16:14:26Z"} - if nested_id: - nested_link = {"href": "http://heat/%s" % nested_id, - "rel": "nested"} - res_info["links"].append(nested_link) - return hc_res.Resource(manager=None, info=res_info) - - @staticmethod - def _mock_event(event_id, resource_id): - ev_info = {"links": [{"href": "http://heat/foo", "rel": "self"}], - "logical_resource_id": resource_id, - "physical_resource_id": resource_id, - "resource_status": "CREATE_COMPLETE", - "resource_status_reason": "state changed", - "event_time": "2014-12-05T14:14:30Z", - "id": event_id} - return hc_ev.Event(manager=None, info=ev_info) - - def test_get_nested_ids(self): - def list_stub(stack_id): - return [self._mock_resource('aresource', 'foo3/3id')] - mock_client = mock.MagicMock() - mock_client.resources.list.side_effect = list_stub - ids = heatclient.v1.shell._get_nested_ids(hc=mock_client, - stack_id='astack/123') - mock_client.resources.list.assert_called_once_with( - stack_id='astack/123') - self.assertEqual(['foo3/3id'], ids) - - def test_get_stack_events(self): - def event_stub(stack_id, argfoo): - return [self._mock_event('event1', 'aresource')] - mock_client = mock.MagicMock() - mock_client.events.list.side_effect = event_stub - ev_args = {'argfoo': 123} - evs = heatclient.v1.shell._get_stack_events(hc=mock_client, - stack_id='astack/123', - event_args=ev_args) - mock_client.events.list.assert_called_once_with( - stack_id='astack/123', argfoo=123) - self.assertEqual(1, len(evs)) - self.assertEqual('event1', evs[0].id) - self.assertEqual('astack', evs[0].stack_name) - - def test_get_nested_events(self): - resources = {'parent': self._mock_resource('resource1', 'foo/child1'), - 'foo/child1': self._mock_resource('res_child1', - 'foo/child2'), - 'foo/child2': self._mock_resource('res_child2', - 'foo/child3'), - 'foo/child3': self._mock_resource('res_child3', - 'foo/END')} - - def resource_list_stub(stack_id): - return [resources[stack_id]] - mock_client = mock.MagicMock() - mock_client.resources.list.side_effect = resource_list_stub - - events = {'foo/child1': self._mock_event('event1', 'res_child1'), - 'foo/child2': self._mock_event('event2', 'res_child2'), - 'foo/child3': self._mock_event('event3', 'res_child3')} - - def event_list_stub(stack_id, argfoo): - return [events[stack_id]] - mock_client.events.list.side_effect = event_list_stub - - ev_args = {'argfoo': 123} - # Check nested_depth=1 (non recursive).. - evs = heatclient.v1.shell._get_nested_events(hc=mock_client, - nested_depth=1, - stack_id='parent', - event_args=ev_args) - - rsrc_calls = [mock.call(stack_id='parent')] - mock_client.resources.list.assert_has_calls(rsrc_calls) - ev_calls = [mock.call(stack_id='foo/child1', argfoo=123)] - mock_client.events.list.assert_has_calls(ev_calls) - self.assertEqual(1, len(evs)) - self.assertEqual('event1', evs[0].id) - - # ..and the recursive case via nested_depth=3 - mock_client.resources.list.reset_mock() - mock_client.events.list.reset_mock() - evs = heatclient.v1.shell._get_nested_events(hc=mock_client, - nested_depth=3, - stack_id='parent', - event_args=ev_args) - - rsrc_calls = [mock.call(stack_id='parent'), - mock.call(stack_id='foo/child1'), - mock.call(stack_id='foo/child2')] - mock_client.resources.list.assert_has_calls(rsrc_calls) - ev_calls = [mock.call(stack_id='foo/child1', argfoo=123), - mock.call(stack_id='foo/child2', argfoo=123), - mock.call(stack_id='foo/child3', argfoo=123)] - mock_client.events.list.assert_has_calls(ev_calls) - self.assertEqual(3, len(evs)) - self.assertEqual('event1', evs[0].id) - self.assertEqual('event2', evs[1].id) - self.assertEqual('event3', evs[2].id) - def test_shell_nested_depth_invalid_xor(self): self.register_keystone_auth_fixture() stack_id = 'teststack/1' diff --git a/heatclient/v1/shell.py b/heatclient/v1/shell.py index 893fb47c..5f4a7a4a 100644 --- a/heatclient/v1/shell.py +++ b/heatclient/v1/shell.py @@ -23,6 +23,7 @@ from six.moves.urllib import request import yaml from heatclient.common import deployment_utils +from heatclient.common import event_utils from heatclient.common import template_format from heatclient.common import template_utils from heatclient.common import utils @@ -880,51 +881,6 @@ def do_hook_clear(hc, args): clear_wildcard_hooks(stack_id, hook[:-1]) -def _get_nested_ids(hc, stack_id): - nested_ids = [] - try: - resources = hc.resources.list(stack_id=stack_id) - except exc.HTTPNotFound: - raise exc.CommandError(_('Stack not found: %s') % stack_id) - for r in resources: - nested_id = utils.resource_nested_identifier(r) - if nested_id: - nested_ids.append(nested_id) - return nested_ids - - -def _get_nested_events(hc, nested_depth, stack_id, event_args): - # FIXME(shardy): this is very inefficient, we should add nested_depth to - # the event_list API in a future heat version, but this will be required - # until kilo heat is EOL. - nested_ids = _get_nested_ids(hc, stack_id) - nested_events = [] - for n_id in nested_ids: - stack_events = _get_stack_events(hc, n_id, event_args) - if stack_events: - nested_events.extend(stack_events) - if nested_depth > 1: - next_depth = nested_depth - 1 - nested_events.extend(_get_nested_events( - hc, next_depth, n_id, event_args)) - return nested_events - - -def _get_stack_events(hc, stack_id, event_args): - event_args['stack_id'] = stack_id - try: - events = hc.events.list(**event_args) - except exc.HTTPNotFound as ex: - # it could be the stack or resource that is not found - # just use the message that the server sent us. - raise exc.CommandError(str(ex)) - else: - # Show which stack the event comes from (for nested events) - for e in events: - e.stack_name = stack_id.split("/")[0] - return events - - @utils.arg('id', metavar='', help=_('Name or ID of stack to show the events for.')) @utils.arg('-r', '--resource', metavar='', @@ -966,10 +922,14 @@ def do_event_list(hc, args): # marker/limit filtering client-side del (event_args['marker']) del (event_args['limit']) + # Nested list adds the stack name to the output + display_fields.append('stack_name') else: nested_depth = 0 - events = _get_stack_events(hc, stack_id=args.id, event_args=event_args) + events = event_utils.get_events( + hc, stack_id=args.id, event_args=event_args, nested_depth=nested_depth, + marker=args.marker, limit=args.limit) if len(events) >= 1: if hasattr(events[0], 'resource_name'): @@ -977,27 +937,6 @@ def do_event_list(hc, args): else: display_fields.insert(0, 'logical_resource_id') - if nested_depth > 0: - events.extend(_get_nested_events(hc, nested_depth, - args.id, event_args)) - display_fields.append('stack_name') - # Because there have been multiple stacks events mangled into - # one list, we need to sort before passing to print_list - # Note we can't use the prettytable sortby_index here, because - # the "start" option doesn't allow post-sort slicing, which - # will be needed to make "--marker" work for nested_depth lists - events.sort(key=lambda x: x.event_time) - - # Slice the list if marker is specified - if args.marker: - marker_index = [e.id for e in events].index(args.marker) - events = events[marker_index:] - - # Slice the list if limit is specified - if args.limit: - limit_index = min(int(args.limit), len(events)) - events = events[:limit_index] - utils.print_list(events, display_fields, sortby_index=None)