--- /dev/null
+# 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.common import exception
+from heat.openstack.common import log as logging
+from heat.engine import resource
+
+logger = logging.getLogger(__name__)
+
+
+class InternetGateway(resource.Resource):
+ tags_schema = {'Key': {'Type': 'String',
+ 'Required': True},
+ 'Value': {'Type': 'String',
+ 'Required': True}}
+
+ properties_schema = {
+ 'Tags': {'Type': 'List', 'Schema': {
+ 'Type': 'Map',
+ 'Implemented': False,
+ 'Schema': tags_schema}}
+ }
+
+ def __init__(self, name, json_snippet, stack):
+ super(InternetGateway, self).__init__(name, json_snippet, stack)
+
+ def handle_create(self):
+ client = self.quantum()
+
+ ext_filter = {'router:external': True}
+ ext_nets = client.list_networks(**ext_filter)['networks']
+ if len(ext_nets) != 1:
+ # TODO sbaker if there is more than one external network
+ # add a heat configuration variable to set the ID of
+ # 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
+
+ def handle_update(self, json_snippet):
+ return self.UPDATE_REPLACE
+
+
+class VPCGatewayAttachment(resource.Resource):
+
+ properties_schema = {
+ 'VpcId': {
+ 'Type': 'String',
+ 'Required': True},
+ 'InternetGatewayId': {'Type': 'String'},
+ 'VpnGatewayId': {
+ 'Type': 'String',
+ 'Implemented': False}
+ }
+
+ def __init__(self, name, json_snippet, stack):
+ super(VPCGatewayAttachment, self).__init__(name, json_snippet, stack)
+
+ def handle_create(self):
+ client = self.quantum()
+ gateway = self.stack[self.properties.get('InternetGatewayId')]
+ vpc = self.stack[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, {
+ 'network_id': external_network_id})
+
+ def handle_delete(self):
+ client = self.quantum()
+ vpc = self.stack[self.properties.get('VpcId')]
+ for router_id in vpc.metadata['all_router_ids']:
+ try:
+ client.remove_gateway_router(router_id)
+ except QuantumClientException as ex:
+ if ex.status_code != 404:
+ raise ex
+
+ def handle_update(self, json_snippet):
+ return self.UPDATE_REPLACE
+
+
+def resource_mapping():
+ return {
+ 'AWS::EC2::InternetGateway': InternetGateway,
+ 'AWS::EC2::VPCGatewayAttachment': VPCGatewayAttachment,
+ }
# vim: tabstop=4 shiftwidth=4 softtabstop=4
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# 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
#
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 import parser
+from heat.engine.resources import internet_gateway
from quantumclient.common.exceptions import QuantumClientException
from quantumclient.v2_0 import client as quantumclient
-test_template_vpc = '''
-HeatTemplateFormatVersion: '2012-12-12'
-Resources:
- the_vpc:
- Type: AWS::EC2::VPC
- Properties: {CidrBlock: '10.0.0.0/16'}
-'''
-
-test_template_subnet = '''
-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
-'''
-
-test_template_nic = '''
-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_nic:
- Type: AWS::EC2::NetworkInterface
- Properties:
- PrivateIpAddress: 10.0.0.100
- SubnetId: {Ref: the_subnet}
-'''
-
class VPCTestBase(unittest.TestCase):
def setUp(self):
self.m = mox.Mox()
+ self.m.StubOutWithMock(quantumclient.Client, 'add_interface_router')
+ self.m.StubOutWithMock(quantumclient.Client, 'add_gateway_router')
self.m.StubOutWithMock(quantumclient.Client, 'create_network')
+ self.m.StubOutWithMock(quantumclient.Client, 'create_port')
self.m.StubOutWithMock(quantumclient.Client, 'create_router')
self.m.StubOutWithMock(quantumclient.Client, 'create_subnet')
- self.m.StubOutWithMock(quantumclient.Client, 'show_subnet')
- self.m.StubOutWithMock(quantumclient.Client, 'create_port')
- self.m.StubOutWithMock(quantumclient.Client, 'add_interface_router')
- self.m.StubOutWithMock(quantumclient.Client, 'remove_interface_router')
self.m.StubOutWithMock(quantumclient.Client, 'delete_network')
+ self.m.StubOutWithMock(quantumclient.Client, 'delete_port')
self.m.StubOutWithMock(quantumclient.Client, 'delete_router')
self.m.StubOutWithMock(quantumclient.Client, 'delete_subnet')
- self.m.StubOutWithMock(quantumclient.Client, 'delete_port')
+ self.m.StubOutWithMock(quantumclient.Client, 'list_networks')
+ 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')
def tearDown(self):
self.m.UnsetStubs()
- def create_stack(self, temlate):
- t = template_format.parse(temlate)
+ def create_stack(self, template):
+ t = template_format.parse(template)
stack = self.parse_stack(t)
- stack.create()
+ self.assertEqual(None, stack.create())
return stack
def parse_stack(self, t):
u'bbbb',
{'subnet_id': 'cccc'}).AndReturn(None)
- def mock_create_network_interface(self):
- quantumclient.Client.show_subnet('cccc').AndReturn({
- 'subnet': {
- 'name': 'test_stack.the_subnet',
- 'network_id': 'aaaa',
- 'tenant_id': 'c1210485b2424d48804aad5d39c61b8f',
- 'allocation_pools': [{
- 'start': '10.10.0.2', 'end': '10.10.0.254'}],
- 'gateway_ip': '10.10.0.1',
- 'ip_version': 4,
- 'cidr': '10.10.0.0/24',
- 'id': 'cccc',
- 'enable_dhcp': False}
- })
- quantumclient.Client.create_port({
- 'port': {
- 'network_id': 'aaaa', 'fixed_ips': [{
- 'subnet_id': u'cccc',
- 'ip_address': u'10.0.0.100'
- }],
- 'name': u'test_stack.the_nic',
- 'admin_state_up': True
- }}).AndReturn({
- 'port': {
- 'admin_state_up': True,
- 'device_id': '',
- 'device_owner': '',
- 'fixed_ips': [
- {
- 'ip_address': '10.0.0.100',
- 'subnet_id': 'cccc'
- }
- ],
- 'id': 'dddd',
- 'mac_address': 'fa:16:3e:25:32:5d',
- 'name': 'test_stack.the_nic',
- 'network_id': 'aaaa',
- 'status': 'ACTIVE',
- 'tenant_id': 'c1210485b2424d48804aad5d39c61b8f'
- }
- })
-
- def mock_delete_network_interface(self):
- quantumclient.Client.delete_port('dddd').AndReturn(None)
-
def mock_delete_network(self):
quantumclient.Client.delete_router('bbbb').AndReturn(None)
quantumclient.Client.delete_network('aaaa').AndReturn(None)
{'subnet_id': 'cccc'}).AndReturn(None)
quantumclient.Client.delete_subnet('cccc').AndReturn(None)
+ def assertResourceState(self, resource, ref_id, metadata={}):
+ self.assertEqual(None, resource.validate())
+ self.assertEqual(resource.CREATE_COMPLETE, resource.state)
+ self.assertEqual(ref_id, resource.FnGetRefId())
+ self.assertEqual(metadata, dict(resource.metadata))
+
@attr(tag=['unit', 'resource'])
@attr(speed='fast')
class VPCTest(VPCTestBase):
+ test_template = '''
+HeatTemplateFormatVersion: '2012-12-12'
+Resources:
+ the_vpc:
+ Type: AWS::EC2::VPC
+ Properties: {CidrBlock: '10.0.0.0/16'}
+'''
+
def test_vpc(self):
self.mock_create_network()
self.mock_delete_network()
self.m.ReplayAll()
- stack = self.create_stack(test_template_vpc)
- resource = stack['the_vpc']
- resource.validate()
-
- ref_id = resource.FnGetRefId()
- self.assertEqual('aaaa:bbbb', ref_id)
-
- self.assertEqual(vpc.VPC.UPDATE_REPLACE, resource.handle_update({}))
+ stack = self.create_stack(self.test_template)
+ resource = stack['the_vpc']
+ self.assertResourceState(resource, 'the_vpc', {
+ 'network_id': 'aaaa',
+ 'router_id': 'bbbb',
+ 'all_router_ids': ['bbbb']})
+ self.assertEqual(resource.UPDATE_REPLACE, resource.handle_update({}))
self.assertEqual(None, resource.delete())
self.m.VerifyAll()
@attr(speed='fast')
class SubnetTest(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
+'''
+
def test_subnet(self):
self.mock_create_network()
self.mock_create_subnet()
QuantumClientException(status_code=404))
self.m.ReplayAll()
- stack = self.create_stack(test_template_subnet)
+ stack = self.create_stack(self.test_template)
resource = stack['the_subnet']
+ self.assertResourceState(resource, 'the_subnet', {
+ 'network_id': 'aaaa',
+ 'router_id': 'bbbb',
+ 'subnet_id': 'cccc'})
- resource.validate()
-
- ref_id = resource.FnGetRefId()
- self.assertEqual('cccc', ref_id)
-
- self.assertEqual(vpc.VPC.UPDATE_REPLACE, resource.handle_update({}))
+ self.assertEqual(resource.UPDATE_REPLACE, resource.handle_update({}))
self.assertRaises(
exception.InvalidTemplateAttribute,
resource.FnGetAtt,
@attr(speed='fast')
class NetworkInterfaceTest(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_nic:
+ Type: AWS::EC2::NetworkInterface
+ Properties:
+ PrivateIpAddress: 10.0.0.100
+ SubnetId: {Ref: the_subnet}
+'''
+
+ def mock_create_network_interface(self):
+ quantumclient.Client.create_port({
+ 'port': {
+ 'network_id': 'aaaa', 'fixed_ips': [{
+ 'subnet_id': u'cccc',
+ 'ip_address': u'10.0.0.100'
+ }],
+ 'name': u'test_stack.the_nic',
+ 'admin_state_up': True
+ }}).AndReturn({
+ 'port': {
+ 'admin_state_up': True,
+ 'device_id': '',
+ 'device_owner': '',
+ 'fixed_ips': [
+ {
+ 'ip_address': '10.0.0.100',
+ 'subnet_id': 'cccc'
+ }
+ ],
+ 'id': 'dddd',
+ 'mac_address': 'fa:16:3e:25:32:5d',
+ 'name': 'test_stack.the_nic',
+ 'network_id': 'aaaa',
+ 'status': 'ACTIVE',
+ 'tenant_id': 'c1210485b2424d48804aad5d39c61b8f'
+ }
+ })
+
+ def mock_delete_network_interface(self):
+ quantumclient.Client.delete_port('dddd').AndReturn(None)
+
def test_network_interface(self):
self.mock_create_network()
self.mock_create_subnet()
self.m.ReplayAll()
- stack = self.create_stack(test_template_nic)
+ stack = self.create_stack(self.test_template)
resource = stack['the_nic']
+ self.assertResourceState(resource, 'the_nic', {
+ 'port_id': 'dddd'})
+
+ self.assertEqual(resource.UPDATE_REPLACE, resource.handle_update({}))
+
+ stack.delete()
+ self.m.VerifyAll()
+
+
+@attr(tag=['unit', 'resource'])
+@attr(speed='fast')
+class InternetGatewayTest(VPCTestBase):
+
+ test_template = '''
+HeatTemplateFormatVersion: '2012-12-12'
+Resources:
+ the_gateway:
+ Type: AWS::EC2::InternetGateway
+ the_vpc:
+ Type: AWS::EC2::VPC
+ Properties:
+ DependsOn : the_gateway
+ 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_attachment:
+ Type: AWS::EC2::VPCGatewayAttachment
+ Properties:
+ VpcId: {Ref: the_vpc}
+ DependsOn : the_subnet
+ InternetGatewayId: {Ref: the_gateway}
+'''
+
+ def mock_create_internet_gateway(self):
+ quantumclient.Client.list_networks(
+ **{'router:external': True}).AndReturn({'networks': [{
+ 'status': 'ACTIVE',
+ 'subnets': [],
+ 'name': 'nova',
+ 'router:external': True,
+ 'tenant_id': 'c1210485b2424d48804aad5d39c61b8f',
+ 'admin_state_up': True,
+ 'shared': True,
+ 'id': 'eeee'
+ }]})
+
+ def mock_create_gateway_attachment(self):
+ quantumclient.Client.add_gateway_router(
+ 'bbbb', {'network_id': 'eeee'}).AndReturn(None)
+
+ def mock_delete_gateway_attachment(self):
+ quantumclient.Client.remove_gateway_router('bbbb').AndReturn(None)
+
+ def test_internet_gateway(self):
+ self.mock_create_internet_gateway()
+ self.mock_create_network()
+ self.mock_create_subnet()
+ self.mock_create_gateway_attachment()
+ self.mock_delete_gateway_attachment()
+ self.mock_delete_subnet()
+ self.mock_delete_network()
+
+ self.m.ReplayAll()
- resource.validate()
+ stack = self.create_stack(self.test_template)
- ref_id = resource.FnGetRefId()
- self.assertEqual('dddd', ref_id)
+ gateway = stack['the_gateway']
+ self.assertResourceState(gateway, 'the_gateway', {
+ 'external_network_id': 'eeee'})
+ self.assertEqual(gateway.UPDATE_REPLACE, gateway.handle_update({}))
- self.assertEqual(vpc.VPC.UPDATE_REPLACE, resource.handle_update({}))
+ attachment = stack['the_attachment']
+ self.assertResourceState(attachment, 'the_attachment')
+ self.assertEqual(gateway.UPDATE_REPLACE, attachment.handle_update({}))
stack.delete()
self.m.VerifyAll()