Add networks property to OS::Trove::Instance

Trove client recently added possibility to provide NICS while creating
Trove instances, which is required in presence of multiple Neutron
networks. This patch adds such property to OS::Trove::Instance resource,
allowing usage of this new feature in Heat.

Closes-Bug: #1312474
Change-Id: Ie691702bb2e2537397ed7ae657eb8d088424d890
This commit is contained in:
Pavlo Shchelokovskyy 2014-04-25 19:22:15 +03:00
parent 04de60093b
commit cab25ee8e0
2 changed files with 250 additions and 31 deletions

View File

@ -18,6 +18,7 @@ from heat.engine import properties
from heat.engine import resource
from heat.openstack.common.gettextutils import _
from heat.openstack.common import log as logging
from heat.openstack.common import uuidutils
LOG = logging.getLogger(__name__)
@ -37,10 +38,10 @@ class OSDBInstance(resource.Resource):
PROPERTIES = (
NAME, FLAVOR, SIZE, DATABASES, USERS, AVAILABILITY_ZONE,
RESTORE_POINT, DATASTORE_TYPE, DATASTORE_VERSION,
RESTORE_POINT, DATASTORE_TYPE, DATASTORE_VERSION, NICS,
) = (
'name', 'flavor', 'size', 'databases', 'users', 'availability_zone',
'restore_point', 'datastore_type', 'datastore_version',
'restore_point', 'datastore_type', 'datastore_version', 'networks',
)
_DATABASE_KEYS = (
@ -55,6 +56,12 @@ class OSDBInstance(resource.Resource):
'name', 'password', 'host', 'databases',
)
_NICS_KEYS = (
NET, PORT, V4_FIXED_IP
) = (
'network', 'port', 'fixed_ip'
)
ATTRIBUTES = (
HOSTNAME, HREF,
) = (
@ -98,6 +105,33 @@ class OSDBInstance(resource.Resource):
constraints.Range(1, 150),
]
),
NICS: properties.Schema(
properties.Schema.LIST,
_("List of network interfaces to create on instance."),
default=[],
schema=properties.Schema(
properties.Schema.MAP,
schema={
NET: properties.Schema(
properties.Schema.STRING,
_('Name or UUID of the network to attach this NIC to. '
'Either %(port)s or %(net)s must be specified.') % {
'port': PORT, 'net': NET}
),
PORT: properties.Schema(
properties.Schema.STRING,
_('Name or UUID of Neutron port to attach this '
'NIC to. '
'Either %(port)s or %(net)s must be specified.') % {
'port': PORT, 'net': NET}
),
V4_FIXED_IP: properties.Schema(
properties.Schema.STRING,
_('Fixed IPv4 address for this NIC.')
),
},
),
),
DATABASES: properties.Schema(
properties.Schema.LIST,
_('List of databases to be created on DB instance creation.'),
@ -194,10 +228,10 @@ class OSDBInstance(resource.Resource):
attributes_schema = {
HOSTNAME: attributes.Schema(
_("Hostname of the instance")
_("Hostname of the instance.")
),
HREF: attributes.Schema(
_("Api endpoint reference of the instance")
_("Api endpoint reference of the instance.")
),
}
@ -245,6 +279,30 @@ class OSDBInstance(resource.Resource):
dbs = [{'name': db} for db in user.get(self.USER_DATABASES, [])]
user[self.USER_DATABASES] = dbs
# convert networks to format required by troveclient
nics = []
for nic in self.properties.get(self.NICS):
nic_dict = {}
net = nic.get(self.NET)
if net:
if uuidutils.is_uuid_like(net):
net_id = net
else:
# using Nova for lookup to cover both neutron and
# nova-network cases
nova = self.client('nova')
net_id = nova.networks.find(label=net).id
nic_dict['net-id'] = net_id
port = nic.get(self.PORT)
if port:
neutron = self.client_plugin('neutron')
nic_dict['port-id'] = neutron.find_neutron_resource(
self.properties, self.PORT, 'port')
ip = nic.get(self.V4_FIXED_IP)
if ip:
nic_dict['v4-fixed-ip'] = ip
nics.append(nic_dict)
# create db instance
instance = self.trove().instances.create(
self._dbinstance_name(),
@ -255,7 +313,8 @@ class OSDBInstance(resource.Resource):
restorePoint=restore_point,
availability_zone=zone,
datastore=self.datastore_type,
datastore_version=self.datastore_version)
datastore_version=self.datastore_version,
nics=nics)
self.resource_id_set(instance.id)
return instance
@ -372,24 +431,35 @@ class OSDBInstance(resource.Resource):
# check validity of user and databases
users = self.properties.get(self.USERS)
if not users:
return
if users:
databases = self.properties.get(self.DATABASES)
if not databases:
msg = _('Databases property is required if users property '
'is provided for resource %s.') % self.name
raise exception.StackValidationFailed(message=msg)
databases = self.properties.get(self.DATABASES)
if not databases:
msg = _('Databases property is required if users property '
'is provided for resource %s.') % self.name
raise exception.StackValidationFailed(message=msg)
db_names = set([db[self.DATABASE_NAME] for db in databases])
for user in users:
missing_db = [db_name for db_name in user[self.USER_DATABASES]
if db_name not in db_names]
db_names = set([db[self.DATABASE_NAME] for db in databases])
for user in users:
missing_db = [db_name for db_name in user[self.USER_DATABASES]
if db_name not in db_names]
if missing_db:
msg = (_('Database %(dbs)s specified for user does '
'not exist in databases for resource %(name)s.')
% {'dbs': missing_db, 'name': self.name})
raise exception.StackValidationFailed(message=msg)
if missing_db:
msg = (_('Database %(dbs)s specified for user does '
'not exist in databases for resource %(name)s.')
% {'dbs': missing_db, 'name': self.name})
# check validity of NICS
is_neutron = self.is_using_neutron()
nics = self.properties.get(self.NICS)
for nic in nics:
if not is_neutron and nic.get(self.PORT):
msg = _("Can not use %s property on Nova-network.") % self.PORT
raise exception.StackValidationFailed(message=msg)
if bool(nic.get(self.NET)) == bool(nic.get(self.PORT)):
msg = _("Either %(net)s or %(port)s must be provided.") % {
'net': self.NET, 'port': self.PORT}
raise exception.StackValidationFailed(message=msg)
def href(self):

