summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2016-01-26 21:48:41 +0000
committerGerrit Code Review <review@openstack.org>2016-01-26 21:48:42 +0000
commit2f2798272b3ec71faee25f794265c28319f29068 (patch)
tree69079bd89ebd50608b460f38f56008dec0dd79e7
parent1940d1d87821290c324b17eefcbce291732e5e1d (diff)
parent03ee72291746143a50b888303a4361042b479954 (diff)
downloadheat-2f2798272b3ec71faee25f794265c28319f29068.tar.gz
Merge "Neutron port, tolerate switching network name/id" into stable/kilo
-rw-r--r--heat/engine/resources/openstack/neutron/neutron.py8
-rw-r--r--heat/engine/resources/openstack/neutron/port.py31
-rw-r--r--heat/tests/neutron/test_neutron_port.py96
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}