Make sure NetworkInterfaces and SubnetId updatable
We should make the properties 'NetworkInterfaces' and 'SubnetId' updatable for AWS::EC2::Instance resource, so we can update the networks for an instance. Change-Id: I4a475fee6416696558807c189473a90d7517cfc0 Closes-Bug: #1280151
This commit is contained in:
parent
f18bb8b5af
commit
0ddd9e0d9f
@ -12,6 +12,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
|
||||
from oslo.config import cfg
|
||||
|
||||
cfg.CONF.import_opt('instance_user', 'heat.common.config')
|
||||
@ -208,7 +210,8 @@ class Instance(resource.Resource):
|
||||
),
|
||||
NETWORK_INTERFACES: properties.Schema(
|
||||
properties.Schema.LIST,
|
||||
_('Network interfaces to associate with instance.')
|
||||
_('Network interfaces to associate with instance.'),
|
||||
update_allowed=True
|
||||
),
|
||||
SOURCE_DEST_CHECK: properties.Schema(
|
||||
properties.Schema.BOOLEAN,
|
||||
@ -217,7 +220,8 @@ class Instance(resource.Resource):
|
||||
),
|
||||
SUBNET_ID: properties.Schema(
|
||||
properties.Schema.STRING,
|
||||
_('Subnet ID to launch instance in.')
|
||||
_('Subnet ID to launch instance in.'),
|
||||
update_allowed=True
|
||||
),
|
||||
TAGS: properties.Schema(
|
||||
properties.Schema.LIST,
|
||||
@ -523,10 +527,18 @@ class Instance(resource.Resource):
|
||||
return ((vol[self.VOLUME_ID],
|
||||
vol[self.VOLUME_DEVICE]) for vol in volumes)
|
||||
|
||||
def _remove_matched_ifaces(self, old_network_ifaces, new_network_ifaces):
|
||||
# find matches and remove them from old and new ifaces
|
||||
old_network_ifaces_copy = copy.deepcopy(old_network_ifaces)
|
||||
for iface in old_network_ifaces_copy:
|
||||
if iface in new_network_ifaces:
|
||||
new_network_ifaces.remove(iface)
|
||||
old_network_ifaces.remove(iface)
|
||||
|
||||
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
|
||||
if 'Metadata' in tmpl_diff:
|
||||
self.metadata = tmpl_diff['Metadata']
|
||||
|
||||
checkers = []
|
||||
server = None
|
||||
if self.TAGS in prop_diff:
|
||||
server = self.nova().servers.get(self.resource_id)
|
||||
@ -541,11 +553,82 @@ class Instance(resource.Resource):
|
||||
server = self.nova().servers.get(self.resource_id)
|
||||
checker = scheduler.TaskRunner(nova_utils.resize, server, flavor,
|
||||
flavor_id)
|
||||
checker.start()
|
||||
return checker
|
||||
checkers.append(checker)
|
||||
if self.NETWORK_INTERFACES in prop_diff:
|
||||
new_network_ifaces = prop_diff.get(self.NETWORK_INTERFACES)
|
||||
old_network_ifaces = self.properties.get(self.NETWORK_INTERFACES)
|
||||
subnet_id = (
|
||||
prop_diff.get(self.SUBNET_ID) or
|
||||
self.properties.get(self.SUBNET_ID))
|
||||
security_groups = self._get_security_groups()
|
||||
if not server:
|
||||
server = self.nova().servers.get(self.resource_id)
|
||||
# if there is entrys in old_network_ifaces and new_network_ifaces,
|
||||
# remove the same entrys from old and new ifaces
|
||||
if old_network_ifaces and new_network_ifaces:
|
||||
# there are four situations:
|
||||
# 1.old includes new, such as: old = 2,3, new = 2
|
||||
# 2.new includes old, such as: old = 2,3, new = 1,2,3
|
||||
# 3.has overlaps, such as: old = 2,3, new = 1,2
|
||||
# 4.different, such as: old = 2,3, new = 1,4
|
||||
# detach unmatched ones in old, attach unmatched ones in new
|
||||
self._remove_matched_ifaces(old_network_ifaces,
|
||||
new_network_ifaces)
|
||||
if old_network_ifaces:
|
||||
old_nics = self._build_nics(old_network_ifaces)
|
||||
for nic in old_nics:
|
||||
checker = scheduler.TaskRunner(
|
||||
server.interface_detach,
|
||||
nic['port-id'])
|
||||
checkers.append(checker)
|
||||
if new_network_ifaces:
|
||||
new_nics = self._build_nics(new_network_ifaces)
|
||||
for nic in new_nics:
|
||||
checker = scheduler.TaskRunner(
|
||||
server.interface_attach,
|
||||
nic['port-id'],
|
||||
None, None)
|
||||
checkers.append(checker)
|
||||
# if the interfaces not come from property 'NetworkInterfaces',
|
||||
# the situation is somewhat complex, so to detach the old ifaces,
|
||||
# and then attach the new ones.
|
||||
else:
|
||||
interfaces = server.interface_list()
|
||||
for iface in interfaces:
|
||||
checker = scheduler.TaskRunner(server.interface_detach,
|
||||
iface.port_id)
|
||||
checkers.append(checker)
|
||||
nics = self._build_nics(new_network_ifaces,
|
||||
security_groups=security_groups,
|
||||
subnet_id=subnet_id)
|
||||
# 'SubnetId' property is empty(or None) and
|
||||
# 'NetworkInterfaces' property is empty(or None),
|
||||
# _build_nics() will return nics = None,we should attach
|
||||
# first free port, according to similar behavior during
|
||||
# instance creation
|
||||
if not nics:
|
||||
checker = scheduler.TaskRunner(server.interface_attach,
|
||||
None, None, None)
|
||||
checkers.append(checker)
|
||||
else:
|
||||
for nic in nics:
|
||||
checker = scheduler.TaskRunner(
|
||||
server.interface_attach,
|
||||
nic['port-id'], None, None)
|
||||
checkers.append(checker)
|
||||
|
||||
def check_update_complete(self, checker):
|
||||
return checker.step() if checker is not None else True
|
||||
if checkers:
|
||||
checkers[0].start()
|
||||
return checkers
|
||||
|
||||
def check_update_complete(self, checkers):
|
||||
'''Push all checkers to completion in list order.'''
|
||||
for checker in checkers:
|
||||
if not checker.started():
|
||||
checker.start()
|
||||
if not checker.step():
|
||||
return False
|
||||
return True
|
||||
|
||||
def metadata_update(self, new_metadata=None):
|
||||
'''
|
||||
|
@ -377,6 +377,270 @@ class InstancesTest(HeatTestCase):
|
||||
self.assertEqual((instance.UPDATE, instance.FAILED), instance.state)
|
||||
self.m.VerifyAll()
|
||||
|
||||
def create_fake_iface(self, port, net, ip):
|
||||
class fake_interface():
|
||||
def __init__(self, port_id, net_id, fixed_ip):
|
||||
self.port_id = port_id
|
||||
self.net_id = net_id
|
||||
self.fixed_ips = [{'ip_address': fixed_ip}]
|
||||
|
||||
return fake_interface(port, net, ip)
|
||||
|
||||
def test_instance_update_network_interfaces(self):
|
||||
"""
|
||||
Instance.handle_update supports changing the NetworkInterfaces,
|
||||
and makes the change making a resize API call against Nova.
|
||||
"""
|
||||
return_server = self.fc.servers.list()[1]
|
||||
return_server.id = 1234
|
||||
instance = self._create_test_instance(return_server,
|
||||
'ud_network_interfaces')
|
||||
# if new overlaps with old, detach the different ones in old, and
|
||||
# attach the different ones in new
|
||||
old_interfaces = [
|
||||
{'NetworkInterfaceId': 'ea29f957-cd35-4364-98fb-57ce9732c10d',
|
||||
'DeviceIndex': '2'},
|
||||
{'NetworkInterfaceId': 'd1e9c73c-04fe-4e9e-983c-d5ef94cd1a46',
|
||||
'DeviceIndex': '1'}]
|
||||
new_interfaces = [
|
||||
{'NetworkInterfaceId': 'ea29f957-cd35-4364-98fb-57ce9732c10d',
|
||||
'DeviceIndex': '2'},
|
||||
{'NetworkInterfaceId': '34b752ec-14de-416a-8722-9531015e04a5',
|
||||
'DeviceIndex': '3'}]
|
||||
|
||||
instance.t['Properties']['NetworkInterfaces'] = old_interfaces
|
||||
update_template = copy.deepcopy(instance.t)
|
||||
update_template['Properties']['NetworkInterfaces'] = new_interfaces
|
||||
|
||||
self.m.StubOutWithMock(self.fc.servers, 'get')
|
||||
self.fc.servers.get(1234).AndReturn(return_server)
|
||||
self.m.StubOutWithMock(return_server, 'interface_detach')
|
||||
return_server.interface_detach(
|
||||
'd1e9c73c-04fe-4e9e-983c-d5ef94cd1a46').AndReturn(None)
|
||||
self.m.StubOutWithMock(return_server, 'interface_attach')
|
||||
return_server.interface_attach('34b752ec-14de-416a-8722-9531015e04a5',
|
||||
None, None).AndReturn(None)
|
||||
self.m.ReplayAll()
|
||||
|
||||
scheduler.TaskRunner(instance.update, update_template)()
|
||||
self.assertEqual((instance.UPDATE, instance.COMPLETE), instance.state)
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_instance_update_network_interfaces_old_include_new(self):
|
||||
"""
|
||||
Instance.handle_update supports changing the NetworkInterfaces,
|
||||
and makes the change making a resize API call against Nova.
|
||||
"""
|
||||
return_server = self.fc.servers.list()[1]
|
||||
return_server.id = 1234
|
||||
instance = self._create_test_instance(return_server,
|
||||
'ud_network_interfaces')
|
||||
# if old include new, just detach the different ones in old
|
||||
old_interfaces = [
|
||||
{'NetworkInterfaceId': 'ea29f957-cd35-4364-98fb-57ce9732c10d',
|
||||
'DeviceIndex': '2'},
|
||||
{'NetworkInterfaceId': 'd1e9c73c-04fe-4e9e-983c-d5ef94cd1a46',
|
||||
'DeviceIndex': '1'}]
|
||||
new_interfaces = [
|
||||
{'NetworkInterfaceId': 'ea29f957-cd35-4364-98fb-57ce9732c10d',
|
||||
'DeviceIndex': '2'}]
|
||||
|
||||
instance.t['Properties']['NetworkInterfaces'] = old_interfaces
|
||||
update_template = copy.deepcopy(instance.t)
|
||||
update_template['Properties']['NetworkInterfaces'] = new_interfaces
|
||||
|
||||
self.m.StubOutWithMock(self.fc.servers, 'get')
|
||||
self.fc.servers.get(1234).AndReturn(return_server)
|
||||
self.m.StubOutWithMock(return_server, 'interface_detach')
|
||||
return_server.interface_detach(
|
||||
'd1e9c73c-04fe-4e9e-983c-d5ef94cd1a46').AndReturn(None)
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
scheduler.TaskRunner(instance.update, update_template)()
|
||||
self.assertEqual((instance.UPDATE, instance.COMPLETE), instance.state)
|
||||
|
||||
def test_instance_update_network_interfaces_new_include_old(self):
|
||||
"""
|
||||
Instance.handle_update supports changing the NetworkInterfaces,
|
||||
and makes the change making a resize API call against Nova.
|
||||
"""
|
||||
return_server = self.fc.servers.list()[1]
|
||||
return_server.id = 1234
|
||||
instance = self._create_test_instance(return_server,
|
||||
'ud_network_interfaces')
|
||||
# if new include old, just attach the different ones in new
|
||||
old_interfaces = [
|
||||
{'NetworkInterfaceId': 'ea29f957-cd35-4364-98fb-57ce9732c10d',
|
||||
'DeviceIndex': '2'}]
|
||||
new_interfaces = [
|
||||
{'NetworkInterfaceId': 'ea29f957-cd35-4364-98fb-57ce9732c10d',
|
||||
'DeviceIndex': '2'},
|
||||
{'NetworkInterfaceId': 'd1e9c73c-04fe-4e9e-983c-d5ef94cd1a46',
|
||||
'DeviceIndex': '1'}]
|
||||
|
||||
instance.t['Properties']['NetworkInterfaces'] = old_interfaces
|
||||
update_template = copy.deepcopy(instance.t)
|
||||
update_template['Properties']['NetworkInterfaces'] = new_interfaces
|
||||
|
||||
self.m.StubOutWithMock(self.fc.servers, 'get')
|
||||
self.fc.servers.get(1234).AndReturn(return_server)
|
||||
self.m.StubOutWithMock(return_server, 'interface_attach')
|
||||
return_server.interface_attach('d1e9c73c-04fe-4e9e-983c-d5ef94cd1a46',
|
||||
None, None).AndReturn(None)
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
scheduler.TaskRunner(instance.update, update_template)()
|
||||
self.assertEqual((instance.UPDATE, instance.COMPLETE), instance.state)
|
||||
|
||||
def test_instance_update_network_interfaces_new_old_all_different(self):
|
||||
"""
|
||||
Instance.handle_update supports changing the NetworkInterfaces,
|
||||
and makes the change making a resize API call against Nova.
|
||||
"""
|
||||
return_server = self.fc.servers.list()[1]
|
||||
return_server.id = 1234
|
||||
instance = self._create_test_instance(return_server,
|
||||
'ud_network_interfaces')
|
||||
# if different, detach the old ones and attach the new ones
|
||||
old_interfaces = [
|
||||
{'NetworkInterfaceId': 'ea29f957-cd35-4364-98fb-57ce9732c10d',
|
||||
'DeviceIndex': '2'}]
|
||||
new_interfaces = [
|
||||
{'NetworkInterfaceId': '34b752ec-14de-416a-8722-9531015e04a5',
|
||||
'DeviceIndex': '3'},
|
||||
{'NetworkInterfaceId': 'd1e9c73c-04fe-4e9e-983c-d5ef94cd1a46',
|
||||
'DeviceIndex': '1'}]
|
||||
|
||||
instance.t['Properties']['NetworkInterfaces'] = old_interfaces
|
||||
update_template = copy.deepcopy(instance.t)
|
||||
update_template['Properties']['NetworkInterfaces'] = new_interfaces
|
||||
|
||||
self.m.StubOutWithMock(self.fc.servers, 'get')
|
||||
self.fc.servers.get(1234).AndReturn(return_server)
|
||||
self.m.StubOutWithMock(return_server, 'interface_detach')
|
||||
return_server.interface_detach(
|
||||
'ea29f957-cd35-4364-98fb-57ce9732c10d').AndReturn(None)
|
||||
self.m.StubOutWithMock(return_server, 'interface_attach')
|
||||
return_server.interface_attach('d1e9c73c-04fe-4e9e-983c-d5ef94cd1a46',
|
||||
None, None).AndReturn(None)
|
||||
return_server.interface_attach('34b752ec-14de-416a-8722-9531015e04a5',
|
||||
None, None).AndReturn(None)
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
scheduler.TaskRunner(instance.update, update_template)()
|
||||
self.assertEqual((instance.UPDATE, instance.COMPLETE), instance.state)
|
||||
|
||||
def test_instance_update_network_interfaces_no_old(self):
|
||||
"""
|
||||
Instance.handle_update supports changing the NetworkInterfaces,
|
||||
and makes the change making a resize API call against Nova.
|
||||
"""
|
||||
return_server = self.fc.servers.list()[1]
|
||||
return_server.id = 1234
|
||||
instance = self._create_test_instance(return_server,
|
||||
'ud_network_interfaces')
|
||||
new_interfaces = [
|
||||
{'NetworkInterfaceId': 'ea29f957-cd35-4364-98fb-57ce9732c10d',
|
||||
'DeviceIndex': '2'},
|
||||
{'NetworkInterfaceId': '34b752ec-14de-416a-8722-9531015e04a5',
|
||||
'DeviceIndex': '3'}]
|
||||
iface = self.create_fake_iface('d1e9c73c-04fe-4e9e-983c-d5ef94cd1a46',
|
||||
'c4485ba1-283a-4f5f-8868-0cd46cdda52f',
|
||||
'10.0.0.4')
|
||||
|
||||
update_template = copy.deepcopy(instance.t)
|
||||
update_template['Properties']['NetworkInterfaces'] = new_interfaces
|
||||
|
||||
self.m.StubOutWithMock(self.fc.servers, 'get')
|
||||
self.fc.servers.get(1234).AndReturn(return_server)
|
||||
self.m.StubOutWithMock(return_server, 'interface_list')
|
||||
return_server.interface_list().AndReturn([iface])
|
||||
self.m.StubOutWithMock(return_server, 'interface_detach')
|
||||
return_server.interface_detach(
|
||||
'd1e9c73c-04fe-4e9e-983c-d5ef94cd1a46').AndReturn(None)
|
||||
self.m.StubOutWithMock(return_server, 'interface_attach')
|
||||
return_server.interface_attach('ea29f957-cd35-4364-98fb-57ce9732c10d',
|
||||
None, None).AndReturn(None)
|
||||
return_server.interface_attach('34b752ec-14de-416a-8722-9531015e04a5',
|
||||
None, None).AndReturn(None)
|
||||
|
||||
self.m.ReplayAll()
|
||||
|
||||
scheduler.TaskRunner(instance.update, update_template)()
|
||||
self.assertEqual((instance.UPDATE, instance.COMPLETE), instance.state)
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_instance_update_network_interfaces_no_old_no_new(self):
|
||||
"""
|
||||
Instance.handle_update supports changing the NetworkInterfaces,
|
||||
and makes the change making a resize API call against Nova.
|
||||
"""
|
||||
return_server = self.fc.servers.list()[1]
|
||||
return_server.id = 1234
|
||||
instance = self._create_test_instance(return_server,
|
||||
'ud_network_interfaces')
|
||||
iface = self.create_fake_iface('d1e9c73c-04fe-4e9e-983c-d5ef94cd1a46',
|
||||
'c4485ba1-283a-4f5f-8868-0cd46cdda52f',
|
||||
'10.0.0.4')
|
||||
|
||||
update_template = copy.deepcopy(instance.t)
|
||||
update_template['Properties']['NetworkInterfaces'] = []
|
||||
|
||||
self.m.StubOutWithMock(self.fc.servers, 'get')
|
||||
self.fc.servers.get(1234).AndReturn(return_server)
|
||||
self.m.StubOutWithMock(return_server, 'interface_list')
|
||||
return_server.interface_list().AndReturn([iface])
|
||||
self.m.StubOutWithMock(return_server, 'interface_detach')
|
||||
return_server.interface_detach(
|
||||
'd1e9c73c-04fe-4e9e-983c-d5ef94cd1a46').AndReturn(None)
|
||||
self.m.StubOutWithMock(return_server, 'interface_attach')
|
||||
return_server.interface_attach(None, None, None).AndReturn(None)
|
||||
self.m.ReplayAll()
|
||||
|
||||
scheduler.TaskRunner(instance.update, update_template)()
|
||||
self.assertEqual((instance.UPDATE, instance.COMPLETE), instance.state)
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_instance_update_network_interfaces_no_old_new_with_subnet(self):
|
||||
"""
|
||||
Instance.handle_update supports changing the NetworkInterfaces,
|
||||
and makes the change making a resize API call against Nova.
|
||||
"""
|
||||
return_server = self.fc.servers.list()[1]
|
||||
return_server.id = 1234
|
||||
instance = self._create_test_instance(return_server,
|
||||
'ud_network_interfaces')
|
||||
iface = self.create_fake_iface('d1e9c73c-04fe-4e9e-983c-d5ef94cd1a46',
|
||||
'c4485ba1-283a-4f5f-8868-0cd46cdda52f',
|
||||
'10.0.0.4')
|
||||
subnet_id = '8c1aaddf-e49e-4f28-93ea-ca9f0b3c6240'
|
||||
nics = [{'port-id': 'ea29f957-cd35-4364-98fb-57ce9732c10d'}]
|
||||
update_template = copy.deepcopy(instance.t)
|
||||
update_template['Properties']['NetworkInterfaces'] = []
|
||||
update_template['Properties']['SubnetId'] = subnet_id
|
||||
|
||||
self.m.StubOutWithMock(self.fc.servers, 'get')
|
||||
self.fc.servers.get(1234).AndReturn(return_server)
|
||||
self.m.StubOutWithMock(return_server, 'interface_list')
|
||||
return_server.interface_list().AndReturn([iface])
|
||||
self.m.StubOutWithMock(return_server, 'interface_detach')
|
||||
return_server.interface_detach(
|
||||
'd1e9c73c-04fe-4e9e-983c-d5ef94cd1a46').AndReturn(None)
|
||||
self.m.StubOutWithMock(instance, '_build_nics')
|
||||
instance._build_nics([], security_groups=None,
|
||||
subnet_id=subnet_id).AndReturn(nics)
|
||||
self.m.StubOutWithMock(return_server, 'interface_attach')
|
||||
return_server.interface_attach('ea29f957-cd35-4364-98fb-57ce9732c10d',
|
||||
None, None).AndReturn(None)
|
||||
self.m.ReplayAll()
|
||||
|
||||
scheduler.TaskRunner(instance.update, update_template)()
|
||||
self.assertEqual((instance.UPDATE, instance.COMPLETE), instance.state)
|
||||
self.m.VerifyAll()
|
||||
|
||||
def test_instance_update_replace(self):
|
||||
return_server = self.fc.servers.list()[1]
|
||||
instance = self._create_test_instance(return_server,
|
||||
|
Loading…
Reference in New Issue
Block a user