Implement AWS::EC2::EIPAssociation updatable

Change-Id: Iedb9a1a42e134e9c524311747d6253a0199ac1d4
This commit is contained in:
huangtianhua 2014-09-03 14:59:48 +08:00
parent 35d7465c11
commit 0c2ee06320
2 changed files with 255 additions and 6 deletions

View File

@ -184,19 +184,23 @@ class ElasticIpAssociation(resource.Resource):
properties_schema = {
INSTANCE_ID: properties.Schema(
properties.Schema.STRING,
_('Instance ID to associate with EIP specified by EIP property.')
_('Instance ID to associate with EIP specified by EIP property.'),
update_allowed=True
),
EIP: properties.Schema(
properties.Schema.STRING,
_('EIP address to associate with instance.')
_('EIP address to associate with instance.'),
update_allowed=True
),
ALLOCATION_ID: properties.Schema(
properties.Schema.STRING,
_('Allocation ID for VPC EIP address.')
_('Allocation ID for VPC EIP address.'),
update_allowed=True
),
NETWORK_INTERFACE_ID: properties.Schema(
properties.Schema.STRING,
_('Network interface ID to associate with EIP.')
_('Network interface ID to associate with EIP.'),
update_allowed=True
),
}
@ -276,6 +280,93 @@ class ElasticIpAssociation(resource.Resource):
return server
def _floatingIp_detach(self,
nova_ignore_not_found=False,
neutron_ignore_not_found=False):
eip = self.properties[self.EIP]
allocation_id = self.properties[self.ALLOCATION_ID]
instance_id = self.properties[self.INSTANCE_ID]
server = None
if eip:
# if has eip_old, to remove the eip_old from the instance
server = self._nova_remove_floating_ip(instance_id,
eip,
nova_ignore_not_found)
else:
# if hasn't eip_old, to update neutron floatingIp
self._neutron_update_floating_ip(allocation_id,
None,
neutron_ignore_not_found)
return server
def _handle_update_eipInfo(self, prop_diff):
eip_update = prop_diff.get(self.EIP)
allocation_id_update = prop_diff.get(self.ALLOCATION_ID)
instance_id = self.properties[self.INSTANCE_ID]
ni_id = self.properties[self.NETWORK_INTERFACE_ID]
if eip_update:
server = self._floatingIp_detach(neutron_ignore_not_found=True)
if server:
# then to attach the eip_update to the instance
server.add_floating_ip(eip_update)
self.resource_id_set(eip_update)
elif allocation_id_update:
self._floatingIp_detach(nova_ignore_not_found=True)
port_id, port_rsrc = self._get_port_info(ni_id, instance_id)
if not port_id or not port_rsrc:
LOG.error(_('Port not specified.'))
raise exception.NotFound(_('Failed to update, can not found '
'port info.'))
network_id = port_rsrc['network_id']
self._neutron_add_gateway_router(allocation_id_update, network_id)
self._neutron_update_floating_ip(allocation_id_update, port_id)
self.resource_id_set(allocation_id_update)
def _handle_update_portInfo(self, prop_diff):
instance_id_update = prop_diff.get(self.INSTANCE_ID)
ni_id_update = prop_diff.get(self.NETWORK_INTERFACE_ID)
eip = self.properties[self.EIP]
allocation_id = self.properties[self.ALLOCATION_ID]
# if update portInfo, no need to detach the port from
# old instance/floatingip.
if eip:
server = self.nova().servers.get(instance_id_update)
server.add_floating_ip(eip)
else:
port_id, port_rsrc = self._get_port_info(ni_id_update,
instance_id_update)
if not port_id or not port_rsrc:
LOG.error(_('Port not specified.'))
raise exception.NotFound(_('Failed to update, can not found '
'port info.'))
network_id = port_rsrc['network_id']
self._neutron_add_gateway_router(allocation_id, network_id)
self._neutron_update_floating_ip(allocation_id, port_id)
def _validate_update_properties(self, prop_diff):
# according to aws doc, when update allocation_id or eip,
# if you also change the InstanceId or NetworkInterfaceId,
# should go to Replacement flow
if self.ALLOCATION_ID in prop_diff or self.EIP in prop_diff:
instance_id = prop_diff.get(self.INSTANCE_ID)
ni_id = prop_diff.get(self.NETWORK_INTERFACE_ID)
if instance_id or ni_id:
raise resource.UpdateReplace(self.name)
# according to aws doc, when update the instance_id or
# network_interface_id, if you also change the EIP or
# ALLOCATION_ID, should go to Replacement flow
if (self.INSTANCE_ID in prop_diff or
self.NETWORK_INTERFACE_ID in prop_diff):
eip = prop_diff.get(self.EIP)
allocation_id = prop_diff.get(self.ALLOCATION_ID)
if eip or allocation_id:
raise resource.UpdateReplace(self.name)
def handle_create(self):
"""Add a floating IP address to a server."""
if self.properties[self.EIP]:
@ -319,6 +410,15 @@ class ElasticIpAssociation(resource.Resource):
port_id=None,
ignore_not_found=True)
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
if prop_diff:
self._validate_update_properties(prop_diff)
if self.ALLOCATION_ID in prop_diff or self.EIP in prop_diff:
self._handle_update_eipInfo(prop_diff)
elif (self.INSTANCE_ID in prop_diff or
self.NETWORK_INTERFACE_ID in prop_diff):
self._handle_update_portInfo(prop_diff)
def resource_mapping():
return {

View File

@ -482,8 +482,8 @@ class AllocTest(HeatTestCase):
id = 'fc68ea2c-b60b-4b4f-bd82-94ec81110766'
neutronclient.Client.delete_floatingip(id).AndReturn(None)
def mock_list_ports(self):
neutronclient.Client.list_ports(id='the_nic').AndReturn(
def mock_list_ports(self, id='the_nic'):
neutronclient.Client.list_ports(id=id).AndReturn(
{"ports": [{
"status": "DOWN",
"binding:host_id": "null",
@ -689,3 +689,152 @@ class AllocTest(HeatTestCase):
self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state)
self.m.VerifyAll()
def test_update_association_with_InstanceId(self):
nova.NovaClientPlugin._create().AndReturn(self.fc)
server = self.fc.servers.list()[0]
self.fc.servers.get('WebServer').MultipleTimes() \
.AndReturn(server)
server_update = self.fc.servers.list()[1]
self.fc.servers.get('5678').AndReturn(server_update)
self.m.ReplayAll()
t = template_format.parse(eip_template_ipassoc)
stack = utils.parse_stack(t)
self.create_eip(t, stack, 'IPAddress')
ass = self.create_association(t, stack, 'IPAssoc')
self.assertEqual('11.0.0.1', ass.properties['EIP'])
# update with the new InstanceId
props = copy.deepcopy(ass.properties.data)
update_server_id = '5678'
props['InstanceId'] = update_server_id
update_snippet = rsrc_defn.ResourceDefinition(ass.name, ass.type(),
stack.t.parse(stack,
props))
scheduler.TaskRunner(ass.update, update_snippet)()
self.assertEqual((ass.UPDATE, ass.COMPLETE), ass.state)
self.m.VerifyAll()
def test_update_association_with_EIP(self):
nova.NovaClientPlugin._create().AndReturn(self.fc)
server = self.fc.servers.list()[0]
self.fc.servers.get('WebServer').MultipleTimes() \
.AndReturn(server)
self.m.ReplayAll()
t = template_format.parse(eip_template_ipassoc)
stack = utils.parse_stack(t)
self.create_eip(t, stack, 'IPAddress')
ass = self.create_association(t, stack, 'IPAssoc')
# update with the new EIP
props = copy.deepcopy(ass.properties.data)
update_eip = '11.0.0.2'
props['EIP'] = update_eip
update_snippet = rsrc_defn.ResourceDefinition(ass.name, ass.type(),
stack.t.parse(stack,
props))
scheduler.TaskRunner(ass.update, update_snippet)()
self.assertEqual((ass.UPDATE, ass.COMPLETE), ass.state)
self.m.VerifyAll()
def test_update_association_with_AllocationId_or_EIP(self):
nova.NovaClientPlugin._create().AndReturn(self.fc)
server = self.fc.servers.list()[0]
self.fc.servers.get('WebServer').MultipleTimes()\
.AndReturn(server)
self.mock_list_instance_ports('WebServer')
self.mock_show_network()
self.mock_no_router_for_vpc()
self.mock_update_floatingip(
port='a000228d-b40b-4124-8394-a4082ae1b76c')
self.mock_update_floatingip(port=None)
self.m.ReplayAll()
t = template_format.parse(eip_template_ipassoc)
stack = utils.parse_stack(t)
self.create_eip(t, stack, 'IPAddress')
ass = self.create_association(t, stack, 'IPAssoc')
self.assertEqual('11.0.0.1', ass.properties['EIP'])
# change EIP to AllocationId
props = copy.deepcopy(ass.properties.data)
update_allocationId = 'fc68ea2c-b60b-4b4f-bd82-94ec81110766'
props['AllocationId'] = update_allocationId
props.pop('EIP')
update_snippet = rsrc_defn.ResourceDefinition(ass.name, ass.type(),
stack.t.parse(stack,
props))
scheduler.TaskRunner(ass.update, update_snippet)()
self.assertEqual((ass.UPDATE, ass.COMPLETE), ass.state)
# change AllocationId to EIP
props = copy.deepcopy(ass.properties.data)
update_eip = '11.0.0.2'
props['EIP'] = update_eip
props.pop('AllocationId')
update_snippet = rsrc_defn.ResourceDefinition(ass.name, ass.type(),
stack.t.parse(stack,
props))
scheduler.TaskRunner(ass.update, update_snippet)()
self.assertEqual((ass.UPDATE, ass.COMPLETE), ass.state)
self.m.VerifyAll()
def test_update_association_with_NetworkInterfaceId_or_InstanceId(self):
self.mock_create_floatingip()
self.mock_list_ports()
self.mock_show_network()
self.mock_no_router_for_vpc()
self.mock_update_floatingip()
self.mock_list_ports(id='a000228d-b40b-4124-8394-a4082ae1b76b')
self.mock_show_network()
self.mock_no_router_for_vpc()
self.mock_update_floatingip(
port='a000228d-b40b-4124-8394-a4082ae1b76b')
self.mock_list_instance_ports('5678')
self.mock_show_network()
self.mock_no_router_for_vpc()
self.mock_update_floatingip(
port='a000228d-b40b-4124-8394-a4082ae1b76c')
self.m.ReplayAll()
t = template_format.parse(eip_template_ipassoc2)
stack = utils.parse_stack(t)
self.create_eip(t, stack, 'the_eip')
ass = self.create_association(t, stack, 'IPAssoc')
# update with the new NetworkInterfaceId
props = copy.deepcopy(ass.properties.data)
update_networkInterfaceId = 'a000228d-b40b-4124-8394-a4082ae1b76b'
props['NetworkInterfaceId'] = update_networkInterfaceId
update_snippet = rsrc_defn.ResourceDefinition(ass.name, ass.type(),
stack.t.parse(stack,
props))
scheduler.TaskRunner(ass.update, update_snippet)()
self.assertEqual((ass.UPDATE, ass.COMPLETE), ass.state)
# update with the InstanceId
props = copy.deepcopy(ass.properties.data)
instance_id = '5678'
props.pop('NetworkInterfaceId')
props['InstanceId'] = instance_id
update_snippet = rsrc_defn.ResourceDefinition(ass.name, ass.type(),
stack.t.parse(stack,
props))
scheduler.TaskRunner(ass.update, update_snippet)()
self.assertEqual((ass.UPDATE, ass.COMPLETE), ass.state)
self.m.VerifyAll()