Adding Metadef Tag support

Adding rest api and db support for CRUD operations on the new
metadef_tags table.

Implements: https://blueprints.launchpad.net/glance/+spec/metadefs-tags
DocImpact

Change-Id: Icfa40555280ce69766381b0abe7ef399b806f6a0
This commit is contained in:
Wayne Okuma 2014-11-11 14:13:51 -08:00
parent 81b3c04eca
commit c7fa300cc5
28 changed files with 2340 additions and 16 deletions

View File

@ -47,6 +47,12 @@
"get_metadef_property":"",
"get_metadef_properties":"",
"modify_metadef_property":"",
"add_metadef_property":""
"add_metadef_property":"",
"get_metadef_tag":"",
"get_metadef_tags":"",
"modify_metadef_tag":"",
"add_metadef_tag":"",
"add_metadef_tags":""
}

View File

@ -815,3 +815,88 @@ class MetadefPropertyRepoProxy(glance.domain.proxy.MetadefPropertyRepo):
*args, **kwargs)
return [proxy_namespace_property(self.context, namespace_property) for
namespace_property in namespace_properties]
# Metadef Tag classes
def is_tag_mutable(context, tag):
"""Return True if the tag is mutable in this context."""
if context.is_admin:
return True
if context.owner is None:
return False
return tag.namespace.owner == context.owner
def proxy_tag(context, tag):
if is_tag_mutable(context, tag):
return tag
else:
return ImmutableMetadefTagProxy(tag)
class ImmutableMetadefTagProxy(object):
def __init__(self, base):
self.base = base
self.resource_name = 'tag'
tag_id = _immutable_attr('base', 'tag_id')
name = _immutable_attr('base', 'name')
created_at = _immutable_attr('base', 'created_at')
updated_at = _immutable_attr('base', 'updated_at')
def delete(self):
message = _("You are not permitted to delete this tag.")
raise exception.Forbidden(message)
def save(self):
message = _("You are not permitted to update this tag.")
raise exception.Forbidden(message)
class MetadefTagProxy(glance.domain.proxy.MetadefTag):
def __init__(self, meta_tag):
super(MetadefTagProxy, self).__init__(meta_tag)
class MetadefTagFactoryProxy(glance.domain.proxy.MetadefTagFactory):
def __init__(self, meta_tag_factory, context):
self.meta_tag_factory = meta_tag_factory
self.context = context
super(MetadefTagFactoryProxy, self).__init__(
meta_tag_factory,
meta_tag_proxy_class=MetadefTagProxy)
def new_tag(self, **kwargs):
owner = kwargs.pop('owner', self.context.owner)
if not self.context.is_admin:
if owner is None:
message = _("Owner must be specified to create a tag.")
raise exception.Forbidden(message)
elif owner != self.context.owner:
message = _("You are not permitted to create a tag"
" in the namespace owned by '%s'")
raise exception.Forbidden(message % (owner))
return super(MetadefTagFactoryProxy, self).new_tag(**kwargs)
class MetadefTagRepoProxy(glance.domain.proxy.MetadefTagRepo):
def __init__(self, tag_repo, context):
self.tag_repo = tag_repo
self.context = context
super(MetadefTagRepoProxy, self).__init__(tag_repo)
def get(self, namespace, tag_name):
meta_tag = self.tag_repo.get(namespace, tag_name)
return proxy_tag(self.context, meta_tag)
def list(self, *args, **kwargs):
tags = self.tag_repo.list(*args, **kwargs)
return [proxy_tag(self.context, meta_tag) for
meta_tag in tags]

View File

@ -594,3 +594,58 @@ class MetadefPropertyFactoryProxy(glance.domain.proxy.MetadefPropertyFactory):
namespace_property_factory,
property_proxy_class=MetadefPropertyProxy,
property_proxy_kwargs=proxy_kwargs)
# Metadef Tag classes
class MetadefTagProxy(glance.domain.proxy.MetadefTag):
def __init__(self, meta_tag, context, policy):
self.context = context
self.policy = policy
super(MetadefTagProxy, self).__init__(meta_tag)
class MetadefTagRepoProxy(glance.domain.proxy.MetadefTagRepo):
def __init__(self, tag_repo, context, tag_policy):
self.context = context
self.policy = tag_policy
self.tag_repo = tag_repo
proxy_kwargs = {'context': self.context, 'policy': self.policy}
super(MetadefTagRepoProxy,
self).__init__(tag_repo,
tag_proxy_class=MetadefTagProxy,
tag_proxy_kwargs=proxy_kwargs)
def get(self, namespace, tag_name):
self.policy.enforce(self.context, 'get_metadef_tag', {})
return super(MetadefTagRepoProxy, self).get(namespace, tag_name)
def list(self, *args, **kwargs):
self.policy.enforce(self.context, 'get_metadef_tags', {})
return super(MetadefTagRepoProxy, self).list(*args, **kwargs)
def save(self, meta_tag):
self.policy.enforce(self.context, 'modify_metadef_tag', {})
return super(MetadefTagRepoProxy, self).save(meta_tag)
def add(self, meta_tag):
self.policy.enforce(self.context, 'add_metadef_tag', {})
return super(MetadefTagRepoProxy, self).add(meta_tag)
def add_tags(self, meta_tags):
self.policy.enforce(self.context, 'add_metadef_tags', {})
return super(MetadefTagRepoProxy, self).add_tags(meta_tags)
class MetadefTagFactoryProxy(glance.domain.proxy.MetadefTagFactory):
def __init__(self, meta_tag_factory, context, policy):
self.meta_tag_factory = meta_tag_factory
self.context = context
self.policy = policy
proxy_kwargs = {'context': self.context, 'policy': self.policy}
super(MetadefTagFactoryProxy, self).__init__(
meta_tag_factory,
meta_tag_proxy_class=MetadefTagProxy,
meta_tag_proxy_kwargs=proxy_kwargs)

View File

@ -26,6 +26,7 @@ from glance.api.v2.model.metadef_namespace import Namespaces
from glance.api.v2.model.metadef_object import MetadefObject
from glance.api.v2.model.metadef_property_type import PropertyType
from glance.api.v2.model.metadef_resource_type import ResourceTypeAssociation
from glance.api.v2.model.metadef_tag import MetadefTag
from glance.common import exception
from glance.common import utils
from glance.common import wsgi
@ -54,6 +55,7 @@ class NamespaceController(object):
policy_enforcer=self.policy)
self.ns_schema_link = '/v2/schemas/metadefs/namespace'
self.obj_schema_link = '/v2/schemas/metadefs/object'
self.tag_schema_link = '/v2/schemas/metadefs/tag'
def index(self, req, marker=None, limit=None, sort_key='created_at',
sort_dir='desc', filters=None):
@ -112,10 +114,11 @@ class NamespaceController(object):
namespace_created = True
# Create Resource Types
rs_factory = (
self.gateway.get_metadef_resource_type_factory(req.context))
rs_repo = self.gateway.get_metadef_resource_type_repo(req.context)
if namespace.resource_type_associations:
rs_factory = (self.gateway.get_metadef_resource_type_factory(
req.context))
rs_repo = self.gateway.get_metadef_resource_type_repo(
req.context)
for resource_type in namespace.resource_type_associations:
new_resource = rs_factory.new_resource_type(
namespace=namespace.namespace,
@ -123,22 +126,34 @@ class NamespaceController(object):
rs_repo.add(new_resource)
# Create Objects
object_factory = self.gateway.get_metadef_object_factory(
req.context)
object_repo = self.gateway.get_metadef_object_repo(req.context)
if namespace.objects:
object_factory = self.gateway.get_metadef_object_factory(
req.context)
object_repo = self.gateway.get_metadef_object_repo(
req.context)
for metadata_object in namespace.objects:
new_meta_object = object_factory.new_object(
namespace=namespace.namespace,
**metadata_object.to_dict())
object_repo.add(new_meta_object)
# Create Tags
if namespace.tags:
tag_factory = self.gateway.get_metadef_tag_factory(
req.context)
tag_repo = self.gateway.get_metadef_tag_repo(req.context)
for metadata_tag in namespace.tags:
new_meta_tag = tag_factory.new_tag(
namespace=namespace.namespace,
**metadata_tag.to_dict())
tag_repo.add(new_meta_tag)
# Create Namespace Properties
prop_factory = (
self.gateway.get_metadef_property_factory(req.context))
prop_repo = self.gateway.get_metadef_property_repo(req.context)
if namespace.properties:
prop_factory = (self.gateway.get_metadef_property_factory(
req.context))
prop_repo = self.gateway.get_metadef_property_repo(
req.context)
for (name, value) in namespace.properties.items():
new_property_type = (
prop_factory.new_namespace_property(
@ -165,6 +180,7 @@ class NamespaceController(object):
new_namespace.objects = namespace.objects
new_namespace.resource_type_associations = (
namespace.resource_type_associations)
new_namespace.tags = namespace.tags
return Namespace.to_wsme_model(new_namespace,
get_namespace_href(new_namespace),
self.ns_schema_link)
@ -232,6 +248,14 @@ class NamespaceController(object):
namespace_detail = self._prefix_property_name(
namespace_detail, filters['resource_type'])
# Get tags
tag_repo = self.gateway.get_metadef_tag_repo(req.context)
db_metatag_list = tag_repo.list(filters=ns_filters)
tag_list = [MetadefTag(**{'name': db_metatag.name})
for db_metatag in db_metatag_list]
if tag_list:
namespace_detail.tags = tag_list
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=e.msg)
except exception.NotFound as e:
@ -299,6 +323,20 @@ class NamespaceController(object):
LOG.error(utils.exception_to_str(e))
raise webob.exc.HTTPInternalServerError()
def delete_tags(self, req, namespace):
ns_repo = self.gateway.get_metadef_namespace_repo(req.context)
try:
namespace_obj = ns_repo.get(namespace)
namespace_obj.delete()
ns_repo.remove_tags(namespace_obj)
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=e.msg)
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.msg)
except Exception as e:
LOG.error(utils.exception_to_str(e))
raise webob.exc.HTTPInternalServerError()
def delete_properties(self, req, namespace):
ns_repo = self.gateway.get_metadef_namespace_repo(req.context)
try:
@ -700,7 +738,18 @@ def _get_base_properties():
},
}
}
}
},
"tags": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
}
}
},
}
@ -733,6 +782,12 @@ def get_object_href(namespace_name, metadef_object):
return base_href
def get_tag_href(namespace_name, metadef_tag):
base_href = ('/v2/metadefs/namespaces/%s/tags/%s' %
(namespace_name, metadef_tag.name))
return base_href
def create_resource():
"""Namespaces resource factory method"""
schema = get_schema()

View File

