From 4c1adb4c83f2f393dfb6a0460a8ecc3f14cd9b50 Mon Sep 17 00:00:00 2001 From: Steve Baker Date: Tue, 21 May 2013 10:01:16 +1200 Subject: [PATCH] Rework associations from vpc to quantum resources. Metadata cannot be used to store the relationships between VPC resources and the underlying quantum resources, since this prevents VPC or Subnet refs from being passed in as parameters. This rework results in no state being stored in metadata. Route table relationships are inferred from the template. An assumption is made that the quantum router associatiated with the VPC will have the same name as the quantum net. The easiest way of ensuring that is to creat the VPC in a different Heat stack. Fixes bug: #1165050 Fixes bug: #1165056 Fixes bug: #1166779 Fixes bug: #1166787 Change-Id: I6fb087bfc63ed13703089a16ed57858e6911b7d8 --- heat/engine/resources/internet_gateway.py | 46 ++-- heat/engine/resources/route_table.py | 71 ++++-- heat/engine/resources/subnet.py | 30 +-- heat/engine/resources/vpc.py | 52 +++-- heat/tests/test_vpc.py | 273 ++++++++++++++-------- 5 files changed, 301 insertions(+), 171 deletions(-) diff --git a/heat/engine/resources/internet_gateway.py b/heat/engine/resources/internet_gateway.py index b25756a8..f265a407 100644 --- a/heat/engine/resources/internet_gateway.py +++ b/heat/engine/resources/internet_gateway.py @@ -35,8 +35,13 @@ class InternetGateway(resource.Resource): } def handle_create(self): - client = self.quantum() + self.resource_id_set(self.physical_resource_name()) + def handle_delete(self): + pass + + @staticmethod + def get_external_network_id(client): ext_filter = {'router:external': True} ext_nets = client.list_networks(**ext_filter)['networks'] if len(ext_nets) != 1: @@ -45,15 +50,8 @@ class InternetGateway(resource.Resource): # the default one raise exception.Error( 'Expected 1 external network, found %d' % len(ext_nets)) - external_network_id = ext_nets[0]['id'] - md = { - 'external_network_id': external_network_id - } - self.metadata = md - - def handle_delete(self): - pass + return external_network_id class VPCGatewayAttachment(resource.Resource): @@ -68,24 +66,36 @@ class VPCGatewayAttachment(resource.Resource): 'Implemented': False} } + def _vpc_route_tables(self): + for resource in self.stack.resources.itervalues(): + if (resource.type() == 'AWS::EC2::RouteTable' and + resource.properties.get('VpcId') == + self.properties.get('VpcId')): + yield resource + + def add_dependencies(self, deps): + super(VPCGatewayAttachment, self).add_dependencies(deps) + # Depend on any route table in this template with the same + # VpcId as this VpcId. + # All route tables must exist before gateway attachment + # as attachment happens to routers (not VPCs) + for route_table in self._vpc_route_tables(): + deps += (self, route_table) + def handle_create(self): client = self.quantum() - gateway = self.stack[self.properties.get('InternetGatewayId')] - vpc = self.stack.resource_by_refid(self.properties.get('VpcId')) - external_network_id = gateway.metadata['external_network_id'] - - for router_id in vpc.metadata['all_router_ids']: - client.add_gateway_router(router_id, { + external_network_id = InternetGateway.get_external_network_id(client) + for router in self._vpc_route_tables(): + client.add_gateway_router(router.resource_id, { 'network_id': external_network_id}) def handle_delete(self): from quantumclient.common.exceptions import QuantumClientException client = self.quantum() - vpc = self.stack.resource_by_refid(self.properties.get('VpcId')) - for router_id in vpc.metadata['all_router_ids']: + for router in self._vpc_route_tables(): try: - client.remove_gateway_router(router_id) + client.remove_gateway_router(router.resource_id) except QuantumClientException as ex: if ex.status_code != 404: raise ex diff --git a/heat/engine/resources/route_table.py b/heat/engine/resources/route_table.py index b2aac165..6311e3e8 100644 --- a/heat/engine/resources/route_table.py +++ b/heat/engine/resources/route_table.py @@ -16,6 +16,8 @@ from heat.engine import clients from heat.openstack.common import log as logging from heat.engine import resource +from heat.engine.resources.quantum import quantum +from heat.engine.resources.vpc import VPC if clients.quantumclient is not None: from quantumclient.common.exceptions import QuantumClientException @@ -43,17 +45,26 @@ class RouteTable(resource.Resource): client = self.quantum() props = {'name': self.physical_resource_name()} router = client.create_router({'router': props})['router'] - - # add this router to the list of all routers in the VPC - vpc = self.stack.resource_by_refid(self.properties.get('VpcId')) - vpc_md = vpc.metadata - vpc_md['all_router_ids'].append(router['id']) - vpc.metadata = vpc_md - - # TODO(sbaker) all_router_ids has changed, any VPCGatewayAttachment - # for this vpc needs to be notified self.resource_id_set(router['id']) + def check_create_complete(self, *args): + client = self.quantum() + attributes = client.show_router( + self.resource_id)['router'] + if not quantum.QuantumResource.is_built(attributes): + return False + + network_id = self.properties.get('VpcId') + default_router = VPC.router_for_vpc(client, network_id) + if default_router and default_router.get('external_gateway_info'): + # the default router for the VPC is connected + # to the external router, so do it for this too. + external_network_id = default_router[ + 'external_gateway_info']['network_id'] + client.add_gateway_router(self.resource_id, { + 'network_id': external_network_id}) + return True + def handle_delete(self): client = self.quantum() @@ -64,13 +75,12 @@ class RouteTable(resource.Resource): if ex.status_code != 404: raise ex - # remove this router from the list of all routers in the VPC - vpc = self.stack.resource_by_refid(self.properties.get('VpcId')) - vpc_md = vpc.metadata - vpc_md['all_router_ids'].remove(router_id) - vpc.metadata = vpc_md - # TODO(sbaker) all_router_ids has changed, any VPCGatewayAttachment - # for this vpc needs to be notified + # just in case this router has been added to a gateway, remove it + try: + client.remove_gateway_router(router_id) + except QuantumClientException as ex: + if ex.status_code != 404: + raise ex class SubnetRouteTableAssocation(resource.Resource): @@ -86,17 +96,17 @@ class SubnetRouteTableAssocation(resource.Resource): def handle_create(self): client = self.quantum() - subnet = self.stack.resource_by_refid(self.properties.get('SubnetId')) subnet_id = self.properties.get('SubnetId') - previous_router_id = subnet.metadata['router_id'] router_id = self.properties.get('RouteTableId') #remove the default router association for this subnet. try: - client.remove_interface_router( - previous_router_id, - {'subnet_id': subnet_id}) + previous_router = self._router_for_subnet(subnet_id) + if previous_router: + client.remove_interface_router( + previous_router['id'], + {'subnet_id': subnet_id}) except QuantumClientException as ex: if ex.status_code != 404: raise ex @@ -104,11 +114,16 @@ class SubnetRouteTableAssocation(resource.Resource): client.add_interface_router( router_id, {'subnet_id': subnet_id}) + def _router_for_subnet(self, subnet_id): + client = self.quantum() + subnet = client.show_subnet( + subnet_id)['subnet'] + network_id = subnet['network_id'] + return VPC.router_for_vpc(client, network_id) + def handle_delete(self): client = self.quantum() - subnet = self.stack.resource_by_refid(self.properties.get('SubnetId')) subnet_id = self.properties.get('SubnetId') - default_router_id = subnet.metadata['default_router_id'] router_id = self.properties.get('RouteTableId') @@ -120,8 +135,14 @@ class SubnetRouteTableAssocation(resource.Resource): raise ex # add back the default router - client.add_interface_router( - default_router_id, {'subnet_id': subnet_id}) + try: + default_router = self._router_for_subnet(subnet_id) + if default_router: + client.add_interface_router( + default_router['id'], {'subnet_id': subnet_id}) + except QuantumClientException as ex: + if ex.status_code != 404: + raise ex def resource_mapping(): diff --git a/heat/engine/resources/subnet.py b/heat/engine/resources/subnet.py index eed95b61..d79fac98 100644 --- a/heat/engine/resources/subnet.py +++ b/heat/engine/resources/subnet.py @@ -17,6 +17,7 @@ from heat.engine import clients from heat.common import exception from heat.openstack.common import log as logging from heat.engine import resource +from heat.engine.resources.vpc import VPC logger = logging.getLogger(__name__) @@ -45,8 +46,6 @@ class Subnet(resource.Resource): client = self.quantum() # TODO(sbaker) Verify that this CidrBlock is within the vpc CidrBlock network_id = self.properties.get('VpcId') - vpc = self.stack.resource_by_refid(network_id) - router_id = vpc.metadata['router_id'] props = { 'network_id': network_id, @@ -56,31 +55,26 @@ class Subnet(resource.Resource): } subnet = client.create_subnet({'subnet': props})['subnet'] - #TODO(sbaker) check for a non-default router for this network - # and use that instead if it exists - client.add_interface_router( - router_id, - {'subnet_id': subnet['id']}) - md = { - 'router_id': router_id, - 'default_router_id': router_id - } - self.metadata = md + router = VPC.router_for_vpc(self.quantum(), network_id) + if router: + client.add_interface_router( + router['id'], + {'subnet_id': subnet['id']}) self.resource_id_set(subnet['id']) def handle_delete(self): from quantumclient.common.exceptions import QuantumClientException client = self.quantum() - router_id = self.metadata['router_id'] + network_id = self.properties.get('VpcId') subnet_id = self.resource_id - #TODO(sbaker) check for a non-default router for this network - # and remove that instead if it exists try: - client.remove_interface_router( - router_id, - {'subnet_id': subnet_id}) + router = VPC.router_for_vpc(self.quantum(), network_id) + if router: + client.remove_interface_router( + router['id'], + {'subnet_id': subnet_id}) except QuantumClientException as ex: if ex.status_code != 404: raise ex diff --git a/heat/engine/resources/vpc.py b/heat/engine/resources/vpc.py index f9a42b9d..7d8f5e2f 100644 --- a/heat/engine/resources/vpc.py +++ b/heat/engine/resources/vpc.py @@ -13,9 +13,11 @@ # License for the specific language governing permissions and limitations # under the License. +from heat.common import exception from heat.engine import clients from heat.openstack.common import log as logging from heat.engine import resource +from heat.engine.resources.quantum import quantum logger = logging.getLogger(__name__) @@ -42,31 +44,53 @@ class VPC(resource.Resource): def handle_create(self): client = self.quantum() - props = {'name': self.physical_resource_name()} - # Creates a network with an implicit router - net = client.create_network({'network': props})['network'] - router = client.create_router({'router': props})['router'] - md = { - 'router_id': router['id'], - 'all_router_ids': [router['id']] - } - self.metadata = md + # The VPC's net and router are associated by having identical names. + net_props = {'name': self.physical_resource_name()} + router_props = {'name': self.physical_resource_name()} + + net = client.create_network({'network': net_props})['network'] + client.create_router({'router': router_props})['router'] + self.resource_id_set(net['id']) + @staticmethod + def network_for_vpc(client, network_id): + return client.show_network(network_id)['network'] + + @staticmethod + def router_for_vpc(client, network_id): + # first get the quantum net + net = VPC.network_for_vpc(client, network_id) + # then find a router with the same name + routers = client.list_routers(name=net['name'])['routers'] + if len(routers) == 0: + # There may be no router if the net was created manually + # instead of in another stack. + return None + if len(routers) > 1: + raise exception.Error( + _('Multiple routers found with name %s') % net['name']) + return routers[0] + + def check_create_complete(self, *args): + net = self.network_for_vpc(self.quantum(), self.resource_id) + if not quantum.QuantumResource.is_built(net): + return False + router = self.router_for_vpc(self.quantum(), self.resource_id) + return quantum.QuantumResource.is_built(router) + def handle_delete(self): from quantumclient.common.exceptions import QuantumClientException - client = self.quantum() - network_id = self.resource_id - router_id = self.metadata['router_id'] + router = self.router_for_vpc(client, self.resource_id) try: - client.delete_router(router_id) + client.delete_router(router['id']) except QuantumClientException as ex: if ex.status_code != 404: raise ex try: - client.delete_network(network_id) + client.delete_network(self.resource_id) except QuantumClientException as ex: if ex.status_code != 404: raise ex diff --git a/heat/tests/test_vpc.py b/heat/tests/test_vpc.py index 0591682f..b9e2cac2 100644 --- a/heat/tests/test_vpc.py +++ b/heat/tests/test_vpc.py @@ -50,10 +50,12 @@ class VPCTestBase(HeatTestCase): self.m.StubOutWithMock(quantumclient.Client, 'delete_router') self.m.StubOutWithMock(quantumclient.Client, 'delete_subnet') self.m.StubOutWithMock(quantumclient.Client, 'list_networks') + self.m.StubOutWithMock(quantumclient.Client, 'list_routers') self.m.StubOutWithMock(quantumclient.Client, 'remove_gateway_router') self.m.StubOutWithMock(quantumclient.Client, 'remove_interface_router') self.m.StubOutWithMock(quantumclient.Client, 'show_subnet') self.m.StubOutWithMock(quantumclient.Client, 'show_network') + self.m.StubOutWithMock(quantumclient.Client, 'show_router') self.m.StubOutWithMock(quantumclient.Client, 'create_security_group') self.m.StubOutWithMock(quantumclient.Client, 'show_security_group') self.m.StubOutWithMock(quantumclient.Client, 'delete_security_group') @@ -87,51 +89,87 @@ class VPCTestBase(HeatTestCase): return stack def mock_create_network(self): - vpc_name = utils.PhysName('test_stack', 'the_vpc') + self.vpc_name = utils.PhysName('test_stack', 'the_vpc') quantumclient.Client.create_network( { - 'network': {'name': vpc_name} + 'network': {'name': self.vpc_name} }).AndReturn({'network': { - 'status': 'ACTIVE', + 'status': 'BUILD', 'subnets': [], - 'name': vpc_name, + 'name': 'name', 'admin_state_up': True, 'shared': False, 'tenant_id': 'c1210485b2424d48804aad5d39c61b8f', 'id': 'aaaa' }}) + quantumclient.Client.show_network( + 'aaaa' + ).AndReturn({"network": { + "status": "BUILD", + "subnets": [], + "name": self.vpc_name, + "admin_state_up": False, + "shared": False, + "tenant_id": "c1210485b2424d48804aad5d39c61b8f", + "id": "aaaa" + }}) + + quantumclient.Client.show_network( + 'aaaa' + ).MultipleTimes().AndReturn({"network": { + "status": "ACTIVE", + "subnets": [], + "name": self.vpc_name, + "admin_state_up": False, + "shared": False, + "tenant_id": "c1210485b2424d48804aad5d39c61b8f", + "id": "aaaa" + }}) quantumclient.Client.create_router( - {'router': {'name': vpc_name}}).AndReturn({'router': { - 'status': 'ACTIVE', - 'name': vpc_name, - 'admin_state_up': True, - 'tenant_id': 'c1210485b2424d48804aad5d39c61b8f', - 'id': 'rrrr' - }}) + {'router': {'name': self.vpc_name}}).AndReturn({ + 'router': { + 'status': 'BUILD', + 'name': self.vpc_name, + 'admin_state_up': True, + 'tenant_id': 'c1210485b2424d48804aad5d39c61b8f', + 'id': 'bbbb' + }}) + quantumclient.Client.list_routers(name=self.vpc_name).AndReturn({ + "routers": [{ + "status": "BUILD", + "external_gateway_info": None, + "name": self.vpc_name, + "admin_state_up": True, + "tenant_id": "3e21026f2dc94372b105808c0e721661", + "routes": [], + "id": "bbbb" + }] + }) + self.mock_router_for_vpc() def mock_create_subnet(self): - subnet_name = utils.PhysName('test_stack', 'the_subnet') + self.subnet_name = utils.PhysName('test_stack', 'the_subnet') quantumclient.Client.create_subnet( {'subnet': { 'network_id': u'aaaa', 'cidr': u'10.0.0.0/24', 'ip_version': 4, - 'name': subnet_name}}).AndReturn({ + 'name': self.subnet_name}}).AndReturn({ 'subnet': { 'status': 'ACTIVE', - 'name': subnet_name, + 'name': self.subnet_name, 'admin_state_up': True, 'tenant_id': 'c1210485b2424d48804aad5d39c61b8f', 'id': 'cccc'}}) + self.mock_router_for_vpc() quantumclient.Client.add_interface_router( - u'rrrr', + u'bbbb', {'subnet_id': 'cccc'}).AndReturn(None) def mock_show_subnet(self): - subnet_name = utils.PhysName('test_stack', 'the_subnet') quantumclient.Client.show_subnet('cccc').AndReturn({ 'subnet': { - 'name': subnet_name, + 'name': self.subnet_name, 'network_id': 'aaaa', 'tenant_id': 'c1210485b2424d48804aad5d39c61b8f', 'allocation_pools': [{'start': '10.0.0.2', @@ -144,16 +182,16 @@ class VPCTestBase(HeatTestCase): }}) def mock_create_security_group(self): - sg_name = utils.PhysName('test_stack', 'the_sg') + self.sg_name = utils.PhysName('test_stack', 'the_sg') quantumclient.Client.create_security_group({ 'security_group': { - 'name': sg_name, + 'name': self.sg_name, 'description': 'SSH access' } }).AndReturn({ 'security_group': { 'tenant_id': 'c1210485b2424d48804aad5d39c61b8f', - 'name': sg_name, + 'name': self.sg_name, 'description': 'SSH access', 'security_group_rules': [], 'id': 'eeee' @@ -205,21 +243,95 @@ class VPCTestBase(HeatTestCase): quantumclient.Client.delete_security_group_rule('bbbb').AndReturn(None) quantumclient.Client.delete_security_group('eeee').AndReturn(None) + def mock_router_for_vpc(self): + quantumclient.Client.list_routers(name=self.vpc_name).AndReturn({ + "routers": [{ + "status": "ACTIVE", + "external_gateway_info": { + "network_id": "zzzz", + "enable_snat": True}, + "name": self.vpc_name, + "admin_state_up": True, + "tenant_id": "3e21026f2dc94372b105808c0e721661", + "routes": [], + "id": "bbbb" + }] + }) + def mock_delete_network(self): - quantumclient.Client.delete_router('rrrr').AndReturn(None) + self.mock_router_for_vpc() + quantumclient.Client.delete_router('bbbb').AndReturn(None) quantumclient.Client.delete_network('aaaa').AndReturn(None) def mock_delete_subnet(self): + self.mock_router_for_vpc() quantumclient.Client.remove_interface_router( - u'rrrr', + u'bbbb', {'subnet_id': 'cccc'}).AndReturn(None) quantumclient.Client.delete_subnet('cccc').AndReturn(None) - def assertResourceState(self, rsrc, ref_id, metadata={}): - self.assertEqual(None, rsrc.validate()) - self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state) - self.assertEqual(ref_id, rsrc.FnGetRefId()) - self.assertEqual(metadata, dict(rsrc.metadata)) + def mock_create_route_table(self): + self.rt_name = utils.PhysName('test_stack', 'the_route_table') + quantumclient.Client.create_router({ + 'router': {'name': self.rt_name}}).AndReturn({ + 'router': { + 'status': 'BUILD', + 'name': self.rt_name, + 'admin_state_up': True, + 'tenant_id': 'c1210485b2424d48804aad5d39c61b8f', + 'id': 'ffff' + } + }) + quantumclient.Client.show_router('ffff').AndReturn({ + 'router': { + 'status': 'BUILD', + 'name': self.rt_name, + 'admin_state_up': True, + 'tenant_id': 'c1210485b2424d48804aad5d39c61b8f', + 'id': 'ffff' + } + }) + quantumclient.Client.show_router('ffff').AndReturn({ + 'router': { + 'status': 'ACTIVE', + 'name': self.rt_name, + 'admin_state_up': True, + 'tenant_id': 'c1210485b2424d48804aad5d39c61b8f', + 'id': 'ffff' + } + }) + self.mock_router_for_vpc() + quantumclient.Client.add_gateway_router( + 'ffff', {'network_id': 'zzzz'}).AndReturn(None) + + def mock_create_association(self): + self.mock_show_subnet() + self.mock_router_for_vpc() + quantumclient.Client.remove_interface_router( + 'bbbb', + {'subnet_id': u'cccc'}).AndReturn(None) + quantumclient.Client.add_interface_router( + u'ffff', + {'subnet_id': 'cccc'}).AndReturn(None) + + def mock_delete_association(self): + self.mock_show_subnet() + self.mock_router_for_vpc() + quantumclient.Client.remove_interface_router( + 'ffff', + {'subnet_id': u'cccc'}).AndReturn(None) + quantumclient.Client.add_interface_router( + u'bbbb', + {'subnet_id': 'cccc'}).AndReturn(None) + + def mock_delete_route_table(self): + quantumclient.Client.delete_router('ffff').AndReturn(None) + quantumclient.Client.remove_gateway_router('ffff').AndReturn(None) + + def assertResourceState(self, resource, ref_id): + self.assertEqual(None, resource.validate()) + self.assertEqual((resource.CREATE, resource.COMPLETE), resource.state) + self.assertEqual(ref_id, resource.FnGetRefId()) class VPCTest(VPCTestBase): @@ -232,23 +344,18 @@ Resources: Properties: {CidrBlock: '10.0.0.0/16'} ''' - def stub_sleep(self): - pass - def test_vpc(self): self.mock_create_network() self.mock_delete_network() self.m.ReplayAll() stack = self.create_stack(self.test_template) - rsrc = stack['the_vpc'] - self.assertResourceState(rsrc, 'aaaa', { - 'router_id': 'rrrr', - 'all_router_ids': ['rrrr']}) + vpc = stack['the_vpc'] + self.assertResourceState(vpc, 'aaaa') self.assertRaises(resource.UpdateReplace, - rsrc.handle_update, {}, {}, {}) + vpc.handle_update, {}, {}, {}) - self.assertEqual(None, rsrc.delete()) + self.assertEqual(None, vpc.delete()) self.m.VerifyAll() @@ -275,8 +382,9 @@ Resources: self.mock_delete_network() # mock delete subnet which is already deleted + self.mock_router_for_vpc() quantumclient.Client.remove_interface_router( - u'rrrr', + u'bbbb', {'subnet_id': 'cccc'}).AndRaise( QuantumClientException(status_code=404)) quantumclient.Client.delete_subnet('cccc').AndRaise( @@ -285,23 +393,21 @@ Resources: self.m.ReplayAll() stack = self.create_stack(self.test_template) - rsrc = stack['the_subnet'] - self.assertResourceState(rsrc, 'cccc', { - 'router_id': 'rrrr', - 'default_router_id': 'rrrr'}) + subnet = stack['the_subnet'] + self.assertResourceState(subnet, 'cccc') self.assertRaises(resource.UpdateReplace, - rsrc.handle_update, {}, {}, {}) + subnet.handle_update, {}, {}, {}) self.assertRaises( exception.InvalidTemplateAttribute, - rsrc.FnGetAtt, + subnet.FnGetAtt, 'Foo') - self.assertEqual('moon', rsrc.FnGetAtt('AvailabilityZone')) + self.assertEqual('moon', subnet.FnGetAtt('AvailabilityZone')) - self.assertEqual(None, rsrc.delete()) - rsrc.state_set(rsrc.CREATE, rsrc.COMPLETE, 'to delete again') - self.assertEqual(None, rsrc.delete()) + self.assertEqual(None, subnet.delete()) + subnet.state_set(subnet.CREATE, subnet.COMPLETE, 'to delete again') + self.assertEqual(None, subnet.delete()) self.assertEqual(None, stack['the_vpc'].delete()) self.m.VerifyAll() @@ -411,13 +517,13 @@ Resources: ''' def mock_create_network_interface(self, security_groups=['eeee']): - nic_name = utils.PhysName('test_stack', 'the_nic') + self.nic_name = utils.PhysName('test_stack', 'the_nic') port = {'network_id': 'aaaa', 'fixed_ips': [{ 'subnet_id': u'cccc', 'ip_address': u'10.0.0.100' }], - 'name': nic_name, + 'name': self.nic_name, 'admin_state_up': True} if security_groups: port['security_groups'] = security_groups @@ -435,7 +541,7 @@ Resources: ], 'id': 'dddd', 'mac_address': 'fa:16:3e:25:32:5d', - 'name': nic_name, + 'name': self.nic_name, 'network_id': 'aaaa', 'status': 'ACTIVE', 'tenant_id': 'c1210485b2424d48804aad5d39c61b8f' @@ -530,7 +636,6 @@ Resources: Type: AWS::EC2::InternetGateway the_vpc: Type: AWS::EC2::VPC - DependsOn : the_gateway Properties: CidrBlock: '10.0.0.0/16' the_subnet: @@ -541,10 +646,18 @@ Resources: AvailabilityZone: moon the_attachment: Type: AWS::EC2::VPCGatewayAttachment - DependsOn : the_subnet Properties: VpcId: {Ref: the_vpc} InternetGatewayId: {Ref: the_gateway} + the_route_table: + Type: AWS::EC2::RouteTable + Properties: + VpcId: {Ref: the_vpc} + the_association: + Type: AWS::EC2::SubnetRouteTableAssocation + Properties: + RouteTableId: {Ref: the_route_table} + SubnetId: {Ref: the_subnet} ''' def mock_create_internet_gateway(self): @@ -562,17 +675,21 @@ Resources: def mock_create_gateway_attachment(self): quantumclient.Client.add_gateway_router( - 'rrrr', {'network_id': 'eeee'}).AndReturn(None) + 'ffff', {'network_id': 'eeee'}).AndReturn(None) def mock_delete_gateway_attachment(self): - quantumclient.Client.remove_gateway_router('rrrr').AndReturn(None) + quantumclient.Client.remove_gateway_router('ffff').AndReturn(None) def test_internet_gateway(self): self.mock_create_internet_gateway() self.mock_create_network() self.mock_create_subnet() + self.mock_create_route_table() + self.mock_create_association() self.mock_create_gateway_attachment() self.mock_delete_gateway_attachment() + self.mock_delete_association() + self.mock_delete_route_table() self.mock_delete_subnet() self.mock_delete_network() @@ -581,8 +698,7 @@ Resources: stack = self.create_stack(self.test_template) gateway = stack['the_gateway'] - self.assertResourceState(gateway, 'the_gateway', { - 'external_network_id': 'eeee'}) + self.assertResourceState(gateway, gateway.physical_resource_name()) self.assertRaises(resource.UpdateReplace, gateway.handle_update, {}, {}, {}) @@ -591,6 +707,9 @@ Resources: self.assertRaises(resource.UpdateReplace, attachment.handle_update, {}, {}, {}) + route_table = stack['the_route_table'] + self.assertEqual([route_table], list(attachment._vpc_route_tables())) + stack.delete() self.m.VerifyAll() @@ -621,38 +740,6 @@ Resources: SubnetId: {Ref: the_subnet} ''' - def mock_create_route_table(self): - rt_name = utils.PhysName('test_stack', 'the_route_table') - quantumclient.Client.create_router( - {'router': {'name': rt_name}}).AndReturn({ - 'router': { - 'status': 'ACTIVE', - 'name': 'name', - 'admin_state_up': True, - 'tenant_id': 'c1210485b2424d48804aad5d39c61b8f', - 'id': 'ffff' - } - }) - - def mock_create_association(self): - quantumclient.Client.remove_interface_router( - 'rrrr', - {'subnet_id': u'cccc'}).AndReturn(None) - quantumclient.Client.add_interface_router( - u'ffff', - {'subnet_id': 'cccc'}).AndReturn(None) - - def mock_delete_association(self): - quantumclient.Client.remove_interface_router( - 'ffff', - {'subnet_id': u'cccc'}).AndReturn(None) - quantumclient.Client.add_interface_router( - u'rrrr', - {'subnet_id': 'cccc'}).AndReturn(None) - - def mock_delete_route_table(self): - quantumclient.Client.delete_router('ffff').AndReturn(None) - def test_route_table(self): self.mock_create_network() self.mock_create_subnet() @@ -667,17 +754,14 @@ Resources: stack = self.create_stack(self.test_template) - vpc = stack['the_vpc'] - self.assertEqual(['rrrr', 'ffff'], vpc.metadata['all_router_ids']) - route_table = stack['the_route_table'] - self.assertResourceState(route_table, 'ffff', {}) + self.assertResourceState(route_table, 'ffff') self.assertRaises( resource.UpdateReplace, route_table.handle_update, {}, {}, {}) association = stack['the_association'] - self.assertResourceState(association, 'the_association', {}) + self.assertResourceState(association, 'the_association') self.assertRaises( resource.UpdateReplace, association.handle_update, {}, {}, {}) @@ -685,8 +769,5 @@ Resources: association.delete() route_table.delete() - vpc = stack['the_vpc'] - self.assertEqual(['rrrr'], vpc.metadata['all_router_ids']) - stack.delete() self.m.VerifyAll() -- 2.45.2