From fecc2bacb51c7dabdb83b87313df48dea527feae Mon Sep 17 00:00:00 2001 From: Pratik Mallya Date: Thu, 1 Oct 2015 17:41:09 -0500 Subject: [PATCH] Check if os-interface extension is present before saving ports If that extension is not present, then the code to extract and save ports created during server create by nova will fail. Change-Id: I6008a0da0c16a34c40e343af4e7237520db4ae36 Closes-Bug: #1499877 (cherry picked from commit 46d8c276b70e7698438988faa9d997577a7e1750) --- heat/engine/clients/os/nova.py | 11 ++++++ .../openstack/nova/server_network_mixin.py | 7 ++++ heat/tests/clients/test_nova_client.py | 28 +++++++++++++ heat/tests/nova/test_server.py | 39 ++++++++++++++++++- 4 files changed, 84 insertions(+), 1 deletion(-) diff --git a/heat/engine/clients/os/nova.py b/heat/engine/clients/os/nova.py index 79f386e6cb..8f810052c5 100644 --- a/heat/engine/clients/os/nova.py +++ b/heat/engine/clients/os/nova.py @@ -58,6 +58,12 @@ class NovaClientPlugin(client_plugin.ClientPlugin): service_types = [COMPUTE] = ['compute'] + EXTENSIONS = ( + OS_INTERFACE_EXTENSION + ) = ( + "OSInterface" + ) + def _create(self): endpoint_type = self._get_client_option('nova', 'endpoint_type') management_url = self.url_for(service_type=self.COMPUTE, @@ -656,6 +662,11 @@ echo -e '%s\tALL=(ALL)\tNOPASSWD: ALL' >> /etc/sudoers else: return False + def _has_extension(self, extension_name): + """Check if extension is present.""" + extensions = self.client().list_extensions.show_all() + return extension_name in [extension.name for extension in extensions] + class ServerConstraint(constraints.BaseCustomConstraint): diff --git a/heat/engine/resources/openstack/nova/server_network_mixin.py b/heat/engine/resources/openstack/nova/server_network_mixin.py index c2a0dc83d9..d92ac36dcc 100644 --- a/heat/engine/resources/openstack/nova/server_network_mixin.py +++ b/heat/engine/resources/openstack/nova/server_network_mixin.py @@ -162,6 +162,13 @@ class ServerNetworkMixin(object): if not self.is_using_neutron(): return + # check if OSInterface extension is installed on this cloud. If it's + # not, then novaclient's interface_list method cannot be used to get + # the list of interfaces. + if not self.client_plugin()._has_extension( + self.client_plugin().OS_INTERFACE_EXTENSION): + return + server = self.client().servers.get(self.resource_id) ifaces = server.interface_list() external_port_ids = set(iface.port_id for iface in ifaces) diff --git a/heat/tests/clients/test_nova_client.py b/heat/tests/clients/test_nova_client.py index e83fc50b31..bd24aa914a 100644 --- a/heat/tests/clients/test_nova_client.py +++ b/heat/tests/clients/test_nova_client.py @@ -581,3 +581,31 @@ class ConsoleUrlsTest(common.HeatTestCase): e = self.assertRaises(exc, urls.__getitem__, self.console_type) self.assertIn('spam', e.args) self.console_method.assert_called_once_with(self.console_type) + + +class NovaClientPluginExtensionsTests(NovaClientPluginTestCase): + """Tests for extensions in novaclient.""" + + def test_defines_required_extension_names(self): + self.assertEqual(self.nova_plugin.OS_INTERFACE_EXTENSION, + "OSInterface") + + def test_has_no_extensions(self): + self.nova_client.list_extensions.show_all.return_value = [] + self.assertFalse(self.nova_plugin._has_extension("OSInterface")) + + def test_has_no_interface_extensions(self): + mock_extension = mock.Mock() + p = mock.PropertyMock(return_value='notOSInterface') + type(mock_extension).name = p + self.nova_client.list_extensions.show_all.return_value = [ + mock_extension] + self.assertFalse(self.nova_plugin._has_extension("OSInterface")) + + def test_has_os_interface_extension(self): + mock_extension = mock.Mock() + p = mock.PropertyMock(return_value='OSInterface') + type(mock_extension).name = p + self.nova_client.list_extensions.show_all.return_value = [ + mock_extension] + self.assertTrue(self.nova_plugin._has_extension("OSInterface")) diff --git a/heat/tests/nova/test_server.py b/heat/tests/nova/test_server.py index 2414f2dc01..b53bf4b23e 100644 --- a/heat/tests/nova/test_server.py +++ b/heat/tests/nova/test_server.py @@ -4154,7 +4154,8 @@ class ServerInternalPortTest(common.HeatTestCase): server.client = mock.Mock() server.client().servers.get.return_value = Fake() - + server.client_plugin = mock.Mock() + server.client_plugin()._has_extension.return_value = True server._data = {"internal_ports": '[{"id": "1122"}]', "external_ports": '[{"id": "3344"},{"id": "5566"}]'} @@ -4289,3 +4290,39 @@ class ServerInternalPortTest(common.HeatTestCase): self.port_update.has_calls((1122, {'port': port1_fixed_ip}), (3344, {'port': port2_fixed_ip}), (5566, {'port': port3_fixed_ip})) + + def test_store_external_ports_os_interface_not_installed(self): + tmpl = """ + heat_template_version: 2015-10-15 + resources: + server: + type: OS::Nova::Server + properties: + flavor: m1.small + image: F17-x86_64-gold + networks: + - network: 4321 + """ + t, stack, server = self._return_template_stack_and_rsrc_defn('test', + tmpl) + + class Fake(object): + def interface_list(self): + return [iface('1122'), + iface('1122'), + iface('2233'), + iface('3344')] + + server.client = mock.Mock() + server.client().servers.get.return_value = Fake() + server.client_plugin = mock.Mock() + server.client_plugin()._has_extension.return_value = False + + server._data = {"internal_ports": '[{"id": "1122"}]', + "external_ports": '[{"id": "3344"},{"id": "5566"}]'} + + iface = collections.namedtuple('iface', ['port_id']) + update_data = self.patchobject(server, '_data_update_ports') + + server.store_external_ports() + self.assertEqual(0, update_data.call_count)