@ -0,0 +1,391 @@
# 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.config import cfg
from oslo.serialization import jsonutils
import six
import webob.exc
from wsme.rest import json
from glance.api import policy
from glance.api.v2.model.metadef_tag import MetadefTag
from glance.api.v2.model.metadef_tag import MetadefTags
from glance.common import exception
from glance.common import utils
from glance.common import wsgi
from glance.common import wsme_utils
import glance.db
from glance import i18n
import glance.notifier
import glance.openstack.common.log as logging
import glance.schema
LOG = logging.getLogger(__name__)
_ = i18n._
_LE = i18n._LE
_LI = i18n._LI
CONF = cfg.CONF
class TagsController(object):
def __init__(self, db_api=None, policy_enforcer=None):
self.db_api = db_api or glance.db.get_api()
self.policy = policy_enforcer or policy.Enforcer()
self.gateway = glance.gateway.Gateway(db_api=self.db_api,
policy_enforcer=self.policy)
self.tag_schema_link = '/v2/schemas/metadefs/tag'
def create(self, req, metadata_tag, namespace):
tag_factory = self.gateway.get_metadef_tag_factory(req.context)
tag_repo = self.gateway.get_metadef_tag_repo(req.context)
try:
new_meta_tag = tag_factory.new_tag(
namespace=namespace,
**metadata_tag.to_dict())
tag_repo.add(new_meta_tag)
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=e.msg)
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Duplicate as e:
raise webob.exc.HTTPConflict(explanation=e.msg)
except Exception as e:
LOG.error(utils.exception_to_str(e))
raise webob.exc.HTTPInternalServerError()
return MetadefTag.to_wsme_model(new_meta_tag)
def create_tags(self, req, metadata_tags, namespace):
tag_factory = self.gateway.get_metadef_tag_factory(req.context)
tag_repo = self.gateway.get_metadef_tag_repo(req.context)
try:
tag_list = []
for metadata_tag in metadata_tags.tags:
tag_list.append(tag_factory.new_tag(
namespace=namespace, **metadata_tag.to_dict()))
tag_repo.add_tags(tag_list)
tag_list_out = [MetadefTag(**{'name': db_metatag.name})
for db_metatag in tag_list]
metadef_tags = MetadefTags()
metadef_tags.tags = tag_list_out
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=e.msg)
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Duplicate as e:
raise webob.exc.HTTPConflict(explanation=e.msg)
except Exception as e:
LOG.error(utils.exception_to_str(e))
raise webob.exc.HTTPInternalServerError()
return metadef_tags
def index(self, req, namespace, marker=None, limit=None,
sort_key='created_at', sort_dir='desc', filters=None):
try:
filters = filters or dict()
filters['namespace'] = namespace
tag_repo = self.gateway.get_metadef_tag_repo(req.context)
if marker:
metadef_tag = tag_repo.get(namespace, marker)
marker = metadef_tag.tag_id
db_metatag_list = tag_repo.list(
marker=marker, limit=limit, sort_key=sort_key,
sort_dir=sort_dir, filters=filters)
tag_list = [MetadefTag(**{'name': db_metatag.name})
for db_metatag in db_metatag_list]
metadef_tags = MetadefTags()
metadef_tags.tags = tag_list
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=e.msg)
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.msg)
except Exception as e:
LOG.error(utils.exception_to_str(e))
raise webob.exc.HTTPInternalServerError()
return metadef_tags
def show(self, req, namespace, tag_name):
meta_tag_repo = self.gateway.get_metadef_tag_repo(req.context)
try:
metadef_tag = meta_tag_repo.get(namespace, tag_name)
return MetadefTag.to_wsme_model(metadef_tag)
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=e.msg)
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.msg)
except Exception as e:
LOG.error(utils.exception_to_str(e))
raise webob.exc.HTTPInternalServerError()
def update(self, req, metadata_tag, namespace, tag_name):
meta_repo = self.gateway.get_metadef_tag_repo(req.context)
try:
metadef_tag = meta_repo.get(namespace, tag_name)
metadef_tag.name = wsme_utils._get_value(
metadata_tag.name)
updated_metadata_tag = meta_repo.save(metadef_tag)
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=e.msg)
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Duplicate as e:
raise webob.exc.HTTPConflict(explanation=e.msg)
except Exception as e:
LOG.error(utils.exception_to_str(e))
raise webob.exc.HTTPInternalServerError()
return MetadefTag.to_wsme_model(updated_metadata_tag)
def delete(self, req, namespace, tag_name):
meta_repo = self.gateway.get_metadef_tag_repo(req.context)
try:
metadef_tag = meta_repo.get(namespace, tag_name)
metadef_tag.delete()
meta_repo.remove(metadef_tag)
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=e.msg)
except exception.NotFound as e:
raise webob.exc.HTTPNotFound(explanation=e.msg)
except Exception as e:
LOG.error(utils.exception_to_str(e))
raise webob.exc.HTTPInternalServerError()
def _get_base_definitions():
return None
def _get_base_properties():
return {
"name": {
"type": "string"
},
"created_at": {
"type": "string",
"description": _("Date and time of tag creation"
" (READ-ONLY)"),
"format": "date-time"
},
"updated_at": {
"type": "string",
"description": _("Date and time of the last tag modification"
" (READ-ONLY)"),
"format": "date-time"
}
}
def _get_base_properties_for_list():
return {
"tags": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
}
},
'required': ['name'],
"additionalProperties": False
}
},
}
def get_schema():
definitions = _get_base_definitions()
properties = _get_base_properties()
mandatory_attrs = MetadefTag.get_mandatory_attrs()
schema = glance.schema.Schema(
'tag',
properties,
required=mandatory_attrs,
definitions=definitions,
)
return schema
def get_schema_for_list():
definitions = _get_base_definitions()
properties = _get_base_properties_for_list()
schema = glance.schema.Schema(
'tags',
properties,
required=None,
definitions=definitions,
)
return schema
def get_collection_schema():
tag_schema = get_schema()
return glance.schema.CollectionSchema('tags', tag_schema)
class RequestDeserializer(wsgi.JSONRequestDeserializer):
_disallowed_properties = ['created_at', 'updated_at']
def __init__(self, schema=None):
super(RequestDeserializer, self).__init__()
self.schema = schema or get_schema()
self.schema_for_list = get_schema_for_list()
def _get_request_body(self, request):
output = super(RequestDeserializer, self).default(request)
if 'body' not in output:
msg = _('Body expected in request.')
raise webob.exc.HTTPBadRequest(explanation=msg)
return output['body']
def _validate_sort_dir(self, sort_dir):
if sort_dir not in ['asc', 'desc']:
msg = _('Invalid sort direction: %s') % sort_dir
raise webob.exc.HTTPBadRequest(explanation=msg)
return sort_dir
def _get_filters(self, filters):
visibility = filters.get('visibility')
if visibility:
if visibility not in ['public', 'private', 'shared']:
msg = _('Invalid visibility value: %s') % visibility
raise webob.exc.HTTPBadRequest(explanation=msg)
return filters
def _validate_limit(self, limit):
try:
limit = int(limit)
except ValueError:
msg = _("limit param must be an integer")
raise webob.exc.HTTPBadRequest(explanation=msg)
if limit < 0:
msg = _("limit param must be positive")
raise webob.exc.HTTPBadRequest(explanation=msg)
return limit
def _create_or_update(self, request):
body = self._get_request_body(request)
self._check_allowed(body)
try:
self.schema.validate(body)
except exception.InvalidObject as e:
raise webob.exc.HTTPBadRequest(explanation=e.msg)
metadata_tag = json.fromjson(MetadefTag, body)
return dict(metadata_tag=metadata_tag)
def index(self, request):
params = request.params.copy()
limit = params.pop('limit', None)
marker = params.pop('marker', None)
sort_dir = params.pop('sort_dir', 'desc')
query_params = {
'sort_key': params.pop('sort_key', 'created_at'),
'sort_dir': self._validate_sort_dir(sort_dir),
'filters': self._get_filters(params)
}
if marker:
query_params['marker'] = marker
if limit:
query_params['limit'] = self._validate_limit(limit)
return query_params
def create(self, request):
return self._create_or_update(request)
def create_tags(self, request):
body = self._get_request_body(request)
self._check_allowed(body)
try:
self.schema_for_list.validate(body)
except exception.InvalidObject as e:
raise webob.exc.HTTPBadRequest(explanation=e.msg)
metadata_tags = json.fromjson(MetadefTags, body)
return dict(metadata_tags=metadata_tags)
def update(self, request):
return self._create_or_update(request)
@classmethod
def _check_allowed(cls, image):
for key in cls._disallowed_properties:
if key in image:
msg = _("Attribute '%s' is read-only.") % key
raise webob.exc.HTTPForbidden(explanation=msg)
class ResponseSerializer(wsgi.JSONResponseSerializer):
def __init__(self, schema=None):
super(ResponseSerializer, self).__init__()
self.schema = schema or get_schema()
def create(self, response, metadata_tag):
response.status_int = 201
self.show(response, metadata_tag)
def create_tags(self, response, result):
response.status_int = 201
metadata_tags_json = json.tojson(MetadefTags, result)
body = jsonutils.dumps(metadata_tags_json, ensure_ascii=False)
response.unicode_body = six.text_type(body)
response.content_type = 'application/json'
def show(self, response, metadata_tag):
metadata_tag_json = json.tojson(MetadefTag, metadata_tag)
body = jsonutils.dumps(metadata_tag_json, ensure_ascii=False)
response.unicode_body = six.text_type(body)
response.content_type = 'application/json'
def update(self, response, metadata_tag):
response.status_int = 200
self.show(response, metadata_tag)
def index(self, response, result):
metadata_tags_json = json.tojson(MetadefTags, result)
body = jsonutils.dumps(metadata_tags_json, ensure_ascii=False)
response.unicode_body = six.text_type(body)
response.content_type = 'application/json'
def delete(self, response, result):
response.status_int = 204
def get_tag_href(namespace_name, metadef_tag):
base_href = ('/v2/metadefs/namespaces/%s/tags/%s' %
(namespace_name, metadef_tag.name))
return base_href
def create_resource():
"""Metadef tags resource factory method"""
schema = get_schema()
deserializer = RequestDeserializer(schema)
serializer = ResponseSerializer(schema)
controller = TagsController()
return wsgi.Resource(controller, deserializer, serializer)

View File

@ -20,6 +20,7 @@ from wsme import types
from glance.api.v2.model.metadef_object import MetadefObject
from glance.api.v2.model.metadef_property_type import PropertyType
from glance.api.v2.model.metadef_resource_type import ResourceTypeAssociation
from glance.api.v2.model.metadef_tag import MetadefTag
from glance.common.wsme_utils import WSMEModelTransformer
@ -43,6 +44,7 @@ class Namespace(types.Base, WSMEModelTransformer):
mandatory=False)
properties = wsme.wsattr({types.text: PropertyType}, mandatory=False)
objects = wsme.wsattr([MetadefObject], mandatory=False)
tags = wsme.wsattr([MetadefTag], mandatory=False)
# Generated fields
self = wsme.wsattr(types.text, mandatory=False)

