]> review.fuel-infra Code Review - openstack-build/heat-build.git/commitdiff
Adding VPN Service to Heat resources
authorBartosz Górski <bartosz.gorski@ntti3.com>
Tue, 3 Sep 2013 01:33:05 +0000 (18:33 -0700)
committerSteve Baker <sbaker@redhat.com>
Wed, 4 Sep 2013 22:57:16 +0000 (10:57 +1200)
Adds Neutron VPN service component to resources
supported by Heat with unit tests.

Change-Id: I3a626166253bcb33d8875cf406b293688f53ffa3
Implements: blueprint vpnaas-support

heat/engine/resources/neutron/vpnservice.py [new file with mode: 0644]
heat/tests/test_neutron_vpnservice.py [new file with mode: 0644]

diff --git a/heat/engine/resources/neutron/vpnservice.py b/heat/engine/resources/neutron/vpnservice.py
new file mode 100644 (file)
index 0000000..8c44204
--- /dev/null
@@ -0,0 +1,92 @@
+# 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 heat.engine import clients
+from heat.engine.resources.neutron import neutron
+from heat.engine import scheduler
+
+if clients.neutronclient is not None:
+    from neutronclient.common.exceptions import NeutronClientException
+
+from heat.openstack.common import log as logging
+
+logger = logging.getLogger(__name__)
+
+
+class VPNService(neutron.NeutronResource):
+    """
+    A resource for VPN service in Neutron.
+    """
+
+    properties_schema = {'name': {'Type': 'String'},
+                         'description': {'Type': 'String'},
+                         'admin_state_up': {'Type': 'Boolean',
+                                            'Default': True},
+                         'subnet_id': {'Type': 'String',
+                                       'Required': True},
+                         'router_id': {'Type': 'String',
+                                       'Required': True}}
+
+    attributes_schema = {
+        'admin_state_up': 'the administrative state of the vpn service',
+        'description': 'description of the vpn service',
+        'id': 'unique identifier for the vpn service',
+        'name': 'name for the vpn service',
+        'router_id': 'unique identifier for router used to create the vpn'
+                     ' service',
+        'status': 'the status of the vpn service',
+        'subnet_id': 'unique identifier for subnet used to create the vpn'
+                     ' service',
+        'tenant_id': 'tenant owning the vpn service'
+    }
+
+    update_allowed_keys = ('Properties',)
+
+    update_allowed_properties = ('name', 'description', 'admin_state_up',)
+
+    def _show_resource(self):
+        return self.neutron().show_vpnservice(self.resource_id)['vpnservice']
+
+    def handle_create(self):
+        props = self.prepare_properties(
+            self.properties,
+            self.physical_resource_name())
+        vpnservice = self.neutron().create_vpnservice({'vpnservice': props})[
+            'vpnservice']
+        self.resource_id_set(vpnservice['id'])
+
+    def handle_update(self, json_snippet, tmpl_diff, prop_diff):
+        if prop_diff:
+            self.neutron().update_vpnservice(self.resource_id,
+                                             {'vpnservice': prop_diff})
+
+    def handle_delete(self):
+        client = self.neutron()
+        try:
+            client.delete_vpnservice(self.resource_id)
+        except NeutronClientException as ex:
+            if ex.status_code != 404:
+                raise ex
+        else:
+            return scheduler.TaskRunner(self._confirm_delete)()
+
+
+def resource_mapping():
+    if clients.neutronclient is None:
+        return {}
+
+    return {
+        'OS::Neutron::VPNService': VPNService,
+    }
diff --git a/heat/tests/test_neutron_vpnservice.py b/heat/tests/test_neutron_vpnservice.py
new file mode 100644 (file)
index 0000000..d53775e
--- /dev/null
@@ -0,0 +1,189 @@
+# 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.
+
+import copy
+
+from testtools import skipIf
+
+from heat.common import exception
+from heat.common import template_format
+from heat.engine import clients
+from heat.engine import scheduler
+from heat.engine.resources.neutron import vpnservice
+from heat.openstack.common.importutils import try_import
+from heat.tests import fakes
+from heat.tests import utils
+from heat.tests.common import HeatTestCase
+
+
+neutronclient = try_import('neutronclient.v2_0.client')
+
+vpnservice_template = '''
+{
+  "AWSTemplateFormatVersion" : "2010-09-09",
+  "Description" : "Template to test VPN service resource",
+  "Parameters" : {},
+  "Resources" : {
+    "VPNService" : {
+      "Type" : "OS::Neutron::VPNService",
+      "Properties" : {
+        "name" : "VPNService",
+        "description" : "My new VPN service",
+        "admin_state_up" : true,
+        "router_id" : "rou123",
+        "subnet_id" : "sub123"
+      }
+    }
+  }
+}
+'''
+
+
+@skipIf(neutronclient is None, 'neutronclient unavailable')
+class VPNServiceTest(HeatTestCase):
+
+    VPN_SERVICE_CONF = {
+        'vpnservice': {
+            'name': 'VPNService',
+            'description': 'My new VPN service',
+            'admin_state_up': True,
+            'router_id': 'rou123',
+            'subnet_id': 'sub123'
+        }
+    }
+
+    def setUp(self):
+        super(VPNServiceTest, self).setUp()
+        self.m.StubOutWithMock(neutronclient.Client, 'create_vpnservice')
+        self.m.StubOutWithMock(neutronclient.Client, 'delete_vpnservice')
+        self.m.StubOutWithMock(neutronclient.Client, 'show_vpnservice')
+        self.m.StubOutWithMock(neutronclient.Client, 'update_vpnservice')
+        self.m.StubOutWithMock(clients.OpenStackClients, 'keystone')
+        utils.setup_dummy_db()
+
+    def create_vpnservice(self):
+        clients.OpenStackClients.keystone().AndReturn(
+            fakes.FakeKeystoneClient())
+        neutronclient.Client.create_vpnservice(
+            self.VPN_SERVICE_CONF).AndReturn({'vpnservice': {'id': 'vpn123'}})
+        snippet = template_format.parse(vpnservice_template)
+        self.stack = utils.parse_stack(snippet)
+        return vpnservice.VPNService('vpnservice',
+                                     snippet['Resources']['VPNService'],
+                                     self.stack)
+
+    @utils.stack_delete_after
+    def test_create(self):
+        rsrc = self.create_vpnservice()
+        self.m.ReplayAll()
+        scheduler.TaskRunner(rsrc.create)()
+        self.assertEqual((rsrc.CREATE, rsrc.COMPLETE), rsrc.state)
+        self.m.VerifyAll()
+
+    @utils.stack_delete_after
+    def test_create_failed(self):
+        clients.OpenStackClients.keystone().AndReturn(
+            fakes.FakeKeystoneClient())
+        neutronclient.Client.create_vpnservice(self.VPN_SERVICE_CONF).AndRaise(
+            vpnservice.NeutronClientException())
+        self.m.ReplayAll()
+        snippet = template_format.parse(vpnservice_template)
+        self.stack = utils.parse_stack(snippet)
+        rsrc = vpnservice.VPNService('vpnservice',
+                                     snippet['Resources']['VPNService'],
+                                     self.stack)
+        error = self.assertRaises(exception.ResourceFailure,
+                                  scheduler.TaskRunner(rsrc.create))
+        self.assertEqual(
+            'NeutronClientException: An unknown exception occurred.',
+            str(error))
+        self.assertEqual((rsrc.CREATE, rsrc.FAILED), rsrc.state)
+        self.m.VerifyAll()
+
+    @utils.stack_delete_after
+    def test_delete(self):
+        neutronclient.Client.delete_vpnservice('vpn123')
+        neutronclient.Client.show_vpnservice('vpn123').AndRaise(
+            vpnservice.NeutronClientException(status_code=404))
+        rsrc = self.create_vpnservice()
+        self.m.ReplayAll()
+        scheduler.TaskRunner(rsrc.create)()
+        scheduler.TaskRunner(rsrc.delete)()
+        self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state)
+        self.m.VerifyAll()
+
+    @utils.stack_delete_after
+    def test_delete_already_gone(self):
+        neutronclient.Client.delete_vpnservice('vpn123').AndRaise(
+            vpnservice.NeutronClientException(status_code=404))
+        rsrc = self.create_vpnservice()
+        self.m.ReplayAll()
+        scheduler.TaskRunner(rsrc.create)()
+        scheduler.TaskRunner(rsrc.delete)()
+        self.assertEqual((rsrc.DELETE, rsrc.COMPLETE), rsrc.state)
+        self.m.VerifyAll()
+
+    @utils.stack_delete_after
+    def test_delete_failed(self):
+        neutronclient.Client.delete_vpnservice('vpn123').AndRaise(
+            vpnservice.NeutronClientException(status_code=400))
+        rsrc = self.create_vpnservice()
+        self.m.ReplayAll()
+        scheduler.TaskRunner(rsrc.create)()
+        error = self.assertRaises(exception.ResourceFailure,
+                                  scheduler.TaskRunner(rsrc.delete))
+        self.assertEqual(
+            'NeutronClientException: An unknown exception occurred.',
+            str(error))
+        self.assertEqual((rsrc.DELETE, rsrc.FAILED), rsrc.state)
+        self.m.VerifyAll()
+
+    @utils.stack_delete_after
+    def test_attribute(self):
+        rsrc = self.create_vpnservice()
+        neutronclient.Client.show_vpnservice('vpn123').MultipleTimes(
+        ).AndReturn(self.VPN_SERVICE_CONF)
+        self.m.ReplayAll()
+        scheduler.TaskRunner(rsrc.create)()
+        self.assertEqual('VPNService', rsrc.FnGetAtt('name'))
+        self.assertEqual('My new VPN service', rsrc.FnGetAtt('description'))
+        self.assertEqual(True, rsrc.FnGetAtt('admin_state_up'))
+        self.assertEqual('rou123', rsrc.FnGetAtt('router_id'))
+        self.assertEqual('sub123', rsrc.FnGetAtt('subnet_id'))
+        self.m.VerifyAll()
+
+    @utils.stack_delete_after
+    def test_attribute_failed(self):
+        rsrc = self.create_vpnservice()
+        self.m.ReplayAll()
+        scheduler.TaskRunner(rsrc.create)()
+        error = self.assertRaises(exception.InvalidTemplateAttribute,
+                                  rsrc.FnGetAtt, 'non-existent_property')
+        self.assertEqual(
+            'The Referenced Attribute (vpnservice non-existent_property) is '
+            'incorrect.',
+            str(error))
+        self.m.VerifyAll()
+
+    @utils.stack_delete_after
+    def test_update(self):
+        rsrc = self.create_vpnservice()
+        neutronclient.Client.update_vpnservice(
+            'vpn123', {'vpnservice': {'admin_state_up': False}})
+        self.m.ReplayAll()
+        scheduler.TaskRunner(rsrc.create)()
+        update_template = copy.deepcopy(rsrc.t)
+        update_template['Properties']['admin_state_up'] = False
+        scheduler.TaskRunner(rsrc.update, update_template)()
+        self.m.VerifyAll()