diff --git a/trove/cmd/manage.py b/trove/cmd/manage.py index abe3c02876..8bccb60d6e 100644 --- a/trove/cmd/manage.py +++ b/trove/cmd/manage.py @@ -73,7 +73,7 @@ class Commands(object): packages, active, version=version) print("Datastore version '%s(%s)' updated." % - (version_name, version)) + (version_name, version or version_name)) except exception.DatastoreNotFound as e: print(e) @@ -99,63 +99,96 @@ class Commands(object): datastore, datastore_version_name) def datastore_version_flavor_add(self, datastore_name, - datastore_version_name, flavor_ids): + datastore_version_name, flavor_ids, + version=None): """Adds flavors for a given datastore version id.""" + dsmetadata = datastore_models.DatastoreVersionMetadata try: - dsmetadata = datastore_models.DatastoreVersionMetadata + datastore_version_id = dsmetadata.datastore_version_find( + datastore_name, + datastore_version_name, + version_number=version) + dsmetadata.add_datastore_version_flavor_association( - datastore_name, datastore_version_name, flavor_ids.split(",")) + datastore_version_id, flavor_ids.split(",")) print("Added flavors '%s' to the '%s' '%s'." % (flavor_ids, datastore_name, datastore_version_name)) - except exception.DatastoreVersionNotFound as e: + except Exception as e: print(e) def datastore_version_flavor_delete(self, datastore_name, - datastore_version_name, flavor_id): + datastore_version_name, flavor_id, + version=None): """Deletes a flavor's association with a given datastore.""" try: dsmetadata = datastore_models.DatastoreVersionMetadata + + datastore_version_id = dsmetadata.datastore_version_find( + datastore_name, + datastore_version_name, + version_number=version) + dsmetadata.delete_datastore_version_flavor_association( - datastore_name, datastore_version_name, flavor_id) + datastore_version_id, flavor_id) print("Deleted flavor '%s' from '%s' '%s'." % (flavor_id, datastore_name, datastore_version_name)) - except exception.DatastoreVersionNotFound as e: + except Exception as e: print(e) def datastore_version_volume_type_add(self, datastore_name, datastore_version_name, - volume_type_ids): + volume_type_ids, version=None): """Adds volume type assiciation for a given datastore version id.""" try: dsmetadata = datastore_models.DatastoreVersionMetadata + + datastore_version_id = dsmetadata.datastore_version_find( + datastore_name, + datastore_version_name, + version_number=version) + dsmetadata.add_datastore_version_volume_type_association( - datastore_name, datastore_version_name, + datastore_version_id, volume_type_ids.split(",")) print("Added volume type '%s' to the '%s' '%s'." % (volume_type_ids, datastore_name, datastore_version_name)) - except exception.DatastoreVersionNotFound as e: + except Exception as e: print(e) def datastore_version_volume_type_delete(self, datastore_name, datastore_version_name, - volume_type_id): + volume_type_id, version=None): """Deletes a volume type association with a given datastore.""" try: dsmetadata = datastore_models.DatastoreVersionMetadata + + datastore_version_id = dsmetadata.datastore_version_find( + datastore_name, + datastore_version_name, + version_number=version) + dsmetadata.delete_datastore_version_volume_type_association( - datastore_name, datastore_version_name, volume_type_id) + datastore_version_id, volume_type_id) print("Deleted volume type '%s' from '%s' '%s'." % (volume_type_id, datastore_name, datastore_version_name)) - except exception.DatastoreVersionNotFound as e: + except Exception as e: print(e) def datastore_version_volume_type_list(self, datastore_name, - datastore_version_name): + datastore_version_name, + version=None): """Lists volume type association with a given datastore.""" try: dsmetadata = datastore_models.DatastoreVersionMetadata - vtlist = dsmetadata.list_datastore_volume_type_associations( - datastore_name, datastore_version_name) + + datastore_version_id = dsmetadata.datastore_version_find( + datastore_name, + datastore_version_name, + version_number=version) + + vtlist = dsmetadata. \ + list_datastore_version_volume_type_associations( + datastore_version_id) if vtlist.count() > 0: for volume_type in vtlist: print("Datastore: %s, Version: %s, Volume Type: %s" % @@ -165,7 +198,7 @@ class Commands(object): print("No Volume Type Associations found for Datastore: %s, " "Version: %s." % (datastore_name, datastore_version_name)) - except exception.DatastoreVersionNotFound as e: + except Exception as e: print(e) def params_of(self, command_name): @@ -262,49 +295,79 @@ def main(): help='Name of the datastore version.') parser = subparser.add_parser( - 'datastore_version_flavor_add', help='Adds flavor association to ' - 'a given datastore and datastore version.') + 'datastore_version_flavor_add', + help='Adds flavor association to a given datastore and datastore ' + 'version.') parser.add_argument('datastore_name', help='Name of the datastore.') parser.add_argument('datastore_version_name', help='Name of the ' 'datastore version.') parser.add_argument('flavor_ids', help='Comma separated list of ' 'flavor ids.') + parser.add_argument( + '--version', + help='The version number of the datastore version, e.g. 5.7.30. ' + 'If not specified, use as default ' + 'value.') parser = subparser.add_parser( - 'datastore_version_flavor_delete', help='Deletes a flavor ' - 'associated with a given datastore and datastore version.') + 'datastore_version_flavor_delete', + help='Deletes a flavor associated with a given datastore and ' + 'datastore version.') parser.add_argument('datastore_name', help='Name of the datastore.') parser.add_argument('datastore_version_name', help='Name of the ' 'datastore version.') parser.add_argument('flavor_id', help='The flavor to be deleted for ' 'a given datastore and datastore version.') + parser.add_argument( + '--version', + help='The version number of the datastore version, e.g. 5.7.30. ' + 'If not specified, use as default ' + 'value.') + parser = subparser.add_parser( - 'datastore_version_volume_type_add', help='Adds volume_type ' - 'association to a given datastore and datastore version.') + 'datastore_version_volume_type_add', + help='Adds volume_type association to a given datastore and ' + 'datastore version.') parser.add_argument('datastore_name', help='Name of the datastore.') parser.add_argument('datastore_version_name', help='Name of the ' 'datastore version.') parser.add_argument('volume_type_ids', help='Comma separated list of ' 'volume_type ids.') + parser.add_argument( + '--version', + help='The version number of the datastore version, e.g. 5.7.30. ' + 'If not specified, use as default ' + 'value.') parser = subparser.add_parser( 'datastore_version_volume_type_delete', help='Deletes a volume_type ' - 'associated with a given datastore and datastore version.') + 'associated with a given datastore and datastore version.') parser.add_argument('datastore_name', help='Name of the datastore.') parser.add_argument('datastore_version_name', help='Name of the ' 'datastore version.') parser.add_argument('volume_type_id', help='The volume_type to be ' 'deleted for a given datastore and datastore ' 'version.') + parser.add_argument( + '--version', + help='The version number of the datastore version, e.g. 5.7.30. ' + 'If not specified, use as default ' + 'value.') parser = subparser.add_parser( 'datastore_version_volume_type_list', help='Lists the volume_types ' - 'associated with a given datastore and datastore version.') + 'associated with a given datastore and datastore version.') parser.add_argument('datastore_name', help='Name of the datastore.') parser.add_argument('datastore_version_name', help='Name of the ' 'datastore version.') + parser.add_argument( + '--version', + help='The version number of the datastore version, e.g. 5.7.30. ' + 'If not specified, use as default ' + 'value.') + cfg.custom_parser('action', actions) cfg.parse_args(sys.argv) diff --git a/trove/common/apischema.py b/trove/common/apischema.py index 3755dc8d60..d1dec14608 100644 --- a/trove/common/apischema.py +++ b/trove/common/apischema.py @@ -410,7 +410,8 @@ instance = { "additionalProperties": True, "properties": { "type": non_empty_string, - "version": non_empty_string + "version": non_empty_string, + "version_number": non_empty_string } }, "nics": nics, diff --git a/trove/common/exception.py b/trove/common/exception.py index f89bef1fb2..d4b44b0c76 100644 --- a/trove/common/exception.py +++ b/trove/common/exception.py @@ -134,38 +134,38 @@ class DatastoresNotFound(NotFound): class DatastoreFlavorAssociationNotFound(NotFound): message = _("Flavor %(id)s is not supported for datastore " - "%(datastore)s version %(datastore_version)s") + "version %(datastore_version_id)s") class DatastoreFlavorAssociationAlreadyExists(TroveError): message = _("Flavor %(id)s is already associated with " - "datastore %(datastore)s version %(datastore_version)s") + "datastore version %(datastore_version_id)s") class DatastoreVolumeTypeAssociationNotFound(NotFound): message = _("The volume type %(id)s is not valid for datastore " - "%(datastore)s and version %(version_id)s.") + "version %(datastore_version_id)s.") class DatastoreVolumeTypeAssociationAlreadyExists(TroveError): - message = _("Datastore '%(datastore)s' version %(datastore_version)s " + message = _("Datastore version %(datastore_version_id)s " "and volume-type %(id)s mapping already exists.") class DataStoreVersionVolumeTypeRequired(TroveError): message = _("Only specific volume types are allowed for a " - "datastore %(datastore)s version %(datastore_version)s. " + "datastore version %(datastore_version_id)s. " "You must specify a valid volume type.") class DatastoreVersionNoVolumeTypes(TroveError): message = _("No valid volume types could be found for datastore " - "%(datastore)s and version %(datastore_version)s.") + "version %(datastore_version_id)s.") class DatastoreNoVersion(TroveError): @@ -194,6 +194,13 @@ class DatastoreVersionsInUse(BadRequest): message = _("Datastore version is in use by %(resource)s.") +class DatastoreVersionsNoUniqueMatch(TroveError): + + message = _("Multiple datastore versions found for '%(name)s', " + "use an UUID or specify both the name and version number to " + "be more specific.") + + class DatastoreDefaultDatastoreNotFound(TroveError): message = _("Please specify datastore. Default datastore " @@ -222,12 +229,6 @@ class DatastoreOperationNotSupported(TroveError): "the '%(datastore)s' datastore.") -class NoUniqueMatch(TroveError): - - message = _("Multiple matches found for '%(name)s', " - "use an UUID to be more specific.") - - class OverLimit(TroveError): # internal_message is used for log, stop translating. diff --git a/trove/datastore/models.py b/trove/datastore/models.py index bf1b890331..f63136f16a 100644 --- a/trove/datastore/models.py +++ b/trove/datastore/models.py @@ -405,15 +405,25 @@ class DatastoreVersion(object): return cls(DBDatastoreVersion.find_by(datastore_id=datastore.id, id=id_or_name)) - version = version or id_or_name - versions = DBDatastoreVersion.find_all(datastore_id=datastore.id, - name=id_or_name, - version=version) - if versions.count() == 0: - raise exception.DatastoreVersionNotFound(version=version) - if versions.count() > 1: - raise exception.NoUniqueMatch(name=id_or_name) - return cls(versions.first()) + if not version: + versions = DBDatastoreVersion.find_all(datastore_id=datastore.id, + name=id_or_name) + if versions.count() == 0: + raise exception.DatastoreVersionNotFound(version=id_or_name) + if versions.count() > 1: + raise exception.DatastoreVersionsNoUniqueMatch(name=id_or_name) + + db_version = versions.first() + else: + try: + db_version = DBDatastoreVersion.find_by( + datastore_id=datastore.id, + name=id_or_name, + version=version) + except exception.ModelNotFoundError: + raise exception.DatastoreVersionNotFound(version=version) + + return cls(db_version) @classmethod def load_by_uuid(cls, uuid): @@ -508,7 +518,8 @@ class DatastoreVersions(object): yield item -def get_datastore_version(type=None, version=None, return_inactive=False): +def get_datastore_version(type=None, version=None, return_inactive=False, + version_number=None): datastore = type or CONF.default_datastore if not datastore: raise exception.DatastoreDefaultDatastoreNotDefined() @@ -520,11 +531,12 @@ def get_datastore_version(type=None, version=None, return_inactive=False): datastore=datastore) raise - version = version or datastore.default_version_id - if not version: + version_id = version or datastore.default_version_id + if not version_id: raise exception.DatastoreDefaultVersionNotFound( datastore=datastore.name) - datastore_version = DatastoreVersion.load(datastore, version) + datastore_version = DatastoreVersion.load(datastore, version_id, + version=version_number) if datastore_version.datastore_id != datastore.id: raise exception.DatastoreNoVersion(datastore=datastore.name, version=datastore_version.name) @@ -616,8 +628,8 @@ def update_datastore_version(datastore, name, manager, image_id, image_tags, class DatastoreVersionMetadata(object): @classmethod - def _datastore_version_find(cls, datastore_name, - datastore_version_name): + def datastore_version_find(cls, datastore_name, + datastore_version_name, version_number=None): """ Helper to find a datastore version id for a given datastore and datastore version name. @@ -626,17 +638,31 @@ class DatastoreVersionMetadata(object): db_ds_record = DBDatastore.find_by( name=datastore_name ) - db_dsv_record = DBDatastoreVersion.find_by( - datastore_id=db_ds_record.id, - name=datastore_version_name - ) + + if not version_number: + db_dsv_records = DBDatastoreVersion.find_all( + datastore_id=db_ds_record.id, + name=datastore_version_name, + ) + if db_dsv_records.count() == 0: + raise exception.DatastoreVersionNotFound( + version=datastore_version_name) + if db_dsv_records.count() > 1: + raise exception.DatastoreVersionsNoUniqueMatch( + name=datastore_version_name) + + db_dsv_record = db_dsv_records.first() + else: + db_dsv_record = DBDatastoreVersion.find_by( + datastore_id=db_ds_record.id, + name=datastore_version_name, + version=version_number + ) return db_dsv_record.id @classmethod - def _datastore_version_metadata_add(cls, datastore_name, - datastore_version_name, - datastore_version_id, + def _datastore_version_metadata_add(cls, datastore_version_id, key, value, exception_class): """ Create a record of the specified key and value in the @@ -657,8 +683,7 @@ class DatastoreVersionMetadata(object): return else: raise exception_class( - datastore=datastore_name, - datastore_version=datastore_version_name, + datastore_version_id=datastore_version_id, id=value) except exception.NotFound: pass @@ -669,8 +694,7 @@ class DatastoreVersionMetadata(object): key=key, value=value) @classmethod - def _datastore_version_metadata_delete(cls, datastore_name, - datastore_version_name, + def _datastore_version_metadata_delete(cls, datastore_version_id, key, value, exception_class): """ Delete a record of the specified key and value in the @@ -679,11 +703,6 @@ class DatastoreVersionMetadata(object): # if an association does not exist, raise an exception # if a deleted association exists, raise an exception # if an un-deleted association exists, delete it - - datastore_version_id = cls._datastore_version_find( - datastore_name, - datastore_version_name) - try: db_record = DBDatastoreVersionMetadata.find_by( datastore_version_id=datastore_version_id, @@ -693,96 +712,69 @@ class DatastoreVersionMetadata(object): return else: raise exception_class( - datastore=datastore_name, - datastore_version=datastore_version_name, + datastore_version_id=datastore_version_id, id=value) except exception.ModelNotFoundError: - raise exception_class(datastore=datastore_name, - datastore_version=datastore_version_name, + raise exception_class(datastore_version_id=datastore_version_id, id=value) @classmethod - def add_datastore_version_flavor_association(cls, datastore_name, - datastore_version_name, + def add_datastore_version_flavor_association(cls, datastore_version_id, flavor_ids): - datastore_version_id = cls._datastore_version_find( - datastore_name, - datastore_version_name) - for flavor_id in flavor_ids: cls._datastore_version_metadata_add( - datastore_name, datastore_version_name, datastore_version_id, 'flavor', flavor_id, exception.DatastoreFlavorAssociationAlreadyExists) @classmethod - def delete_datastore_version_flavor_association(cls, datastore_name, - datastore_version_name, + def delete_datastore_version_flavor_association(cls, datastore_version_id, flavor_id): cls._datastore_version_metadata_delete( - datastore_name, datastore_version_name, 'flavor', flavor_id, + datastore_version_id, 'flavor', flavor_id, exception.DatastoreFlavorAssociationNotFound) @classmethod def list_datastore_version_flavor_associations(cls, context, - datastore_type, datastore_version_id): - if datastore_type and datastore_version_id: - """ - All nova flavors are permitted for a datastore_version unless - one or more entries are found in datastore_version_metadata, - in which case only those are permitted. - """ - (datastore, datastore_version) = get_datastore_version( - type=datastore_type, version=datastore_version_id) - # If datastore_version_id and flavor key exists in the - # metadata table return all the associated flavors for - # that datastore version. - nova_flavors = create_nova_client(context).flavors.list() - bound_flavors = DBDatastoreVersionMetadata.find_all( - datastore_version_id=datastore_version.id, - key='flavor', deleted=False - ) - if (bound_flavors.count() != 0): - bound_flavors = tuple(f.value for f in bound_flavors) - # Generate a filtered list of nova flavors - ds_nova_flavors = (f for f in nova_flavors - if f.id in bound_flavors) - associated_flavors = tuple(flavor_model(flavor=item) - for item in ds_nova_flavors) - else: - # Return all nova flavors if no flavor metadata found - # for datastore_version. - associated_flavors = tuple(flavor_model(flavor=item) - for item in nova_flavors) - return associated_flavors + """Get allowed flavors for a given datastore version. + + All nova flavors are permitted for a datastore_version unless + one or more entries are found in datastore_version_metadata, + in which case only those are permitted. + """ + nova_flavors = create_nova_client(context).flavors.list() + bound_flavors = DBDatastoreVersionMetadata.find_all( + datastore_version_id=datastore_version_id, + key='flavor', deleted=False + ) + if (bound_flavors.count() != 0): + bound_flavors = tuple(f.value for f in bound_flavors) + # Generate a filtered list of nova flavors + ds_nova_flavors = (f for f in nova_flavors + if f.id in bound_flavors) + associated_flavors = tuple(flavor_model(flavor=item) + for item in ds_nova_flavors) else: - msg = _("Specify both the datastore and datastore_version_id.") - raise exception.BadRequest(msg) + # Return all nova flavors if no flavor metadata found + # for datastore_version. + associated_flavors = tuple(flavor_model(flavor=item) + for item in nova_flavors) + return associated_flavors @classmethod - def add_datastore_version_volume_type_association(cls, datastore_name, - datastore_version_name, + def add_datastore_version_volume_type_association(cls, + datastore_version_id, volume_type_names): - datastore_version_id = cls._datastore_version_find( - datastore_name, - datastore_version_name) - - # the database record will contain - # datastore_version_id, 'volume_type', volume_type_name for volume_type_name in volume_type_names: cls._datastore_version_metadata_add( - datastore_name, datastore_version_name, datastore_version_id, 'volume_type', volume_type_name, exception.DatastoreVolumeTypeAssociationAlreadyExists) @classmethod def delete_datastore_version_volume_type_association( - cls, datastore_name, - datastore_version_name, - volume_type_name): + cls, datastore_version_id, volume_type_name): cls._datastore_version_metadata_delete( - datastore_name, datastore_version_name, 'volume_type', + datastore_version_id, 'volume_type', volume_type_name, exception.DatastoreVolumeTypeAssociationNotFound) @@ -811,7 +803,7 @@ class DatastoreVersionMetadata(object): List the datastore associations for a given datastore and version. """ if datastore_name and datastore_version_name: - datastore_version_id = cls._datastore_version_find( + datastore_version_id = cls.datastore_version_find( datastore_name, datastore_version_name) return cls.list_datastore_version_volume_type_associations( datastore_version_id) @@ -820,17 +812,13 @@ class DatastoreVersionMetadata(object): raise exception.BadRequest(msg) @classmethod - def datastore_volume_type_associations_exist(cls, - datastore_name, - datastore_version_name): - return cls.list_datastore_volume_type_associations( - datastore_name, - datastore_version_name).count() > 0 + def datastore_volume_type_associations_exist(cls, datastore_version_id): + return cls.list_datastore_version_volume_type_associations( + datastore_version_id).count() > 0 @classmethod def allowed_datastore_version_volume_types(cls, context, - datastore_name, - datastore_version_name): + datastore_version_id): """ List all allowed volume types for a given datastore and datastore version. If datastore version metadata is @@ -838,59 +826,44 @@ class DatastoreVersionMetadata(object): allowed. If datastore version metadata is not provided then all volume types known to cinder are allowed. """ - if datastore_name and datastore_version_name: - # first obtain the list in the dsvmetadata - datastore_version_id = cls._datastore_version_find( - datastore_name, datastore_version_name) + metadata = cls.list_datastore_version_volume_type_associations( + datastore_version_id) - metadata = cls.list_datastore_version_volume_type_associations( - datastore_version_id) + # then get the list of all volume types + all_volume_types = volume_type_models.VolumeTypes(context) - # then get the list of all volume types - all_volume_types = volume_type_models.VolumeTypes(context) + # if there's metadata: intersect, + # else, whatever cinder has. + if (metadata.count() != 0): + # the volume types from metadata first + ds_volume_types = tuple(f.value for f in metadata) - # if there's metadata: intersect, - # else, whatever cinder has. - if (metadata.count() != 0): - # the volume types from metadata first - ds_volume_types = tuple(f.value for f in metadata) - - # Cinder volume type names are unique, intersect - allowed_volume_types = tuple( - f for f in all_volume_types - if ((f.name in ds_volume_types) or - (f.id in ds_volume_types))) - else: - allowed_volume_types = tuple(all_volume_types) - - return allowed_volume_types + # Cinder volume type names are unique, intersect + allowed_volume_types = tuple( + f for f in all_volume_types + if ((f.name in ds_volume_types) or + (f.id in ds_volume_types))) else: - msg = _("Specify the datastore_name and datastore_version_name.") - raise exception.BadRequest(msg) + allowed_volume_types = tuple(all_volume_types) + + return allowed_volume_types @classmethod - def validate_volume_type(cls, context, volume_type, - datastore_name, datastore_version_name): - if cls.datastore_volume_type_associations_exist( - datastore_name, datastore_version_name): + def validate_volume_type(cls, context, volume_type, datastore_version_id): + if cls.datastore_volume_type_associations_exist(datastore_version_id): allowed = cls.allowed_datastore_version_volume_types( - context, datastore_name, datastore_version_name) + context, datastore_version_id) if len(allowed) == 0: raise exception.DatastoreVersionNoVolumeTypes( - datastore=datastore_name, - datastore_version=datastore_version_name) + datastore_version_id=datastore_version_id) if volume_type is None: raise exception.DataStoreVersionVolumeTypeRequired( - datastore=datastore_name, - datastore_version=datastore_version_name) + datastore_version_id=datastore_version_id) allowed_names = tuple(f.name for f in allowed) - for n in allowed_names: - LOG.debug("Volume Type: %s is allowed for datastore " - "%s, version %s." % - (n, datastore_name, datastore_version_name)) + LOG.debug(f"Allowed volume types: {allowed_names}") + if volume_type not in allowed_names: raise exception.DatastoreVolumeTypeAssociationNotFound( - datastore=datastore_name, - version_id=datastore_version_name, + datastore_version_id=datastore_version_id, id=volume_type) diff --git a/trove/datastore/service.py b/trove/datastore/service.py index b9366690eb..ada7b4ad78 100644 --- a/trove/datastore/service.py +++ b/trove/datastore/service.py @@ -93,7 +93,7 @@ class DatastoreController(wsgi.Controller): context = req.environ[wsgi.CONTEXT_KEY] flavors = (models.DatastoreVersionMetadata. list_datastore_version_flavor_associations( - context, datastore, version_id)) + context, version_id)) return wsgi.Result(flavor_views.FlavorsView(flavors, req).data(), 200) def list_associated_volume_types(self, req, tenant_id, datastore, @@ -106,7 +106,7 @@ class DatastoreController(wsgi.Controller): context = req.environ[wsgi.CONTEXT_KEY] volume_types = (models.DatastoreVersionMetadata. allowed_datastore_version_volume_types( - context, datastore, version_id)) + context, version_id)) return wsgi.Result(volume_type_view.VolumeTypesView( volume_types, req).data(), 200) diff --git a/trove/instance/models.py b/trove/instance/models.py index 01ae293828..80bef79d0e 100644 --- a/trove/instance/models.py +++ b/trove/instance/models.py @@ -1133,9 +1133,8 @@ class Instance(BuiltInstance): valid_flavors = tuple(f.value for f in bound_flavors) if flavor_id not in valid_flavors: raise exception.DatastoreFlavorAssociationNotFound( - datastore=datastore.name, - datastore_version=datastore_version.name, - flavor_id=flavor_id) + datastore_version_id=datastore_version.id, + id=flavor_id) try: flavor = nova_client.flavors.get(flavor_id) except nova_exceptions.NotFound: @@ -1166,7 +1165,7 @@ class Instance(BuiltInstance): volume_size = volume.size dvm.validate_volume_type(context, volume_type, - datastore.name, datastore_version.name) + datastore_version.id) validate_volume_size(volume_size) call_args['volume_type'] = volume_type call_args['volume_size'] = volume_size @@ -1335,7 +1334,7 @@ class Instance(BuiltInstance): nics, overrides, slave_of_id, cluster_config, volume_type=volume_type, modules=module_list, locality=locality, access=access, - ds_version=datastore_version.name) + ds_version=datastore_version.version) return SimpleInstance(context, db_info, service_status, root_password, locality=locality) diff --git a/trove/instance/views.py b/trove/instance/views.py index e0413c18aa..96d8c7960f 100644 --- a/trove/instance/views.py +++ b/trove/instance/views.py @@ -129,6 +129,8 @@ class InstanceDetailView(InstanceView): if self.instance.datastore_version: result['instance']['datastore']['version'] = \ self.instance.datastore_version.name + result['instance']['datastore']['version_number'] = \ + self.instance.datastore_version.version if self.instance.fault: result['instance']['fault'] = self._build_fault_info() diff --git a/trove/tests/api/datastores.py b/trove/tests/api/datastores.py index af8197e5a9..fe911f63ea 100644 --- a/trove/tests/api/datastores.py +++ b/trove/tests/api/datastores.py @@ -161,13 +161,9 @@ class DatastoreVersions(object): @test def test_datastore_version_not_found(self): - try: - assert_raises(exceptions.NotFound, - self.rd_client.datastore_versions.get, - self.datastore_active.name, NAME) - except exceptions.BadRequest as e: - assert_equal(e.message, - "Datastore version '%s' cannot be found." % NAME) + assert_raises(exceptions.BadRequest, + self.rd_client.datastore_versions.get, + self.datastore_active.name, NAME) @test def test_datastore_version_list_by_uuid(self): diff --git a/trove/tests/api/instances.py b/trove/tests/api/instances.py index c36b692ed2..643e120ee3 100644 --- a/trove/tests/api/instances.py +++ b/trove/tests/api/instances.py @@ -204,7 +204,7 @@ class CheckInstance(AttrCheck): if 'datastore' not in self.instance: self.fail("'datastore' not found in instance.") else: - allowed_attrs = ['type', 'version'] + allowed_attrs = ['type', 'version', 'version_number'] self.contains_allowed_attrs( self.instance['datastore'], allowed_attrs, msg="datastore") @@ -714,18 +714,13 @@ class CreateInstanceFail(object): users = [] datastore = CONFIG.dbaas_datastore datastore_version = "nonexistent" - try: - assert_raises(exceptions.NotFound, - dbaas.instances.create, instance_name, - instance_info.dbaas_flavor_href, - volume, databases, users, - datastore=datastore, - datastore_version=datastore_version, - nics=instance_info.nics) - except exceptions.BadRequest as e: - assert_equal(e.message, - "Datastore version '%s' cannot be found." % - datastore_version) + assert_raises(exceptions.BadRequest, + dbaas.instances.create, instance_name, + instance_info.dbaas_flavor_href, + volume, databases, users, + datastore=datastore, + datastore_version=datastore_version, + nics=instance_info.nics) @test( diff --git a/trove/tests/unittests/datastore/base.py b/trove/tests/unittests/datastore/base.py index bf38c09a7c..f543b77f2d 100644 --- a/trove/tests/unittests/datastore/base.py +++ b/trove/tests/unittests/datastore/base.py @@ -41,15 +41,15 @@ class TestDatastoreBase(trove_testtools.TestCase): datastore_models.update_datastore_version( cls.ds_name, cls.ds_version_name, "mysql", "", "", "", True) - DatastoreVersionMetadata.add_datastore_version_flavor_association( - cls.ds_name, cls.ds_version_name, [cls.flavor_id]) - DatastoreVersionMetadata.add_datastore_version_volume_type_association( - cls.ds_name, cls.ds_version_name, [cls.volume_type]) - cls.datastore_version = DatastoreVersion.load(cls.datastore, cls.ds_version_name) cls.test_id = cls.datastore_version.id + DatastoreVersionMetadata.add_datastore_version_flavor_association( + cls.datastore_version.id, [cls.flavor_id]) + DatastoreVersionMetadata.add_datastore_version_volume_type_association( + cls.datastore_version.id, [cls.volume_type]) + cls.cap1 = Capability.create(cls.capability_name, cls.capability_desc, True) cls.cap2 = Capability.create( diff --git a/trove/tests/unittests/datastore/test_datastore_version_metadata.py b/trove/tests/unittests/datastore/test_datastore_version_metadata.py index 2574ef1084..964cab0dd5 100644 --- a/trove/tests/unittests/datastore/test_datastore_version_metadata.py +++ b/trove/tests/unittests/datastore/test_datastore_version_metadata.py @@ -57,48 +57,40 @@ class TestDatastoreVersionMetadata(TestDatastoreBase): def test_add_existing_flavor_associations(self): dsmetadata = datastore_models.DatastoreVersionMetadata - self.assertRaisesRegex( + self.assertRaises( exception.DatastoreFlavorAssociationAlreadyExists, - "Flavor %s is already associated with datastore %s version %s" - % (self.flavor_id, self.ds_name, self.ds_version_name), dsmetadata.add_datastore_version_flavor_association, - self.ds_name, self.ds_version_name, [self.flavor_id]) + self.test_id, [self.flavor_id]) def test_add_existing_volume_type_associations(self): dsmetadata = datastore_models.DatastoreVersionMetadata self.assertRaises( exception.DatastoreVolumeTypeAssociationAlreadyExists, dsmetadata.add_datastore_version_volume_type_association, - self.ds_name, self.ds_version_name, [self.volume_type]) + self.test_id, [self.volume_type]) def test_delete_nonexistent_flavor_mapping(self): dsmeta = datastore_models.DatastoreVersionMetadata - self.assertRaisesRegex( + self.assertRaises( exception.DatastoreFlavorAssociationNotFound, - "Flavor 2 is not supported for datastore %s version %s" - % (self.ds_name, self.ds_version_name), dsmeta.delete_datastore_version_flavor_association, - self.ds_name, self.ds_version_name, flavor_id=2) + self.test_id, flavor_id=2) def test_delete_nonexistent_volume_type_mapping(self): dsmeta = datastore_models.DatastoreVersionMetadata self.assertRaises( exception.DatastoreVolumeTypeAssociationNotFound, dsmeta.delete_datastore_version_volume_type_association, - self.ds_name, self.ds_version_name, + self.test_id, volume_type_name='some random thing') def test_delete_flavor_mapping(self): flavor_id = 2 dsmetadata = datastore_models.DatastoreVersionMetadata dsmetadata.add_datastore_version_flavor_association( - self.ds_name, - self.ds_version_name, - [flavor_id]) + self.test_id, [flavor_id]) dsmetadata.delete_datastore_version_flavor_association( - self.ds_name, - self.ds_version_name, - flavor_id) + self.test_id, flavor_id) datastore = datastore_models.Datastore.load(self.ds_name) ds_version = datastore_models.DatastoreVersion.load( datastore, @@ -108,27 +100,22 @@ class TestDatastoreVersionMetadata(TestDatastoreBase): self.assertTrue(mapping.deleted) # check update dsmetadata.add_datastore_version_flavor_association( - self.ds_name, self.ds_version_name, [flavor_id]) + self.test_id, [flavor_id]) mapping = datastore_models.DBDatastoreVersionMetadata.find_by( datastore_version_id=ds_version.id, value=flavor_id, key='flavor') self.assertFalse(mapping.deleted) # clear the mapping datastore_models.DatastoreVersionMetadata. \ - delete_datastore_version_flavor_association(self.ds_name, - self.ds_version_name, + delete_datastore_version_flavor_association(self.test_id, flavor_id) def test_delete_volume_type_mapping(self): volume_type = 'this is bogus' dsmetadata = datastore_models.DatastoreVersionMetadata dsmetadata.add_datastore_version_volume_type_association( - self.ds_name, - self.ds_version_name, - [volume_type]) + self.test_id, [volume_type]) dsmetadata.delete_datastore_version_volume_type_association( - self.ds_name, - self.ds_version_name, - volume_type) + self.test_id, volume_type) datastore = datastore_models.Datastore.load(self.ds_name) ds_version = datastore_models.DatastoreVersion.load( datastore, @@ -139,19 +126,17 @@ class TestDatastoreVersionMetadata(TestDatastoreBase): self.assertTrue(mapping.deleted) # check update dsmetadata.add_datastore_version_volume_type_association( - self.ds_name, self.ds_version_name, [volume_type]) + self.test_id, [volume_type]) mapping = datastore_models.DBDatastoreVersionMetadata.find_by( datastore_version_id=ds_version.id, value=volume_type, key='volume_type') self.assertFalse(mapping.deleted) # clear the mapping dsmetadata.delete_datastore_version_volume_type_association( - self.ds_name, - self.ds_version_name, - volume_type) + self.test_id, volume_type) @mock.patch.object(datastore_models.DatastoreVersionMetadata, - '_datastore_version_find') + 'datastore_version_find') @mock.patch.object(datastore_models.DatastoreVersionMetadata, 'list_datastore_version_volume_type_associations') @mock.patch.object(clients, 'create_cinder_client') @@ -179,7 +164,7 @@ class TestDatastoreVersionMetadata(TestDatastoreBase): mock_list.return_value = mock_trove_list_result return self.dsmetadata.allowed_datastore_version_volume_types( - None, 'ds', 'dsv') + None, self.random_uuid()) def _assert_equal_types(self, test_dict, output_obj): self.assertEqual(test_dict.get('id'), output_obj.id) diff --git a/trove/tests/unittests/instance/test_service.py b/trove/tests/unittests/instance/test_service.py index 07444a4c6c..a38c62f57b 100644 --- a/trove/tests/unittests/instance/test_service.py +++ b/trove/tests/unittests/instance/test_service.py @@ -13,7 +13,9 @@ # limitations under the License. from unittest import mock +from trove.common import cfg from trove.common import clients +from trove.common import exception from trove.datastore import models as ds_models from trove.instance import models as ins_models from trove.instance import service @@ -21,6 +23,8 @@ from trove.instance import service_status as srvstatus from trove.tests.unittests import trove_testtools from trove.tests.unittests.util import util +CONF = cfg.CONF + class TestInstanceController(trove_testtools.TestCase): @classmethod @@ -37,7 +41,14 @@ class TestInstanceController(trove_testtools.TestCase): 1) ds_models.update_datastore_version( cls.ds_name, 'test_image_tags', 'mysql', '', ['trove', 'mysql'], - '', 1) + '', 1, version='test_image_tags version') + ds_models.update_datastore_version( + cls.ds_name, 'test_version', 'mysql', '', ['trove'], '', 1, + version='version 1') + ds_models.update_datastore_version( + cls.ds_name, 'test_version', 'mysql', '', ['trove'], '', 1, + version='version 2') + cls.ds_version_imageid = ds_models.DatastoreVersion.load( cls.ds, 'test_image_id') cls.ds_version_imagetags = ds_models.DatastoreVersion.load( @@ -61,19 +72,22 @@ class TestInstanceController(trove_testtools.TestCase): @mock.patch('trove.instance.models.Instance.create') def test_create_by_ds_version_image_tags(self, mock_model_create, mock_create_client): + image_id = self.random_uuid() mock_glance_client = mock.MagicMock() - mock_glance_client.images.list.return_value = [ - {'id': self.random_uuid()}] + mock_glance_client.images.list.return_value = [{'id': image_id}] mock_create_client.return_value = mock_glance_client + name = self.random_name(name='instance', + prefix='TestInstanceController') + flavor = self.random_uuid() body = { 'instance': { - 'name': self.random_name(name='instance', - prefix='TestInstanceController'), - 'flavorRef': self.random_uuid(), + 'name': name, + 'flavorRef': flavor, 'datastore': { 'type': self.ds_name, - 'version': self.ds_version_imagetags.name + 'version': self.ds_version_imagetags.name, + 'version_number': self.ds_version_imagetags.version } } } @@ -85,6 +99,40 @@ class TestInstanceController(trove_testtools.TestCase): sort='created_at:desc', limit=1 ) + mock_model_create.assert_called_once_with( + mock.ANY, name, flavor, image_id, + [], [], + mock.ANY, mock.ANY, + None, None, None, [], None, None, + replica_count=None, volume_type=None, modules=None, locality=None, + region_name=CONF.service_credentials.region_name, access=None + ) + args = mock_model_create.call_args[0] + actual_ds_version = args[7] + self.assertEqual(self.ds_version_imagetags.name, + actual_ds_version.name) + self.assertEqual(self.ds_version_imagetags.version, + actual_ds_version.version) + + def test_create_multiple_versions(self): + body = { + 'instance': { + 'name': self.random_name(name='instance', + prefix='TestInstanceController'), + 'flavorRef': self.random_uuid(), + 'datastore': { + 'type': self.ds_name, + 'version': 'test_version' + } + } + } + + self.assertRaises( + exception.DatastoreVersionsNoUniqueMatch, + self.controller.create, + mock.MagicMock(), body, mock.ANY + ) + @mock.patch.object(clients, 'create_nova_client', return_value=mock.MagicMock()) @mock.patch('trove.rpc.get_client')