View File

@ -0,0 +1,34 @@
# 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 wsme
from wsme import types
from glance.common import wsme_utils
class MetadefTag(types.Base, wsme_utils.WSMEModelTransformer):
name = wsme.wsattr(types.text, mandatory=True)
# Not using datetime since time format has to be
# in oslo.utils.timeutils.isotime() format
created_at = wsme.wsattr(types.text, mandatory=False)
updated_at = wsme.wsattr(types.text, mandatory=False)
class MetadefTags(types.Base, wsme_utils.WSMEModelTransformer):
tags = wsme.wsattr([MetadefTag], mandatory=False)

View File

@ -21,6 +21,7 @@ from glance.api.v2 import metadef_namespaces
from glance.api.v2 import metadef_objects
from glance.api.v2 import metadef_properties
from glance.api.v2 import metadef_resource_types
from glance.api.v2 import metadef_tags
from glance.api.v2 import schemas
from glance.api.v2 import tasks
from glance.common import wsgi
@ -186,6 +187,28 @@ class API(wsgi.Router):
conditions={'method': ['POST', 'PUT', 'DELETE',
'PATCH', 'HEAD']})
mapper.connect('/schemas/metadefs/tag',
controller=schemas_resource,
action='metadef_tag',
conditions={'method': ['GET']})
mapper.connect('/schemas/metadefs/tag',
controller=reject_method_resource,
action='reject',
allowed_methods='GET',
conditions={'method': ['POST', 'PUT', 'DELETE',
'PATCH', 'HEAD']})
mapper.connect('/schemas/metadefs/tags',
controller=schemas_resource,
action='metadef_tags',
conditions={'method': ['GET']})
mapper.connect('/schemas/metadefs/tags',
controller=reject_method_resource,
action='reject',
allowed_methods='GET',
conditions={'method': ['POST', 'PUT', 'DELETE',
'PATCH', 'HEAD']})
# Metadef resource types
metadef_resource_types_resource = (
metadef_resource_types.create_resource())
@ -348,6 +371,48 @@ class API(wsgi.Router):
allowed_methods='GET, PUT, DELETE',
conditions={'method': ['POST', 'PATCH', 'HEAD']})
# Metadef tags
metadef_tags_resource = metadef_tags.create_resource()
mapper.connect('/metadefs/namespaces/{namespace}/tags',
controller=metadef_tags_resource,
action='index',
conditions={'method': ['GET']})
mapper.connect('/metadefs/namespaces/{namespace}/tags',
controller=metadef_tags_resource,
action='create',
conditions={'method': ['POST']})
mapper.connect('/metadefs/namespaces/{namespace}/tags',
controller=metadef_tags_resource,
action='create_tags',
conditions={'method': ['PUT']})
mapper.connect('/metadefs/namespaces/{namespace}/tags',
controller=metadef_namespace_resource,
action='delete_tags',
conditions={'method': ['DELETE']})
mapper.connect('/metadefs/namespaces/{namespace}/tags',
controller=reject_method_resource,
action='reject',
allowed_methods='GET, POST, PUT, DELETE',
conditions={'method': ['PATCH', 'HEAD']})
mapper.connect('/metadefs/namespaces/{namespace}/tags/{tag_name}',
controller=metadef_tags_resource,
action='show',
conditions={'method': ['GET']})
mapper.connect('/metadefs/namespaces/{namespace}/tags/{tag_name}',
controller=metadef_tags_resource,
action='update',
conditions={'method': ['PUT']})
mapper.connect('/metadefs/namespaces/{namespace}/tags/{tag_name}',
controller=metadef_tags_resource,
action='delete',
conditions={'method': ['DELETE']})
mapper.connect('/metadefs/namespaces/{namespace}/tags/{tag_name}',
controller=reject_method_resource,
action='reject',
allowed_methods='GET, PUT, DELETE',
conditions={'method': ['POST', 'PATCH', 'HEAD']})
images_resource = images.create_resource(custom_image_properties)
mapper.connect('/images',
controller=images_resource,

View File

@ -19,6 +19,7 @@ from glance.api.v2 import metadef_namespaces
from glance.api.v2 import metadef_objects
from glance.api.v2 import metadef_properties
from glance.api.v2 import metadef_resource_types
from glance.api.v2 import metadef_tags
from glance.api.v2 import tasks
from glance.common import wsgi
@ -50,6 +51,10 @@ class Controller(object):
self.metadef_object_collection_schema = \
metadef_objects.get_collection_schema()
self.metadef_tag_schema = metadef_tags.get_schema()
self.metadef_tag_collection_schema = (
metadef_tags.get_collection_schema())
def image(self, req):
return self.image_schema.raw()
@ -92,6 +97,12 @@ class Controller(object):
def metadef_objects(self, req):
return self.metadef_object_collection_schema.raw()
def metadef_tag(self, req):
return self.metadef_tag_schema.raw()
def metadef_tags(self, req):
return self.metadef_tag_collection_schema.raw()
def create_resource(custom_image_properties=None):
controller = Controller(custom_image_properties)

View File

@ -151,6 +151,11 @@ class ProtectedMetadefResourceTypeSystemDelete(Forbidden):
" a seeded-system type and cannot be deleted.")
class ProtectedMetadefTagDelete(Forbidden):
message = _("Metadata definition tag %(tag_name)s is protected"
" and cannot be deleted.")
class Invalid(GlanceException):
message = _("Data supplied was not valid.")
@ -381,6 +386,11 @@ class MetadefDuplicateResourceTypeAssociation(Duplicate):
" already exists.")
class MetadefDuplicateTag(Duplicate):
message = _("A metadata tag with name=%(name)s"
" already exists in namespace=%(namespace_name)s.")
class MetadefForbidden(Forbidden):
message = _("You are not authorized to complete this action.")
@ -418,3 +428,9 @@ class MetadefResourceTypeAssociationNotFound(NotFound):
" resource-type=%(resource_type_name)s to"
" namespace=%(namespace_name)s,"
" was not found.")
class MetadefTagNotFound(NotFound):
message = _("The metadata definition tag with"
" name=%(name)s was not found in"
" namespace=%(namespace_name)s.")

View File

