]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
Impement VPC subnet resource
authorSteve Baker <sbaker@redhat.com>
Sun, 2 Dec 2012 20:00:22 +0000 (09:00 +1300)
committerSteve Baker <sbaker@redhat.com>
Tue, 12 Feb 2013 21:37:18 +0000 (10:37 +1300)
Implements blueprint resource-type-subnet

Change-Id: I870e69249b591f9724b3a9c7bf076853a4eb880a

heat/engine/resources/subnet.py [new file with mode: 0644]
heat/tests/test_vpc.py

diff --git a/heat/engine/resources/subnet.py b/heat/engine/resources/subnet.py
new file mode 100644 (file)
index 0000000..bda5b2e
--- /dev/null
@@ -0,0 +1,93 @@
+# 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 Subnet(resource.Resource):
+    tags_schema = {'Key': {'Type': 'String',
+                           'Required': True},
+                   'Value': {'Type': 'String',
+                             'Required': True}}
+
+    properties_schema = {
+        'AvailabilityZone': {'Type': 'String'},
+        'CidrBlock': {
+            'Type': 'String',
+            'Required': True},
+        'VpcId': {
+            'Type': 'String',
+            'Required': True},
+        'Tags': {'Type': 'List', 'Schema': {
+            'Type': 'Map',
+            'Implemented': False,
+            'Schema': tags_schema}}
+    }
+
+    def __init__(self, name, json_snippet, stack):
+        super(Subnet, self).__init__(name, json_snippet, stack)
+
+    def handle_create(self):
+        client = self.quantum()
+        # TODO sbaker Verify that this CidrBlock is within the vpc CidrBlock
+        (network_id, router_id) = self.properties.get('VpcId').split(':')
+        props = {
+            'network_id': network_id,
+            'cidr': self.properties.get('CidrBlock'),
+            'name': self.name
+        }
+        subnet = self.quantum().create_subnet({'subnet': props})['subnet']
+
+        client.add_interface_router(
+            router_id,
+            {'subnet_id': subnet['id']})
+        self.resource_id_set(subnet['id'])
+
+    def handle_delete(self):
+        client = self.quantum()
+        (network_id, router_id) = self.properties.get('VpcId').split(':')
+        try:
+            client.remove_interface_router(
+                router_id,
+                {'subnet_id': self.resource_id})
+        except QuantumClientException as ex:
+            if ex.status_code != 404:
+                raise ex
+
+        try:
+            client.delete_subnet(self.resource_id)
+        except QuantumClientException as ex:
+            if ex.status_code != 404:
+                raise ex
+
+    def handle_update(self, json_snippet):
+        return self.UPDATE_REPLACE
+
+    def FnGetAtt(self, key):
+        if key == 'AvailabilityZone':
+            return self.properties.get(key, '')
+        raise exception.InvalidTemplateAttribute(resource=self.name, key=key)
+
+
+def resource_mapping():
+    return {
+        'AWS::EC2::Subnet': Subnet,
+    }
index 90e9ab22b3e3cbf21bc6f8e027bd4021760f4af4..5ce2afd453bcf538a2bf729ac93bd83044b2a165 100644 (file)
@@ -19,57 +19,49 @@ import mox
 from nose.plugins.attrib import attr
 
 from heat.common import context
+from heat.common import exception
 from heat.common import template_format
 from heat.engine.resources import vpc
+from heat.engine.resources import subnet
 from heat.engine import parser
 
+from quantumclient.common.exceptions import QuantumClientException
+from quantumclient.v2_0 import client as quantumclient
+
 test_template_vpc = '''
 Resources:
   the_vpc:
     Type: AWS::EC2::VPC
-    Properties: {CidrBlock: '10.0.0.0/24'}
+    Properties: {CidrBlock: '10.0.0.0/16'}
 '''
 
