diff --git a/glance/common/exception.py b/glance/common/exception.py index f49c5a6b6b..6155dd3d5c 100644 --- a/glance/common/exception.py +++ b/glance/common/exception.py @@ -130,6 +130,31 @@ class ProtectedImageDelete(Forbidden): message = _("Image %(image_id)s is protected and cannot be deleted.") +class ProtectedMetadefNamespaceDelete(Forbidden): + message = _("Metadata definition namespace %(namespace)s is protected" + " and cannot be deleted.") + + +class ProtectedMetadefNamespacePropDelete(Forbidden): + message = _("Metadata definition property %(property_name)s is protected" + " and cannot be deleted.") + + +class ProtectedMetadefObjectDelete(Forbidden): + message = _("Metadata definition object %(object_name)s is protected" + " and cannot be deleted.") + + +class ProtectedMetadefResourceTypeAssociationDelete(Forbidden): + message = _("Metadata definition resource-type-association" + " %(resource_type)s is protected and cannot be deleted.") + + +class ProtectedMetadefResourceTypeSystemDelete(Forbidden): + message = _("Metadata definition resource-type %(resource_type_name)s is" + " a seeded-system type and cannot be deleted.") + + class Invalid(GlanceException): message = _("Data supplied was not valid.") @@ -353,3 +378,74 @@ class InvalidParameterValue(Invalid): class InvalidImageStatusTransition(Invalid): message = _("Image status transition from %(cur_status)s to" " %(new_status)s is not allowed") + + +class MetadefDuplicateNamespace(Duplicate): + message = _("The metadata definition namespace=%(namespace_name)s" + " already exists.") + + +class MetadefDuplicateObject(Duplicate): + message = _("A metadata definition object with name=%(object_name)s" + " already exists in namespace=%(namespace_name)s.") + + +class MetadefDuplicateProperty(Duplicate): + message = _("A metadata definition property with name=%(property_name)s" + " already exists in namespace=%(namespace_name)s.") + + +class MetadefDuplicateResourceType(Duplicate): + message = _("A metadata definition resource-type with" + " name=%(resource_type_name)s already exists.") + + +class MetadefDuplicateResourceTypeAssociation(Duplicate): + message = _("The metadata definition resource-type association of" + " resource-type=%(resource_type_name)s to" + " namespace=%(namespace_name)s" + " already exists.") + + +class MetadefForbidden(Forbidden): + message = _("You are not authorized to complete this action.") + + +class MetadefIntegrityError(Forbidden): + message = _("The metadata definition %(record_type)s with" + " name=%(record_name)s not deleted." + " Other records still refer to it.") + + +class MetadefNamespaceNotFound(NotFound): + message = _("Metadata definition namespace=%(namespace_name)s" + "was not found.") + + +class MetadefObjectNotFound(NotFound): + message = _("The metadata definition object with" + " name=%(object_name)s was not found in" + " namespace=%(namespace_name)s.") + + +class MetadefPropertyNotFound(NotFound): + message = _("The metadata definition property with" + " name=%(property_name)s was not found in" + " namespace=%(namespace_name)s.") + + +class MetadefResourceTypeNotFound(NotFound): + message = _("The metadata definition resource-type with" + " name=%(resource_type_name)s, was not found.") + + +class MetadefResourceTypeAssociationNotFound(NotFound): + message = _("The metadata definition resource-type association of" + " resource-type=%(resource_type_name)s to" + " namespace=%(namespace_name)s," + " was not found.") + + +class MetadefRecordNotFound(NotFound): + message = _("Metadata definition %(record_type)s record not found" + " for id %(id)s.") diff --git a/glance/db/registry/api.py b/glance/db/registry/api.py index 004cdcd43b..64351048b8 100644 --- a/glance/db/registry/api.py +++ b/glance/db/registry/api.py @@ -262,3 +262,214 @@ def task_get_all(client, filters=None, marker=None, limit=None, def task_create(client, values, session=None): """Create a task object""" return client.task_create(values=values) + + +# Metadef +@_get_client +def metadef_namespace_get_all( + client, marker=None, limit=None, sort_key='created_at', + sort_dir=None, filters=None, session=None): + return client.metadef_namespace_get_all( + marker=marker, limit=limit, + sort_key=sort_key, sort_dir=sort_dir, filters=filters) + + +@_get_client +def metadef_namespace_get(client, namespace_name, session=None): + return client.metadef_namespace_get(namespace_name=namespace_name) + + +@_get_client +def metadef_namespace_create(client, values, session=None): + return client.metadef_namespace_create(values=values) + + +@_get_client +def metadef_namespace_update( + client, namespace_id, namespace_dict, + session=None): + return client.metadef_namespace_update( + namespace_id=namespace_id, namespace_dict=namespace_dict) + + +@_get_client +def metadef_namespace_delete(client, namespace_name, session=None): + return client.metadef_namespace_delete( + namespace_name=namespace_name) + + +@_get_client +def metadef_object_get_all(client, namespace_name, session=None): + return client.metadef_object_get_all( + namespace_name=namespace_name) + + +@_get_client +def metadef_object_get( + client, + namespace_name, object_name, session=None): + return client.metadef_object_get( + namespace_name=namespace_name, object_name=object_name) + + +@_get_client +def metadef_object_create( + client, + namespace_name, object_dict, session=None): + return client.metadef_object_create( + namespace_name=namespace_name, object_dict=object_dict) + + +@_get_client +def metadef_object_update( + client, + namespace_name, object_id, + object_dict, session=None): + return client.metadef_object_update( + namespace_name=namespace_name, object_id=object_id, + object_dict=object_dict) + + +@_get_client +def metadef_object_delete( + client, + namespace_name, object_name, + session=None): + return client.metadef_object_delete( + namespace_name=namespace_name, object_name=object_name) + + +@_get_client +def metadef_object_delete_namespace_content( + client, + namespace_name, session=None): + return client.metadef_object_delete_namespace_content( + namespace_name=namespace_name) + + +@_get_client +def metadef_object_count( + client, + namespace_name, session=None): + return client.metadef_object_count( + namespace_name=namespace_name) + + +@_get_client +def metadef_property_get_all( + client, + namespace_name, session=None): + return client.metadef_property_get_all( + namespace_name=namespace_name) + + +@_get_client +def metadef_property_get( + client, + namespace_name, property_name, + session=None): + return client.metadef_property_get( + namespace_name=namespace_name, property_name=property_name) + + +@_get_client +def metadef_property_create( + client, + namespace_name, property_dict, + session=None): + return client.metadef_property_create( + namespace_name=namespace_name, property_dict=property_dict) + + +@_get_client +def metadef_property_update( + client, + namespace_name, property_id, + property_dict, session=None): + return client.metadef_property_update( + namespace_name=namespace_name, property_id=property_id, + property_dict=property_dict) + + +@_get_client +def metadef_property_delete( + client, + namespace_name, property_name, + session=None): + return client.metadef_property_delete( + namespace_name=namespace_name, property_name=property_name) + + +@_get_client +def metadef_property_delete_namespace_content( + client, + namespace_name, session=None): + return client.metadef_property_delete_namespace_content( + namespace_name=namespace_name) + + +@_get_client +def metadef_property_count( + client, + namespace_name, session=None): + return client.metadef_property_count( + namespace_name=namespace_name) + + +@_get_client +def metadef_resource_type_create(client, values, session=None): + return client.metadef_resource_type_create(values=values) + + +@_get_client +def metadef_resource_type_get( + client, + resource_type_name, session=None): + return client.metadef_resource_type_get( + resource_type_name=resource_type_name) + + +@_get_client +def metadef_resource_type_get_all(client, session=None): + return client.metadef_resource_type_get_all() + + +@_get_client +def metadef_resource_type_delete( + client, + resource_type_name, session=None): + return client.metadef_resource_type_delete( + resource_type_name=resource_type_name) + + +@_get_client +def metadef_resource_type_association_get( + client, + namespace_name, resource_type_name, + session=None): + return client.metadef_resource_type_association_get( + namespace_name=namespace_name, resource_type_name=resource_type_name) + + +@_get_client +def metadef_resource_type_association_create( + client, + namespace_name, values, session=None): + return client.metadef_resource_type_association_create( + namespace_name=namespace_name, values=values) + + +@_get_client +def metadef_resource_type_association_delete( + client, + namespace_name, resource_type_name, session=None): + return client.metadef_resource_type_association_delete( + namespace_name=namespace_name, resource_type_name=resource_type_name) + + +@_get_client +def metadef_resource_type_association_get_all_by_namespace( + client, + namespace_name, session=None): + return client.metadef_resource_type_association_get_all_by_namespace( + namespace_name=namespace_name) diff --git a/glance/db/simple/api.py b/glance/db/simple/api.py index f5a985e6bd..63c379c478 100644 --- a/glance/db/simple/api.py +++ b/glance/db/simple/api.py @@ -30,12 +30,19 @@ LOG = logging.getLogger(__name__) DATA = { 'images': {}, 'members': {}, + 'metadef_namespace_resource_types': [], + 'metadef_namespaces': [], + 'metadef_objects': [], + 'metadef_properties': [], + 'metadef_resource_types': [], 'tags': {}, 'locations': [], 'tasks': {}, 'task_info': {} } +INDEX = 0 + def log_call(func): @functools.wraps(func) @@ -57,6 +64,11 @@ def reset(): DATA = { 'images': {}, 'members': [], + 'metadef_namespace_resource_types': [], + 'metadef_namespaces': [], + 'metadef_objects': [], + 'metadef_properties': [], + 'metadef_resource_types': [], 'tags': {}, 'locations': [], 'tasks': {}, @@ -1017,3 +1029,737 @@ def _task_info_get(task_id): raise exception.TaskNotFound(task_id=task_id) return task_info + + +@log_call +def metadef_namespace_create(context, values): + """Create a namespace object""" + global DATA + + namespace_values = copy.deepcopy(values) + namespace_name = namespace_values.get('namespace') + required_attributes = ['namespace', 'owner'] + allowed_attributes = ['namespace', 'owner', 'display_name', 'description', + 'visibility', 'protected'] + + for namespace in DATA['metadef_namespaces']: + if namespace['namespace'] == namespace_name: + msg = ("Can not create the metadata definition namespace. " + "Namespace=%s already exists.") % namespace_name + LOG.debug(msg) + raise exception.MetadefDuplicateNamespace( + namespace_name=namespace_name) + + for key in required_attributes: + if key not in namespace_values: + raise exception.Invalid('%s is a required attribute' % key) + + incorrect_keys = set(namespace_values.keys()) - set(allowed_attributes) + if incorrect_keys: + raise exception.Invalid( + 'The keys %s are not valid' % str(incorrect_keys)) + + namespace = _format_namespace(namespace_values) + DATA['metadef_namespaces'].append(namespace) + + return namespace + + +@log_call +def metadef_namespace_update(context, namespace_id, values): + """Update a namespace object""" + global DATA + namespace_values = copy.deepcopy(values) + + namespace = metadef_namespace_get_by_id(context, namespace_id) + if namespace['namespace'] != values['namespace']: + for db_namespace in DATA['metadef_namespaces']: + if db_namespace['namespace'] == values['namespace']: + msg = ("Invalid update. It would result in a duplicate" + " metadata definition namespace with the same" + " name of %s" + % values['namespace']) + LOG.debug(msg) + emsg = (_("Invalid update. It would result in a duplicate" + " metadata definition namespace with the same" + " name of %s") + % values['namespace']) + raise exception.MetadefDuplicateNamespace(emsg) + DATA['metadef_namespaces'].remove(namespace) + + namespace.update(namespace_values) + namespace['updated_at'] = timeutils.utcnow() + DATA['metadef_namespaces'].append(namespace) + + return namespace + + +@log_call +def metadef_namespace_get_by_id(context, namespace_id): + """Get a namespace object""" + try: + namespace = next(namespace for namespace in DATA['metadef_namespaces'] + if namespace['id'] == namespace_id) + except StopIteration: + msg = "No namespace found with id %s" % namespace_id + LOG.debug(msg) + raise exception.MetadefRecordNotFound( + record_type='namespace', id=namespace_id) + + if not _is_namespace_visible(context, namespace): + msg = ("Forbidding request, metadata definition namespace=%s" + " is not visible.") % namespace.namespace + LOG.debug(msg) + emsg = _("Forbidding request, metadata definition namespace=%s" + " is not visible.") % namespace.namespace + raise exception.MetadefForbidden(emsg) + + return namespace + + +@log_call +def metadef_namespace_get(context, namespace_name): + """Get a namespace object""" + try: + namespace = next(namespace for namespace in DATA['metadef_namespaces'] + if namespace['namespace'] == namespace_name) + except StopIteration: + msg = "No namespace found with name %s" % namespace_name + LOG.debug(msg) + raise exception.MetadefNamespaceNotFound( + namespace_name=namespace_name) + + _check_namespace_visibility(context, namespace, namespace_name) + + return namespace + + +@log_call +def metadef_namespace_get_all(context, + marker=None, + limit=None, + sort_key='created_at', + sort_dir='desc', + filters=None): + """Get a namespaces list""" + resource_types = filters.get('resource_types', []) if filters else [] + visibility = filters.get('visibility', None) if filters else None + + namespaces = [] + for namespace in DATA['metadef_namespaces']: + if not _is_namespace_visible(context, namespace): + continue + + if visibility and namespace['visibility'] != visibility: + continue + + if resource_types: + for association in DATA['metadef_namespace_resource_types']: + if association['namespace_id'] == namespace['id']: + if association['name'] in resource_types: + break + else: + continue + + namespaces.append(namespace) + + return namespaces + + +@log_call +def metadef_namespace_delete(context, namespace_name): + """Delete a namespace object""" + global DATA + + namespace = metadef_namespace_get(context, namespace_name) + DATA['metadef_namespaces'].remove(namespace) + + return namespace + + +@log_call +def metadef_namespace_delete_content(context, namespace_name): + """Delete a namespace content""" + global DATA + namespace = metadef_namespace_get(context, namespace_name) + namespace_id = namespace['id'] + + objects = [] + + for object in DATA['metadef_objects']: + if object['namespace_id'] != namespace_id: + objects.append(object) + + DATA['metadef_objects'] = objects + + properties = [] + + for property in DATA['metadef_objects']: + if property['namespace_id'] != namespace_id: + properties.append(object) + + DATA['metadef_objects'] = properties + + return namespace + + +@log_call +def metadef_object_get(context, namespace_name, object_name): + """Get a metadef object""" + namespace = metadef_namespace_get(context, namespace_name) + + _check_namespace_visibility(context, namespace, namespace_name) + + for object in DATA['metadef_objects']: + if (object['namespace_id'] == namespace['id'] and + object['name'] == object_name): + return object + else: + msg = ("The metadata definition object with name=%(name)s" + " was not found in namespace=%(namespace_name)s." + % {'name': object_name, 'namespace_name': namespace_name}) + LOG.debug(msg) + raise exception.MetadefObjectNotFound(namespace_name=namespace_name, + object_name=object_name) + + +@log_call +def metadef_object_get_by_id(context, namespace_name, object_id): + """Get a metadef object""" + namespace = metadef_namespace_get(context, namespace_name) + + _check_namespace_visibility(context, namespace, namespace_name) + + for object in DATA['metadef_objects']: + if (object['namespace_id'] == namespace['id'] and + object['id'] == object_id): + return object + else: + msg = ("No metadata definition object found with id %s" + % object_id) + LOG.debug(msg) + raise exception.MetadefRecordNotFound(record_type='object', + id=object_id) + + +@log_call +def metadef_object_get_all(context, namespace_name): + """Get a metadef objects list""" + namespace = metadef_namespace_get(context, namespace_name) + + objects = [] + + _check_namespace_visibility(context, namespace, namespace_name) + + for object in DATA['metadef_objects']: + if object['namespace_id'] == namespace['id']: + objects.append(object) + + return objects + + +@log_call +def metadef_object_create(context, namespace_name, values): + """Create a metadef object""" + global DATA + + object_values = copy.deepcopy(values) + object_name = object_values['name'] + required_attributes = ['name'] + allowed_attributes = ['name', 'description', 'schema', 'required'] + + namespace = metadef_namespace_get(context, namespace_name) + + for object in DATA['metadef_objects']: + if (object['name'] == object_name and + object['namespace_id'] == namespace['id']): + msg = ("A metadata definition object with name=%(name)s" + " in namespace=%(namespace_name)s already exists." + % {'name': object_name, 'namespace_name': namespace_name}) + LOG.debug(msg) + raise exception.MetadefDuplicateObject( + object_name=object_name, namespace_name=namespace_name) + + for key in required_attributes: + if key not in object_values: + raise exception.Invalid('%s is a required attribute' % key) + + incorrect_keys = set(object_values.keys()) - set(allowed_attributes) + if incorrect_keys: + raise exception.Invalid( + 'The keys %s are not valid' % str(incorrect_keys)) + + object_values['namespace_id'] = namespace['id'] + + _check_namespace_visibility(context, namespace, namespace_name) + + object = _format_object(object_values) + DATA['metadef_objects'].append(object) + + return object + + +@log_call +def metadef_object_update(context, namespace_name, object_id, values): + """Update a metadef object""" + global DATA + + namespace = metadef_namespace_get(context, namespace_name) + + _check_namespace_visibility(context, namespace, namespace_name) + + object = metadef_object_get_by_id(context, namespace_name, object_id) + if object['name'] != values['name']: + for db_object in DATA['metadef_objects']: + if (db_object['name'] == values['name'] and + db_object['namespace_id'] == namespace['id']): + msg = ("Invalid update. It would result in a duplicate" + " metadata definition object with same name=%(name)s " + " in namespace=%(namespace_name)s." + % {'name': object['name'], + 'namespace_name': namespace_name}) + LOG.debug(msg) + emsg = (_("Invalid update. It would result in a duplicate" + " metadata definition object with the same" + " name=%(name)s " + " in namespace=%(namespace_name)s.") + % {'name': object['name'], + 'namespace_name': namespace_name}) + raise exception.MetadefDuplicateObject(emsg) + DATA['metadef_objects'].remove(object) + + object.update(values) + object['updated_at'] = timeutils.utcnow() + DATA['metadef_objects'].append(object) + + return object + + +@log_call +def metadef_object_delete(context, namespace_name, object_name): + """Delete a metadef object""" + global DATA + + object = metadef_object_get(context, namespace_name, object_name) + DATA['metadef_objects'].remove(object) + + return object + + +@log_call +def metadef_object_count(context, namespace_name): + """Get metadef object count in a namespace""" + namespace = metadef_namespace_get(context, namespace_name) + + _check_namespace_visibility(context, namespace, namespace_name) + + count = 0 + for object in DATA['metadef_objects']: + if object['namespace_id'] == namespace['id']: + count = count + 1 + + return count + + +@log_call +def metadef_property_count(context, namespace_name): + """Get properties count in a namespace""" + namespace = metadef_namespace_get(context, namespace_name) + + _check_namespace_visibility(context, namespace, namespace_name) + + count = 0 + for property in DATA['metadef_properties']: + if property['namespace_id'] == namespace['id']: + count = count + 1 + + return count + + +@log_call +def metadef_property_create(context, namespace_name, values): + """Create a metadef property""" + global DATA + + property_values = copy.deepcopy(values) + property_name = property_values['name'] + required_attributes = ['name'] + allowed_attributes = ['name', 'description', 'schema', 'required'] + + namespace = metadef_namespace_get(context, namespace_name) + + for property in DATA['metadef_properties']: + if (property['name'] == property_name and + property['namespace_id'] == namespace['id']): + msg = ("Can not create metadata definition property. A property" + " with name=%(name)s already exists in" + " namespace=%(namespace_name)s." + % {'name': property_name, + 'namespace_name': namespace_name}) + LOG.debug(msg) + raise exception.MetadefDuplicateProperty( + property_name=property_name, + namespace_name=namespace_name) + + for key in required_attributes: + if key not in property_values: + raise exception.Invalid('%s is a required attribute' % key) + + incorrect_keys = set(property_values.keys()) - set(allowed_attributes) + if incorrect_keys: + raise exception.Invalid( + 'The keys %s are not valid' % str(incorrect_keys)) + + property_values['namespace_id'] = namespace['id'] + + _check_namespace_visibility(context, namespace, namespace_name) + + property = _format_property(property_values) + DATA['metadef_properties'].append(property) + + return property + + +@log_call +def metadef_property_update(context, namespace_name, property_id, values): + """Update a metadef property""" + global DATA + + namespace = metadef_namespace_get(context, namespace_name) + + _check_namespace_visibility(context, namespace, namespace_name) + + property = metadef_property_get_by_id(context, namespace_name, property_id) + if property['name'] != values['name']: + for db_property in DATA['metadef_properties']: + if (db_property['name'] == values['name'] and + db_property['namespace_id'] == namespace['id']): + msg = ("Invalid update. It would result in a duplicate" + " metadata definition property with the same" + " name=%(name)s" + " in namespace=%(namespace_name)s." + % {'name': property['name'], + 'namespace_name': namespace_name}) + LOG.debug(msg) + emsg = (_("Invalid update. It would result in a duplicate" + " metadata definition property with the same" + " name=%(name)s" + " in namespace=%(namespace_name)s.") + % {'name': property['name'], + 'namespace_name': namespace_name}) + raise exception.MetadefDuplicateProperty(emsg) + DATA['metadef_properties'].remove(property) + + property.update(values) + property['updated_at'] = timeutils.utcnow() + DATA['metadef_properties'].append(property) + + return property + + +@log_call +def metadef_property_get_all(context, namespace_name): + """Get a metadef properties list""" + namespace = metadef_namespace_get(context, namespace_name) + + properties = [] + + _check_namespace_visibility(context, namespace, namespace_name) + + for property in DATA['metadef_properties']: + if property['namespace_id'] == namespace['id']: + properties.append(property) + + return properties + + +@log_call +def metadef_property_get_by_id(context, namespace_name, property_id): + """Get a metadef property""" + namespace = metadef_namespace_get(context, namespace_name) + + _check_namespace_visibility(context, namespace, namespace_name) + + for property in DATA['metadef_properties']: + if (property['namespace_id'] == namespace['id'] and + property['id'] == property_id): + return property + else: + msg = ("No metadata definition property found with id=%s" + % property_id) + LOG.debug(msg) + raise exception.MetadefRecordNotFound(record_type='property', + id=property_id) + + +@log_call +def metadef_property_get(context, namespace_name, property_name): + """Get a metadef property""" + namespace = metadef_namespace_get(context, namespace_name) + + _check_namespace_visibility(context, namespace, namespace_name) + + for property in DATA['metadef_properties']: + if (property['namespace_id'] == namespace['id'] and + property['name'] == property_name): + return property + else: + msg = ("No property found with name=%(name)s in" + " namespace=%(namespace_name)s " + % {'name': property_name, 'namespace_name': namespace_name}) + LOG.debug(msg) + raise exception.MetadefPropertyNotFound(namespace_name=namespace_name, + property_name=property_name) + + +@log_call +def metadef_property_delete(context, namespace_name, property_name): + """Delete a metadef property""" + global DATA + + property = metadef_property_get(context, namespace_name, property_name) + DATA['metadef_properties'].remove(property) + + return property + + +@log_call +def metadef_resource_type_create(context, values): + """Create a metadef resource type""" + global DATA + + resource_type_values = copy.deepcopy(values) + resource_type_name = resource_type_values['name'] + + allowed_attrubites = ['name', 'protected'] + + for resource_type in DATA['metadef_resource_types']: + if resource_type['name'] == resource_type_name: + raise exception.Duplicate() + + incorrect_keys = set(resource_type_values.keys()) - set(allowed_attrubites) + if incorrect_keys: + raise exception.Invalid( + 'The keys %s are not valid' % str(incorrect_keys)) + + resource_type = _format_resource_type(resource_type_values) + DATA['metadef_resource_types'].append(resource_type) + + return resource_type + + +@log_call +def metadef_resource_type_get_all(context): + """List all resource types""" + return DATA['metadef_resource_types'] + + +@log_call +def metadef_resource_type_get(context, resource_type_name): + """Get a resource type""" + try: + resource_type = next(resource_type for resource_type in + DATA['metadef_resource_types'] + if resource_type['name'] == + resource_type_name) + except StopIteration: + msg = "No resource type found with name %s" % resource_type_name + LOG.debug(msg) + raise exception.MetadefResourceTypeNotFound( + resource_type_name=resource_type_name) + + return resource_type + + +@log_call +def metadef_resource_type_association_create(context, namespace_name, + values): + global DATA + + association_values = copy.deepcopy(values) + + namespace = metadef_namespace_get(context, namespace_name) + resource_type_name = association_values['name'] + resource_type = metadef_resource_type_get(context, + resource_type_name) + + required_attributes = ['name', 'properties_target', 'prefix'] + allowed_attributes = copy.deepcopy(required_attributes) + + for association in DATA['metadef_namespace_resource_types']: + if (association['namespace_id'] == namespace['id'] and + association['resource_type'] == resource_type['id']): + msg = ("The metadata definition resource-type association of" + " resource_type=%(resource_type_name)s to" + " namespace=%(namespace_name)s, already exists." + % {'resource_type_name': resource_type_name, + 'namespace_name': namespace_name}) + LOG.debug(msg) + raise exception.MetadefDuplicateResourceTypeAssociation( + resource_type_name=resource_type_name, + namespace_name=namespace_name) + + for key in required_attributes: + if key not in association_values: + raise exception.Invalid('%s is a required attribute' % key) + + incorrect_keys = set(association_values.keys()) - set(allowed_attributes) + if incorrect_keys: + raise exception.Invalid( + 'The keys %s are not valid' % str(incorrect_keys)) + + association = _format_association(namespace, resource_type, + association_values) + DATA['metadef_namespace_resource_types'].append(association) + + return association + + +@log_call +def metadef_resource_type_association_get(context, namespace_name, + resource_type_name): + namespace = metadef_namespace_get(context, namespace_name) + resource_type = metadef_resource_type_get(context, resource_type_name) + + for association in DATA['metadef_namespace_resource_types']: + if (association['namespace_id'] == namespace['id'] and + association['resource_type'] == resource_type['id']): + return association + else: + msg = ("No resource type association found associated with namespace " + "%s and resource type %s" % namespace_name, resource_type_name) + LOG.debug(msg) + raise exception.MetadefResourceTypeAssociationNotFound( + resource_type_name=resource_type_name, + namespace_name=namespace_name) + + +@log_call +def metadef_resource_type_association_get_all_by_namespace(context, + namespace_name): + namespace = metadef_namespace_get(context, namespace_name) + + namespace_resource_types = [] + for resource_type in DATA['metadef_namespace_resource_types']: + if resource_type['namespace_id'] == namespace['id']: + namespace_resource_types.append(resource_type) + + return namespace_resource_types + + +@log_call +def metadef_resource_type_association_delete(context, namespace_name, + resource_type_name): + global DATA + + resource_type = metadef_resource_type_association_get(context, + namespace_name, + resource_type_name) + DATA['metadef_namespace_resource_types'].remove(resource_type) + + return resource_type + + +def _format_association(namespace, resource_type, association_values): + association = { + 'namespace_id': namespace['id'], + 'resource_type': resource_type['id'], + 'properties_target': None, + 'prefix': None, + 'created_at': timeutils.utcnow(), + 'updated_at': timeutils.utcnow() + + } + association.update(association_values) + return association + + +def _format_resource_type(values): + dt = timeutils.utcnow() + resource_type = { + 'id': _get_metadef_id(), + 'name': values['name'], + 'protected': True, + 'created_at': dt, + 'updated_at': dt + } + resource_type.update(values) + return resource_type + + +def _format_property(values): + property = { + 'id': _get_metadef_id(), + 'namespace_id': None, + 'name': None, + 'schema': None + } + property.update(values) + return property + + +def _format_namespace(values): + dt = timeutils.utcnow() + namespace = { + 'id': _get_metadef_id(), + 'namespace': None, + 'display_name': None, + 'description': None, + 'visibility': 'private', + 'protected': False, + 'owner': None, + 'created_at': dt, + 'updated_at': dt + } + namespace.update(values) + return namespace + + +def _format_object(values): + dt = timeutils.utcnow() + object = { + 'id': _get_metadef_id(), + 'namespace_id': None, + 'name': None, + 'description': None, + 'schema': None, + 'required': None, + 'created_at': dt, + 'updated_at': dt + } + object.update(values) + return object + + +def _is_namespace_visible(context, namespace): + """Return true if namespace is visible in this context""" + if context.is_admin: + return True + + if namespace.get('visibility', '') == 'public': + return True + + if namespace['owner'] is None: + return True + + if context.owner is not None: + if context.owner == namespace['owner']: + return True + + return False + + +def _check_namespace_visibility(context, namespace, namespace_name): + if not _is_namespace_visible(context, namespace): + msg = ("Forbidding request, metadata definition namespace=%s" + " not visible." % namespace_name) + LOG.debug(msg) + emsg = _("Forbidding request, metadata definition namespace=%s" + " not visible.") % namespace_name + raise exception.MetadefForbidden(emsg) + + +def _get_metadef_id(): + global INDEX + INDEX += 1 + return INDEX diff --git a/glance/db/sqlalchemy/api.py b/glance/db/sqlalchemy/api.py index 662310f384..1f38f6d100 100644 --- a/glance/db/sqlalchemy/api.py +++ b/glance/db/sqlalchemy/api.py @@ -39,6 +39,13 @@ from glance import i18n import glance.openstack.common.log as os_logging from glance.openstack.common import timeutils +from glance.db.sqlalchemy.metadef_api import namespace as metadef_namespace_api +from glance.db.sqlalchemy.metadef_api import object as metadef_object_api +from glance.db.sqlalchemy.metadef_api import property as metadef_property_api +from glance.db.sqlalchemy.metadef_api\ + import resource_type as metadef_resource_type_api +from glance.db.sqlalchemy.metadef_api\ + import resource_type_association as metadef_association_api BASE = models.BASE sa_logger = None @@ -1426,3 +1433,199 @@ def _task_format(task_ref, task_info_ref=None): task_dict.update(task_info_dict) return task_dict + + +def metadef_namespace_get_all(context, marker=None, limit=None, sort_key=None, + sort_dir=None, filters=None, session=None): + """List all available namespaces.""" + session = session or get_session() + namespaces = metadef_namespace_api.get_all( + context, session, marker, limit, sort_key, sort_dir, filters) + return namespaces + + +def metadef_namespace_get(context, namespace_name, session=None): + """Get a namespace or raise if it does not exist or is not visible.""" + session = session or get_session() + return metadef_namespace_api.get( + context, namespace_name, session) + + +def metadef_namespace_create(context, values, session=None): + """Create a namespace or raise if it already exists.""" + session = session or get_session() + return metadef_namespace_api.create(context, values, session) + + +def metadef_namespace_update(context, namespace_id, namespace_dict, + session=None): + """Update a namespace or raise if it does not exist or not visible""" + session = session or get_session() + return metadef_namespace_api.\ + update(context, namespace_id, namespace_dict, session) + + +def metadef_namespace_delete(context, namespace_name, session=None): + """Delete the namespace and all foreign references""" + session = session or get_session() + return metadef_namespace_api.delete_cascade( + context, namespace_name, session) + + +def metadef_object_get_all(context, namespace_name, session=None): + """Get a metadata-schema object or raise if it does not exist.""" + session = session or get_session() + return metadef_object_api.get_all( + context, namespace_name, session) + + +def metadef_object_get(context, namespace_name, object_name, session=None): + """Get a metadata-schema object or raise if it does not exist.""" + session = session or get_session() + return metadef_object_api.get( + context, namespace_name, object_name, session) + + +def metadef_object_create(context, namespace_name, object_dict, + session=None): + """Create a metadata-schema object or raise if it already exists.""" + session = session or get_session() + return metadef_object_api.create( + context, namespace_name, object_dict, session) + + +def metadef_object_update(context, namespace_name, object_id, object_dict, + session=None): + """Update an object or raise if it does not exist or not visible.""" + session = session or get_session() + return metadef_object_api.update( + context, namespace_name, object_id, object_dict, session) + + +def metadef_object_delete(context, namespace_name, object_name, + session=None): + """Delete an object or raise if namespace or object doesn't exist.""" + session = session or get_session() + return metadef_object_api.delete( + context, namespace_name, object_name, session) + + +def metadef_object_delete_namespace_content( + context, namespace_name, session=None): + """Delete an object or raise if namespace or object doesn't exist.""" + session = session or get_session() + return metadef_object_api.delete_by_namespace_name( + context, namespace_name, session) + + +def metadef_object_count(context, namespace_name, session=None): + """Get count of properties for a namespace, raise if ns doesn't exist.""" + session = session or get_session() + return metadef_object_api.count(context, namespace_name, session) + + +def metadef_property_get_all(context, namespace_name, session=None): + """Get a metadef property or raise if it does not exist.""" + session = session or get_session() + return metadef_property_api.get_all(context, namespace_name, session) + + +def metadef_property_get(context, namespace_name, + property_name, session=None): + """Get a metadef property or raise if it does not exist.""" + session = session or get_session() + return metadef_property_api.get( + context, namespace_name, property_name, session) + + +def metadef_property_create(context, namespace_name, property_dict, + session=None): + """Create a metadef property or raise if it already exists.""" + session = session or get_session() + return metadef_property_api.create( + context, namespace_name, property_dict, session) + + +def metadef_property_update(context, namespace_name, property_id, + property_dict, session=None): + """Update an object or raise if it does not exist or not visible.""" + session = session or get_session() + return metadef_property_api.update( + context, namespace_name, property_id, property_dict, session) + + +def metadef_property_delete(context, namespace_name, property_name, + session=None): + """Delete a property or raise if it or namespace doesn't exist.""" + session = session or get_session() + return metadef_property_api.delete( + context, namespace_name, property_name, session) + + +def metadef_property_delete_namespace_content( + context, namespace_name, session=None): + """Delete a property or raise if it or namespace doesn't exist.""" + session = session or get_session() + return metadef_property_api.delete_by_namespace_name( + context, namespace_name, session) + + +def metadef_property_count(context, namespace_name, session=None): + """Get count of properties for a namespace, raise if ns doesn't exist.""" + session = session or get_session() + return metadef_property_api.count(context, namespace_name, session) + + +def metadef_resource_type_create(context, values, session=None): + """Create a resource_type""" + session = session or get_session() + return metadef_resource_type_api.create( + context, values, session) + + +def metadef_resource_type_get(context, resource_type_name, session=None): + """Get a resource_type""" + session = session or get_session() + return metadef_resource_type_api.get( + context, resource_type_name, session) + + +def metadef_resource_type_get_all(context, session=None): + """list all resource_types""" + session = session or get_session() + return metadef_resource_type_api.get_all(context, session) + + +def metadef_resource_type_delete(context, resource_type_name, session=None): + """Get a resource_type""" + session = session or get_session() + return metadef_resource_type_api.delete( + context, resource_type_name, session) + + +def metadef_resource_type_association_get( + context, namespace_name, resource_type_name, session=None): + session = session or get_session() + return metadef_association_api.get( + context, namespace_name, resource_type_name, session) + + +def metadef_resource_type_association_create( + context, namespace_name, values, session=None): + session = session or get_session() + return metadef_association_api.create( + context, namespace_name, values, session) + + +def metadef_resource_type_association_delete( + context, namespace_name, resource_type_name, session=None): + session = session or get_session() + return metadef_association_api.delete( + context, namespace_name, resource_type_name, session) + + +def metadef_resource_type_association_get_all_by_namespace( + context, namespace_name, session=None): + session = session or get_session() + return metadef_association_api.\ + get_all_by_namespace(context, namespace_name, session) diff --git a/glance/db/sqlalchemy/metadef_api/__init__.py b/glance/db/sqlalchemy/metadef_api/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/glance/db/sqlalchemy/metadef_api/namespace.py b/glance/db/sqlalchemy/metadef_api/namespace.py new file mode 100644 index 0000000000..2b76d238f8 --- /dev/null +++ b/glance/db/sqlalchemy/metadef_api/namespace.py @@ -0,0 +1,307 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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 oslo.db import exception as db_exc +from oslo.db.sqlalchemy.utils import paginate_query +import sqlalchemy.exc as sa_exc +from sqlalchemy import or_ +import sqlalchemy.orm as sa_orm + +from glance.common import exception as exc +import glance.db.sqlalchemy.metadef_api as metadef_api +from glance.db.sqlalchemy import models_metadef as models +from glance import i18n +import glance.openstack.common.log as os_logging + +LOG = os_logging.getLogger(__name__) +_LW = i18n._LW + + +def _is_namespace_visible(context, namespace, status=None): + """Return True if the namespace is visible in this context.""" + + # Is admin == visible + if context.is_admin: + return True + + # No owner == visible + if namespace['owner'] is None: + return True + + # Is public == visible + if 'visibility' in namespace: + if namespace['visibility'] == 'public': + return True + + # context.owner has a value and is the namespace owner == visible + if context.owner is not None: + if context.owner == namespace['owner']: + return True + + # Private + return False + + +def _select_namespaces_query(context, session): + """Build the query to get all namespaces based on the context""" + + LOG.debug("context.is_admin=%(is_admin)s; context.owner=%(owner)s" % + {'is_admin': context.is_admin, 'owner': context.owner}) + + # If admin, return everything. + query_ns = session.query(models.MetadefNamespace) + if context.is_admin: + return query_ns + else: + # If regular user, return only public namespaces. + # However, if context.owner has a value, return both + # public and private namespaces of the context.owner. + if context.owner is not None: + query = ( + query_ns.filter( + or_(models.MetadefNamespace.owner == context.owner, + models.MetadefNamespace.visibility == 'public'))) + else: + query = query_ns.filter( + models.MetadefNamespace.visibility == 'public') + return query + + +def _get(context, namespace_id, session): + """Get a namespace by id, raise if not found""" + + try: + query = session.query(models.MetadefNamespace)\ + .filter_by(id=namespace_id) + namespace_rec = query.one() + except sa_orm.exc.NoResultFound: + LOG.warn(_LW("Metadata definition namespace not found for id=%s", + namespace_id)) + raise exc.MetadefRecordNotFound(record_type='namespace', + id=namespace_id) + + # Make sure they are allowed to view it. + if not _is_namespace_visible(context, namespace_rec.as_dict()): + msg = ("Forbidding request, metadata definition namespace=%s" + " is not visible.") % namespace_rec.namespace + LOG.debug(msg) + emsg = _("Forbidding request, metadata definition namespace=%s" + " is not visible.") % namespace_rec.namespace + raise exc.MetadefForbidden(emsg) + + return namespace_rec + + +def _get_by_name(context, name, session): + """Get a namespace by name, raise if not found""" + + try: + query = session.query(models.MetadefNamespace)\ + .filter_by(namespace=name) + namespace_rec = query.one() + except sa_orm.exc.NoResultFound: + msg = "Metadata definition namespace=%s was not found." % name + LOG.debug(msg) + raise exc.MetadefNamespaceNotFound(namespace_name=name) + + # Make sure they are allowed to view it. + if not _is_namespace_visible(context, namespace_rec.as_dict()): + msg = ("Forbidding request, metadata definition namespace=%s" + " not visible." % name) + LOG.debug(msg) + emsg = _("Forbidding request, metadata definition namespace=%s" + " not visible.") % name + raise exc.MetadefForbidden(emsg) + + return namespace_rec + + +def _get_all(context, session, filters=None, marker=None, + limit=None, sort_key='created_at', sort_dir='desc'): + """Get all namespaces that match zero or more filters. + + :param filters: dict of filter keys and values. + :param marker: namespace id after which to start page + :param limit: maximum number of namespaces to return + :param sort_key: namespace attribute by which results should be sorted + :param sort_dir: direction in which results should be sorted (asc, desc) + """ + + filters = filters or {} + + query = _select_namespaces_query(context, session) + + # if visibility filter, apply it to the context based query + visibility = filters.pop('visibility', None) + if visibility is not None: + query = query.filter(models.MetadefNamespace.visibility == visibility) + + # if id_list filter, apply it to the context based query + id_list = filters.pop('id_list', None) + if id_list is not None: + query = query.filter(models.MetadefNamespace.id.in_(id_list)) + + marker_namespace = None + if marker is not None: + marker_namespace = _get(context, marker, session) + + sort_keys = ['created_at', 'id'] + sort_keys.insert(0, sort_key) if sort_key not in sort_keys else sort_keys + + query = paginate_query(query=query, + model=models.MetadefNamespace, + limit=limit, + sort_keys=sort_keys, + marker=marker_namespace, sort_dir=sort_dir) + + return query.all() + + +def _get_all_by_resource_types(context, session, filters, marker=None, + limit=None, sort_key=None, sort_dir=None): + """get all visible namespaces for the specified resource_types""" + + resource_types = filters['resource_types'] + resource_type_list = resource_types.split(',') + db_recs = ( + session.query(models.MetadefResourceType) + .join(models.MetadefResourceType.associations) + .filter(models.MetadefResourceType.name.in_(resource_type_list)) + .values(models.MetadefResourceType.name, + models.MetadefNamespaceResourceType.namespace_id) + ) + + namespace_id_list = [] + for name, namespace_id in db_recs: + namespace_id_list.append(namespace_id) + + if len(namespace_id_list) is 0: + return [] + + filters2 = filters + filters2.update({'id_list': namespace_id_list}) + + return _get_all(context, session, filters2, + marker, limit, sort_key, sort_dir) + + +def get_all(context, session, marker=None, limit=None, + sort_key=None, sort_dir=None, filters=None): + """List all visible namespaces""" + + namespaces = [] + filters = filters or {} + + if 'resource_types' in filters: + namespaces = _get_all_by_resource_types( + context, session, filters, marker, limit, sort_key, sort_dir) + else: + namespaces = _get_all( + context, session, filters, marker, limit, sort_key, sort_dir) + + return map(lambda ns: ns.as_dict(), namespaces) + + +def get(context, name, session): + """Get a namespace by name, raise if not found""" + namespace_rec = _get_by_name(context, name, session) + return namespace_rec.as_dict() + + +def create(context, values, session): + """Create a namespace, raise if namespace already exists.""" + + namespace_name = values['namespace'] + namespace = models.MetadefNamespace() + metadef_api.utils.drop_protected_attrs(models.MetadefNamespace, values) + namespace.update(values.copy()) + try: + namespace.save(session=session) + except db_exc.DBDuplicateEntry: + msg = ("Can not create the metadata definition namespace." + " Namespace=%s already exists.") % namespace_name + LOG.debug(msg) + raise exc.MetadefDuplicateNamespace( + namespace_name=namespace_name) + + return namespace.as_dict() + + +def update(context, namespace_id, values, session): + """Update a namespace, raise if not found/visible or duplicate result""" + + namespace_rec = _get(context, namespace_id, session) + metadef_api.utils.drop_protected_attrs(models.MetadefNamespace, values) + + try: + namespace_rec.update(values.copy()) + namespace_rec.save(session=session) + except db_exc.DBDuplicateEntry: + msg = ("Invalid update. It would result in a duplicate" + " metadata definition namespace with the same name of %s" + % values['namespace']) + LOG.debug(msg) + emsg = (_("Invalid update. It would result in a duplicate" + " metadata definition namespace with the same name of %s") + % values['namespace']) + raise exc.MetadefDuplicateNamespace(emsg) + + return namespace_rec.as_dict() + + +def delete(context, name, session): + """Raise if not found, has references or not visible""" + + namespace_rec = _get_by_name(context, name, session) + try: + session.delete(namespace_rec) + session.flush() + except db_exc.DBError as e: + if isinstance(e.inner_exception, sa_exc.IntegrityError): + msg = ("Metadata definition namespace=%s not deleted." + " Other records still refer to it." % name) + LOG.debug(msg) + raise exc.MetadefIntegrityError( + record_type='namespace', record_name=name) + else: + raise e + + return namespace_rec.as_dict() + + +def delete_cascade(context, name, session): + """Raise if not found, has references or not visible""" + + namespace_rec = _get_by_name(context, name, session) + with session.begin(): + try: + metadef_api.object.delete_namespace_content( + context, namespace_rec.id, session) + metadef_api.property.delete_namespace_content( + context, namespace_rec.id, session) + metadef_api.resource_type_association.delete_namespace_content( + context, namespace_rec.id, session) + session.delete(namespace_rec) + session.flush() + except db_exc.DBError as e: + if isinstance(e.inner_exception, sa_exc.IntegrityError): + msg = ("Metadata definition namespace=%s not deleted." + " Other records still refer to it." % name) + LOG.debug(msg) + raise exc.MetadefIntegrityError( + record_type='namespace', record_name=name) + else: + raise e + + return namespace_rec.as_dict() diff --git a/glance/db/sqlalchemy/metadef_api/object.py b/glance/db/sqlalchemy/metadef_api/object.py new file mode 100644 index 0000000000..f3ddaa8c98 --- /dev/null +++ b/glance/db/sqlalchemy/metadef_api/object.py @@ -0,0 +1,156 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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 oslo.db import exception as db_exc +from sqlalchemy import func +import sqlalchemy.orm as sa_orm + +from glance.common import exception as exc +from glance.db.sqlalchemy.metadef_api import namespace as namespace_api +import glance.db.sqlalchemy.metadef_api.utils as metadef_utils +from glance.db.sqlalchemy import models_metadef as models +from glance import i18n +import glance.openstack.common.log as os_logging + +LOG = os_logging.getLogger(__name__) +_LW = i18n._LW + + +def _get(context, object_id, session): + try: + query = session.query(models.MetadefObject)\ + .filter_by(id=object_id) + metadef_object = query.one() + except sa_orm.exc.NoResultFound: + LOG.warn(_LW("Metadata definition object not found for id %s", + object_id)) + raise exc.MetadefRecordNotFound(record_type='object', id=object_id) + + return metadef_object + + +def _get_by_name(context, namespace_name, name, session): + namespace = namespace_api.get(context, namespace_name, session) + try: + query = session.query(models.MetadefObject)\ + .filter_by(name=name, namespace_id=namespace['id']) + metadef_object = query.one() + except sa_orm.exc.NoResultFound: + msg = ("The metadata definition object with name=%(name)s" + " was not found in namespace=%(namespace_name)s." + % {'name': name, 'namespace_name': namespace_name}) + LOG.debug(msg) + raise exc.MetadefObjectNotFound(object_name=name, + namespace_name=namespace_name) + + return metadef_object + + +def get_all(context, namespace_name, session): + namespace = namespace_api.get(context, namespace_name, session) + query = session.query(models.MetadefObject)\ + .filter_by(namespace_id=namespace['id']) + md_objects = query.all() + + md_objects_list = [] + for obj in md_objects: + md_objects_list.append(obj.as_dict()) + return md_objects_list + + +def create(context, namespace_name, values, session): + namespace = namespace_api.get(context, namespace_name, session) + values.update({'namespace_id': namespace['id']}) + + md_object = models.MetadefObject() + metadef_utils.drop_protected_attrs(models.MetadefObject, values) + md_object.update(values.copy()) + try: + md_object.save(session=session) + except db_exc.DBDuplicateEntry: + msg = ("A metadata definition object with name=%(name)s" + " in namespace=%(namespace_name)s already exists." + % {'name': md_object.name, + 'namespace_name': namespace_name}) + LOG.debug(msg) + raise exc.MetadefDuplicateObject( + object_name=md_object.name, namespace_name=namespace_name) + + return md_object.as_dict() + + +def get(context, namespace_name, name, session): + md_object = _get_by_name(context, namespace_name, name, session) + + return md_object.as_dict() + + +def update(context, namespace_name, object_id, values, session): + """Update an object, raise if ns not found/visible or duplicate result""" + namespace_api.get(context, namespace_name, session) + + md_object = _get(context, object_id, session) + metadef_utils.drop_protected_attrs(models.MetadefObject, values) + # values['updated_at'] = timeutils.utcnow() - done by TS mixin + try: + md_object.update(values.copy()) + md_object.save(session=session) + except db_exc.DBDuplicateEntry: + msg = ("Invalid update. It would result in a duplicate" + " metadata definition object with same name=%(name)s" + " in namespace=%(namespace_name)s." + % {'name': md_object.name, 'namespace_name': namespace_name}) + LOG.debug(msg) + emsg = (_("Invalid update. It would result in a duplicate" + " metadata definition object with the same name=%(name)s" + " in namespace=%(namespace_name)s.") + % {'name': md_object.name, 'namespace_name': namespace_name}) + raise exc.MetadefDuplicateObject(emsg) + + return md_object.as_dict() + + +def delete(context, namespace_name, object_name, session): + namespace_api.get(context, namespace_name, session) + md_object = _get_by_name(context, namespace_name, object_name, session) + + session.delete(md_object) + session.flush() + + return md_object.as_dict() + + +def delete_namespace_content(context, namespace_id, session): + """Use this def only if the ns for the id has been verified as visible""" + + count = 0 + query = session.query(models.MetadefObject)\ + .filter_by(namespace_id=namespace_id) + count = query.delete(synchronize_session='fetch') + return count + + +def delete_by_namespace_name(context, namespace_name, session): + namespace = namespace_api.get(context, namespace_name, session) + return delete_namespace_content(context, namespace['id'], session) + + +def count(context, namespace_name, session): + """Get the count of objects for a namespace, raise if ns not found""" + namespace = namespace_api.get(context, namespace_name, session) + + query = session.query(func.count(models.MetadefObject.id))\ + .filter_by(namespace_id=namespace['id']) + return query.scalar() diff --git a/glance/db/sqlalchemy/metadef_api/property.py b/glance/db/sqlalchemy/metadef_api/property.py new file mode 100644 index 0000000000..24e5a59d8a --- /dev/null +++ b/glance/db/sqlalchemy/metadef_api/property.py @@ -0,0 +1,169 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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 oslo.db import exception as db_exc +from sqlalchemy import func +import sqlalchemy.orm as sa_orm + +from glance.common import exception as exc +from glance.db.sqlalchemy.metadef_api import namespace as namespace_api +from glance.db.sqlalchemy.metadef_api import utils as metadef_utils +from glance.db.sqlalchemy import models_metadef as models +from glance import i18n +import glance.openstack.common.log as os_logging + +LOG = os_logging.getLogger(__name__) +_LW = i18n._LW + + +def _get(context, property_id, session): + + try: + query = session.query(models.MetadefProperty)\ + .filter_by(id=property_id) + property_rec = query.one() + + except sa_orm.exc.NoResultFound: + LOG.warn(_LW("Metadata definition property not found for id=%s", + property_id)) + raise exc.MetadefRecordNotFound( + record_type='property', id=property_id) + + return property_rec + + +def _get_by_name(context, namespace_name, name, session): + """get a property; raise if ns not found/visible or property not found""" + + namespace = namespace_api.get(context, namespace_name, session) + try: + query = session.query(models.MetadefProperty)\ + .filter_by(name=name, namespace_id=namespace['id']) + property_rec = query.one() + + except sa_orm.exc.NoResultFound: + msg = ("The metadata definition property with name=%(name)s" + " was not found in namespace=%(namespace_name)s." + % {'name': name, 'namespace_name': namespace_name}) + LOG.debug(msg) + raise exc.MetadefPropertyNotFound(property_name=name, + namespace_name=namespace_name) + + return property_rec + + +def get(context, namespace_name, name, session): + """get a property; raise if ns not found/visible or property not found""" + + property_rec = _get_by_name(context, namespace_name, name, session) + return property_rec.as_dict() + + +def get_all(context, namespace_name, session): + namespace = namespace_api.get(context, namespace_name, session) + query = session.query(models.MetadefProperty)\ + .filter_by(namespace_id=namespace['id']) + properties = query.all() + + properties_list = [] + for prop in properties: + properties_list.append(prop.as_dict()) + return properties_list + + +def create(context, namespace_name, values, session): + namespace = namespace_api.get(context, namespace_name, session) + values.update({'namespace_id': namespace['id']}) + + property_rec = models.MetadefProperty() + metadef_utils.drop_protected_attrs(models.MetadefProperty, values) + property_rec.update(values.copy()) + + try: + property_rec.save(session=session) + except db_exc.DBDuplicateEntry: + msg = ("Can not create metadata definition property. A property" + " with name=%(name)s already exists in" + " namespace=%(namespace_name)s." + % {'name': property_rec.name, + 'namespace_name': namespace_name}) + LOG.debug(msg) + raise exc.MetadefDuplicateProperty( + property_name=property_rec.name, + namespace_name=namespace_name) + + return property_rec.as_dict() + + +def update(context, namespace_name, property_id, values, session): + """Update a property, raise if ns not found/visible or duplicate result""" + + namespace_api.get(context, namespace_name, session) + property_rec = _get(context, property_id, session) + metadef_utils.drop_protected_attrs(models.MetadefProperty, values) + # values['updated_at'] = timeutils.utcnow() - done by TS mixin + try: + property_rec.update(values.copy()) + property_rec.save(session=session) + except db_exc.DBDuplicateEntry: + msg = ("Invalid update. It would result in a duplicate" + " metadata definition property with the same name=%(name)s" + " in namespace=%(namespace_name)s." + % {'name': property_rec.name, + 'namespace_name': namespace_name}) + LOG.debug(msg) + emsg = (_("Invalid update. It would result in a duplicate" + " metadata definition property with the same name=%(name)s" + " in namespace=%(namespace_name)s.") + % {'name': property_rec.name, + 'namespace_name': namespace_name}) + raise exc.MetadefDuplicateProperty(emsg) + + return property_rec.as_dict() + + +def delete(context, namespace_name, property_name, session): + property_rec = _get_by_name( + context, namespace_name, property_name, session) + if property_rec: + session.delete(property_rec) + session.flush() + + return property_rec.as_dict() + + +def delete_namespace_content(context, namespace_id, session): + """Use this def only if the ns for the id has been verified as visible""" + + count = 0 + query = session.query(models.MetadefProperty)\ + .filter_by(namespace_id=namespace_id) + count = query.delete(synchronize_session='fetch') + return count + + +def delete_by_namespace_name(context, namespace_name, session): + namespace = namespace_api.get(context, namespace_name, session) + return delete_namespace_content(context, namespace['id'], session) + + +def count(context, namespace_name, session): + """Get the count of properties for a namespace, raise if ns not found""" + + namespace = namespace_api.get(context, namespace_name, session) + + query = session.query(func.count(models.MetadefProperty.id))\ + .filter_by(namespace_id=namespace['id']) + return query.scalar() diff --git a/glance/db/sqlalchemy/metadef_api/resource_type.py b/glance/db/sqlalchemy/metadef_api/resource_type.py new file mode 100644 index 0000000000..ce830410fc --- /dev/null +++ b/glance/db/sqlalchemy/metadef_api/resource_type.py @@ -0,0 +1,111 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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 oslo.db import exception as db_exc +import sqlalchemy.exc as sa_exc +import sqlalchemy.orm as sa_orm + +from glance.common import exception as exc +import glance.db.sqlalchemy.metadef_api.utils as metadef_utils +from glance.db.sqlalchemy import models_metadef as models +import glance.openstack.common.log as os_logging + +LOG = os_logging.getLogger(__name__) + + +def get(context, name, session): + """Get a resource type, raise if not found""" + + try: + query = session.query(models.MetadefResourceType)\ + .filter_by(name=name) + resource_type = query.one() + except sa_orm.exc.NoResultFound: + msg = "No metadata definition resource-type found with name %s" % name + LOG.debug(msg) + raise exc.MetadefResourceTypeNotFound(resource_type_name=name) + + return resource_type.as_dict() + + +def get_all(context, session): + """Get a list of all resource types""" + + query = session.query(models.MetadefResourceType) + resource_types = query.all() + + resource_types_list = [] + for rt in resource_types: + resource_types_list.append(rt.as_dict()) + + return resource_types_list + + +def create(context, values, session): + """Create a resource_type, raise if it already exists.""" + + resource_type = models.MetadefResourceType() + metadef_utils.drop_protected_attrs(models.MetadefResourceType, values) + resource_type.update(values.copy()) + try: + resource_type.save(session=session) + except db_exc.DBDuplicateEntry: + msg = ("Can not create the metadata definition resource-type." + " A resource-type with name=%s already exists." + % resource_type.name) + LOG.debug(msg) + raise exc.MetadefDuplicateResourceType( + resource_type_name=resource_type.name) + + return resource_type.as_dict() + + +def update(context, values, session): + """Update a resource type, raise if not found""" + + name = values['name'] + metadef_utils.drop_protected_attrs(models.MetadefResourceType, values) + db_rec = get(context, name, session) + db_rec.update(values.copy()) + db_rec.save(session=session) + + return db_rec.as_dict() + + +def delete(context, name, session): + """Delete a resource type or raise if not found or is protected""" + + db_rec = get(context, name, session) + if db_rec.protected is True: + msg = ("Delete forbidden. Metadata definition resource-type %s is a" + " seeded-system type and can not be deleted.") % name + LOG.debug(msg) + raise exc.ProtectedMetadefResourceTypeSystemDelete( + resource_type_name=name) + + try: + session.delete(db_rec) + session.flush() + except db_exc.DBError as e: + if isinstance(e.inner_exception, sa_exc.IntegrityError): + msg = ("Could not delete Metadata definition resource-type %s" + ". It still has content") % name + LOG.debug(msg) + raise exc.MetadefIntegrityError( + record_type='resource-type', record_name=name) + else: + raise e + + return db_rec.as_dict() diff --git a/glance/db/sqlalchemy/metadef_api/resource_type_association.py b/glance/db/sqlalchemy/metadef_api/resource_type_association.py new file mode 100644 index 0000000000..5dd534ad13 --- /dev/null +++ b/glance/db/sqlalchemy/metadef_api/resource_type_association.py @@ -0,0 +1,217 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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 oslo.db import exception as db_exc +import sqlalchemy.orm as sa_orm + +from glance.common import exception as exc +from glance.db.sqlalchemy.metadef_api\ + import namespace as namespace_api +from glance.db.sqlalchemy.metadef_api\ + import resource_type as resource_type_api +from glance.db.sqlalchemy.metadef_api\ + import utils as metadef_utils +from glance.db.sqlalchemy import models_metadef as models +import glance.openstack.common.log as os_logging + +LOG = os_logging.getLogger(__name__) + + +def _to_db_dict(namespace_id, resource_type_id, model_dict): + """transform a model dict to a metadef_namespace_resource_type dict""" + db_dict = {'namespace_id': namespace_id, + 'resource_type_id': resource_type_id, + 'properties_target': model_dict['properties_target'], + 'prefix': model_dict['prefix']} + return db_dict + + +def _to_model_dict(resource_type_name, ns_res_type_dict): + """transform a metadef_namespace_resource_type dict to a model dict""" + model_dict = {'name': resource_type_name, + 'properties_target': ns_res_type_dict['properties_target'], + 'prefix': ns_res_type_dict['prefix'], + 'created_at': ns_res_type_dict['created_at'], + 'updated_at': ns_res_type_dict['updated_at']} + return model_dict + + +def _set_model_dict(resource_type_name, properties_target, prefix, + created_at, updated_at): + """return a model dict set with the passed in key values""" + model_dict = {'name': resource_type_name, + 'properties_target': properties_target, + 'prefix': prefix, + 'created_at': created_at, + 'updated_at': updated_at} + return model_dict + + +def _get(context, namespace_name, resource_type_name, + namespace_id, resource_type_id, session): + """Get a namespace resource_type association""" + + # visibility check assumed done in calling routine via namespace_get + try: + query = session.query(models.MetadefNamespaceResourceType).\ + filter_by(namespace_id=namespace_id, + resource_type_id=resource_type_id) + db_rec = query.one() + except sa_orm.exc.NoResultFound: + msg = ("The metadata definition resource-type association of" + " resource_type=%(resource_type_name)s to" + " namespace_name=%(namespace_name)s was not found." + % {'resource_type_name': resource_type_name, + 'namespace_name': namespace_name}) + LOG.debug(msg) + raise exc.MetadefResourceTypeAssociationNotFound( + resource_type_name=resource_type_name, + namespace_name=namespace_name) + + return db_rec + + +def _create_association( + context, namespace_name, resource_type_name, values, session): + """Create an association, raise if it already exists.""" + + namespace_resource_type_rec = models.MetadefNamespaceResourceType() + metadef_utils.drop_protected_attrs( + models.MetadefNamespaceResourceType, values) + # values['updated_at'] = timeutils.utcnow() # TS mixin should do this + namespace_resource_type_rec.update(values.copy()) + try: + namespace_resource_type_rec.save(session=session) + except db_exc.DBDuplicateEntry: + msg = ("The metadata definition resource-type association of" + " resource_type=%(resource_type_name)s to" + " namespace=%(namespace_name)s, already exists." + % {'resource_type_name': resource_type_name, + 'namespace_name': namespace_name}) + LOG.debug(msg) + raise exc.MetadefDuplicateResourceTypeAssociation( + resource_type_name=resource_type_name, + namespace_name=namespace_name) + + return namespace_resource_type_rec.as_dict() + + +def _delete(context, namespace_name, resource_type_name, + namespace_id, resource_type_id, session): + """Delete a resource type association or raise if not found.""" + + db_rec = _get(context, namespace_name, resource_type_name, + namespace_id, resource_type_id, session) + session.delete(db_rec) + session.flush() + + return db_rec.as_dict() + + +def get(context, namespace_name, resource_type_name, session): + """Get a resource_type associations; raise if not found""" + namespace = namespace_api.get( + context, namespace_name, session) + + resource_type = resource_type_api.get( + context, resource_type_name, session) + + found = _get(context, namespace_name, resource_type_name, + namespace['id'], resource_type['id'], session) + + return _to_model_dict(resource_type_name, found) + + +def get_all_by_namespace(context, namespace_name, session): + """List resource_type associations by namespace, raise if not found""" + + # namespace get raises an exception if not visible + namespace = namespace_api.get( + context, namespace_name, session) + + db_recs = ( + session.query(models.MetadefResourceType) + .join(models.MetadefResourceType.associations) + .filter_by(namespace_id=namespace['id']) + .values(models.MetadefResourceType.name, + models.MetadefNamespaceResourceType.properties_target, + models.MetadefNamespaceResourceType.prefix, + models.MetadefNamespaceResourceType.created_at, + models.MetadefNamespaceResourceType.updated_at)) + + model_dict_list = [] + for name, properties_target, prefix, created_at, updated_at in db_recs: + model_dict_list.append( + _set_model_dict + (name, properties_target, prefix, created_at, updated_at) + ) + + return model_dict_list + + +def create(context, namespace_name, values, session): + """Create an association, raise if already exists or ns not found.""" + + namespace = namespace_api.get( + context, namespace_name, session) + + # if the resource_type does not exist, create it + resource_type_name = values['name'] + metadef_utils.drop_protected_attrs( + models.MetadefNamespaceResourceType, values) + try: + resource_type = resource_type_api.get( + context, resource_type_name, session) + except exc.NotFound: + resource_type = None + LOG.debug("Creating resource-type %s" % resource_type_name) + + if resource_type is None: + resource_type_dict = {'name': resource_type_name, 'protected': 0} + resource_type = resource_type_api.create( + context, resource_type_dict, session) + + # Create the association record, set the field values + ns_resource_type_dict = _to_db_dict( + namespace['id'], resource_type['id'], values) + new_rec = _create_association(context, namespace_name, resource_type_name, + ns_resource_type_dict, session) + + return _to_model_dict(resource_type_name, new_rec) + + +def delete(context, namespace_name, resource_type_name, session): + """Delete an association or raise if not found""" + + namespace = namespace_api.get( + context, namespace_name, session) + + resource_type = resource_type_api.get( + context, resource_type_name, session) + + deleted = _delete(context, namespace_name, resource_type_name, + namespace['id'], resource_type['id'], session) + + return _to_model_dict(resource_type_name, deleted) + + +def delete_namespace_content(context, namespace_id, session): + """Use this def only if the ns for the id has been verified as visible""" + + count = 0 + query = session.query(models.MetadefNamespaceResourceType)\ + .filter_by(namespace_id=namespace_id) + count = query.delete(synchronize_session='fetch') + return count diff --git a/glance/db/sqlalchemy/metadef_api/utils.py b/glance/db/sqlalchemy/metadef_api/utils.py new file mode 100644 index 0000000000..ca20150ea4 --- /dev/null +++ b/glance/db/sqlalchemy/metadef_api/utils.py @@ -0,0 +1,23 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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. + + +def drop_protected_attrs(model_class, values): + """ + Removed protected attributes from values dictionary using the models + __protected_attributes__ field. + """ + for attr in model_class.__protected_attributes__: + if attr in values: + del values[attr] diff --git a/glance/db/sqlalchemy/migrate_repo/versions/035_add_metadef_tables.py b/glance/db/sqlalchemy/migrate_repo/versions/035_add_metadef_tables.py new file mode 100644 index 0000000000..44b58aa598 --- /dev/null +++ b/glance/db/sqlalchemy/migrate_repo/versions/035_add_metadef_tables.py @@ -0,0 +1,215 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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 sqlalchemy +from sqlalchemy.schema import ( + Column, ForeignKey, Index, MetaData, Table, UniqueConstraint) # noqa + +from glance.db.sqlalchemy.migrate_repo.schema import ( + Boolean, DateTime, Integer, String, Text, create_tables, + drop_tables) # noqa +from glance.openstack.common import timeutils + + +RESOURCE_TYPES = [u'OS::Glance::Image', u'OS::Cinder::Volume', + u'OS::Nova::Flavor', u'OS::Nova::Aggregate', + u'OS::Nova::Instance'] + + +def _get_metadef_resource_types_table(meta): + return sqlalchemy.Table('metadef_resource_types', meta, autoload=True) + + +def _populate_resource_types(resource_types_table): + now = timeutils.utcnow() + for resource_type in RESOURCE_TYPES: + values = { + 'name': resource_type, + 'protected': True, + 'created_at': now, + 'updated_at': now + } + resource_types_table.insert(values=values).execute() + + +def define_metadef_namespaces_table(meta): + + # NOTE: For DB2 if UniqueConstraint is used when creating a table + # an index will automatically be created. So, for DB2 specify the + # index name up front. If not DB2 then create the Index. + _constr_kwargs = {} + if meta.bind.name == 'ibm_db_sa': + _constr_kwargs['name'] = 'ix_namespaces_namespace' + + namespaces = Table('metadef_namespaces', + meta, + Column('id', Integer(), primary_key=True, + nullable=False), + Column('namespace', String(80), nullable=False), + Column('display_name', String(80)), + Column('description', Text()), + Column('visibility', String(32)), + Column('protected', Boolean()), + Column('owner', String(255), nullable=False), + Column('created_at', DateTime(), nullable=False), + Column('updated_at', DateTime()), + UniqueConstraint('namespace', **_constr_kwargs), + mysql_engine='InnoDB', + extend_existing=True) + + if meta.bind.name != 'ibm_db_sa': + Index('ix_namespaces_namespace', namespaces.c.namespace) + + return namespaces + + +def define_metadef_objects_table(meta): + + _constr_kwargs = {} + if meta.bind.name == 'ibm_db_sa': + _constr_kwargs['name'] = 'ix_objects_namespace_id_name' + + objects = Table('metadef_objects', + meta, + Column('id', Integer(), primary_key=True, nullable=False), + Column('namespace_id', Integer(), + ForeignKey('metadef_namespaces.id'), + nullable=False), + Column('name', String(80), nullable=False), + Column('description', Text()), + Column('required', Text()), + Column('schema', Text()), + Column('created_at', DateTime(), nullable=False), + Column('updated_at', DateTime()), + UniqueConstraint('namespace_id', 'name', + **_constr_kwargs), + mysql_engine='InnoDB', + extend_existing=True) + + if meta.bind.name != 'ibm_db_sa': + Index('ix_objects_namespace_id_name', + objects.c.namespace_id, + objects.c.name) + + return objects + + +def define_metadef_properties_table(meta): + + _constr_kwargs = {} + if meta.bind.name == 'ibm_db_sa': + _constr_kwargs['name'] = 'ix_metadef_properties_namespace_id_name' + + metadef_properties = Table( + 'metadef_properties', + meta, + Column('id', Integer(), primary_key=True, nullable=False), + Column('namespace_id', Integer(), ForeignKey('metadef_namespaces.id'), + nullable=False), + Column('name', String(80), nullable=False), + Column('schema', Text()), + Column('created_at', DateTime(), nullable=False), + Column('updated_at', DateTime()), + UniqueConstraint('namespace_id', 'name', **_constr_kwargs), + mysql_engine='InnoDB', + extend_existing=True) + + if meta.bind.name != 'ibm_db_sa': + Index('ix_metadef_properties_namespace_id_name', + metadef_properties.c.namespace_id, + metadef_properties.c.name) + + return metadef_properties + + +def define_metadef_resource_types_table(meta): + + _constr_kwargs = {} + if meta.bind.name == 'ibm_db_sa': + _constr_kwargs['name'] = 'ix_metadef_resource_types_name' + + metadef_res_types = Table( + 'metadef_resource_types', + meta, + Column('id', Integer(), primary_key=True, nullable=False), + Column('name', String(80), nullable=False), + Column('protected', Boolean(), nullable=False, default=False), + Column('created_at', DateTime(), nullable=False), + Column('updated_at', DateTime()), + UniqueConstraint('name', **_constr_kwargs), + mysql_engine='InnoDB', + extend_existing=True) + + if meta.bind.name != 'ibm_db_sa': + Index('ix_metadef_resource_types_name', + metadef_res_types.c.name) + + return metadef_res_types + + +def define_metadef_namespace_resource_types_table(meta): + + _constr_kwargs = {} + if meta.bind.name == 'ibm_db_sa': + _constr_kwargs['name'] = 'ix_metadef_ns_res_types_res_type_id_ns_id' + + metadef_associations = Table( + 'metadef_namespace_resource_types', + meta, + Column('resource_type_id', Integer(), + ForeignKey('metadef_resource_types.id'), + primary_key=True, nullable=False), + Column('namespace_id', Integer(), + ForeignKey('metadef_namespaces.id'), + primary_key=True, nullable=False), + Column('properties_target', String(80)), + Column('prefix', String(80)), + Column('created_at', DateTime(), nullable=False), + Column('updated_at', DateTime()), + UniqueConstraint('resource_type_id', 'namespace_id', + **_constr_kwargs), + mysql_engine='InnoDB', + extend_existing=True) + + if meta.bind.name != 'ibm_db_sa': + Index('ix_metadef_ns_res_types_res_type_id_ns_id', + metadef_associations.c.resource_type_id, + metadef_associations.c.namespace_id) + + return metadef_associations + + +def upgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + tables = [define_metadef_namespaces_table(meta), + define_metadef_objects_table(meta), + define_metadef_properties_table(meta), + define_metadef_resource_types_table(meta), + define_metadef_namespace_resource_types_table(meta)] + create_tables(tables) + + resource_types_table = _get_metadef_resource_types_table(meta) + _populate_resource_types(resource_types_table) + + +def downgrade(migrate_engine): + meta = MetaData() + meta.bind = migrate_engine + tables = [define_metadef_objects_table(meta), + define_metadef_properties_table(meta), + define_metadef_namespace_resource_types_table(meta), + define_metadef_resource_types_table(meta), + define_metadef_namespaces_table(meta)] + drop_tables(tables) diff --git a/glance/db/sqlalchemy/models_metadef.py b/glance/db/sqlalchemy/models_metadef.py new file mode 100644 index 0000000000..cfa8106e14 --- /dev/null +++ b/glance/db/sqlalchemy/models_metadef.py @@ -0,0 +1,152 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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. + +""" +SQLAlchemy models for glance metadata schema +""" + +from oslo.db.sqlalchemy import models +from sqlalchemy import Boolean +from sqlalchemy import Column +from sqlalchemy import DateTime +from sqlalchemy.ext.declarative import declarative_base +from sqlalchemy import ForeignKey +from sqlalchemy import Index +from sqlalchemy import Integer +from sqlalchemy.orm import relationship +from sqlalchemy import String +from sqlalchemy import Text + +from glance.openstack.common import timeutils + + +class DictionaryBase(models.ModelBase): + metadata = None + + def as_dict(self): + d = {} + for c in self.__table__.columns: + d[c.name] = self[c.name] + return d + + +BASE_DICT = declarative_base(cls=DictionaryBase) + + +class GlanceMetadefBase(models.TimestampMixin): + """Base class for Glance Metadef Models.""" + + __table_args__ = {'mysql_engine': 'InnoDB'} + __table_initialized__ = False + __protected_attributes__ = set(["created_at", "updated_at"]) + + created_at = Column(DateTime, default=lambda: timeutils.utcnow(), + nullable=False) + # TODO(wko): Column `updated_at` have no default value in + # openstack common code. We should decide, is this value + # required and make changes in oslo (if required) or + # in glance (if not). + updated_at = Column(DateTime, default=lambda: timeutils.utcnow(), + nullable=False, onupdate=lambda: timeutils.utcnow()) + + +class MetadefNamespace(BASE_DICT, GlanceMetadefBase): + """Represents a metadata-schema namespace in the datastore.""" + __tablename__ = 'metadef_namespaces' + __table_args__ = (Index('ix_metadef_namespaces_namespace', 'namespace'), + Index('ix_metadef_namespaces_owner', 'owner')) + + id = Column(Integer, primary_key=True, nullable=False) + namespace = Column(String(80)) + display_name = Column(String(80)) + description = Column(Text()) + visibility = Column(String(32)) + protected = Column(Boolean) + owner = Column(String(255), nullable=False) + + +class MetadefObject(BASE_DICT, GlanceMetadefBase): + """Represents a metadata-schema object in the datastore.""" + __tablename__ = 'metadef_objects' + __table_args__ = (Index('ix_metadef_objects_namespace_id', 'namespace_id'), + Index('ix_metadef_objects_name', 'name')) + + id = Column(Integer, primary_key=True, nullable=False) + namespace_id = Column(Integer(), ForeignKey('metadef_namespaces.id'), + nullable=False) + name = Column(String(80), nullable=False) + description = Column(Text()) + required = Column(Text()) + schema = Column(Text(), default={}) + + +class MetadefProperty(BASE_DICT, GlanceMetadefBase): + """Represents a metadata-schema namespace-property in the datastore.""" + __tablename__ = 'metadef_properties' + __table_args__ = (Index('ix_metadef_properties_namespace_id', + 'namespace_id'), + Index('ix_metadef_properties_name', 'name')) + + id = Column(Integer, primary_key=True, nullable=False) + namespace_id = Column(Integer(), ForeignKey('metadef_namespaces.id'), + nullable=False) + name = Column(String(80), nullable=False) + schema = Column(Text(), default={}) + + +class MetadefNamespaceResourceType(BASE_DICT, GlanceMetadefBase): + """Represents a metadata-schema namespace-property in the datastore.""" + __tablename__ = 'metadef_namespace_resource_types' + __table_args__ = (Index('ix_metadef_ns_res_types_res_type_id_ns_id', + 'resource_type_id', 'namespace_id'), + Index('ix_metadef_ns_res_types_namespace_id', + 'namespace_id')) + + resource_type_id = Column(Integer, + ForeignKey('metadef_resource_types.id'), + primary_key=True, nullable=False) + namespace_id = Column(Integer, ForeignKey('metadef_namespaces.id'), + primary_key=True, nullable=False) + properties_target = Column(String(80)) + prefix = Column(String(80)) + + +class MetadefResourceType(BASE_DICT, GlanceMetadefBase): + """Represents a metadata-schema resource type in the datastore.""" + __tablename__ = 'metadef_resource_types' + __table_args__ = (Index('ix_metadef_resource_types_name', 'name'), ) + + id = Column(Integer, primary_key=True, nullable=False) + name = Column(String(80), nullable=False) + protected = Column(Boolean, nullable=False, default=False) + + associations = relationship( + "MetadefNamespaceResourceType", + primaryjoin=id == MetadefNamespaceResourceType.resource_type_id) + + +def register_models(engine): + """Create database tables for all models with the given engine.""" + models = (MetadefNamespace, MetadefObject, MetadefProperty, + MetadefResourceType, MetadefNamespaceResourceType) + for model in models: + model.metadata.create_all(engine) + + +def unregister_models(engine): + """Drop database tables for all models with the given engine.""" + models = (MetadefObject, MetadefProperty, MetadefNamespaceResourceType, + MetadefNamespace, MetadefResourceType) + for model in models: + model.metadata.drop_all(engine) diff --git a/glance/tests/functional/db/base_metadef.py b/glance/tests/functional/db/base_metadef.py new file mode 100644 index 0000000000..e6dba8e973 --- /dev/null +++ b/glance/tests/functional/db/base_metadef.py @@ -0,0 +1,479 @@ +# Copyright (c) 2014 Hewlett-Packard Development Company, L.P. +# +# 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 copy + +from glance import context +import glance.tests.functional.db as db_tests + +from glance.common import config +from glance.common import exception +from glance.tests import utils as test_utils + + +def build_namespace_fixture(**kwargs): + namespace = { + 'namespace': u'MyTestNamespace', + 'display_name': u'test-display-name', + 'description': u'test-description', + 'visibility': u'public', + 'protected': 0, + 'owner': u'test-owner' + } + namespace.update(kwargs) + return namespace + + +def build_resource_type_fixture(**kwargs): + resource_type = { + 'name': u'MyTestResourceType', + 'protected': 0 + } + resource_type.update(kwargs) + return resource_type + + +def build_association_fixture(**kwargs): + association = { + 'name': u'MyTestResourceType', + 'properties_target': 'test-properties-target', + 'prefix': 'test-prefix' + } + association.update(kwargs) + return association + + +def build_object_fixture(**kwargs): + # Full testing of required and schema done via rest api tests + object = { + 'namespace_id': 1, + 'name': u'test-object-name', + 'description': u'test-object-description', + 'required': u'fake-required-properties-list', + 'schema': u'{fake-schema}' + } + object.update(kwargs) + return object + + +def build_property_fixture(**kwargs): + # Full testing of required and schema done via rest api tests + property = { + 'namespace_id': 1, + 'name': u'test-property-name', + 'schema': u'{fake-schema}' + } + property.update(kwargs) + return property + + +class TestMetadefDriver(test_utils.BaseTestCase): + + """Test Driver class for Metadef tests.""" + + def setUp(self): + """Run before each test method to initialize test environment.""" + super(TestMetadefDriver, self).setUp() + config.parse_args(args=[]) + context_cls = context.RequestContext + self.adm_context = context_cls(is_admin=True, + auth_tok='user:user:admin') + self.context = context_cls(is_admin=False, + auth_tok='user:user:user') + self.db_api = db_tests.get_db(self.config) + db_tests.reset_db(self.db_api) + + def _assert_saved_fields(self, expected, actual): + for k in expected.keys(): + self.assertEqual(expected[k], actual[k]) + + +class MetadefNamespaceTests(object): + + def test_namespace_create(self): + fixture = build_namespace_fixture() + created = self.db_api.metadef_namespace_create(self.context, fixture) + self.assertIsNotNone(created) + self._assert_saved_fields(fixture, created) + + def test_namespace_get(self): + fixture = build_namespace_fixture() + created = self.db_api.metadef_namespace_create(self.context, fixture) + self.assertIsNotNone(created) + self._assert_saved_fields(fixture, created) + + found = self.db_api.metadef_namespace_get( + self.context, created['namespace']) + self.assertIsNotNone(found, "Namespace not found.") + + def test_namespace_get_all_with_resource_types_filter(self): + ns_fixture = build_namespace_fixture() + ns_created = self.db_api.metadef_namespace_create( + self.context, ns_fixture) + self.assertIsNotNone(ns_created, "Could not create a namespace.") + self._assert_saved_fields(ns_fixture, ns_created) + + fixture = build_association_fixture() + created = self.db_api.metadef_resource_type_association_create( + self.context, ns_created['namespace'], fixture) + self.assertIsNotNone(created, "Could not create an association.") + + rt_filters = {'resource_types': fixture['name']} + found = self.db_api.\ + metadef_namespace_get_all(self.context, filters=rt_filters, + sort_key='created_at') + self.assertEqual(len(found), 1) + for item in found: + self._assert_saved_fields(ns_fixture, item) + + def test_namespace_update(self): + delta = {'owner': u'New Owner'} + fixture = build_namespace_fixture() + + created = self.db_api.metadef_namespace_create(self.context, fixture) + self.assertIsNotNone(created['namespace']) + self.assertEqual(created['namespace'], fixture['namespace']) + delta_dict = copy.deepcopy(created) + delta_dict.update(delta.copy()) + + updated = self.db_api.metadef_namespace_update( + self.context, created['id'], delta_dict) + self.assertEqual(delta['owner'], updated['owner']) + + def test_namespace_delete(self): + fixture = build_namespace_fixture() + created = self.db_api.metadef_namespace_create(self.context, fixture) + self.assertIsNotNone(created, "Could not create a Namespace.") + self.db_api.metadef_namespace_delete( + self.context, created['namespace']) + self.assertRaises(exception.NotFound, + self.db_api.metadef_namespace_get, + self.context, created['namespace']) + + def test_namespace_delete_with_content(self): + fixture_ns = build_namespace_fixture() + created_ns = self.db_api.metadef_namespace_create( + self.context, fixture_ns) + self._assert_saved_fields(fixture_ns, created_ns) + + # Create object content for the namespace + fixture_obj = build_object_fixture() + created_obj = self.db_api.metadef_object_create( + self.context, created_ns['namespace'], fixture_obj) + self.assertIsNotNone(created_obj) + + # Create property content for the namespace + fixture_prop = build_property_fixture(namespace_id=created_ns['id']) + created_prop = self.db_api.metadef_property_create( + self.context, created_ns['namespace'], fixture_prop) + self.assertIsNotNone(created_prop) + + # Create associations + fixture_assn = build_association_fixture() + created_assn = self.db_api.metadef_resource_type_association_create( + self.context, created_ns['namespace'], fixture_assn) + self.assertIsNotNone(created_assn) + + deleted_ns = self.db_api.metadef_namespace_delete( + self.context, created_ns['namespace']) + + self.assertRaises(exception.NotFound, + self.db_api.metadef_namespace_get, + self.context, deleted_ns['namespace']) + + +class MetadefPropertyTests(object): + + def test_property_create(self): + fixture = build_namespace_fixture() + created_ns = self.db_api.metadef_namespace_create( + self.context, fixture) + self.assertIsNotNone(created_ns) + self._assert_saved_fields(fixture, created_ns) + + fixture_prop = build_property_fixture(namespace_id=created_ns['id']) + created_prop = self.db_api.metadef_property_create( + self.context, created_ns['namespace'], fixture_prop) + self._assert_saved_fields(fixture_prop, created_prop) + + def test_property_get(self): + fixture_ns = build_namespace_fixture() + created_ns = self.db_api.metadef_namespace_create( + self.context, fixture_ns) + self.assertIsNotNone(created_ns) + self._assert_saved_fields(fixture_ns, created_ns) + + fixture_prop = build_property_fixture(namespace_id=created_ns['id']) + created_prop = self.db_api.metadef_property_create( + self.context, created_ns['namespace'], fixture_prop) + + found_prop = self.db_api.metadef_property_get( + self.context, created_ns['namespace'], created_prop['name']) + self._assert_saved_fields(fixture_prop, found_prop) + + def test_property_get_all(self): + ns_fixture = build_namespace_fixture() + ns_created = self.db_api.metadef_namespace_create( + self.context, ns_fixture) + self.assertIsNotNone(ns_created, "Could not create a namespace.") + self._assert_saved_fields(ns_fixture, ns_created) + + fixture1 = build_property_fixture(namespace_id=ns_created['id']) + created_p1 = self.db_api.metadef_property_create( + self.context, ns_created['namespace'], fixture1) + self.assertIsNotNone(created_p1, "Could not create a property.") + + fixture2 = build_property_fixture(namespace_id=ns_created['id'], + name='test-prop-2') + created_p2 = self.db_api.metadef_property_create( + self.context, ns_created['namespace'], fixture2) + self.assertIsNotNone(created_p2, "Could not create a property.") + + found = self.db_api.\ + metadef_property_get_all(self.context, ns_created['namespace']) + self.assertEqual(len(found), 2) + + def test_property_update(self): + delta = {'name': u'New-name', 'schema': u'new-schema'} + + fixture_ns = build_namespace_fixture() + created_ns = self.db_api.metadef_namespace_create( + self.context, fixture_ns) + self.assertIsNotNone(created_ns['namespace']) + + prop_fixture = build_property_fixture(namespace_id=created_ns['id']) + created_prop = self.db_api.metadef_property_create( + self.context, created_ns['namespace'], prop_fixture) + self.assertIsNotNone(created_prop, "Could not create a property.") + + delta_dict = copy.deepcopy(created_prop) + delta_dict.update(delta.copy()) + + updated = self.db_api.metadef_property_update( + self.context, created_ns['namespace'], + created_prop['id'], delta_dict) + self.assertEqual(delta['name'], updated['name']) + self.assertEqual(delta['schema'], updated['schema']) + + def test_property_delete(self): + fixture_ns = build_namespace_fixture() + created_ns = self.db_api.metadef_namespace_create( + self.context, fixture_ns) + self.assertIsNotNone(created_ns['namespace']) + + prop_fixture = build_property_fixture(namespace_id=created_ns['id']) + created_prop = self.db_api.metadef_property_create( + self.context, created_ns['namespace'], prop_fixture) + self.assertIsNotNone(created_prop, "Could not create a property.") + + self.db_api.metadef_property_delete( + self.context, created_ns['namespace'], created_prop['name']) + self.assertRaises(exception.NotFound, + self.db_api.metadef_property_get, + self.context, created_ns['namespace'], + created_prop['name']) + + def test_property_delete_namespace_content(self): + fixture_ns = build_namespace_fixture() + created_ns = self.db_api.metadef_namespace_create( + self.context, fixture_ns) + self.assertIsNotNone(created_ns['namespace']) + + prop_fixture = build_property_fixture(namespace_id=created_ns['id']) + created_prop = self.db_api.metadef_property_create( + self.context, created_ns['namespace'], prop_fixture) + self.assertIsNotNone(created_prop, "Could not create a property.") + + self.db_api.metadef_property_delete_namespace_content( + self.context, created_ns['namespace']) + self.assertRaises(exception.NotFound, + self.db_api.metadef_property_get, + self.context, created_ns['namespace'], + created_prop['name']) + + +class MetadefObjectTests(object): + + def test_object_create(self): + fixture = build_namespace_fixture() + created_ns = self.db_api.metadef_namespace_create(self.context, + fixture) + self.assertIsNotNone(created_ns) + self._assert_saved_fields(fixture, created_ns) + + fixture_object = build_object_fixture(namespace_id=created_ns['id']) + created_object = self.db_api.metadef_object_create( + self.context, created_ns['namespace'], fixture_object) + self._assert_saved_fields(fixture_object, created_object) + + def test_object_get(self): + fixture_ns = build_namespace_fixture() + created_ns = self.db_api.metadef_namespace_create(self.context, + fixture_ns) + self.assertIsNotNone(created_ns) + self._assert_saved_fields(fixture_ns, created_ns) + + fixture_object = build_object_fixture(namespace_id=created_ns['id']) + created_object = self.db_api.metadef_object_create( + self.context, created_ns['namespace'], fixture_object) + + found_object = self.db_api.metadef_object_get( + self.context, created_ns['namespace'], created_object['name']) + self._assert_saved_fields(fixture_object, found_object) + + def test_object_get_all(self): + ns_fixture = build_namespace_fixture() + ns_created = self.db_api.metadef_namespace_create(self.context, + ns_fixture) + self.assertIsNotNone(ns_created, "Could not create a namespace.") + self._assert_saved_fields(ns_fixture, ns_created) + + fixture1 = build_object_fixture(namespace_id=ns_created['id']) + created_o1 = self.db_api.metadef_object_create( + self.context, ns_created['namespace'], fixture1) + self.assertIsNotNone(created_o1, "Could not create an object.") + + fixture2 = build_object_fixture(namespace_id=ns_created['id'], + name='test-object-2') + created_o2 = self.db_api.metadef_object_create( + self.context, ns_created['namespace'], fixture2) + self.assertIsNotNone(created_o2, "Could not create an object.") + + found = self.db_api.\ + metadef_object_get_all(self.context, ns_created['namespace']) + self.assertEqual(len(found), 2) + + def test_object_update(self): + delta = {'name': u'New-name', 'schema': u'new-schema', + 'required': u'new-required'} + + fixture_ns = build_namespace_fixture() + created_ns = self.db_api.metadef_namespace_create(self.context, + fixture_ns) + self.assertIsNotNone(created_ns['namespace']) + + object_fixture = build_object_fixture(namespace_id=created_ns['id']) + created_object = self.db_api.metadef_object_create( + self.context, created_ns['namespace'], object_fixture) + self.assertIsNotNone(created_object, "Could not create an object.") + + delta_dict = {} + delta_dict.update(delta.copy()) + + updated = self.db_api.metadef_object_update( + self.context, created_ns['namespace'], + created_object['id'], delta_dict) + self.assertEqual(delta['name'], updated['name']) + self.assertEqual(delta['schema'], updated['schema']) + + def test_object_delete(self): + fixture_ns = build_namespace_fixture() + created_ns = self.db_api.metadef_namespace_create( + self.context, fixture_ns) + self.assertIsNotNone(created_ns['namespace']) + + object_fixture = build_object_fixture(namespace_id=created_ns['id']) + created_object = self.db_api.metadef_object_create( + self.context, created_ns['namespace'], object_fixture) + self.assertIsNotNone(created_object, "Could not create an object.") + + self.db_api.metadef_object_delete( + self.context, created_ns['namespace'], created_object['name']) + self.assertRaises(exception.NotFound, + self.db_api.metadef_object_get, + self.context, created_ns['namespace'], + created_object['name']) + + +class MetadefResourceTypeTests(object): + + def test_resource_type_get_all(self): + resource_types_orig = self.db_api.metadef_resource_type_get_all( + self.context) + + fixture = build_resource_type_fixture() + self.db_api.metadef_resource_type_create(self.context, fixture) + + resource_types = self.db_api.metadef_resource_type_get_all( + self.context) + + test_len = len(resource_types_orig) + 1 + self.assertEqual(len(resource_types), test_len) + + +class MetadefResourceTypeAssociationTests(object): + + def test_association_create(self): + ns_fixture = build_namespace_fixture() + ns_created = self.db_api.metadef_namespace_create( + self.context, ns_fixture) + self.assertIsNotNone(ns_created) + self._assert_saved_fields(ns_fixture, ns_created) + + assn_fixture = build_association_fixture() + assn_created = self.db_api.metadef_resource_type_association_create( + self.context, ns_created['namespace'], assn_fixture) + self.assertIsNotNone(assn_created) + self._assert_saved_fields(assn_fixture, assn_created) + + def test_association_delete(self): + ns_fixture = build_namespace_fixture() + ns_created = self.db_api.metadef_namespace_create( + self.context, ns_fixture) + self.assertIsNotNone(ns_created, "Could not create a namespace.") + self._assert_saved_fields(ns_fixture, ns_created) + + fixture = build_association_fixture() + created = self.db_api.metadef_resource_type_association_create( + self.context, ns_created['namespace'], fixture) + self.assertIsNotNone(created, "Could not create an association.") + + created_resource = self.db_api.metadef_resource_type_get( + self.context, fixture['name']) + self.assertIsNotNone(created_resource, "resource_type not created") + + self.db_api.metadef_resource_type_association_delete( + self.context, ns_created['namespace'], created_resource['name']) + self.assertRaises(exception.NotFound, + self.db_api.metadef_resource_type_association_get, + self.context, ns_created['namespace'], + created_resource['name']) + + def test_association_get_all_by_namespace(self): + ns_fixture = build_namespace_fixture() + ns_created = self.db_api.metadef_namespace_create( + self.context, ns_fixture) + self.assertIsNotNone(ns_created, "Could not create a namespace.") + self._assert_saved_fields(ns_fixture, ns_created) + + fixture = build_association_fixture() + created = self.db_api.metadef_resource_type_association_create( + self.context, ns_created['namespace'], fixture) + self.assertIsNotNone(created, "Could not create an association.") + + found = self.db_api.\ + metadef_resource_type_association_get_all_by_namespace( + self.context, ns_created['namespace']) + self.assertEqual(len(found), 1) + for item in found: + self._assert_saved_fields(fixture, item) + + +class MetadefDriverTests(MetadefNamespaceTests, + MetadefResourceTypeTests, + MetadefResourceTypeAssociationTests, + MetadefPropertyTests, + MetadefObjectTests): + # collection class + pass diff --git a/glance/tests/functional/db/test_registry.py b/glance/tests/functional/db/test_registry.py index b9bb6eb78f..b4a2004f2b 100644 --- a/glance/tests/functional/db/test_registry.py +++ b/glance/tests/functional/db/test_registry.py @@ -20,6 +20,7 @@ import glance.db from glance.tests import functional import glance.tests.functional.db as db_tests from glance.tests.functional.db import base +from glance.tests.functional.db import base_metadef CONF = cfg.CONF @@ -85,3 +86,17 @@ class TestRegistryQuota(base.DriverQuotaTests, FunctionalInitWrapper): def tearDown(self): self.registry_server.stop() super(TestRegistryQuota, self).tearDown() + + +class TestRegistryMetadefDriver(base_metadef.TestMetadefDriver, + base_metadef.MetadefDriverTests, + FunctionalInitWrapper): + + def setUp(self): + db_tests.load(get_db, reset_db) + super(TestRegistryMetadefDriver, self).setUp() + self.addCleanup(db_tests.reset) + + def tearDown(self): + self.registry_server.stop() + super(TestRegistryMetadefDriver, self).tearDown() diff --git a/glance/tests/functional/db/test_sqlalchemy.py b/glance/tests/functional/db/test_sqlalchemy.py index 668d237d81..3a50bbb80f 100644 --- a/glance/tests/functional/db/test_sqlalchemy.py +++ b/glance/tests/functional/db/test_sqlalchemy.py @@ -20,8 +20,10 @@ from oslo.db import options from glance.common import exception import glance.db.sqlalchemy.api from glance.db.sqlalchemy import models as db_models +from glance.db.sqlalchemy import models_metadef as metadef_models import glance.tests.functional.db as db_tests from glance.tests.functional.db import base +from glance.tests.functional.db import base_metadef CONF = cfg.CONF @@ -38,6 +40,11 @@ def reset_db(db_api): db_models.register_models(db_api.get_engine()) +def reset_db_metadef(db_api): + metadef_models.unregister_models(db_api.get_engine()) + metadef_models.register_models(db_api.get_engine()) + + class TestSqlAlchemyDriver(base.TestDriver, base.DriverTests): def setUp(self): @@ -136,3 +143,12 @@ class TestSqlAlchemyQuota(base.DriverQuotaTests): db_tests.load(get_db, reset_db) super(TestSqlAlchemyQuota, self).setUp() self.addCleanup(db_tests.reset) + + +class TestMetadefSqlAlchemyDriver(base_metadef.TestMetadefDriver, + base_metadef.MetadefDriverTests): + + def setUp(self): + db_tests.load(get_db, reset_db_metadef) + super(TestMetadefSqlAlchemyDriver, self).setUp() + self.addCleanup(db_tests.reset) diff --git a/glance/tests/unit/test_migrations.py b/glance/tests/unit/test_migrations.py index 7956c5a721..acac4bafbc 100644 --- a/glance/tests/unit/test_migrations.py +++ b/glance/tests/unit/test_migrations.py @@ -1338,3 +1338,125 @@ class TestMigrations(test_utils.BaseTestCase): def _post_downgrade_034(self, engine): images = get_table(engine, 'images') self.assertNotIn('virtual_size', images.c) + + def _pre_upgrade_035(self, engine): + self.assertRaises(sqlalchemy.exc.NoSuchTableError, + get_table, engine, 'metadef_namespaces') + self.assertRaises(sqlalchemy.exc.NoSuchTableError, + get_table, engine, 'metadef_properties') + self.assertRaises(sqlalchemy.exc.NoSuchTableError, + get_table, engine, 'metadef_objects') + self.assertRaises(sqlalchemy.exc.NoSuchTableError, + get_table, engine, 'metadef_resource_types') + self.assertRaises(sqlalchemy.exc.NoSuchTableError, + get_table, engine, + 'metadef_namespace_resource_types') + + def _check_035(self, engine, data): + meta = sqlalchemy.MetaData() + meta.bind = engine + + # metadef_namespaces + table = sqlalchemy.Table("metadef_namespaces", meta, autoload=True) + index_namespace = ('ix_namespaces_namespace', ['namespace']) + index_data = [(idx.name, idx.columns.keys()) + for idx in table.indexes] + self.assertIn(index_namespace, index_data) + + expected_cols = [u'id', + u'namespace', + u'display_name', + u'description', + u'visibility', + u'protected', + u'owner', + u'created_at', + u'updated_at'] + col_data = [col.name for col in table.columns] + self.assertEqual(expected_cols, col_data) + + # metadef_objects + table = sqlalchemy.Table("metadef_objects", meta, autoload=True) + index_namespace_id_name = ( + 'ix_objects_namespace_id_name', ['namespace_id', 'name']) + index_data = [(idx.name, idx.columns.keys()) + for idx in table.indexes] + self.assertIn(index_namespace_id_name, index_data) + + expected_cols = [u'id', + u'namespace_id', + u'name', + u'description', + u'required', + u'schema', + u'created_at', + u'updated_at'] + col_data = [col.name for col in table.columns] + self.assertEqual(expected_cols, col_data) + + # metadef_properties + table = sqlalchemy.Table("metadef_properties", meta, autoload=True) + index_namespace_id_name = ( + 'ix_metadef_properties_namespace_id_name', + ['namespace_id', 'name']) + index_data = [(idx.name, idx.columns.keys()) + for idx in table.indexes] + self.assertIn(index_namespace_id_name, index_data) + + expected_cols = [u'id', + u'namespace_id', + u'name', + u'schema', + u'created_at', + u'updated_at'] + col_data = [col.name for col in table.columns] + self.assertEqual(expected_cols, col_data) + + # metadef_resource_types + table = sqlalchemy.Table( + "metadef_resource_types", meta, autoload=True) + index_resource_types_name = ( + 'ix_metadef_resource_types_name', ['name']) + index_data = [(idx.name, idx.columns.keys()) + for idx in table.indexes] + self.assertIn(index_resource_types_name, index_data) + + expected_cols = [u'id', + u'name', + u'protected', + u'created_at', + u'updated_at'] + col_data = [col.name for col in table.columns] + self.assertEqual(expected_cols, col_data) + + # metadef_namespace_resource_types + table = sqlalchemy.Table( + "metadef_namespace_resource_types", meta, autoload=True) + index_ns_res_types_res_type_id_ns_id = ( + 'ix_metadef_ns_res_types_res_type_id_ns_id', + ['resource_type_id', 'namespace_id']) + index_data = [(idx.name, idx.columns.keys()) + for idx in table.indexes] + self.assertIn(index_ns_res_types_res_type_id_ns_id, index_data) + + expected_cols = [u'resource_type_id', + u'namespace_id', + u'properties_target', + u'prefix', + u'created_at', + u'updated_at'] + col_data = [col.name for col in table.columns] + self.assertEqual(expected_cols, col_data) + + def _post_downgrade_035(self, engine): + self.assertRaises(sqlalchemy.exc.NoSuchTableError, + get_table, engine, 'metadef_namespaces') + self.assertRaises(sqlalchemy.exc.NoSuchTableError, + get_table, engine, 'metadef_properties') + self.assertRaises(sqlalchemy.exc.NoSuchTableError, + get_table, engine, 'metadef_objects') + self.assertRaises(sqlalchemy.exc.NoSuchTableError, + get_table, engine, 'metadef_resource_types') + self.assertRaises(sqlalchemy.exc.NoSuchTableError, + get_table, engine, + 'metadef_namespace_resource_types')