@ -472,6 +472,16 @@ class MetadefNamespaceRepo(object):
msg = _("The specified namespace %s could not be found")
raise exception.NotFound(msg % namespace.namespace)
def remove_tags(self, namespace):
try:
self.db_api.metadef_tag_delete_namespace_content(
self.context,
namespace.namespace
)
except (exception.NotFound, exception.Forbidden):
msg = _("The specified namespace %s could not be found")
raise exception.NotFound(msg % namespace.namespace)
def object_count(self, namespace_name):
return self.db_api.metadef_object_count(
self.context,
@ -758,3 +768,92 @@ class MetadefPropertyRepo(object):
except exception.NotFound as e:
raise exception.NotFound(explanation=e.msg)
return property
class MetadefTagRepo(object):
def __init__(self, context, db_api):
self.context = context
self.db_api = db_api
self.meta_namespace_repo = MetadefNamespaceRepo(context, db_api)
def _format_metadef_tag_from_db(self, metadata_tag,
namespace_entity):
return glance.domain.MetadefTag(
namespace=namespace_entity,
tag_id=metadata_tag['id'],
name=metadata_tag['name'],
created_at=metadata_tag['created_at'],
updated_at=metadata_tag['updated_at']
)
def _format_metadef_tag_to_db(self, metadata_tag):
db_metadata_tag = {
'name': metadata_tag.name
}
return db_metadata_tag
def add(self, metadata_tag):
self.db_api.metadef_tag_create(
self.context,
metadata_tag.namespace,
self._format_metadef_tag_to_db(metadata_tag)
)
def add_tags(self, metadata_tags):
tag_list = []
namespace = None
for metadata_tag in metadata_tags:
tag_list.append(self._format_metadef_tag_to_db(metadata_tag))
if namespace is None:
namespace = metadata_tag.namespace
self.db_api.metadef_tag_create_tags(
self.context, namespace, tag_list)
def get(self, namespace, name):
try:
namespace_entity = self.meta_namespace_repo.get(namespace)
db_metadata_tag = self.db_api.metadef_tag_get(
self.context,
namespace,
name)
except (exception.NotFound, exception.Forbidden):
msg = _('Could not find metadata tag %s') % name
raise exception.NotFound(msg)
return self._format_metadef_tag_from_db(db_metadata_tag,
namespace_entity)
def list(self, marker=None, limit=None, sort_key='created_at',
sort_dir='desc', filters=None):
namespace = filters['namespace']
namespace_entity = self.meta_namespace_repo.get(namespace)
db_metadata_tag = self.db_api.metadef_tag_get_all(
self.context, namespace, filters, marker, limit, sort_key,
sort_dir)
return [self._format_metadef_tag_from_db(metadata_tag,
namespace_entity)
for metadata_tag in db_metadata_tag]
def remove(self, metadata_tag):
try:
self.db_api.metadef_tag_delete(
self.context,
metadata_tag.namespace.namespace,
metadata_tag.name
)
except (exception.NotFound, exception.Forbidden):
msg = _("The specified metadata tag %s could not be found")
raise exception.NotFound(msg % metadata_tag.name)
def save(self, metadata_tag):
try:
self.db_api.metadef_tag_update(
self.context, metadata_tag.namespace.namespace,
metadata_tag.tag_id,
self._format_metadef_tag_to_db(metadata_tag))
except exception.NotFound as e:
raise exception.NotFound(explanation=e.msg)
return metadata_tag

View File

@ -499,3 +499,58 @@ def metadef_resource_type_association_get_all_by_namespace(
namespace_name, session=None):
return client.metadef_resource_type_association_get_all_by_namespace(
namespace_name=namespace_name)
@_get_client
def metadef_tag_get_all(client, namespace_name, filters=None, marker=None,
limit=None, sort_key='created_at', sort_dir=None,
session=None):
return client.metadef_tag_get_all(
namespace_name=namespace_name, filters=filters, marker=marker,
limit=limit, sort_key=sort_key, sort_dir=sort_dir, session=session)
@_get_client
def metadef_tag_get(client, namespace_name, name, session=None):
return client.metadef_tag_get(
namespace_name=namespace_name, name=name)
@_get_client
def metadef_tag_create(
client, namespace_name, tag_dict, session=None):
return client.metadef_tag_create(
namespace_name=namespace_name, tag_dict=tag_dict)
@_get_client
def metadef_tag_create_tags(
client, namespace_name, tag_list, session=None):
return client.metadef_tag_create_tags(
namespace_name=namespace_name, tag_list=tag_list)
@_get_client
def metadef_tag_update(
client, namespace_name, id, tag_dict, session=None):
return client.metadef_tag_update(
namespace_name=namespace_name, id=id, tag_dict=tag_dict)
@_get_client
def metadef_tag_delete(
client, namespace_name, name, session=None):
return client.metadef_tag_delete(
namespace_name=namespace_name, name=name)
@_get_client
def metadef_tag_delete_namespace_content(
client, namespace_name, session=None):
return client.metadef_tag_delete_namespace_content(
namespace_name=namespace_name)
@_get_client
def metadef_tag_count(client, namespace_name, session=None):
return client.metadef_tag_count(namespace_name=namespace_name)

View File

@ -39,6 +39,7 @@ DATA = {
'metadef_objects': [],
'metadef_properties': [],
'metadef_resource_types': [],
'metadef_tags': [],
'tags': {},
'locations': [],
'tasks': {},
@ -74,6 +75,7 @@ def reset():
'metadef_objects': [],
'metadef_properties': [],
'metadef_resource_types': [],
'metadef_tags': [],
'tags': {},
'locations': [],
'tasks': {},
@ -1668,6 +1670,200 @@ def metadef_resource_type_association_delete(context, namespace_name,
return resource_type
@log_call
def metadef_tag_get(context, namespace_name, name):
"""Get a metadef tag"""
namespace = metadef_namespace_get(context, namespace_name)
_check_namespace_visibility(context, namespace, namespace_name)
for tag in DATA['metadef_tags']:
if (tag['namespace_id'] == namespace['id'] and
tag['name'] == name):
return tag
else:
msg = ("The metadata definition tag with name=%(name)s"
" was not found in namespace=%(namespace_name)s."
% {'name': name, 'namespace_name': namespace_name})
LOG.debug(msg)
raise exception.MetadefTagNotFound(name=name,
namespace_name=namespace_name)
@log_call
def metadef_tag_get_by_id(context, namespace_name, id):
"""Get a metadef tag"""
namespace = metadef_namespace_get(context, namespace_name)
_check_namespace_visibility(context, namespace, namespace_name)
for tag in DATA['metadef_tags']:
if (tag['namespace_id'] == namespace['id'] and
tag['id'] == id):
return tag
else:
msg = (_("Metadata definition tag not found for id=%s") % id)
LOG.warn(msg)
raise exception.MetadefTagNotFound(msg)
@log_call
def metadef_tag_get_all(context, namespace_name, filters=None, marker=None,
limit=None, sort_key='created_at', sort_dir=None,
session=None):
"""Get a metadef tags list"""
namespace = metadef_namespace_get(context, namespace_name)
_check_namespace_visibility(context, namespace, namespace_name)
tags = []
for tag in DATA['metadef_tags']:
if tag['namespace_id'] == namespace['id']:
tags.append(tag)
return tags
@log_call
def metadef_tag_create(context, namespace_name, values):
"""Create a metadef tag"""
global DATA
tag_values = copy.deepcopy(values)
tag_name = tag_values['name']
required_attributes = ['name']
allowed_attributes = ['name']
namespace = metadef_namespace_get(context, namespace_name)
for tag in DATA['metadef_tags']:
if (tag['name'] == tag_name and
tag['namespace_id'] == namespace['id']):
msg = ("A metadata definition tag with name=%(name)s"
" in namespace=%(namespace_name)s already exists."
% {'name': tag_name, 'namespace_name': namespace_name})
LOG.debug(msg)
raise exception.MetadefDuplicateTag(
name=tag_name, namespace_name=namespace_name)
for key in required_attributes:
if key not in tag_values:
raise exception.Invalid('%s is a required attribute' % key)
incorrect_keys = set(tag_values.keys()) - set(allowed_attributes)
if incorrect_keys:
raise exception.Invalid(
'The keys %s are not valid' % str(incorrect_keys))
tag_values['namespace_id'] = namespace['id']
_check_namespace_visibility(context, namespace, namespace_name)
tag = _format_tag(tag_values)
DATA['metadef_tags'].append(tag)
return tag
@log_call
def metadef_tag_create_tags(context, namespace_name, tag_list):
"""Create a metadef tag"""
global DATA
namespace = metadef_namespace_get(context, namespace_name)
_check_namespace_visibility(context, namespace, namespace_name)
required_attributes = ['name']
allowed_attributes = ['name']
data_tag_list = []
tag_name_list = []
for tag_value in tag_list:
tag_values = copy.deepcopy(tag_value)
tag_name = tag_values['name']
for key in required_attributes:
if key not in tag_values:
raise exception.Invalid('%s is a required attribute' % key)
incorrect_keys = set(tag_values.keys()) - set(allowed_attributes)
if incorrect_keys:
raise exception.Invalid(
'The keys %s are not valid' % str(incorrect_keys))
if tag_name in tag_name_list:
msg = ("A metadata definition tag with name=%(name)s"
" in namespace=%(namespace_name)s already exists."
% {'name': tag_name, 'namespace_name': namespace_name})
LOG.debug(msg)
raise exception.MetadefDuplicateTag(
name=tag_name, namespace_name=namespace_name)
else:
tag_name_list.append(tag_name)
tag_values['namespace_id'] = namespace['id']
data_tag_list.append(_format_tag(tag_values))
DATA['metadef_tags'] = []
for tag in data_tag_list:
DATA['metadef_tags'].append(tag)
return data_tag_list
@log_call
def metadef_tag_update(context, namespace_name, id, values):
"""Update a metadef tag"""
global DATA
namespace = metadef_namespace_get(context, namespace_name)
_check_namespace_visibility(context, namespace, namespace_name)
tag = metadef_tag_get_by_id(context, namespace_name, id)
if tag['name'] != values['name']:
for db_tag in DATA['metadef_tags']:
if (db_tag['name'] == values['name'] and
db_tag['namespace_id'] == namespace['id']):
msg = ("Invalid update. It would result in a duplicate"
" metadata definition tag with same name=%(name)s "
" in namespace=%(namespace_name)s."
% {'name': tag['name'],
'namespace_name': namespace_name})
LOG.debug(msg)
raise exception.MetadefDuplicateTag(
name=tag['name'], namespace_name=namespace_name)
DATA['metadef_tags'].remove(tag)
tag.update(values)
tag['updated_at'] = timeutils.utcnow()
DATA['metadef_tags'].append(tag)
return tag
@log_call
def metadef_tag_delete(context, namespace_name, name):
"""Delete a metadef tag"""
global DATA
tags = metadef_tag_get(context, namespace_name, name)
DATA['metadef_tags'].remove(tags)
return tags
@log_call
def metadef_tag_count(context, namespace_name):
"""Get metadef tag count in a namespace"""
namespace = metadef_namespace_get(context, namespace_name)
_check_namespace_visibility(context, namespace, namespace_name)
count = 0
for tag in DATA['metadef_tags']:
if tag['namespace_id'] == namespace['id']:
count = count + 1
return count
def _format_association(namespace, resource_type, association_values):
association = {
'namespace_id': namespace['id'],
@ -1739,6 +1935,19 @@ def _format_object(values):
return object
def _format_tag(values):
dt = timeutils.utcnow()
tag = {
'id': _get_metadef_id(),
'namespace_id': None,
'name': None,
'created_at': dt,
'updated_at': dt
}
tag.update(values)
return tag
def _is_namespace_visible(context, namespace):
"""Return true if namespace is visible in this context"""
if context.is_admin:

View File

@ -43,6 +43,7 @@ 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
from glance.db.sqlalchemy.metadef_api import tag as metadef_tag_api
from glance.db.sqlalchemy import models
from glance import i18n
import glance.openstack.common.log as os_logging
@ -1627,3 +1628,66 @@ def metadef_resource_type_association_get_all_by_namespace(
session = session or get_session()
return metadef_association_api.\
get_all_by_namespace(context, namespace_name, session)
def metadef_tag_get_all(
context, namespace_name, filters=None, marker=None, limit=None,
sort_key=None, sort_dir=None, session=None):
"""Get metadata-schema tags or raise if none exist."""
session = session or get_session()
return metadef_tag_api.get_all(
context, namespace_name, session,
filters, marker, limit, sort_key, sort_dir)
def metadef_tag_get(context, namespace_name, name, session=None):
"""Get a metadata-schema tag or raise if it does not exist."""
session = session or get_session()
return metadef_tag_api.get(
context, namespace_name, name, session)
def metadef_tag_create(context, namespace_name, tag_dict,
session=None):
"""Create a metadata-schema tag or raise if it already exists."""
session = session or get_session()
return metadef_tag_api.create(
context, namespace_name, tag_dict, session)
def metadef_tag_create_tags(context, namespace_name, tag_list,
session=None):
"""Create a metadata-schema tag or raise if it already exists."""
session = get_session()
return metadef_tag_api.create_tags(
context, namespace_name, tag_list, session)
def metadef_tag_update(context, namespace_name, id, tag_dict,
session=None):
"""Update an tag or raise if it does not exist or not visible."""
session = session or get_session()
return metadef_tag_api.update(
context, namespace_name, id, tag_dict, session)
def metadef_tag_delete(context, namespace_name, name,
session=None):
"""Delete an tag or raise if namespace or tag doesn't exist."""
session = session or get_session()
return metadef_tag_api.delete(
context, namespace_name, name, session)
def metadef_tag_delete_namespace_content(
context, namespace_name, session=None):
"""Delete an tag or raise if namespace or tag doesn't exist."""
session = session or get_session()
return metadef_tag_api.delete_by_namespace_name(
context, namespace_name, session)
def metadef_tag_count(context, namespace_name, session=None):
"""Get count of tags for a namespace, raise if ns doesn't exist."""
session = session or get_session()
return metadef_tag_api.count(context, namespace_name, session)

View File

@ -71,6 +71,10 @@ def get_metadef_objects_table(meta):
return sqlalchemy.Table('metadef_objects', meta, autoload=True)
def get_metadef_tags_table(meta):
return sqlalchemy.Table('metadef_tags', meta, autoload=True)
def _get_resource_type_id(meta, name):
resource_types_table = get_metadef_resource_types_table(meta)
return resource_types_table.select().\
@ -106,6 +110,14 @@ def _get_objects(meta, namespace_id):
execute().fetchall()
def _get_tags(meta, namespace_id):
tags_table = get_metadef_tags_table(meta)
return (
tags_table.select().
where(tags_table.c.namespace_id == namespace_id).
execute().fetchall())
def _populate_metadata(meta, metadata_path=None):
if not metadata_path:
metadata_path = CONF.metadata_source_path
@ -122,6 +134,7 @@ def _populate_metadata(meta, metadata_path=None):
metadef_namespace_resource_types_tables =\
get_metadef_namespace_resource_types_table(meta)
metadef_objects_table = get_metadef_objects_table(meta)
metadef_tags_table = get_metadef_tags_table(meta)
metadef_properties_table = get_metadef_properties_table(meta)
metadef_resource_types_table = get_metadef_resource_types_table(meta)
@ -208,7 +221,7 @@ def _populate_metadata(meta, metadata_path=None):
for object in metadata.get('objects', []):
values = {
'name': object.get('name', None),
'name': object.get('name'),
'description': object.get('description', None),
'namespace_id': namespace_id,
'json_schema': json.dumps(object.get('properties', None)),
@ -216,6 +229,16 @@ def _populate_metadata(meta, metadata_path=None):
}
_insert_data_to_db(metadef_objects_table, values)
for tag in metadata.get('tags', []):
timeutils_utcnow = timeutils.utcnow()
values = {
'name': tag.get('name'),
'namespace_id': namespace_id,
'created_at': timeutils_utcnow,
'updated_at': timeutils_utcnow
}
_insert_data_to_db(metadef_tags_table, values)
LOG.info(_LI("File %s loaded to database."), file)
LOG.info(_LI("Metadata loading finished"))
@ -224,6 +247,7 @@ def _populate_metadata(meta, metadata_path=None):
def _clear_metadata(meta):
metadef_tables = [get_metadef_properties_table(meta),
get_metadef_objects_table(meta),
get_metadef_tags_table(meta),
get_metadef_namespace_resource_types_table(meta),
get_metadef_namespaces_table(meta)]
@ -262,13 +286,15 @@ def _export_data_to_file(meta, path):
'owner': namespace['owner'],
'resource_type_associations': [],
'properties': {},
'objects': []
'objects': [],
'tags': []
}
namespace_resource_types = _get_namespace_resource_types(meta,
namespace_id)
db_objects = _get_objects(meta, namespace_id)
db_properties = _get_properties(meta, namespace_id)
db_tags = _get_tags(meta, namespace_id)
resource_types = []
for namespace_resource_type in namespace_resource_types:
@ -303,6 +329,15 @@ def _export_data_to_file(meta, path):
'properties': properties
})
tags = []
for tag in db_tags:
tags.append({
"name": tag['name']
})
values.update({
'tags': tags
})
try:
file_name = ''.join([path, namespace_file_name, '.json'])
with open(file_name, 'w') as json_file:

View File

@ -287,6 +287,8 @@ def delete_cascade(context, name, session):
namespace_rec = _get_by_name(context, name, session)
with session.begin():
try:
metadef_api.tag.delete_namespace_content(
context, namespace_rec.id, session)
metadef_api.object.delete_namespace_content(
context, namespace_rec.id, session)
metadef_api.property.delete_namespace_content(

View File

@ -0,0 +1,204 @@
# 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
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, id, session):
try:
query = (session.query(models.MetadefTag).filter_by(id=id))
metadef_tag = query.one()
except sa_orm.exc.NoResultFound:
msg = (_LW("Metadata tag not found for id %s") % id)
LOG.warn(msg)
raise exc.MetadefTagNotFound(message=msg)
return metadef_tag
def _get_by_name(context, namespace_name, name, session):
namespace = namespace_api.get(context, namespace_name, session)
try:
query = (session.query(models.MetadefTag).filter_by(
name=name, namespace_id=namespace['id']))
metadef_tag = query.one()
except sa_orm.exc.NoResultFound:
msg = ("The metadata tag with name=%(name)s"
" was not found in namespace=%(namespace_name)s."
% {'name': name, 'namespace_name': namespace_name})
LOG.debug(msg)
raise exc.MetadefTagNotFound(name=name,
namespace_name=namespace_name)
return metadef_tag
def get_all(context, namespace_name, session, filters=None, marker=None,
limit=None, sort_key='created_at', sort_dir='desc'):
"""Get all tags that match zero or more filters.
:param filters: dict of filter keys and values.
:param marker: tag 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)
"""
namespace = namespace_api.get(context, namespace_name, session)
query = (session.query(models.MetadefTag).filter_by(
namespace_id=namespace['id']))
marker_tag = None
if marker is not None:
marker_tag = _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.MetadefTag,
limit=limit,
sort_keys=sort_keys,
marker=marker_tag, sort_dir=sort_dir)
metadef_tag = query.all()
metadef_tag_list = []
for tag in metadef_tag:
metadef_tag_list.append(tag.to_dict())
return metadef_tag_list
def create(context, namespace_name, values, session):
namespace = namespace_api.get(context, namespace_name, session)
values.update({'namespace_id': namespace['id']})
metadef_tag = models.MetadefTag()
metadef_utils.drop_protected_attrs(models.MetadefTag, values)
metadef_tag.update(values.copy())
try:
metadef_tag.save(session=session)
except db_exc.DBDuplicateEntry:
msg = ("A metadata tag name=%(name)s"
" in namespace=%(namespace_name)s already exists."
% {'name': metadef_tag.name,
'namespace_name': namespace_name})
LOG.debug(msg)
raise exc.MetadefDuplicateTag(
name=metadef_tag.name, namespace_name=namespace_name)
return metadef_tag.to_dict()
def create_tags(context, namespace_name, tag_list, session):
metadef_tags_list = []
if tag_list:
namespace = namespace_api.get(context, namespace_name, session)
try:
with session.begin():
query = (session.query(models.MetadefTag).filter_by(
namespace_id=namespace['id']))
query.delete(synchronize_session='fetch')
for value in tag_list:
value.update({'namespace_id': namespace['id']})
metadef_utils.drop_protected_attrs(
models.MetadefTag, value)
metadef_tag = models.MetadefTag()
metadef_tag.update(value.copy())
metadef_tag.save(session=session)
metadef_tags_list.append(metadef_tag.to_dict())
except db_exc.DBDuplicateEntry:
msg = ("A metadata tag name=%(name)s"
" in namespace=%(namespace_name)s already exists."
% {'name': metadef_tag.name,
'namespace_name': namespace_name})
LOG.debug(msg)
raise exc.MetadefDuplicateTag(
name=metadef_tag.name, namespace_name=namespace_name)
return metadef_tags_list
def get(context, namespace_name, name, session):
metadef_tag = _get_by_name(context, namespace_name, name, session)
return metadef_tag.to_dict()
def update(context, namespace_name, id, values, session):
"""Update an tag, raise if ns not found/visible or duplicate result"""
namespace_api.get(context, namespace_name, session)
metadata_tag = _get(context, id, session)
metadef_utils.drop_protected_attrs(models.MetadefTag, values)
# values['updated_at'] = timeutils.utcnow() - done by TS mixin
try:
metadata_tag.update(values.copy())
metadata_tag.save(session=session)
except db_exc.DBDuplicateEntry:
msg = ("Invalid update. It would result in a duplicate"
" metadata tag with same name=%(name)s"
" in namespace=%(namespace_name)s."
% {'name': values['name'],
'namespace_name': namespace_name})
LOG.debug(msg)
raise exc.MetadefDuplicateTag(
name=values['name'], namespace_name=namespace_name)
return metadata_tag.to_dict()
def delete(context, namespace_name, name, session):
namespace_api.get(context, namespace_name, session)
md_tag = _get_by_name(context, namespace_name, name, session)
session.delete(md_tag)
session.flush()
return md_tag.to_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.MetadefTag).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.MetadefTag.id)).filter_by(
namespace_id=namespace['id']))
return query.scalar()