-
-class FakeQuantum():
-
-    def create_network(self, name):
-        return {"network": {
-            "status": "ACTIVE",
-            "subnets": [],
-            "name": "name",
-            "admin_state_up": True,
-            "shared": False,
-            "tenant_id": "c1210485b2424d48804aad5d39c61b8f",
-            "id": "aaaa"
-        }}
-
-    def create_router(self, name):
-        return {"router": {
-            "status": "ACTIVE",
-            "name": "name",
-            "admin_state_up": True,
-            "tenant_id": "c1210485b2424d48804aad5d39c61b8f",
-            "id": "bbbb"
-        }}
-
-    def delete_network(self, id):
-        pass
-
-    def delete_router(self, id):
-        pass
+test_template_subnet = '''
+Resources:
+  the_vpc2:
+    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_vpc2}
+      AvailabilityZone: moon
+'''
 
 
 @attr(tag=['unit', 'resource'])
 @attr(speed='fast')
-class QuantumTest(unittest.TestCase):
+class VPCTest(unittest.TestCase):
     def setUp(self):
         self.m = mox.Mox()
-        self.m.StubOutWithMock(vpc.VPC, 'quantum')
+        self.m.StubOutWithMock(quantumclient.Client, 'create_network')
+        self.m.StubOutWithMock(quantumclient.Client, 'create_router')
+        self.m.StubOutWithMock(quantumclient.Client, 'delete_network')
+        self.m.StubOutWithMock(quantumclient.Client, 'delete_router')
 
     def tearDown(self):
         self.m.UnsetStubs()
-        print "QuantumTest teardown complete"
+        print "VPCTest teardown complete"
 
     def parse_stack(self, t):
         ctx = context.RequestContext.from_dict({
@@ -79,22 +71,41 @@ class QuantumTest(unittest.TestCase):
             'auth_url': 'http://localhost:5000/v2.0'})
         stack_name = 'test_stack'
         tmpl = parser.Template(t)
-        params = parser.Parameters(stack_name, tmpl,
-                                   {'external_network': 'abcd1234'})
+        params = parser.Parameters(stack_name, tmpl, {})
         stack = parser.Stack(ctx, stack_name, tmpl, params)
 
         return stack
 
     def create_vpc(self, t, stack, resource_name):
-        resource = vpc.VPC('the_vpc', t['Resources'][resource_name], stack)
+        resource = vpc.VPC(
+            resource_name,
+            t['Resources'][resource_name],
+            stack)
         self.assertEqual(None, resource.create())
         self.assertEqual(vpc.VPC.CREATE_COMPLETE, resource.state)
         return resource
 
     def test_vpc(self):
-        fq = FakeQuantum()
-        vpc.VPC.quantum().MultipleTimes().AndReturn(fq)
-
+        quantumclient.Client.create_network(
+            {'network': {'name': 'the_vpc'}}).AndReturn({"network": {
+                "status": "ACTIVE",
+                "subnets": [],
+                "name": "name",
+                "admin_state_up": True,
+                "shared": False,
+                "tenant_id": "c1210485b2424d48804aad5d39c61b8f",
+                "id": "aaaa"
+            }})
+        quantumclient.Client.create_router(
+            {'router': {'name': 'the_vpc'}}).AndReturn({"router": {
+                "status": "ACTIVE",
+                "name": "name",
+                "admin_state_up": True,
+                "tenant_id": "c1210485b2424d48804aad5d39c61b8f",
+                "id": "bbbb"
+            }})
+        quantumclient.Client.delete_router('bbbb').AndReturn(None)
+        quantumclient.Client.delete_network('aaaa').AndReturn(None)
         self.m.ReplayAll()
         t = template_format.parse(test_template_vpc)
         stack = self.parse_stack(t)
@@ -109,3 +120,127 @@ class QuantumTest(unittest.TestCase):
 
         self.assertEqual(None, resource.delete())
         self.m.VerifyAll()