View File

@ -18,6 +18,8 @@ import six
from heat.common import exception
from heat.common import template_format
from heat.engine.clients.os import neutron
from heat.engine.clients.os import nova
from heat.engine.clients.os import trove
from heat.engine import parser
from heat.engine.resources import os_database
@ -49,6 +51,21 @@ db_template = '''
}
'''
db_template_with_nics = '''
heat_template_version: 2013-05-23
description: MySQL instance running on openstack DBaaS cloud
resources:
MySqlCloudDB:
type: OS::Trove::Instance
properties:
name: test
flavor: 1GB
size: 30
networks:
- port: someportname
fixed_ip: 1.2.3.4
'''
class FakeDBInstance(object):
def __init__(self):
@ -81,7 +98,10 @@ class FakeVersion(object):
class OSDBInstanceTest(HeatTestCase):
def setUp(self):
super(OSDBInstanceTest, self).setUp()
self.stub_keystoneclient()
self.fc = self.m.CreateMockAnything()
self.nova = self.m.CreateMockAnything()
self.m.StubOutWithMock(trove.TroveClientPlugin, '_create')
def _setup_test_clouddbinstance(self, name, t):
stack_name = '%s_stack' % name
@ -97,13 +117,16 @@ class OSDBInstanceTest(HeatTestCase):
stack)
return instance
def _stubout_create(self, instance, fake_dbinstance):
self.m.StubOutWithMock(trove.TroveClientPlugin, '_create')
def _stubout_common_create(self):
trove.TroveClientPlugin._create().AndReturn(self.fc)
self.m.StubOutWithMock(self.fc, 'flavors')
self.m.StubOutWithMock(trove.TroveClientPlugin, 'get_flavor_id')
trove.TroveClientPlugin.get_flavor_id('1GB').AndReturn(1)
self.m.StubOutWithMock(self.fc, 'instances')
self.m.StubOutWithMock(self.fc.instances, 'create')
def _stubout_create(self, instance, fake_dbinstance):
self._stubout_common_create()
users = [{"name": "testuser", "password": "pass", "host": "%",
"databases": [{"name": "validdb"}]}]
databases = [{"collate": "utf8_general_ci",
@ -115,17 +138,20 @@ class OSDBInstanceTest(HeatTestCase):
restorePoint=None,
availability_zone=None,
datastore="SomeDStype",
datastore_version="MariaDB-5.5"
datastore_version="MariaDB-5.5",
nics=[]
).AndReturn(fake_dbinstance)
self.m.ReplayAll()
def _stubout_validate(self, instance):
self.m.StubOutWithMock(trove.TroveClientPlugin, '_create')
def _stubout_validate(self, instance, neutron=None):
trove.TroveClientPlugin._create().AndReturn(self.fc)
self.m.StubOutWithMock(self.fc, 'datastore_versions')
self.m.StubOutWithMock(self.fc.datastore_versions, 'list')
self.fc.datastore_versions.list(instance.properties['datastore_type']
).AndReturn([FakeVersion()])
if neutron is not None:
self.m.StubOutWithMock(instance, 'is_using_neutron')
instance.is_using_neutron().AndReturn(bool(neutron))
self.m.ReplayAll()
def test_osdatabase_create(self):
@ -143,7 +169,6 @@ class OSDBInstanceTest(HeatTestCase):
t['Resources']['MySqlCloudDB']['Properties']['restore_point'] = "1234"
instance = self._setup_test_clouddbinstance('dbinstance_create', t)
self.m.StubOutWithMock(trove.TroveClientPlugin, '_create')
trove.TroveClientPlugin._create().AndReturn(self.fc)
self.m.StubOutWithMock(self.fc, 'flavors')
self.m.StubOutWithMock(self.fc.flavors, "list")
@ -162,7 +187,8 @@ class OSDBInstanceTest(HeatTestCase):
restorePoint={"backupRef": "1234"},
availability_zone=None,
datastore="SomeDStype",
datastore_version="MariaDB-5.5"
datastore_version="MariaDB-5.5",
nics=[]
).AndReturn(fake_dbinstance)
self.m.ReplayAll()
@ -277,7 +303,6 @@ class OSDBInstanceTest(HeatTestCase):
t = template_format.parse(db_template)
instance = self._setup_test_clouddbinstance('dbinstance_test', t)
instance.resource_id = 12345
self.m.StubOutWithMock(trove.TroveClientPlugin, '_create')
trove.TroveClientPlugin._create().AndReturn(self.fc)
self.m.StubOutWithMock(self.fc, 'instances')
self.m.StubOutWithMock(self.fc.instances, 'get')
@ -292,7 +317,6 @@ class OSDBInstanceTest(HeatTestCase):
t = template_format.parse(db_template)
instance = self._setup_test_clouddbinstance('dbinstance_test', t)
instance.resource_id = 12345
self.m.StubOutWithMock(trove.TroveClientPlugin, '_create')
trove.TroveClientPlugin._create().AndReturn(self.fc)
self.m.StubOutWithMock(self.fc, 'instances')
self.m.StubOutWithMock(self.fc.instances, 'get')
@ -308,7 +332,6 @@ class OSDBInstanceTest(HeatTestCase):
t = template_format.parse(db_template)
instance = self._setup_test_clouddbinstance('dbinstance_test', t)
instance.resource_id = 12345
self.m.StubOutWithMock(trove.TroveClientPlugin, '_create')
trove.TroveClientPlugin._create().AndReturn(self.fc)
self.m.StubOutWithMock(self.fc, 'instances')
self.m.StubOutWithMock(self.fc.instances, 'get')
@ -415,7 +438,6 @@ class OSDBInstanceTest(HeatTestCase):
'datastore_type'] = 'mysql'
t['Resources']['MySqlCloudDB']['Properties'].pop('datastore_version')
instance = self._setup_test_clouddbinstance('dbinstance_test', t)
self.m.StubOutWithMock(trove.TroveClientPlugin, '_create')
trove.TroveClientPlugin._create().AndReturn(self.fc)
self.m.StubOutWithMock(self.fc, 'datastore_versions')
self.m.StubOutWithMock(self.fc.datastore_versions, 'list')
@ -432,3 +454,130 @@ class OSDBInstanceTest(HeatTestCase):
"Allowed versions are MariaDB-5.5, MariaDB-5.0.")
self.assertEqual(expected_msg, six.text_type(ex))
self.m.VerifyAll()
def test_osdatabase_prop_validation_net_with_port_fail(self):
t = template_format.parse(db_template)
t['Resources']['MySqlCloudDB']['Properties']['networks'] = [
{
"port": "someportuuid",
"network": "somenetuuid"
}]
instance = self._setup_test_clouddbinstance('dbinstance_test', t)
self._stubout_validate(instance, neutron=True)
ex = self.assertRaises(
exception.StackValidationFailed, instance.validate)
self.assertEqual('Either network or port must be provided.',
six.text_type(ex))
self.m.VerifyAll()
def test_osdatabase_prop_validation_no_net_no_port_fail(self):
t = template_format.parse(db_template)
t['Resources']['MySqlCloudDB']['Properties']['networks'] = [
{
"fixed_ip": "1.2.3.4"
}]
instance = self._setup_test_clouddbinstance('dbinstance_test', t)
self._stubout_validate(instance, neutron=True)
ex = self.assertRaises(
exception.StackValidationFailed, instance.validate)
self.assertEqual('Either network or port must be provided.',
six.text_type(ex))
self.m.VerifyAll()
def test_osdatabase_prop_validation_nic_port_on_novanet_fails(self):
t = template_format.parse(db_template)
t['Resources']['MySqlCloudDB']['Properties']['networks'] = [
{
"port": "someportuuid",
}]
instance = self._setup_test_clouddbinstance('dbinstance_test', t)
self._stubout_validate(instance, neutron=False)
ex = self.assertRaises(
exception.StackValidationFailed, instance.validate)
self.assertEqual('Can not use port property on Nova-network.',
six.text_type(ex))
self.m.VerifyAll()
def test_osdatabase_create_with_port(self):
fake_dbinstance = FakeDBInstance()
t = template_format.parse(db_template_with_nics)
instance = self._setup_test_clouddbinstance('dbinstance_test', t)
self._stubout_common_create()
self.m.StubOutWithMock(neutron.NeutronClientPlugin,
'find_neutron_resource')
neutron.NeutronClientPlugin.find_neutron_resource(
instance.properties, 'port', 'port').AndReturn('someportid')
self.fc.instances.create('test', 1, volume={'size': 30},
databases=[],
users=[],
restorePoint=None,
availability_zone=None,
datastore=None,
datastore_version=None,
nics=[{'port-id': 'someportid',
'v4-fixed-ip': '1.2.3.4'}]
).AndReturn(fake_dbinstance)
self.m.ReplayAll()
scheduler.TaskRunner(instance.create)()
self.assertEqual((instance.CREATE, instance.COMPLETE), instance.state)
self.m.VerifyAll()
def test_osdatabase_create_with_net_id(self):
net_id = '034aa4d5-0f36-4127-8481-5caa5bfc9403'
fake_dbinstance = FakeDBInstance()
t = template_format.parse(db_template_with_nics)
t['resources']['MySqlCloudDB']['properties']['networks'] = [
{'network': net_id}]
instance = self._setup_test_clouddbinstance('dbinstance_test', t)
self._stubout_common_create()
self.fc.instances.create('test', 1, volume={'size': 30},
databases=[],
users=[],
restorePoint=None,
availability_zone=None,
datastore=None,
datastore_version=None,
nics=[{'net-id': net_id}]
).AndReturn(fake_dbinstance)
self.m.ReplayAll()
scheduler.TaskRunner(instance.create)()
self.assertEqual((instance.CREATE, instance.COMPLETE), instance.state)
self.m.VerifyAll()
def test_osdatabase_create_with_net_name(self):
class FakeNet(object):
id = 'somenetid'
fake_dbinstance = FakeDBInstance()
t = template_format.parse(db_template_with_nics)
t['resources']['MySqlCloudDB']['properties']['networks'] = [
{'network': 'somenetname'}]
instance = self._setup_test_clouddbinstance('dbinstance_test', t)
self._stubout_common_create()
self.m.StubOutWithMock(nova.NovaClientPlugin, '_create')
nova.NovaClientPlugin._create().AndReturn(self.nova)
self.m.StubOutWithMock(self.nova, 'networks')
self.m.StubOutWithMock(self.nova.networks, 'find')
self.nova.networks.find(label='somenetname').AndReturn(FakeNet())
self.fc.instances.create('test', 1, volume={'size': 30},
databases=[],
users=[],
restorePoint=None,
availability_zone=None,
datastore=None,
datastore_version=None,
nics=[{'net-id': 'somenetid'}]
).AndReturn(fake_dbinstance)
self.m.ReplayAll()
scheduler.TaskRunner(instance.create)()
self.assertEqual((instance.CREATE, instance.COMPLETE), instance.state)
self.m.VerifyAll()