View File

@ -0,0 +1,57 @@
# 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 sqlalchemy.schema import (
Column, Index, MetaData, Table, UniqueConstraint) # noqa
from glance.db.sqlalchemy.migrate_repo.schema import (
DateTime, Integer, String, create_tables, drop_tables) # noqa
def define_metadef_tags_table(meta):
_constr_kwargs = {}
metadef_tags = Table('metadef_tags',
meta,
Column('id', Integer(), primary_key=True,
nullable=False),
Column('namespace_id', Integer(),
nullable=False),
Column('name', String(80), nullable=False),
Column('created_at', DateTime(), nullable=False),
Column('updated_at', DateTime()),
UniqueConstraint('namespace_id', 'name',
**_constr_kwargs),
mysql_engine='InnoDB',
extend_existing=False)
if meta.bind.name != 'ibm_db_sa':
Index('ix_tags_namespace_id_name',
metadef_tags.c.namespace_id,
metadef_tags.c.name)
return metadef_tags
def upgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
tables = [define_metadef_tags_table(meta)]
create_tables(tables)
def downgrade(migrate_engine):
meta = MetaData()
meta.bind = migrate_engine
tables = [define_metadef_tags_table(meta)]
drop_tables(tables)

View File

@ -137,9 +137,23 @@ class MetadefResourceType(BASE_DICT, GlanceMetadefBase):
primaryjoin=id == MetadefNamespaceResourceType.resource_type_id)
class MetadefTag(BASE_DICT, GlanceMetadefBase):
"""Represents a metadata-schema tag in the data store."""
__tablename__ = 'metadef_tags'
__table_args__ = (Index('ix_metadef_tags_namespace_id',
'namespace_id', 'name'),
Index('ix_metadef_tags_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)
def register_models(engine):
"""Create database tables for all models with the given engine."""
models = (MetadefNamespace, MetadefObject, MetadefProperty,
MetadefTag,
MetadefResourceType, MetadefNamespaceResourceType)
for model in models:
model.metadata.create_all(engine)
@ -148,6 +162,7 @@ def register_models(engine):
def unregister_models(engine):
"""Drop database tables for all models with the given engine."""
models = (MetadefObject, MetadefProperty, MetadefNamespaceResourceType,
MetadefTag,
MetadefNamespace, MetadefResourceType)
for model in models:
model.metadata.drop_all(engine)

View File

@ -606,3 +606,32 @@ class MetadefPropertyFactory(object):
name,
schema
)
class MetadefTag(object):
def __init__(self, namespace, tag_id, name, created_at, updated_at):
self.namespace = namespace
self.tag_id = tag_id
self.name = name
self.created_at = created_at
self.updated_at = updated_at
def delete(self):
if self.namespace.protected:
raise exception.ProtectedMetadefTagDelete(tag_name=self.name)
class MetadefTagFactory(object):
def new_tag(self, namespace, name, **kwargs):
tag_id = str(uuid.uuid4())
created_at = timeutils.utcnow()
updated_at = created_at
return MetadefTag(
namespace,
tag_id,
name,
created_at,
updated_at
)

View File

@ -255,6 +255,11 @@ class MetadefNamespaceRepo(object):
result = self.base.remove_properties(base_item)
return self.namespace_proxy_helper.proxy(result)
def remove_tags(self, item):
base_item = self.namespace_proxy_helper.unproxy(item)
result = self.base.remove_tags(base_item)
return self.namespace_proxy_helper.proxy(result)
def save(self, item):
base_item = self.namespace_proxy_helper.unproxy(item)
result = self.base.save(base_item)
@ -464,3 +469,68 @@ class MetadefPropertyFactory(object):
def new_namespace_property(self, **kwargs):
t = self.base.new_namespace_property(**kwargs)
return self.meta_object_helper.proxy(t)
# Metadef tag classes
class MetadefTagRepo(object):
def __init__(self, base,
tag_proxy_class=None, tag_proxy_kwargs=None):
self.base = base
self.tag_proxy_helper = Helper(tag_proxy_class,
tag_proxy_kwargs)
def get(self, namespace, name):
meta_tag = self.base.get(namespace, name)
return self.tag_proxy_helper.proxy(meta_tag)
def add(self, meta_tag):
self.base.add(self.tag_proxy_helper.unproxy(meta_tag))
def add_tags(self, meta_tags):
tags_list = []
for meta_tag in meta_tags:
tags_list.append(self.tag_proxy_helper.unproxy(meta_tag))
self.base.add_tags(tags_list)
def list(self, *args, **kwargs):
tags = self.base.list(*args, **kwargs)
return [self.tag_proxy_helper.proxy(meta_tag) for meta_tag
in tags]
def remove(self, item):
base_item = self.tag_proxy_helper.unproxy(item)
result = self.base.remove(base_item)
return self.tag_proxy_helper.proxy(result)
def save(self, item):
base_item = self.tag_proxy_helper.unproxy(item)
result = self.base.save(base_item)
return self.tag_proxy_helper.proxy(result)
class MetadefTag(object):
def __init__(self, base):
self.base = base
namespace = _proxy('base', 'namespace')
tag_id = _proxy('base', 'tag_id')
name = _proxy('base', 'name')
created_at = _proxy('base', 'created_at')
updated_at = _proxy('base', 'updated_at')
def delete(self):
self.base.delete()
class MetadefTagFactory(object):
def __init__(self,
base,
meta_tag_proxy_class=None,
meta_tag_proxy_kwargs=None):
self.meta_tag_helper = Helper(meta_tag_proxy_class,
meta_tag_proxy_kwargs)
self.base = base
def new_tag(self, **kwargs):
t = self.base.new_tag(**kwargs)
return self.meta_tag_helper.proxy(t)

View File

@ -189,3 +189,19 @@ class Gateway(object):
authorized_prop_repo = authorization.MetadefPropertyRepoProxy(
policy_prop_repo, context)
return authorized_prop_repo
def get_metadef_tag_factory(self, context):
tag_factory = glance.domain.MetadefTagFactory()
policy_tag_factory = policy.MetadefTagFactoryProxy(
tag_factory, context, self.policy)
authorized_tag_factory = authorization.MetadefTagFactoryProxy(
policy_tag_factory, context)
return authorized_tag_factory
def get_metadef_tag_repo(self, context):
tag_repo = glance.db.MetadefTagRepo(context, self.db_api)
policy_tag_repo = policy.MetadefTagRepoProxy(
tag_repo, context, self.policy)
authorized_tag_repo = authorization.MetadefTagRepoProxy(
policy_tag_repo, context)
return authorized_tag_repo

View File

@ -48,5 +48,11 @@
"get_metadef_property":"",
"get_metadef_properties":"",
"modify_metadef_property":"",
"add_metadef_property":""
"add_metadef_property":"",
"get_metadef_tag":"",
"get_metadef_tags":"",
"modify_metadef_tag":"",
"add_metadef_tag":"",
"add_metadef_tags":""
}

