From 2162b8047be83875e3f226140dfade88517371a1 Mon Sep 17 00:00:00 2001 From: Steve Baker Date: Tue, 30 Oct 2012 08:41:13 +1300 Subject: [PATCH] Add a set of native quantum resource types. The properties schemas map directly to the Quantum REST API, which makes the implementation (and documentation) simpler. The base class QuantumResource contains some default methods and common utility functions. templates/Quantum.template can be run without any parameters and only creates network resources, no instances. More example templates and tests will come later. Change-Id: Ia270294440eeec5163e35009f6be0b5db9ad78c1 --- heat/engine/resources/quantum/__init__.py | 0 heat/engine/resources/quantum/floatingip.py | 77 +++++++++ heat/engine/resources/quantum/net.py | 47 +++++ heat/engine/resources/quantum/port.py | 59 +++++++ heat/engine/resources/quantum/quantum.py | 92 ++++++++++ heat/engine/resources/quantum/router.py | 102 +++++++++++ heat/engine/resources/quantum/subnet.py | 65 +++++++ heat/engine/resources/register.py | 13 ++ heat/engine/resources/resource.py | 40 +++++ heat/tests/test_quantum.py | 182 ++++++++++++++++++++ templates/Quantum.template | 100 +++++++++++ tools/pip-requires | 2 +- 12 files changed, 778 insertions(+), 1 deletion(-) create mode 100644 heat/engine/resources/quantum/__init__.py create mode 100644 heat/engine/resources/quantum/floatingip.py create mode 100644 heat/engine/resources/quantum/net.py create mode 100644 heat/engine/resources/quantum/port.py create mode 100644 heat/engine/resources/quantum/quantum.py create mode 100644 heat/engine/resources/quantum/router.py create mode 100644 heat/engine/resources/quantum/subnet.py create mode 100644 heat/tests/test_quantum.py create mode 100644 templates/Quantum.template diff --git a/heat/engine/resources/quantum/__init__.py b/heat/engine/resources/quantum/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/heat/engine/resources/quantum/floatingip.py b/heat/engine/resources/quantum/floatingip.py new file mode 100644 index 00000000..92d54345 --- /dev/null +++ b/heat/engine/resources/quantum/floatingip.py @@ -0,0 +1,77 @@ +# 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.openstack.common import log as logging +from heat.engine.resources.quantum import quantum + +logger = logging.getLogger('heat.engine.quantum') + + +class FloatingIP(quantum.QuantumResource): + properties_schema = {'floating_network_id': {'Type': 'String', + 'Required': True}, + 'value_specs': {'Type': 'Map', + 'Default': {}}, + 'port_id': {'Type': 'String'}, + 'fixed_ip_address': {'Type': 'String'}, + } + + def handle_create(self): + props = self.prepare_properties(self.properties, self.name) + fip = self.quantum().create_floatingip({ + 'floatingip': props})['floatingip'] + self.instance_id_set(fip['id']) + + def handle_delete(self): + client = self.quantum() + try: + client.delete_floatingip(self.instance_id) + except: + pass + + def FnGetAtt(self, key): + attributes = self.quantum().show_floatingip( + self.instance_id)['floatingip'] + return self.handle_get_attributes(self.name, key, attributes) + + +class FloatingIPAssociation(quantum.QuantumResource): + properties_schema = {'floatingip_id': {'Type': 'String', + 'Required': True}, + 'port_id': {'Type': 'String', + 'Required': True}, + 'fixed_ip_address': {'Type': 'String'} + } + + def __init__(self, name, json_snippet, stack): + super(FloatingIPAssociation, self).__init__(name, json_snippet, stack) + + def handle_create(self): + props = self.prepare_properties(self.properties, self.name) + + floatingip_id = props.pop('floatingip_id') + + self.quantum().update_floatingip(floatingip_id, { + 'floatingip': props})['floatingip'] + self.instance_id_set('%s:%s' % (floatingip_id, props['port_id'])) + + def handle_delete(self): + client = self.quantum() + try: + (floatingip_id, port_id) = self.instance_id.split(':') + client.update_floatingip(floatingip_id, + {'floatingip': {'port_id': None}}) + except: + pass diff --git a/heat/engine/resources/quantum/net.py b/heat/engine/resources/quantum/net.py new file mode 100644 index 00000000..d625eb4e --- /dev/null +++ b/heat/engine/resources/quantum/net.py @@ -0,0 +1,47 @@ +# 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.openstack.common import log as logging +from heat.engine.resources.quantum import quantum + +logger = logging.getLogger('heat.engine.quantum') + + +class Net(quantum.QuantumResource): + properties_schema = {'name': {'Type': 'String'}, + 'value_specs': {'Type': 'Map', + 'Default': {}}, + 'admin_state_up': {'Default': True}, + } + + def __init__(self, name, json_snippet, stack): + super(Net, self).__init__(name, json_snippet, stack) + + def handle_create(self): + props = self.prepare_properties(self.properties, self.name) + net = self.quantum().create_network({'network': props})['network'] + self.instance_id_set(net['id']) + + def handle_delete(self): + client = self.quantum() + try: + client.delete_network(self.instance_id) + except: + pass + + def FnGetAtt(self, key): + attributes = self.quantum().show_network( + self.instance_id)['network'] + return self.handle_get_attributes(self.name, key, attributes) diff --git a/heat/engine/resources/quantum/port.py b/heat/engine/resources/quantum/port.py new file mode 100644 index 00000000..939040f9 --- /dev/null +++ b/heat/engine/resources/quantum/port.py @@ -0,0 +1,59 @@ +# 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.openstack.common import log as logging +from heat.engine.resources.quantum import quantum + +logger = logging.getLogger('heat.engine.quantum') + + +class Port(quantum.QuantumResource): + + fixed_ip_schema = {'subnet_id': {'Type': 'String', + 'Required': True}, + 'ip_address': {'Type': 'String', + 'Required': True}} + + properties_schema = {'network_id': {'Type': 'String', + 'Required': True}, + 'name': {'Type': 'String'}, + 'value_specs': {'Type': 'Map', + 'Default': {}}, + 'admin_state_up': {'Default': True}, + 'fixed_ips': {'Type': 'List', + 'Schema': fixed_ip_schema}, + 'mac_address': {'Type': 'String'}, + 'device_id': {'Type': 'String'}, + } + + def __init__(self, name, json_snippet, stack): + super(Port, self).__init__(name, json_snippet, stack) + + def handle_create(self): + props = self.prepare_properties(self.properties, self.name) + port = self.quantum().create_port({'port': props})['port'] + self.instance_id_set(port['id']) + + def handle_delete(self): + client = self.quantum() + try: + client.delete_port(self.instance_id) + except: + pass + + def FnGetAtt(self, key): + attributes = self.quantum().show_port( + self.instance_id)['port'] + return self.handle_get_attributes(self.name, key, attributes) diff --git a/heat/engine/resources/quantum/quantum.py b/heat/engine/resources/quantum/quantum.py new file mode 100644 index 00000000..30e3df4c --- /dev/null +++ b/heat/engine/resources/quantum/quantum.py @@ -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.common import exception +from heat.engine.resources import resource + +from heat.openstack.common import log as logging + +logger = logging.getLogger('heat.engine.quantum') + + +class QuantumResource(resource.Resource): + + def __init__(self, name, json_snippet, stack): + super(QuantumResource, self).__init__(name, json_snippet, stack) + + def validate(self): + ''' + Validate any of the provided params + ''' + res = super(QuantumResource, self).validate() + if res: + return res + return self.validate_properties(self.properties) + + @staticmethod + def validate_properties(properties): + ''' + Validates to ensure nothing in value_specs overwrites + any key that exists in the schema. + + Also ensures that shared and tenant_id is not specified + in value_specs. + ''' + if 'value_specs' in properties.keys(): + vs = properties.get('value_specs') + banned_keys = set(['shared', 'tenant_id']).union( + properties.keys()) + for k in banned_keys.intersection(vs.keys()): + return '%s not allowed in value_specs' % k + + @staticmethod + def prepare_properties(properties, name): + ''' + Prepares the property values so that they can be passed directly to + the Quantum call. + + Removes None values and value_specs, merges value_specs with the main + values. + ''' + props = dict((k, v) for k, v in properties.items() + if v is not None and k != 'value_specs') + + if 'name' in properties.keys(): + props.setdefault('name', name) + + if 'value_specs' in properties.keys(): + props.update(properties.get('value_specs')) + + return props + + @staticmethod + def handle_get_attributes(name, key, attributes): + ''' + Support method for responding to FnGetAtt + ''' + if key == 'show': + return attributes + + if key in attributes.keys(): + return attributes[key] + + raise exception.InvalidTemplateAttribute(resource=name, + key=key) + + def handle_update(self): + return self.UPDATE_REPLACE + + def FnGetRefId(self): + return unicode(self.instance_id) diff --git a/heat/engine/resources/quantum/router.py b/heat/engine/resources/quantum/router.py new file mode 100644 index 00000000..c4336872 --- /dev/null +++ b/heat/engine/resources/quantum/router.py @@ -0,0 +1,102 @@ +# 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.resources.quantum import quantum + +from heat.openstack.common import log as logging + +logger = logging.getLogger('heat.engine.quantum') + + +class Router(quantum.QuantumResource): + properties_schema = {'name': {'Type': 'String'}, + 'value_specs': {'Type': 'Map', + 'Default': {}}, + 'admin_state_up': {'Type': 'Boolean', + 'Default': True}, + } + + def __init__(self, name, json_snippet, stack): + super(Router, self).__init__(name, json_snippet, stack) + + def handle_create(self): + props = self.prepare_properties(self.properties, self.name) + router = self.quantum().create_router({'router': props})['router'] + self.instance_id_set(router['id']) + + def handle_delete(self): + client = self.quantum() + try: + client.delete_router(self.instance_id) + except: + pass + + def FnGetAtt(self, key): + attributes = self.quantum().show_router( + self.instance_id)['router'] + return self.handle_get_attributes(self.name, key, attributes) + + +class RouterInterface(quantum.QuantumResource): + properties_schema = {'router_id': {'Type': 'String', + 'Required': True}, + 'subnet_id': {'Type': 'String', + 'Required': True}, + } + + def __init__(self, name, json_snippet, stack): + super(RouterInterface, self).__init__(name, json_snippet, stack) + + def handle_create(self): + router_id = self.properties.get('router_id') + subnet_id = self.properties.get('subnet_id') + self.quantum().add_interface_router(router_id, + {'subnet_id': subnet_id}) + self.instance_id_set('%s:%s' % (router_id, subnet_id)) + + def handle_delete(self): + client = self.quantum() + try: + (router_id, subnet_id) = self.instance_id.split(':') + client.remove_interface_router(router_id, + {'subnet_id': subnet_id}) + except: + pass + + +class RouterGateway(quantum.QuantumResource): + properties_schema = {'router_id': {'Type': 'String', + 'Required': True}, + 'network_id': {'Type': 'String', + 'Required': True}, + } + + def __init__(self, name, json_snippet, stack): + super(RouterGateway, self).__init__(name, json_snippet, stack) + + def handle_create(self): + router_id = self.properties.get('router_id') + network_id = self.properties.get('network_id') + self.quantum().add_gateway_router(router_id, + {'network_id': network_id}) + self.instance_id_set('%s:%s' % (router_id, network_id)) + + def handle_delete(self): + client = self.quantum() + try: + (router_id, network_id) = self.instance_id.split(':') + client.remove_gateway_router(router_id) + except: + pass diff --git a/heat/engine/resources/quantum/subnet.py b/heat/engine/resources/quantum/subnet.py new file mode 100644 index 00000000..90c6a02b --- /dev/null +++ b/heat/engine/resources/quantum/subnet.py @@ -0,0 +1,65 @@ +# 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.common import exception + +from heat.openstack.common import log as logging +from heat.engine.resources.quantum import quantum + +logger = logging.getLogger('heat.engine.quantum') + + +class Subnet(quantum.QuantumResource): + + allocation_schema = {'start': {'Type': 'String', + 'Required': True}, + 'end': {'Type': 'String', + 'Required': True}} + + properties_schema = {'network_id': {'Type': 'String', + 'Required': True}, + 'cidr': {'Type': 'String', + 'Required': True}, + 'value_specs': {'Type': 'Map', + 'Default': {}}, + 'name': {'Type': 'String'}, + 'admin_state_up': {'Default': True}, + 'ip_version': {'Type': 'Integer', + 'AllowedValues': [4, 6], + 'Default': 4}, + 'gateway_ip': {'Type': 'String'}, + 'allocation_pools': {'Type': 'List', + 'Schema': allocation_schema} + } + + def __init__(self, name, json_snippet, stack): + super(Subnet, self).__init__(name, json_snippet, stack) + + def handle_create(self): + props = self.prepare_properties(self.properties, self.name) + subnet = self.quantum().create_subnet({'subnet': props})['subnet'] + self.instance_id_set(subnet['id']) + + def handle_delete(self): + client = self.quantum() + try: + client.delete_subnet(self.instance_id) + except: + pass + + def FnGetAtt(self, key): + attributes = self.quantum().show_subnet( + self.instance_id)['subnet'] + return self.handle_get_attributes(self.name, key, attributes) diff --git a/heat/engine/resources/register.py b/heat/engine/resources/register.py index c626f1f3..16d6e4eb 100644 --- a/heat/engine/resources/register.py +++ b/heat/engine/resources/register.py @@ -29,6 +29,11 @@ from heat.engine.resources import stack from heat.engine.resources import user from heat.engine.resources import volume from heat.engine.resources import wait_condition +from heat.engine.resources.quantum import floatingip +from heat.engine.resources.quantum import net +from heat.engine.resources.quantum import port +from heat.engine.resources.quantum import router +from heat.engine.resources.quantum import subnet _resource_classes = { @@ -52,6 +57,14 @@ _resource_classes = { 'AWS::AutoScaling::AutoScalingGroup': autoscaling.AutoScalingGroup, 'AWS::AutoScaling::ScalingPolicy': autoscaling.ScalingPolicy, 'AWS::RDS::DBInstance': dbinstance.DBInstance, + 'OS::Quantum::FloatingIP': floatingip.FloatingIP, + 'OS::Quantum::FloatingIPAssociation': floatingip.FloatingIPAssociation, + 'OS::Quantum::Net': net.Net, + 'OS::Quantum::Port': port.Port, + 'OS::Quantum::Router': router.Router, + 'OS::Quantum::RouterInterface': router.RouterInterface, + 'OS::Quantum::RouterGateway': router.RouterGateway, + 'OS::Quantum::Subnet': subnet.Subnet, } diff --git a/heat/engine/resources/resource.py b/heat/engine/resources/resource.py index 712f42b1..28681ced 100644 --- a/heat/engine/resources/resource.py +++ b/heat/engine/resources/resource.py @@ -26,6 +26,13 @@ try: swiftclient_present = True except ImportError: swiftclient_present = False +# quantumclient not available in all distributions - make quantum an optional +# feature +try: + from quantumclient.v2_0 import client as quantumclient + quantumclient_present = True +except ImportError: + quantumclient_present = False from heat.common import exception from heat.common import config @@ -124,6 +131,7 @@ class Resource(object): self._nova = {} self._keystone = None self._swift = None + self._quantum = None def __eq__(self, other): '''Allow == comparison of two resources''' @@ -281,6 +289,38 @@ class Resource(object): self._swift = swiftclient.Connection(**args) return self._swift + def quantum(self): + if quantumclient_present == False: + return None + if self._quantum: + logger.debug('using existing _quantum') + return self._quantum + + con = self.context + args = { + 'auth_url': con.auth_url, + 'service_type': 'network', + } + + if con.password is not None: + args['username'] = con.username + args['password'] = con.password + args['tenant_name'] = con.tenant + elif con.auth_token is not None: + args['username'] = con.service_user + args['password'] = con.service_password + args['tenant_name'] = con.service_tenant + args['token'] = con.auth_token + else: + logger.error("Quantum connection failed, " + "no password or auth_token!") + return None + logger.debug('quantum args %s', args) + + self._quantum = quantumclient.Client(**args) + + return self._quantum + def calculate_properties(self): for p, v in self.parsed_template('Properties').items(): self.properties[p] = v diff --git a/heat/tests/test_quantum.py b/heat/tests/test_quantum.py new file mode 100644 index 00000000..9091cca6 --- /dev/null +++ b/heat/tests/test_quantum.py @@ -0,0 +1,182 @@ +# 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 sys +import os + +import nose +import unittest +import mox +import json + +from nose.plugins.attrib import attr + +from heat.common import exception +from heat.engine import checkeddict +from heat.engine.resources.quantum import net +from heat.engine.resources.quantum.quantum import QuantumResource as qr +from heat.engine import parser +from utils import skip_if + +try: + from quantumclient.v2_0 import client as quantumclient +except: + skip_test = True +else: + skip_test = False + + +class FakeQuantum(): + + def create_network(self, name): + return {"network": { + "status": "ACTIVE", + "subnets": [], + "name": "name", + "admin_state_up": False, + "shared": False, + "tenant_id": "c1210485b2424d48804aad5d39c61b8f", + "id": "fc68ea2c-b60b-4b4f-bd82-94ec81110766" + }} + + def show_network(self, id): + return {"network": { + "status": "ACTIVE", + "subnets": [], + "name": "name", + "admin_state_up": False, + "shared": False, + "tenant_id": "c1210485b2424d48804aad5d39c61b8f", + "id": "fc68ea2c-b60b-4b4f-bd82-94ec81110766" + }} + + +@attr(tag=['unit', 'resource']) +@attr(speed='fast') +class QuantumTest(unittest.TestCase): + @skip_if(skip_test, 'unable to import quantumclient') + def setUp(self): + self.m = mox.Mox() + self.m.CreateMock(quantumclient) + self.m.StubOutWithMock(net.Net, 'quantum') + + def tearDown(self): + self.m.UnsetStubs() + print "QuantumTest teardown complete" + + def load_template(self): + self.path = os.path.dirname(os.path.realpath(__file__)).\ + replace('heat/tests', 'templates') + f = open("%s/Quantum.template" % self.path) + t = json.loads(f.read()) + f.close() + return t + + def parse_stack(self, t): + class DummyContext(): + tenant = 'test_tenant' + username = 'test_username' + password = 'password' + auth_url = 'http://localhost:5000/v2.0' + stack = parser.Stack(DummyContext(), 'test_stack', parser.Template(t), + stack_id=-1, parameters={'external_network': 'abcd1234'}) + + return stack + + def create_net(self, t, stack, resource_name): + resource = net.Net('test_net', + t['Resources'][resource_name], + stack) + self.assertEqual(None, resource.create()) + self.assertEqual(net.Net.CREATE_COMPLETE, resource.state) + return resource + + def test_validate_properties(self): + p = checkeddict.Properties('foo', net.Net.properties_schema) + vs = {'router:external': True} + p.update({ + 'admin_state_up': False, + 'value_specs': vs + }) + self.assertEqual(None, qr.validate_properties(p)) + + vs['shared'] = True + self.assertEqual('shared not allowed in value_specs', + qr.validate_properties(p)) + vs.pop('shared') + + vs['name'] = 'foo' + self.assertEqual('name not allowed in value_specs', + qr.validate_properties(p)) + vs.pop('name') + + vs['tenant_id'] = '1234' + self.assertEqual('tenant_id not allowed in value_specs', + qr.validate_properties(p)) + vs.pop('tenant_id') + + vs['foo'] = '1234' + self.assertEqual(None, qr.validate_properties(p)) + + def test_prepare_properties(self): + p = checkeddict.Properties('foo', net.Net.properties_schema) + p.update({ + 'admin_state_up': False, + 'value_specs': {'router:external': True} + }) + props = qr.prepare_properties(p, 'resource_name') + self.assertEqual({ + 'name': 'resource_name', + 'router:external': True, + 'admin_state_up': False + }, props) + + @skip_if(skip_test, 'unable to import quantumclient') + def test_net(self): + fq = FakeQuantum() + net.Net.quantum().MultipleTimes().AndReturn(fq) + + self.m.ReplayAll() + t = self.load_template() + stack = self.parse_stack(t) + resource = self.create_net(t, stack, 'network') + + resource.validate() + + ref_id = resource.FnGetRefId() + self.assertEqual('fc68ea2c-b60b-4b4f-bd82-94ec81110766', ref_id) + + self.assertEqual('ACTIVE', resource.FnGetAtt('status')) + try: + resource.FnGetAtt('Foo') + raise Exception('Expected InvalidTemplateAttribute') + except exception.InvalidTemplateAttribute: + pass + + try: + resource.FnGetAtt('id') + raise Exception('Expected InvalidTemplateAttribute') + except exception.InvalidTemplateAttribute: + pass + + self.assertEqual(net.Net.UPDATE_REPLACE, resource.handle_update()) + + resource.delete() + self.m.VerifyAll() + + # allows testing of the test directly, shown below + if __name__ == '__main__': + sys.argv.append(__file__) + nose.main() diff --git a/templates/Quantum.template b/templates/Quantum.template new file mode 100644 index 00000000..1fd32985 --- /dev/null +++ b/templates/Quantum.template @@ -0,0 +1,100 @@ +{ + "AWSTemplateFormatVersion" : "2010-09-09", + + "Description" : "Template to test Quantum resources", + + "Parameters" : { + + }, + + "Resources" : { + "network": { + "Type": "OS::Quantum::Net", + "Properties": { + "name": "the_network" + } + }, + "unnamed_network": { + "Type": "OS::Quantum::Net" + }, + "admin_down_network": { + "Type": "OS::Quantum::Net", + "Properties": { + "admin_state_up": false + } + }, + + "subnet": { + "Type": "OS::Quantum::Subnet", + "Properties": { + "network_id": { "Ref" : "network" }, + "ip_version": 4, + "cidr": "10.0.3.0/24", + "allocation_pools": [{"start": "10.0.3.20", "end": "10.0.3.150"}] + } + }, + + "port": { + "Type": "OS::Quantum::Port", + "Properties": { + "device_id": "d6b4d3a5-c700-476f-b609-1493dd9dadc0", + "name": "port1", + "network_id": { "Ref" : "network" }, + "fixed_ips": [{ + "subnet_id": { "Ref" : "subnet" }, + "ip_address": "10.0.3.21" + }] + } + }, + + "router": { + "Type": "OS::Quantum::Router" + }, + + "router_interface": { + "Type": "OS::Quantum::RouterInterface", + "Properties": { + "router_id": { "Ref" : "router" }, + "subnet_id": { "Ref" : "subnet" } + } + } + }, + "Outputs" : { + "the_network_status" : { + "Value" : { "Fn::GetAtt" : [ "network", "status" ]}, + "Description" : "Status of network" + }, + "port_device_owner" : { + "Value" : { "Fn::GetAtt" : [ "port", "device_owner" ]}, + "Description" : "Device owner of the port" + }, + "port_fixed_ips" : { + "Value" : { "Fn::GetAtt" : [ "port", "fixed_ips" ]}, + "Description" : "Fixed IPs of the port" + }, + "port_mac_address" : { + "Value" : { "Fn::GetAtt" : [ "port", "mac_address" ]}, + "Description" : "MAC address of the port" + }, + "port_status" : { + "Value" : { "Fn::GetAtt" : [ "port", "status" ]}, + "Description" : "Status of the port" + }, + "port_show" : { + "Value" : { "Fn::GetAtt" : [ "port", "show" ]}, + "Description" : "All attributes for port" + }, + "subnet_show" : { + "Value" : { "Fn::GetAtt" : [ "subnet", "show" ]}, + "Description" : "All attributes for subnet" + }, + "network_show" : { + "Value" : { "Fn::GetAtt" : [ "network", "show" ]}, + "Description" : "All attributes for network" + }, + "router_show" : { + "Value" : { "Fn::GetAtt" : [ "router", "show" ]}, + "Description" : "All attributes for router" + } + } +} \ No newline at end of file diff --git a/tools/pip-requires b/tools/pip-requires index 092419da..9651ae92 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -27,4 +27,4 @@ python-keystoneclient glance python-memcached python-swiftclient - +python-quantumclient -- 2.45.2