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:
huangtianhua 2014-03-13 14:54:55 +08:00
parent f18bb8b5af
commit 0ddd9e0d9f
2 changed files with 354 additions and 7 deletions

View File

@ -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):
'''

View File

@ -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,