Improve OS::Trove::Instance resource

Improvements include:
- better handling of possible Trove instance states;
- usage of proper troveclient exception;
- better error messages;
- setting length constraint on user databases property;
- NAME property is no longer required as it can be autogenerated;
- name autogeneration fixed to prevent the like of bug #1324499;
- simplification in tests by removing environment in test template;
- some typos fixed.

Change-Id: I27c569a16a316448001d857e839c9aabc28ca611
This commit is contained in:
Pavlo Shchelokovskyy 2014-06-03 13:14:43 +03:00
parent 59517eae5c
commit 3f80c2bb8e
2 changed files with 55 additions and 61 deletions

View File

@ -11,6 +11,8 @@
# License for the specific language governing permissions and limitations
# under the License.
from troveclient.openstack.common.apiclient import exceptions as troveexc
from heat.common import exception
from heat.engine import attributes
from heat.engine.clients import troveclient
@ -29,6 +31,14 @@ class OSDBInstance(resource.Resource):
OpenStack cloud database instance resource.
'''
TROVE_STATUS = (
ERROR, FAILED, ACTIVE,
) = (
'ERROR', 'FAILED', 'ACTIVE',
)
BAD_STATUSES = (ERROR, FAILED)
PROPERTIES = (
NAME, FLAVOR, SIZE, DATABASES, USERS, AVAILABILITY_ZONE,
RESTORE_POINT,
@ -59,7 +69,6 @@ class OSDBInstance(resource.Resource):
NAME: properties.Schema(
properties.Schema.STRING,
_('Name of the DB instance to create.'),
required=True,
constraints=[
constraints.Length(max=255),
]
@ -153,7 +162,10 @@ class OSDBInstance(resource.Resource):
schema=properties.Schema(
properties.Schema.STRING,
),
required=True
required=True,
constraints=[
constraints.Length(min=1),
]
),
},
)
@ -190,18 +202,17 @@ class OSDBInstance(resource.Resource):
return self._dbinstance
def physical_resource_name(self):
def _dbinstance_name(self):
name = self.properties.get(self.NAME)
if name:
return name
return super(OSDBInstance, self).physical_resource_name()
return self.physical_resource_name()
def handle_create(self):
'''
Create cloud database instance.
'''
self.dbinstancename = self.physical_resource_name()
self.flavor = nova_utils.get_flavor_id(self.trove(),
self.properties[self.FLAVOR])
self.volume = {'size': self.properties[self.SIZE]}
@ -218,7 +229,7 @@ class OSDBInstance(resource.Resource):
# create db instance
instance = self.trove().instances.create(
self.dbinstancename,
self._dbinstance_name(),
self.flavor,
volume=self.volume,
databases=self.databases,
@ -243,17 +254,16 @@ class OSDBInstance(resource.Resource):
'''
Check if cloud DB instance creation is complete.
'''
self._refresh_instance(instance)
if instance.status == 'ERROR':
self._refresh_instance(instance) # get updated attributes
if instance.status in self.BAD_STATUSES:
raise exception.Error(_("Database instance creation failed."))
if instance.status != 'ACTIVE':
if instance.status != self.ACTIVE:
return False
msg = _("Database instance %(database)s created (flavor:%(flavor)s, "
"volume:%(volume)s)")
LOG.info(msg % ({'database': self.dbinstancename,
LOG.info(msg % ({'database': self._dbinstance_name(),
'flavor': self.flavor,
'volume': self.volume}))
return True
@ -268,7 +278,7 @@ class OSDBInstance(resource.Resource):
instance = None
try:
instance = self.trove().instances.get(self.resource_id)
except troveclient.exceptions.NotFound:
except troveexc.NotFound:
LOG.debug("Database instance %s not found." % self.resource_id)
self.resource_id_set(None)
else:
@ -277,14 +287,15 @@ class OSDBInstance(resource.Resource):
def check_delete_complete(self, instance):
'''
Check for completion of cloud DB instance delettion
Check for completion of cloud DB instance deletion
'''
if not instance:
return True
try:
# For some time trove instance may continue to live
self._refresh_instance(instance)
except troveclient.exceptions.NotFound:
except troveexc.NotFound:
self.resource_id_set(None)
return True
@ -305,23 +316,19 @@ class OSDBInstance(resource.Resource):
databases = self.properties.get(self.DATABASES)
if not databases:
msg = _('Databases property is required if users property'
' is provided')
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:
if not user.get(self.USER_DATABASES, []):
msg = _('Must provide access to at least one database for '
'user %s') % user[self.USER_NAME]
raise exception.StackValidationFailed(message=msg)
missing_db = [db_name for db_name in user[self.USER_DATABASES]
if db_name not in db_names]
if missing_db:
msg = _('Database %s specified for user does not exist in '
'databases.') % 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)
def href(self):

View File

@ -16,7 +16,6 @@ import uuid
from heat.common import exception
from heat.common import template_format
from heat.engine.clients import troveclient
from heat.engine import environment
from heat.engine import parser
from heat.engine.resources import os_database
from heat.engine import scheduler
@ -24,27 +23,10 @@ from heat.tests.common import HeatTestCase
from heat.tests import utils
wp_template = '''
db_template = '''
{
"AWSTemplateFormatVersion" : "2010-09-09",
"Description" : "MySQL instance running on openstack DBaaS cloud",
"Parameters" : {
"flavor": {
"Description" : "Flavor reference",
"Type": "String",
"Default": '1GB'
},
"size": {
"Description" : "The volume size",
"Type": "Number",
"Default": '30'
},
"name": {
"Description" : "The database instance name",
"Type": "String",
"Default": "OpenstackDbaas"
}
},
"Resources" : {
"MySqlCloudDB": {
"Type": "OS::Trove::Instance",
@ -91,14 +73,12 @@ class OSDBInstanceTest(HeatTestCase):
super(OSDBInstanceTest, self).setUp()
self.fc = self.m.CreateMockAnything()
def _setup_test_clouddbinstance(self, name, parsed_t):
def _setup_test_clouddbinstance(self, name, t):
stack_name = '%s_stack' % name
t = parsed_t
template = parser.Template(t)
stack = parser.Stack(utils.dummy_context(),
stack_name,
template,
environment.Environment({'name': 'test'}),
stack_id=str(uuid.uuid4()))
instance = os_database.OSDBInstance(
@ -131,7 +111,7 @@ class OSDBInstanceTest(HeatTestCase):
def test_osdatabase_create(self):
fake_dbinstance = FakeDBInstance()
t = template_format.parse(wp_template)
t = template_format.parse(db_template)
instance = self._setup_test_clouddbinstance('dbinstance_create', t)
self._stubout_create(instance, fake_dbinstance)
scheduler.TaskRunner(instance.create)()
@ -140,7 +120,7 @@ class OSDBInstanceTest(HeatTestCase):
def test_osdatabase_create_overlimit(self):
fake_dbinstance = FakeDBInstance()
t = template_format.parse(wp_template)
t = template_format.parse(db_template)
instance = self._setup_test_clouddbinstance('dbinstance_create', t)
self._stubout_create(instance, fake_dbinstance)
@ -159,7 +139,7 @@ class OSDBInstanceTest(HeatTestCase):
def test_osdatabase_create_fails(self):
fake_dbinstance = FakeDBInstance()
fake_dbinstance.status = 'ERROR'
t = template_format.parse(wp_template)
t = template_format.parse(db_template)
instance = self._setup_test_clouddbinstance('dbinstance_create', t)
self._stubout_create(instance, fake_dbinstance)
self.assertRaises(exception.ResourceFailure,
@ -168,7 +148,7 @@ class OSDBInstanceTest(HeatTestCase):
def test_osdatabase_delete(self):
fake_dbinstance = FakeDBInstance()
t = template_format.parse(wp_template)
t = template_format.parse(db_template)
instance = self._setup_test_clouddbinstance('dbinstance_del', t)
self._stubout_create(instance, fake_dbinstance)
scheduler.TaskRunner(instance.create)()
@ -187,7 +167,7 @@ class OSDBInstanceTest(HeatTestCase):
def test_osdatabase_delete_overlimit(self):
fake_dbinstance = FakeDBInstance()
t = template_format.parse(wp_template)
t = template_format.parse(db_template)
instance = self._setup_test_clouddbinstance('dbinstance_del', t)
self._stubout_create(instance, fake_dbinstance)
scheduler.TaskRunner(instance.create)()
@ -210,7 +190,7 @@ class OSDBInstanceTest(HeatTestCase):
def test_osdatabase_delete_resource_none(self):
fake_dbinstance = FakeDBInstance()
t = template_format.parse(wp_template)
t = template_format.parse(db_template)
instance = self._setup_test_clouddbinstance('dbinstance_del', t)
self._stubout_create(instance, fake_dbinstance)
scheduler.TaskRunner(instance.create)()
@ -223,7 +203,7 @@ class OSDBInstanceTest(HeatTestCase):
def test_osdatabase_resource_not_found(self):
fake_dbinstance = FakeDBInstance()
t = template_format.parse(wp_template)
t = template_format.parse(db_template)
instance = self._setup_test_clouddbinstance('dbinstance_del', t)
self._stubout_create(instance, fake_dbinstance)
scheduler.TaskRunner(instance.create)()
@ -237,7 +217,7 @@ class OSDBInstanceTest(HeatTestCase):
self.m.VerifyAll()
def test_osdatabase_invalid_attribute(self):
t = template_format.parse(wp_template)
t = template_format.parse(db_template)
instance = self._setup_test_clouddbinstance("db_invalid_attrib", t)
attrib = instance._resolve_attribute("invalid_attrib")
self.assertIsNone(attrib)
@ -245,7 +225,7 @@ class OSDBInstanceTest(HeatTestCase):
def test_osdatabase_get_hostname(self):
fake_dbinstance = FakeDBInstance()
t = template_format.parse(wp_template)
t = template_format.parse(db_template)
instance = self._setup_test_clouddbinstance('dbinstance_test', t)
instance.resource_id = 12345
self.m.StubOutWithMock(instance, 'trove')
@ -260,7 +240,7 @@ class OSDBInstanceTest(HeatTestCase):
def test_osdatabase_get_href(self):
fake_dbinstance = FakeDBInstance()
t = template_format.parse(wp_template)
t = template_format.parse(db_template)
instance = self._setup_test_clouddbinstance('dbinstance_test', t)
instance.resource_id = 12345
self.m.StubOutWithMock(instance, 'trove')
@ -271,11 +251,12 @@ class OSDBInstanceTest(HeatTestCase):
self.m.ReplayAll()
attrib = instance._resolve_attribute('href')
self.assertEqual(fake_dbinstance.links[0]['href'], attrib)
self.m.VerifyAll()
def test_osdatabase_get_href_links_none(self):
fake_dbinstance = FakeDBInstance()
fake_dbinstance.links = None
t = template_format.parse(wp_template)
t = template_format.parse(db_template)
instance = self._setup_test_clouddbinstance('dbinstance_test', t)
instance.resource_id = 12345
self.m.StubOutWithMock(instance, 'trove')
@ -286,15 +267,17 @@ class OSDBInstanceTest(HeatTestCase):
self.m.ReplayAll()
attrib = instance._resolve_attribute('href')
self.assertIsNone(attrib)
self.m.VerifyAll()
def test_osdatabase_prop_validation_success(self):
t = template_format.parse(wp_template)
t = template_format.parse(db_template)
instance = self._setup_test_clouddbinstance('dbinstance_test', t)
ret = instance.validate()
self.assertIsNone(ret)
self.m.VerifyAll()
def test_osdatabase_prop_validation_invaliddb(self):
t = template_format.parse(wp_template)
t = template_format.parse(db_template)
t['Resources']['MySqlCloudDB']['Properties']['databases'] = [
{"name": "onedb"}]
t['Resources']['MySqlCloudDB']['Properties']['users'] = [
@ -303,16 +286,18 @@ class OSDBInstanceTest(HeatTestCase):
"databases": ["invaliddb"]}]
instance = self._setup_test_clouddbinstance('dbinstance_test', t)
self.assertRaises(exception.StackValidationFailed, instance.validate)
self.m.VerifyAll()
def test_osdatabase_prop_validation_users_none(self):
t = template_format.parse(wp_template)
t = template_format.parse(db_template)
t['Resources']['MySqlCloudDB']['Properties']['users'] = []
instance = self._setup_test_clouddbinstance('dbinstance_test', t)
ret = instance.validate()
self.assertIsNone(ret)
self.m.VerifyAll()
def test_osdatabase_prop_validation_databases_none(self):
t = template_format.parse(wp_template)
t = template_format.parse(db_template)
t['Resources']['MySqlCloudDB']['Properties']['databases'] = []
t['Resources']['MySqlCloudDB']['Properties']['users'] = [
{"name": "testuser",
@ -320,9 +305,10 @@ class OSDBInstanceTest(HeatTestCase):
"databases": ["invaliddb"]}]
instance = self._setup_test_clouddbinstance('dbinstance_test', t)
self.assertRaises(exception.StackValidationFailed, instance.validate)
self.m.VerifyAll()
def test_osdatabase_prop_validation_user_no_db(self):
t = template_format.parse(wp_template)
t = template_format.parse(db_template)
t['Resources']['MySqlCloudDB']['Properties']['databases'] = [
{"name": "validdb"}]
t['Resources']['MySqlCloudDB']['Properties']['users'] = [
@ -330,3 +316,4 @@ class OSDBInstanceTest(HeatTestCase):
instance = self._setup_test_clouddbinstance('dbinstance_test', t)
self.assertRaises(exception.StackValidationFailed, instance.validate)
self.m.VerifyAll()