+
+
+@attr(tag=['unit', 'resource'])
+@attr(speed='fast')
+class SubnetTest(unittest.TestCase):
+    def setUp(self):
+        self.m = mox.Mox()
+        self.m.StubOutWithMock(quantumclient.Client, 'create_network')
+        self.m.StubOutWithMock(quantumclient.Client, 'create_router')
+        self.m.StubOutWithMock(quantumclient.Client, 'create_subnet')
+        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_router')
+        self.m.StubOutWithMock(quantumclient.Client, 'delete_subnet')
+
+    def tearDown(self):
+        self.m.UnsetStubs()
+        print "SubnetTest teardown complete"
+
+    def parse_stack(self, t):
+        ctx = context.RequestContext.from_dict({
+            'tenant': 'test_tenant',
+            'username': 'test_username',
+            'password': 'password',
+            'auth_url': 'http://localhost:5000/v2.0'})
+        params = parser.Parameters('test_stack', t, {})
+        stack = parser.Stack(
+            ctx,
+            'test_stack',
+            parser.Template(t),
+            parameters=params)
+        stack.store()
+        return stack
+
+    def create_vpc(self, t, stack, resource_name):
+        resource = vpc.VPC(
+            resource_name,
+            t['Resources'][resource_name],
+            stack)
+        self.assertEqual(None, resource.create())
+        self.assertEqual(vpc.VPC.CREATE_COMPLETE, resource.state)
+        return resource
+
+    def create_subnet(self, t, stack, resource_name):
+        resource = subnet.Subnet(
+            resource_name,
+            t['Resources'][resource_name],
+            stack)
+        self.assertEqual(None, resource.create())
+        self.assertEqual(subnet.Subnet.CREATE_COMPLETE, resource.state)
+        return resource
+
+    def test_subnet(self):
+        quantumclient.Client.create_network(
+            {'network': {'name': 'the_vpc2'}}).AndReturn({"network": {
+                "status": "ACTIVE",
+                "subnets": [],
+                "name": "the_vpc2",
+                "admin_state_up": True,
+                "shared": False,
+                "tenant_id": "c1210485b2424d48804aad5d39c61b8f",
+                "id": "aaaa"
+            }})
+        quantumclient.Client.create_router(
+            {'router': {'name': 'the_vpc2'}}).AndReturn({"router": {
+                "status": "ACTIVE",
+                "name": "the_vpc2",
+                "admin_state_up": True,
+                "tenant_id": "c1210485b2424d48804aad5d39c61b8f",
+                "id": "bbbb"
+            }})
+        quantumclient.Client.create_subnet(
+            {'subnet': {
+                'network_id': u'aaaa',
+                'cidr': u'10.0.0.0/24',
+                'name': u'the_subnet'}}).AndReturn({
+                    "subnet": {
+                        "status": "ACTIVE",
+                        "name": "the_subnet",
+                        "admin_state_up": True,
+                        "tenant_id": "c1210485b2424d48804aad5d39c61b8f",
+                        "id": "cccc"}})
+        quantumclient.Client.add_interface_router(
+            u'bbbb',
+            {'subnet_id': 'cccc'}).AndReturn(None)
+        quantumclient.Client.remove_interface_router(
+            u'bbbb',
+            {'subnet_id': 'cccc'}).AndReturn(None)
+        quantumclient.Client.delete_subnet('cccc').AndReturn(None)
+        quantumclient.Client.delete_router('bbbb').AndReturn(None)
+        quantumclient.Client.delete_network('aaaa').AndReturn(None)
+
+        quantumclient.Client.remove_interface_router(
+            u'bbbb',
+            {'subnet_id': 'cccc'}).AndRaise(
+                QuantumClientException(status_code=404))
+        quantumclient.Client.delete_subnet('cccc').AndRaise(
+            QuantumClientException(status_code=404))
+
+        self.m.ReplayAll()
+        t = template_format.parse(test_template_subnet)
+        stack = self.parse_stack(t)
+        stack.create()
+        resource = stack['the_subnet']
+
+        resource.validate()
+
+        ref_id = resource.FnGetRefId()
+        self.assertEqual('cccc', ref_id)
+
+        self.assertEqual(vpc.VPC.UPDATE_REPLACE, resource.handle_update({}))
+        self.assertRaises(
+            exception.InvalidTemplateAttribute,
+            resource.FnGetAtt,
+            'Foo')
+
+        self.assertEqual('moon', resource.FnGetAtt('AvailabilityZone'))
+
+        self.assertEqual(None, resource.delete())
+        resource.state_set(resource.CREATE_COMPLETE, 'to delete again')
+        self.assertEqual(None, resource.delete())
+        self.assertEqual(None, stack['the_vpc2'].delete())
+        self.m.VerifyAll()