diff options
author | Jenkins <jenkins@review.openstack.org> | 2016-01-26 21:48:41 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2016-01-26 21:48:42 +0000 |
commit | 2f2798272b3ec71faee25f794265c28319f29068 (patch) | |
tree | 69079bd89ebd50608b460f38f56008dec0dd79e7 | |
parent | 1940d1d87821290c324b17eefcbce291732e5e1d (diff) | |
parent | 03ee72291746143a50b888303a4361042b479954 (diff) | |
download | heat-2f2798272b3ec71faee25f794265c28319f29068.tar.gz |
Merge "Neutron port, tolerate switching network name/id" into stable/kilo
-rw-r--r-- | heat/engine/resources/openstack/neutron/neutron.py | 8 | ||||
-rw-r--r-- | heat/engine/resources/openstack/neutron/port.py | 31 | ||||
-rw-r--r-- | heat/tests/neutron/test_neutron_port.py | 96 |
3 files changed, 130 insertions, 5 deletions
diff --git a/heat/engine/resources/openstack/neutron/neutron.py b/heat/engine/resources/openstack/neutron/neutron.py index 12773bc44..f54bca01c 100644 --- a/heat/engine/resources/openstack/neutron/neutron.py +++ b/heat/engine/resources/openstack/neutron/neutron.py @@ -24,6 +24,11 @@ class NeutronResource(resource.Resource): default_client_name = 'neutron' + # Subclasses provide a list of properties which, although + # update_allowed in the schema, should be excluded from the + # call to neutron, because they are handled in _needs_update + update_exclude_properties = [] + def validate(self): ''' Validate any of the provided params @@ -91,7 +96,8 @@ class NeutronResource(resource.Resource): ''' p = definition.properties(self.properties_schema, self.context) update_props = dict((k, v) for k, v in p.items() - if p.props.get(k).schema.update_allowed) + if p.props.get(k).schema.update_allowed and + k not in self.update_exclude_properties) props = self.prepare_properties( update_props, diff --git a/heat/engine/resources/openstack/neutron/port.py b/heat/engine/resources/openstack/neutron/port.py index 2a17ec1be..9b45dbcff 100644 --- a/heat/engine/resources/openstack/neutron/port.py +++ b/heat/engine/resources/openstack/neutron/port.py @@ -71,18 +71,21 @@ class Port(neutron.NeutronResource): constraints=[ constraints.CustomConstraint('neutron.network') ], + update_allowed=True, ), NETWORK: properties.Schema( properties.Schema.STRING, _('Network this port belongs to. If you plan to use current port ' 'to assign Floating IP, you should specify %(fixed_ips)s ' - 'with %(subnet)s') % {'fixed_ips': FIXED_IPS, - 'subnet': FIXED_IP_SUBNET}, + 'with %(subnet)s. Note if this changes to a different network ' + 'update, the port will be replaced') % + {'fixed_ips': FIXED_IPS, 'subnet': FIXED_IP_SUBNET}, support_status=support.SupportStatus(version='2014.2'), constraints=[ constraints.CustomConstraint('neutron.network') ], + update_allowed=True, ), NAME: properties.Schema( @@ -247,6 +250,10 @@ class Port(neutron.NeutronResource): ), } + # The network property can be updated, but only to switch between + # a name and ID for the same network, which is handled in _needs_update + update_exclude_properties = [NETWORK, NETWORK_ID] + def validate(self): super(Port, self).validate() self._validate_depr_property_required(self.properties, @@ -351,6 +358,26 @@ class Port(neutron.NeutronResource): if after_props.get(self.REPLACEMENT_POLICY) == 'REPLACE_ALWAYS': raise resource.UpdateReplace(self.name) + if after_props != before_props: + # Switching between name and ID is OK, provided the value resolves + # to the same network. If the network changes, port is replaced. + # Support NETWORK and NETWORK_ID, as switching between those is OK. + if before_props.get(self.NETWORK): + before_prop = self.NETWORK + else: + before_prop = self.NETWORK_ID + before_id = self.client_plugin().find_neutron_resource( + before_props, before_prop, 'network') + + if after_props.get(self.NETWORK): + after_prop = self.NETWORK + else: + after_prop = self.NETWORK_ID + after_id = self.client_plugin().find_neutron_resource( + after_props, after_prop, 'network') + if before_id != after_id: + raise resource.UpdateReplace(self.name) + return super(Port, self)._needs_update( after, before, after_props, before_props, prev_resource) diff --git a/heat/tests/neutron/test_neutron_port.py b/heat/tests/neutron/test_neutron_port.py index 32a6e4774..ecdadbe3f 100644 --- a/heat/tests/neutron/test_neutron_port.py +++ b/heat/tests/neutron/test_neutron_port.py @@ -444,11 +444,12 @@ class NeutronPortTest(common.HeatTestCase): # test always replace new_props['replacement_policy'] = 'REPLACE_ALWAYS' + new_props['network'] = new_props.pop('network_id') update_snippet = rsrc_defn.ResourceDefinition(port.name, port.type(), new_props) self.assertRaises(resource.UpdateReplace, port._needs_update, update_snippet, port.frozen_definition(), - new_props, props, None) + new_props, port.properties, None) # test deferring to Resource._needs_update new_props['replacement_policy'] = 'AUTO' @@ -456,7 +457,93 @@ class NeutronPortTest(common.HeatTestCase): new_props) self.assertTrue(port._needs_update(update_snippet, port.frozen_definition(), - new_props, props, None)) + new_props, port.properties, None)) + + self.m.VerifyAll() + + def test_port_needs_update_network(self): + props = {'network_id': u'net1234', + 'name': utils.PhysName('test_stack', 'port'), + 'admin_state_up': True, + 'device_owner': u'network:dhcp'} + neutronV20.find_resourceid_by_name_or_id( + mox.IsA(neutronclient.Client), + 'network', + 'net1234', + ).AndReturn('net1234') + create_props = props.copy() + neutronclient.Client.create_port( + {'port': create_props} + ).AndReturn({'port': { + "status": "BUILD", + "id": "fc68ea2c-b60b-4b4f-bd82-94ec81110766" + }}) + neutronclient.Client.show_port( + 'fc68ea2c-b60b-4b4f-bd82-94ec81110766' + ).AndReturn({'port': { + "status": "ACTIVE", + "id": "fc68ea2c-b60b-4b4f-bd82-94ec81110766", + "fixed_ips": { + "subnet_id": "d0e971a6-a6b4-4f4c-8c88-b75e9c120b7e", + "ip_address": "10.0.0.2" + } + }}) + neutronV20.find_resourceid_by_name_or_id( + mox.IsA(neutronclient.Client), + 'network', + 'net1234', + ).MultipleTimes().AndReturn('net1234') + neutronV20.find_resourceid_by_name_or_id( + mox.IsA(neutronclient.Client), + 'network', + 'old_network', + ).AndReturn('net1234') + neutronV20.find_resourceid_by_name_or_id( + mox.IsA(neutronclient.Client), + 'network', + 'net1234', + ).MultipleTimes().AndReturn('net1234') + neutronV20.find_resourceid_by_name_or_id( + mox.IsA(neutronclient.Client), + 'network', + 'new_network', + ).AndReturn('net5678') + + self.m.ReplayAll() + + # create port + t = template_format.parse(neutron_port_template) + t['resources']['port']['properties'].pop('fixed_ips') + stack = utils.parse_stack(t) + + port = stack['port'] + scheduler.TaskRunner(port.create)() + + # Switch from network_id=ID to network=ID (no replace) + new_props = props.copy() + new_props['network'] = new_props.pop('network_id') + new_props['network_id'] = None + update_snippet = rsrc_defn.ResourceDefinition(port.name, port.type(), + new_props) + self.assertTrue(port._needs_update(update_snippet, + port.frozen_definition(), + new_props, port.properties, None)) + + # Switch from network=ID to network=NAME (no replace) + new_props['network'] = 'old_network' + update_snippet = rsrc_defn.ResourceDefinition(port.name, port.type(), + new_props) + self.assertTrue(port._needs_update(update_snippet, + port.frozen_definition(), + new_props, port.properties, None)) + + # Switch to a different network (replace) + new_props['network'] = 'new_network' + update_snippet = rsrc_defn.ResourceDefinition(port.name, port.type(), + new_props) + self.assertRaises(resource.UpdateReplace, port._needs_update, + update_snippet, port.frozen_definition(), + new_props, port.properties, None) self.m.VerifyAll() @@ -623,6 +710,11 @@ class NeutronPortTest(common.HeatTestCase): }}) self.stub_SubnetConstraint_validate() self.stub_NetworkConstraint_validate() + neutronV20.find_resourceid_by_name_or_id( + mox.IsA(neutronclient.Client), + 'network', + 'net1234' + ).MultipleTimes().AndReturn('net1234') neutronclient.Client.update_port( 'fc68ea2c-b60b-4b4f-bd82-94ec81110766', {'port': prop_update} |