--- /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 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,
+ }
--- /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.
+
+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()