View File

@ -77,6 +77,23 @@ def build_property_fixture(**kwargs):
return property
def build_tag_fixture(**kwargs):
# Full testing of required and schema done via rest api tests
tag = {
'namespace_id': 1,
'name': u'test-tag-name',
}
tag.update(kwargs)
return tag
def build_tags_fixture(tag_name_list):
tag_list = []
for tag_name in tag_name_list:
tag_list.append({'name': tag_name})
return tag_list
class TestMetadefDriver(test_utils.BaseTestCase):
"""Test Driver class for Metadef tests."""
@ -469,10 +486,117 @@ class MetadefResourceTypeAssociationTests(object):
self._assert_saved_fields(fixture, item)
class MetadefTagTests(object):
def test_tag_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_tag = build_tag_fixture(namespace_id=created_ns['id'])
created_tag = self.db_api.metadef_tag_create(
self.context, created_ns['namespace'], fixture_tag)
self._assert_saved_fields(fixture_tag, created_tag)
def test_tag_create_tags(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)
tags = build_tags_fixture(['Tag1', 'Tag2', 'Tag3'])
created_tags = self.db_api.metadef_tag_create_tags(
self.context, created_ns['namespace'], tags)
actual = set([tag['name'] for tag in created_tags])
expected = set(['Tag1', 'Tag2', 'Tag3'])
self.assertEqual(expected, actual)
def test_tag_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_tag = build_tag_fixture(namespace_id=created_ns['id'])
created_tag = self.db_api.metadef_tag_create(
self.context, created_ns['namespace'], fixture_tag)
found_tag = self.db_api.metadef_tag_get(
self.context, created_ns['namespace'], created_tag['name'])
self._assert_saved_fields(fixture_tag, found_tag)
def test_tag_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_tag_fixture(namespace_id=ns_created['id'])
created_tag1 = self.db_api.metadef_tag_create(
self.context, ns_created['namespace'], fixture1)
self.assertIsNotNone(created_tag1, "Could not create tag 1.")
fixture2 = build_tag_fixture(namespace_id=ns_created['id'],
name='test-tag-2')
created_tag2 = self.db_api.metadef_tag_create(
self.context, ns_created['namespace'], fixture2)
self.assertIsNotNone(created_tag2, "Could not create tag 2.")
found = self.db_api.metadef_tag_get_all(
self.context, ns_created['namespace'], sort_key='created_at')
self.assertEqual(2, len(found))
def test_tag_update(self):
delta = {'name': u'New-name'}
fixture_ns = build_namespace_fixture()
created_ns = self.db_api.metadef_namespace_create(self.context,
fixture_ns)
self.assertIsNotNone(created_ns['namespace'])
tag_fixture = build_tag_fixture(namespace_id=created_ns['id'])
created_tag = self.db_api.metadef_tag_create(
self.context, created_ns['namespace'], tag_fixture)
self.assertIsNotNone(created_tag, "Could not create a tag.")
delta_dict = {}
delta_dict.update(delta.copy())
updated = self.db_api.metadef_tag_update(
self.context, created_ns['namespace'],
created_tag['id'], delta_dict)
self.assertEqual(delta['name'], updated['name'])
def test_tag_delete(self):
fixture_ns = build_namespace_fixture()
created_ns = self.db_api.metadef_namespace_create(
self.context, fixture_ns)
self.assertIsNotNone(created_ns['namespace'])
tag_fixture = build_tag_fixture(namespace_id=created_ns['id'])
created_tag = self.db_api.metadef_tag_create(
self.context, created_ns['namespace'], tag_fixture)
self.assertIsNotNone(created_tag, "Could not create a tag.")
self.db_api.metadef_tag_delete(
self.context, created_ns['namespace'], created_tag['name'])
self.assertRaises(exception.NotFound,
self.db_api.metadef_tag_get,
self.context, created_ns['namespace'],
created_tag['name'])
class MetadefDriverTests(MetadefNamespaceTests,
MetadefResourceTypeTests,
MetadefResourceTypeAssociationTests,
MetadefPropertyTests,
MetadefObjectTests):
MetadefObjectTests,
MetadefTagTests):
# collection class
pass

View File

@ -0,0 +1,179 @@
# 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 uuid
from oslo.serialization import jsonutils
import requests
from glance.tests import functional
TENANT1 = str(uuid.uuid4())
class TestMetadefTags(functional.FunctionalTest):
def setUp(self):
super(TestMetadefTags, self).setUp()
self.cleanup()
self.api_server.deployment_flavor = 'noauth'
self.start_servers(**self.__dict__.copy())
def _url(self, path):
return 'http://127.0.0.1:%d%s' % (self.api_port, path)
def _headers(self, custom_headers=None):
base_headers = {
'X-Identity-Status': 'Confirmed',
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
'X-Tenant-Id': TENANT1,
'X-Roles': 'admin',
}
base_headers.update(custom_headers or {})
return base_headers
def test_metadata_tags_lifecycle(self):
# Namespace should not exist
path = self._url('/v2/metadefs/namespaces/MyNamespace')
response = requests.get(path, headers=self._headers())
self.assertEqual(404, response.status_code)
# Create a namespace
path = self._url('/v2/metadefs/namespaces')
headers = self._headers({'content-type': 'application/json'})
namespace_name = 'MyNamespace'
data = jsonutils.dumps({
"namespace": namespace_name,
"display_name": "My User Friendly Namespace",
"description": "My description",
"visibility": "public",
"protected": False,
"owner": "The Test Owner"}
)
response = requests.post(path, headers=headers, data=data)
self.assertEqual(201, response.status_code)
# Metadata tags should not exist
path = self._url('/v2/metadefs/namespaces/MyNamespace/tags/tag1')
response = requests.get(path, headers=self._headers())
self.assertEqual(404, response.status_code)
# Create a tag
path = self._url('/v2/metadefs/namespaces/MyNamespace/tags')
headers = self._headers({'content-type': 'application/json'})
metadata_tag_name = "tag1"
data = jsonutils.dumps(
{
"name": metadata_tag_name
}
)
response = requests.post(path, headers=headers, data=data)
self.assertEqual(201, response.status_code)
# Get the metadata tag created above
path = self._url('/v2/metadefs/namespaces/%s/tags/%s' %
(namespace_name, metadata_tag_name))
response = requests.get(path,
headers=self._headers())
self.assertEqual(200, response.status_code)
metadata_tag = jsonutils.loads(response.text)
self.assertEqual("tag1", metadata_tag['name'])
# Returned tag should match the created tag
metadata_tag = jsonutils.loads(response.text)
checked_keys = set([
u'name',
u'created_at',
u'updated_at'
])
self.assertEqual(checked_keys, set(metadata_tag.keys()))
expected_metadata_tag = {
"name": metadata_tag_name
}
# Simple key values
checked_values = set([
u'name'
])
for key, value in expected_metadata_tag.items():
if(key in checked_values):
self.assertEqual(metadata_tag[key], value, key)
# The metadata_tag should be mutable
path = self._url('/v2/metadefs/namespaces/%s/tags/%s' %
(namespace_name, metadata_tag_name))
media_type = 'application/json'
headers = self._headers({'content-type': media_type})
metadata_tag_name = "tag1-UPDATED"
data = jsonutils.dumps(
{
"name": metadata_tag_name
}
)
response = requests.put(path, headers=headers, data=data)
self.assertEqual(200, response.status_code, response.text)
# Returned metadata_tag should reflect the changes
metadata_tag = jsonutils.loads(response.text)
self.assertEqual('tag1-UPDATED', metadata_tag['name'])
# Updates should persist across requests
path = self._url('/v2/metadefs/namespaces/%s/tags/%s' %
(namespace_name, metadata_tag_name))
response = requests.get(path, headers=self._headers())
self.assertEqual(200, response.status_code)
self.assertEqual('tag1-UPDATED', metadata_tag['name'])
# Deletion of metadata_tag_name
path = self._url('/v2/metadefs/namespaces/%s/tags/%s' %
(namespace_name, metadata_tag_name))
response = requests.delete(path, headers=self._headers())
self.assertEqual(204, response.status_code)
# metadata_tag_name should not exist
path = self._url('/v2/metadefs/namespaces/%s/tags/%s' %
(namespace_name, metadata_tag_name))
response = requests.get(path, headers=self._headers())
self.assertEqual(404, response.status_code)
# Create multiple tags.
path = self._url('/v2/metadefs/namespaces/%s/tags' %
(namespace_name))
headers = self._headers({'content-type': 'application/json'})
data = jsonutils.dumps(
{"tags": [{"name": "tag1"}, {"name": "tag2"}, {"name": "tag3"}]}
)
response = requests.put(path, headers=headers, data=data)
self.assertEqual(201, response.status_code)
# List out the three new tags.
response = requests.get(path, headers=self._headers())
self.assertEqual(200, response.status_code)
tags = jsonutils.loads(response.text)['tags']
self.assertEqual(3, len(tags))
# Attempt to create bogus duplicate tag4
data = jsonutils.dumps(
{"tags": [{"name": "tag4"}, {"name": "tag5"}, {"name": "tag4"}]}
)
response = requests.put(path, headers=headers, data=data)
self.assertEqual(409, response.status_code)
# Verify the previous 3 still exist
response = requests.get(path, headers=self._headers())
self.assertEqual(200, response.status_code)
tags = jsonutils.loads(response.text)['tags']
self.assertEqual(3, len(tags))

