Implement AWS::EC2::EIPAssociation updatable
Change-Id: Iedb9a1a42e134e9c524311747d6253a0199ac1d4
This commit is contained in:
parent
35d7465c11
commit
0c2ee06320
@ -184,19 +184,23 @@ class ElasticIpAssociation(resource.Resource):
|
|||||||
properties_schema = {
|
properties_schema = {
|
||||||
INSTANCE_ID: properties.Schema(
|
INSTANCE_ID: properties.Schema(
|
||||||
properties.Schema.STRING,
|
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(
|
EIP: properties.Schema(
|
||||||
properties.Schema.STRING,
|
properties.Schema.STRING,
|
||||||
_('EIP address to associate with instance.')
|
_('EIP address to associate with instance.'),
|
||||||
|
update_allowed=True
|
||||||
),
|
),
|
||||||
ALLOCATION_ID: properties.Schema(
|
ALLOCATION_ID: properties.Schema(
|
||||||
properties.Schema.STRING,
|
properties.Schema.STRING,
|
||||||
_('Allocation ID for VPC EIP address.')
|
_('Allocation ID for VPC EIP address.'),
|
||||||
|
update_allowed=True
|
||||||
),
|
),
|
||||||
NETWORK_INTERFACE_ID: properties.Schema(
|
NETWORK_INTERFACE_ID: properties.Schema(
|
||||||
properties.Schema.STRING,
|
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
|
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):
|
def handle_create(self):
|
||||||
"""Add a floating IP address to a server."""
|
"""Add a floating IP address to a server."""
|
||||||
if self.properties[self.EIP]:
|
if self.properties[self.EIP]:
|
||||||
@ -319,6 +410,15 @@ class ElasticIpAssociation(resource.Resource):
|
|||||||
port_id=None,
|
port_id=None,
|
||||||
ignore_not_found=True)
|
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():
|
def resource_mapping():
|
||||||
return {
|
return {
|
||||||
|
@ -482,8 +482,8 @@ class AllocTest(HeatTestCase):
|
|||||||
id = 'fc68ea2c-b60b-4b4f-bd82-94ec81110766'
|
id = 'fc68ea2c-b60b-4b4f-bd82-94ec81110766'
|
||||||
neutronclient.Client.delete_floatingip(id).AndReturn(None)
|
neutronclient.Client.delete_floatingip(id).AndReturn(None)
|
||||||
|
|
||||||
def mock_list_ports(self):
|
def mock_list_ports(self, id='the_nic'):
|
||||||
neutronclient.Client.list_ports(id='the_nic').AndReturn(
|
neutronclient.Client.list_ports(id=id).AndReturn(
|
||||||
{"ports": [{
|
{"ports": [{
|
||||||
"status": "DOWN",
|
"status": "DOWN",
|
||||||
"binding:host_id": "null",
|
"binding:host_id": "null",
|
||||||
@ -689,3 +689,152 @@ class AllocTest(HeatTestCase):
|
|||||||
self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state)
|
self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state)
|
||||||
|
|
||||||
self.m.VerifyAll()
|
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()
|
||||||
|
Loading…
Reference in New Issue
Block a user