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: Ia270294440eeec5163e35009f6be0b5db9ad78c1changes/76/15376/1
parent
b3a57062ad
commit
2162b8047b
@ -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
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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
|
@ -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)
|
@ -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()
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in new issue