From 4b93f989efd3db89c3264f8b4096f7594a377cfc Mon Sep 17 00:00:00 2001 From: Steve Baker Date: Mon, 18 Feb 2013 09:12:12 +1300 Subject: [PATCH] Implement RouteTable and subnet association Implements blueprint resource-type-routetable and blueprint resource-type-srta Change-Id: Idb6e8d060563d22847d100220e9a1750340583a8 --- heat/engine/resources/route_table.py | 149 +++++++++++++++++++++++++++ heat/engine/resources/subnet.py | 1 + heat/tests/test_vpc.py | 105 ++++++++++++++++++- 3 files changed, 251 insertions(+), 4 deletions(-) create mode 100644 heat/engine/resources/route_table.py diff --git a/heat/engine/resources/route_table.py b/heat/engine/resources/route_table.py new file mode 100644 index 00000000..dd50227f --- /dev/null +++ b/heat/engine/resources/route_table.py @@ -0,0 +1,149 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from quantumclient.common.exceptions import QuantumClientException + +from heat.openstack.common import log as logging +from heat.engine import resource + +logger = logging.getLogger(__name__) + + +class RouteTable(resource.Resource): + tags_schema = {'Key': {'Type': 'String', + 'Required': True}, + 'Value': {'Type': 'String', + 'Required': True}} + + properties_schema = { + 'VpcId': { + 'Type': 'String', + 'Required': True}, + 'Tags': {'Type': 'List', 'Schema': { + 'Type': 'Map', + 'Implemented': False, + 'Schema': tags_schema}} + } + + def __init__(self, name, json_snippet, stack): + super(RouteTable, self).__init__(name, json_snippet, stack) + + def handle_create(self): + client = self.quantum() + props = {'name': self.physical_resource_name()} + router = client.create_router({'router': props})['router'] + + router_id = router['id'] + + # add this router to the list of all routers in the VPC + vpc = self.stack[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 + md = { + 'router_id': router_id + } + self.metadata = md + + def handle_delete(self): + client = self.quantum() + + router_id = self.metadata['router_id'] + try: + client.delete_router(router_id) + except QuantumClientException as ex: + if ex.status_code != 404: + raise ex + + # remove this router from the list of all routers in the VPC + vpc = self.stack[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 + + def handle_update(self, json_snippet): + return self.UPDATE_REPLACE + + +class SubnetRouteTableAssocation(resource.Resource): + + properties_schema = { + 'RouteTableId': { + 'Type': 'String', + 'Required': True}, + 'SubnetId': { + 'Type': 'String', + 'Required': True} + } + + def __init__(self, name, json_snippet, stack): + super(SubnetRouteTableAssocation, self).__init__( + name, json_snippet, stack) + + def handle_create(self): + client = self.quantum() + subnet = self.stack[self.properties.get('SubnetId')] + subnet_id = subnet.metadata['subnet_id'] + previous_router_id = subnet.metadata['router_id'] + + route_table = self.stack[self.properties.get('RouteTableId')] + router_id = route_table.metadata['router_id'] + + #remove the default router association for this subnet. + try: + client.remove_interface_router( + previous_router_id, + {'subnet_id': subnet_id}) + except QuantumClientException as ex: + if ex.status_code != 404: + raise ex + + client.add_interface_router( + router_id, {'subnet_id': subnet_id}) + + def handle_delete(self): + client = self.quantum() + subnet = self.stack[self.properties.get('SubnetId')] + subnet_id = subnet.metadata['subnet_id'] + default_router_id = subnet.metadata['default_router_id'] + + route_table = self.stack[self.properties.get('RouteTableId')] + router_id = route_table.metadata['router_id'] + + try: + client.remove_interface_router(router_id, { + 'subnet_id': subnet_id}) + except QuantumClientException as ex: + if ex.status_code != 404: + raise ex + + # add back the default router + client.add_interface_router( + default_router_id, {'subnet_id': subnet_id}) + + def handle_update(self, json_snippet): + return self.UPDATE_REPLACE + + +def resource_mapping(): + return { + 'AWS::EC2::RouteTable': RouteTable, + 'AWS::EC2::SubnetRouteTableAssocation': SubnetRouteTableAssocation, + } diff --git a/heat/engine/resources/subnet.py b/heat/engine/resources/subnet.py index b2d7d715..5e988c30 100644 --- a/heat/engine/resources/subnet.py +++ b/heat/engine/resources/subnet.py @@ -68,6 +68,7 @@ class Subnet(resource.Resource): md = { 'network_id': network_id, 'router_id': router_id, + 'default_router_id': router_id, 'subnet_id': subnet['id'] } self.metadata = md diff --git a/heat/tests/test_vpc.py b/heat/tests/test_vpc.py index ca37a0fc..c42f9cf3 100644 --- a/heat/tests/test_vpc.py +++ b/heat/tests/test_vpc.py @@ -22,10 +22,7 @@ from heat.common import context from heat.common import exception from heat.common import template_format from heat.engine import parser -from heat.engine.resources import network_interface -from heat.engine.resources import subnet -from heat.engine.resources import vpc -from heat.engine.resources import internet_gateway +import heat.engine.resources from quantumclient.common.exceptions import QuantumClientException from quantumclient.v2_0 import client as quantumclient @@ -197,6 +194,7 @@ Resources: self.assertResourceState(resource, 'the_subnet', { 'network_id': 'aaaa', 'router_id': 'bbbb', + 'default_router_id': 'bbbb', 'subnet_id': 'cccc'}) self.assertEqual(resource.UPDATE_REPLACE, resource.handle_update({})) @@ -362,3 +360,102 @@ Resources: stack.delete() self.m.VerifyAll() + + +@attr(tag=['unit', 'resource']) +@attr(speed='fast') +class RouteTableTest(VPCTestBase): + + test_template = ''' +HeatTemplateFormatVersion: '2012-12-12' +Resources: + the_vpc: + Type: AWS::EC2::VPC + Properties: + CidrBlock: '10.0.0.0/16' + the_subnet: + Type: AWS::EC2::Subnet + Properties: + CidrBlock: 10.0.0.0/24 + VpcId: {Ref: the_vpc} + AvailabilityZone: moon + 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_route_table(self): + quantumclient.Client.create_router( + {'router': {'name': u'test_stack.the_route_table'}}).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( + 'bbbb', + {'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'bbbb', + {'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() + self.mock_create_route_table() + self.mock_create_association() + self.mock_delete_association() + self.mock_delete_route_table() + self.mock_delete_subnet() + self.mock_delete_network() + + self.m.ReplayAll() + + stack = self.create_stack(self.test_template) + + vpc = stack['the_vpc'] + self.assertEqual(['bbbb', 'ffff'], vpc.metadata['all_router_ids']) + + route_table = stack['the_route_table'] + self.assertResourceState(route_table, 'the_route_table', { + 'router_id': 'ffff'}) + self.assertEqual( + route_table.UPDATE_REPLACE, + route_table.handle_update({})) + + association = stack['the_association'] + self.assertResourceState(association, 'the_association', {}) + self.assertEqual( + association.UPDATE_REPLACE, + association.handle_update({})) + + association.delete() + route_table.delete() + + vpc = stack['the_vpc'] + self.assertEqual(['bbbb'], vpc.metadata['all_router_ids']) + + stack.delete() + self.m.VerifyAll() -- 2.45.2