--- /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.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
--- /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.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)
--- /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.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)
--- /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.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)
--- /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.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
--- /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.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)
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 = {
'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,
}
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
self._nova = {}
self._keystone = None
self._swift = None
+ self._quantum = None
def __eq__(self, other):
'''Allow == comparison of two resources'''
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
--- /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 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()
--- /dev/null
+{
+ "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
glance
python-memcached
python-swiftclient
-
+python-quantumclient