View File

@ -41,6 +41,12 @@ OBJECT1 = 'Object1'
OBJECT2 = 'Object2'
OBJECT3 = 'Object3'
TAG1 = 'Tag1'
TAG2 = 'Tag2'
TAG3 = 'Tag3'
TAG4 = 'Tag4'
TAG5 = 'Tag5'
RESOURCE_TYPE1 = 'ResourceType1'
RESOURCE_TYPE2 = 'ResourceType2'
RESOURCE_TYPE3 = 'ResourceType3'
@ -79,6 +85,26 @@ def _db_object_fixture(name, **kwargs):
return obj
def _db_tag_fixture(name, **kwargs):
obj = {
'name': name
}
obj.update(kwargs)
return obj
def _db_tags_fixture(names=None):
tags = []
if names:
tag_name_list = names
else:
tag_name_list = [TAG1, TAG2, TAG3]
for tag_name in tag_name_list:
tags.append(_db_tag_fixture(tag_name))
return tags
def _db_resource_type_fixture(name, **kwargs):
obj = {
'name': name,
@ -112,15 +138,19 @@ class TestMetadefRepo(test_utils.BaseTestCase):
self.db)
self.object_repo = glance.db.MetadefObjectRepo(self.context,
self.db)
self.tag_repo = glance.db.MetadefTagRepo(self.context,
self.db)
self.resource_type_repo = glance.db.\
MetadefResourceTypeRepo(self.context, self.db)
self.namespace_factory = glance.domain.MetadefNamespaceFactory()
self.property_factory = glance.domain.MetadefPropertyFactory()
self.object_factory = glance.domain.MetadefObjectFactory()
self.tag_factory = glance.domain.MetadefTagFactory()
self.resource_type_factory = glance.domain.MetadefResourceTypeFactory()
self._create_namespaces()
self._create_properties()
self._create_objects()
self._create_tags()
self._create_resource_types()
def _create_namespaces(self):
@ -179,6 +209,17 @@ class TestMetadefRepo(test_utils.BaseTestCase):
[self.db.metadef_object_create(self.context, NAMESPACE4, object)
for object in self.objects]
def _create_tags(self):
self.tags = [
_db_tag_fixture(name=TAG1),
_db_tag_fixture(name=TAG2),
_db_tag_fixture(name=TAG3),
]
[self.db.metadef_tag_create(self.context, NAMESPACE1, tag)
for tag in self.tags]
[self.db.metadef_tag_create(self.context, NAMESPACE4, tag)
for tag in self.tags]
def _create_resource_types(self):
self.resource_types = [
_db_resource_type_fixture(name=RESOURCE_TYPE1,
@ -426,3 +467,101 @@ class TestMetadefRepo(test_utils.BaseTestCase):
resource_type = self.resource_type_repo.list(
filters={'namespace': NAMESPACE1})
self.assertEqual(0, len(resource_type))
def test_get_tag(self):
tag = self.tag_repo.get(NAMESPACE1, TAG1)
namespace = self.namespace_repo.get(NAMESPACE1)
self.assertEqual(TAG1, tag.name)
self.assertEqual(namespace.namespace, tag.namespace.namespace)
def test_get_tag_not_found(self):
exc = self.assertRaises(exception.NotFound, self.tag_repo.get,
NAMESPACE2, TAG1)
self.assertIn(TAG1, utils.exception_to_str(exc))
def test_list_tag(self):
tags = self.tag_repo.list(filters={'namespace': NAMESPACE1})
tag_names = set([t.name for t in tags])
self.assertEqual(set([TAG1, TAG2, TAG3]), tag_names)
def test_list_tag_empty_result(self):
tags = self.tag_repo.list(filters={'namespace': NAMESPACE2})
tag_names = set([t.name for t in tags])
self.assertEqual(set([]), tag_names)
def test_list_tag_namespace_not_found(self):
exc = self.assertRaises(exception.NotFound, self.tag_repo.list,
filters={'namespace': 'not-a-namespace'})
self.assertIn('not-a-namespace', utils.exception_to_str(exc))
def test_add_tag(self):
# NOTE(pawel-koniszewski): Change db_tag_fixture to
# tag_factory when tag primary key in DB
# will be changed from Integer to UUID
tag = _db_tag_fixture(name='added_tag')
self.assertEqual('added_tag', tag['name'])
self.db.metadef_tag_create(self.context, NAMESPACE1, tag)
retrieved_tag = self.tag_repo.get(NAMESPACE1, 'added_tag')
self.assertEqual('added_tag', retrieved_tag.name)
def test_add_tags(self):
tags = self.tag_repo.list(filters={'namespace': NAMESPACE1})
tag_names = set([t.name for t in tags])
self.assertEqual(set([TAG1, TAG2, TAG3]), tag_names)
tags = _db_tags_fixture([TAG3, TAG4, TAG5])
self.db.metadef_tag_create_tags(self.context, NAMESPACE1, tags)
tags = self.tag_repo.list(filters={'namespace': NAMESPACE1})
tag_names = set([t.name for t in tags])
self.assertEqual(set([TAG3, TAG4, TAG5]), tag_names)
def test_add_duplicate_tags_with_pre_existing_tags(self):
tags = self.tag_repo.list(filters={'namespace': NAMESPACE1})
tag_names = set([t.name for t in tags])
self.assertEqual(set([TAG1, TAG2, TAG3]), tag_names)
tags = _db_tags_fixture([TAG5, TAG4, TAG5])
self.assertRaises(exception.Duplicate,
self.db.metadef_tag_create_tags,
self.context, NAMESPACE1, tags)
tags = self.tag_repo.list(filters={'namespace': NAMESPACE1})
tag_names = set([t.name for t in tags])
self.assertEqual(set([TAG1, TAG2, TAG3]), tag_names)
def test_add_tag_namespace_forbidden(self):
# NOTE(pawel-koniszewski): Change db_tag_fixture to
# tag_factory when tag primary key in DB
# will be changed from Integer to UUID
tag = _db_tag_fixture(name='added_tag')
self.assertEqual('added_tag', tag['name'])
self.assertRaises(exception.Forbidden, self.db.metadef_tag_create,
self.context, NAMESPACE3, tag)
def test_add_tag_namespace_not_found(self):
# NOTE(pawel-koniszewski): Change db_tag_fixture to
# tag_factory when tag primary key in DB
# will be changed from Integer to UUID
tag = _db_tag_fixture(name='added_tag')
self.assertEqual('added_tag', tag['name'])
self.assertRaises(exception.NotFound, self.db.metadef_tag_create,
self.context, 'not-a-namespace', tag)
def test_save_tag(self):
tag = self.tag_repo.get(NAMESPACE1, TAG1)
self.tag_repo.save(tag)
tag = self.tag_repo.get(NAMESPACE1, TAG1)
self.assertEqual(TAG1, tag.name)
def test_remove_tag(self):
tag = self.tag_repo.get(NAMESPACE1, TAG1)
self.tag_repo.remove(tag)
self.assertRaises(exception.NotFound, self.tag_repo.get,
NAMESPACE1, TAG1)
def test_remove_tag_not_found(self):
fake_name = 'fake_name'
tag = self.tag_repo.get(NAMESPACE1, TAG1)
tag.name = fake_name
self.assertRaises(exception.NotFound, self.tag_repo.remove, tag)

View File

@ -1336,6 +1336,28 @@ class MigrationsMixin(test_migrations.WalkVersionsMixin):
self.assertIsNone(image_member['status'])
def _pre_upgrade_038(self, engine):
self.assertRaises(sqlalchemy.exc.NoSuchTableError,
db_utils.get_table, engine, 'metadef_tags')
def _check_038(self, engine, data):
meta = sqlalchemy.MetaData()
meta.bind = engine
# metadef_tags
table = sqlalchemy.Table("metadef_tags", meta, autoload=True)
expected_cols = [u'id',
u'namespace_id',
u'name',
u'created_at',
u'updated_at']
col_data = [col.name for col in table.columns]
self.assertEqual(expected_cols, col_data)
def _post_downgrade_038(self, engine):
self.assertRaises(sqlalchemy.exc.NoSuchTableError,
db_utils.get_table, engine, 'metadef_tags')
class TestMysqlMigrations(test_base.MySQLOpportunisticTestCase,
MigrationsMixin):
@ -1392,6 +1414,7 @@ class ModelsMigrationSyncMixin(object):
# (except 'migrate_version')
if name in ['migrate_version', 'metadef_objects', 'metadef_namespaces',
'metadef_properties', 'metadef_resource_types',
'metadef_tags',
'metadef_namespace_resource_types'] and type_ == 'table':
return False
return True

View File

@ -21,6 +21,7 @@ from glance.api.v2 import metadef_namespaces as namespaces
from glance.api.v2 import metadef_objects as objects
from glance.api.v2 import metadef_properties as properties
from glance.api.v2 import metadef_resource_types as resource_types
from glance.api.v2 import metadef_tags as tags
import glance.api.v2.model.metadef_namespace
from glance.tests.unit import base
import glance.tests.unit.utils as unit_test_utils
@ -49,6 +50,12 @@ RESOURCE_TYPE2 = 'ResourceType2'
RESOURCE_TYPE3 = 'ResourceType3'
RESOURCE_TYPE4 = 'ResourceType4'
TAG1 = 'Tag1'
TAG2 = 'Tag2'
TAG3 = 'Tag3'
TAG4 = 'Tag4'
TAG5 = 'Tag5'
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
TENANT2 = '2c014f32-55eb-467d-8fcb-4bd706012f81'
TENANT3 = '5a3e60e8-cfa9-4a9e-a90a-62b42cea92b8'
@ -99,6 +106,26 @@ def _db_resource_type_fixture(name, **kwargs):
return obj
def _db_tag_fixture(name, **kwargs):
obj = {
'name': name
}
obj.update(kwargs)
return obj
def _db_tags_fixture(tag_names=None):
tag_list = []
if not tag_names:
tag_names = [TAG1, TAG2, TAG3]
for tag_name in tag_names:
tag = glance.api.v2.model.metadef_tag.MetadefTag()
tag.name = tag_name
tag_list.append(tag)
return tag_list
def _db_namespace_resource_type_fixture(name, **kwargs):
obj = {
'name': name,
@ -120,6 +147,7 @@ class TestMetadefsControllers(base.IsolatedUnitTest):
self._create_objects()
self._create_resource_types()
self._create_namespaces_resource_types()
self._create_tags()
self.namespace_controller = namespaces.NamespaceController(self.db,
self.policy)
self.property_controller = \
@ -128,6 +156,7 @@ class TestMetadefsControllers(base.IsolatedUnitTest):
self.policy)
self.rt_controller = resource_types.ResourceTypeController(self.db,
self.policy)
self.tag_controller = tags.TagsController(self.db, self.policy)
def _create_namespaces(self):
self.db.reset()
@ -175,6 +204,16 @@ class TestMetadefsControllers(base.IsolatedUnitTest):
[self.db.metadef_resource_type_create(req.context, resource_type)
for resource_type in self.resource_types]
def _create_tags(self):
req = unit_test_utils.get_fake_request()
self.tags = [
(NAMESPACE3, _db_tag_fixture(TAG1)),
(NAMESPACE3, _db_tag_fixture(TAG2)),
(NAMESPACE1, _db_tag_fixture(TAG1)),
]
[self.db.metadef_tag_create(req.context, namespace, tag)
for namespace, tag in self.tags]
def _create_namespaces_resource_types(self):
req = unit_test_utils.get_fake_request(is_admin=True)
self.ns_resource_types = [
@ -1141,3 +1180,242 @@ class TestMetadefsControllers(base.IsolatedUnitTest):
actual = set([x.name for x in output.resource_type_associations])
expected = set([RESOURCE_TYPE1, RESOURCE_TYPE2])
self.assertEqual(expected, actual)
def test_tag_index(self):
request = unit_test_utils.get_fake_request()
output = self.tag_controller.index(request, NAMESPACE3)
output = output.to_dict()
self.assertEqual(2, len(output['tags']))
actual = set([tag.name for tag in output['tags']])
expected = set([TAG1, TAG2])
self.assertEqual(expected, actual)
def test_tag_index_empty(self):
request = unit_test_utils.get_fake_request()
output = self.tag_controller.index(request, NAMESPACE5)
output = output.to_dict()
self.assertEqual(0, len(output['tags']))
def test_tag_index_non_existing_namespace(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound, self.tag_controller.index,
request, NAMESPACE4)
def test_tag_show(self):
request = unit_test_utils.get_fake_request()
output = self.tag_controller.show(request, NAMESPACE3, TAG1)
self.assertEqual(TAG1, output.name)
def test_tag_show_non_existing(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound, self.tag_controller.show,
request, NAMESPACE5, TAG1)
def test_tag_show_non_visible(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2)
self.assertRaises(webob.exc.HTTPNotFound, self.tag_controller.show,
request, NAMESPACE1, TAG1)
def test_tag_show_non_visible_admin(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2,
is_admin=True)
output = self.tag_controller.show(request, NAMESPACE1, TAG1)
self.assertEqual(TAG1, output.name)
def test_tag_delete(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
self.tag_controller.delete(request, NAMESPACE3, TAG1)
self.assertRaises(webob.exc.HTTPNotFound, self.tag_controller.show,
request, NAMESPACE3, TAG1)
def test_tag_delete_other_owner(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPForbidden,
self.tag_controller.delete, request, NAMESPACE3,
TAG1)
def test_tag_delete_other_owner_admin(self):
request = unit_test_utils.get_fake_request(is_admin=True)
self.tag_controller.delete(request, NAMESPACE3, TAG1)
self.assertRaises(webob.exc.HTTPNotFound, self.tag_controller.show,
request, NAMESPACE3, TAG1)
def test_tag_delete_non_existing(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound,
self.tag_controller.delete, request, NAMESPACE5,
TAG1)
def test_tag_delete_non_existing_namespace(self):
request = unit_test_utils.get_fake_request()
self.assertRaises(webob.exc.HTTPNotFound,
self.tag_controller.delete, request, NAMESPACE4,
TAG1)
def test_tag_delete_non_visible(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2)
self.assertRaises(webob.exc.HTTPNotFound,
self.tag_controller.delete, request, NAMESPACE1,
TAG1)
def test_tag_delete_admin_protected(self):
request = unit_test_utils.get_fake_request(is_admin=True)
self.assertRaises(webob.exc.HTTPForbidden,
self.tag_controller.delete, request, NAMESPACE1,
TAG1)
def test_tag_create(self):
request = unit_test_utils.get_fake_request()
tag = glance.api.v2.model.metadef_tag.MetadefTag()
tag.name = TAG2
tag = self.tag_controller.create(request, tag, NAMESPACE1)
self.assertEqual(TAG2, tag.name)
tag = self.tag_controller.show(request, NAMESPACE1, TAG2)
self.assertEqual(TAG2, tag.name)
def test_tag_create_tags(self):
request = unit_test_utils.get_fake_request()
metadef_tags = glance.api.v2.model.metadef_tag.MetadefTags()
metadef_tags.tags = _db_tags_fixture()
output = self.tag_controller.create_tags(
request, metadef_tags, NAMESPACE1)
output = output.to_dict()
self.assertEqual(3, len(output['tags']))
actual = set([tag.name for tag in output['tags']])
expected = set([TAG1, TAG2, TAG3])
self.assertEqual(expected, actual)
def test_tag_create_duplicate_tags(self):
request = unit_test_utils.get_fake_request()
metadef_tags = glance.api.v2.model.metadef_tag.MetadefTags()
metadef_tags.tags = _db_tags_fixture([TAG4, TAG5, TAG4])
self.assertRaises(
webob.exc.HTTPConflict,
self.tag_controller.create_tags,
request, metadef_tags, NAMESPACE1)
def test_tag_create_duplicate_with_pre_existing_tags(self):
request = unit_test_utils.get_fake_request()
metadef_tags = glance.api.v2.model.metadef_tag.MetadefTags()
metadef_tags.tags = _db_tags_fixture([TAG1, TAG2, TAG3])
output = self.tag_controller.create_tags(
request, metadef_tags, NAMESPACE1)
output = output.to_dict()
self.assertEqual(3, len(output['tags']))
actual = set([tag.name for tag in output['tags']])
expected = set([TAG1, TAG2, TAG3])
self.assertEqual(expected, actual)
metadef_tags = glance.api.v2.model.metadef_tag.MetadefTags()
metadef_tags.tags = _db_tags_fixture([TAG4, TAG5, TAG4])
self.assertRaises(
webob.exc.HTTPConflict,
self.tag_controller.create_tags,
request, metadef_tags, NAMESPACE1)
output = self.tag_controller.index(request, NAMESPACE1)
output = output.to_dict()
self.assertEqual(3, len(output['tags']))
actual = set([tag.name for tag in output['tags']])
expected = set([TAG1, TAG2, TAG3])
self.assertEqual(expected, actual)
def test_tag_create_conflict(self):
request = unit_test_utils.get_fake_request()
tag = glance.api.v2.model.metadef_tag.MetadefTag()
tag.name = TAG1
self.assertRaises(webob.exc.HTTPConflict,
self.tag_controller.create, request, tag,
NAMESPACE1)
def test_tag_create_non_existing_namespace(self):
request = unit_test_utils.get_fake_request()
tag = glance.api.v2.model.metadef_tag.MetadefTag()
tag.name = TAG1
self.assertRaises(webob.exc.HTTPNotFound,
self.tag_controller.create, request, tag,
NAMESPACE4)
def test_tag_create_non_visible_namespace(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2)
tag = glance.api.v2.model.metadef_tag.MetadefTag()
tag.name = TAG1
self.assertRaises(webob.exc.HTTPForbidden,
self.tag_controller.create, request, tag,
NAMESPACE1)
def test_tag_create_non_visible_namespace_admin(self):
request = unit_test_utils.get_fake_request(tenant=TENANT2,
is_admin=True)
tag = glance.api.v2.model.metadef_tag.MetadefTag()
tag.name = TAG2
tag = self.tag_controller.create(request, tag, NAMESPACE1)
self.assertEqual(TAG2, tag.name)
tag = self.tag_controller.show(request, NAMESPACE1, TAG2)
self.assertEqual(TAG2, tag.name)
def test_tag_update(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
tag = self.tag_controller.show(request, NAMESPACE3, TAG1)
tag.name = TAG3
tag = self.tag_controller.update(request, tag, NAMESPACE3, TAG1)
self.assertEqual(TAG3, tag.name)
property = self.tag_controller.show(request, NAMESPACE3, TAG3)
self.assertEqual(TAG3, property.name)
def test_tag_update_name(self):
request = unit_test_utils.get_fake_request()
tag = self.tag_controller.show(request, NAMESPACE1, TAG1)
tag.name = TAG2
tag = self.tag_controller.update(request, tag, NAMESPACE1, TAG1)
self.assertEqual(TAG2, tag.name)
tag = self.tag_controller.show(request, NAMESPACE1, TAG2)
self.assertEqual(TAG2, tag.name)
def test_tag_update_conflict(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
tag = self.tag_controller.show(request, NAMESPACE3, TAG1)
tag.name = TAG2
self.assertRaises(webob.exc.HTTPConflict,
self.tag_controller.update, request, tag,
NAMESPACE3, TAG1)
def test_tag_update_non_existing(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
tag = glance.api.v2.model.metadef_tag.MetadefTag()
tag.name = TAG1
self.assertRaises(webob.exc.HTTPNotFound,
self.tag_controller.update, request, tag,
NAMESPACE5, TAG1)
def test_tag_update_namespace_non_existing(self):
request = unit_test_utils.get_fake_request(tenant=TENANT3)
tag = glance.api.v2.model.metadef_tag.MetadefTag()
tag.name = TAG1
self.assertRaises(webob.exc.HTTPNotFound,
self.tag_controller.update, request, tag,
NAMESPACE4, TAG1)