Glance Metadata Definitions Catalog - API

Implements: blueprint metadata-schema-catalog

A common API hosted by the Glance service for vendors, admins,
services, and users to meaningfully define available key / value
pair and tag metadata. The intent is to enable better metadata
collaboration across artifacts, services, and projects for
OpenStack users.

This is about the definition of the available metadata that can
be used on different types of resources (images, artifacts,
volumes, flavors, aggregates, etc). A definition includes the
properties type, its key, it's description, and it's constraints.
This catalogue will not store the values for specific instance
properties.

 - REST API for CRUD on metadef namespace
 - REST API for CRUD on metadef objects
 - REST API for CRUD on metadef properites
 - REST API for CRUD on metadef resource types
 - REST API for JSON schemas on metadef API's

Change-Id: I8e6d88ffee9a9337bf82b1da85648ba638a154ab
DocImpact
Co-Authored-By: Lakshmi N Sampath <lakshmi.sampath@hp.com>
Co-Authored-By: Wayne Okuma <wayne.okuma@hp.com>
Co-Authored-By: Travis Tripp <travis.tripp@hp.com>
Co-Authored-By: Pawel Koniszewski <pawel.koniszewski@intel.com>
Co-Authored-By: Michal Jastrzebski <michal.jastrzebski@intel.com>
Co-Authored-By: Michal Dulko <michal.dulko@intel.com>
This commit is contained in:
Wayne Okuma 2014-08-26 03:22:58 -04:00 committed by Michal Dulko
parent b9107a7cce
commit 2e7de07c5a
30 changed files with 6687 additions and 5 deletions

View File

@ -0,0 +1,510 @@
..
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.
Using Glance's Metadata Definitions Catalog Public APIs
=======================================================
A common API hosted by the Glance service for vendors, admins, services,
and users to meaningfully define available key / value pair and tag metadata.
The intent is to enable better metadata collaboration across artifacts,
services, and projects for OpenStack users.
This is about the definition of the available metadata that can be used on
different types of resources (images, artifacts, volumes, flavors,
aggregates, etc). A definition includes the properties type, its key,
it's description, and it's constraints. This catalog will not store the
values for specific instance properties.
Glance Metadata Definitions Catalog implementation started with API version v2.
Authentication
--------------
Glance depends on Keystone and the OpenStack Identity API to handle
authentication of clients. You must obtain an authentication token from
Keystone send it along with all API requests to Glance through the
``X-Auth-Token`` header. Glance will communicate back to Keystone to verify
the token validity and obtain your identity credentials.
See :doc:`authentication` for more information on integrating with Keystone.
Using v2.X
----------
For the purpose of examples, assume there is a Glance API server running
at the URL ``http://glance.example.com`` on the default port 80.
List Available Namespaces
*************************
We want to see a list of available namespaces that the authenticated user
has access to. This includes namespaces owned by the user,
namespaces shared with the user and public namespaces.
We issue a ``GET`` request to ``http://glance.example.com/v2/metadefs/namespaces``
to retrieve this list of available images.
The data is returned as a JSON-encoded mapping in the following format::
{
"namespaces": [
{
"namespace": "MyNamespace",
"display_name": "My User Friendly Namespace",
"description": "My description",
"property_count": 0,
"object_count": 2,
"resource_types": [
{
"name": "OS::Nova::Aggregate"
},
{
"name": "OS::Nova::Flavor",
"prefix": "aggregate_instance_extra_specs:"
}
],
"visibility": "public",
"protected": true,
"owner": "The Test Owner"
}
]
}
.. note::
Listing namespaces will only show the summary of each namespace including
counts and resource type associations. Detailed response including all its
objects definitions, property definitions etc. will only be available on
each individual GET namespace request.
Filtering Namespaces Lists
**************************
``GET /v2/metadefs/namespaces`` requests take query parameters that serve to
filter the returned list of namespaces. The following
list details these query parameters.
* ``resource_types=RESOURCE_TYPES``
Filters namespaces having a ``resource_types`` within the list of
comma separated ``RESOURCE_TYPES``.
GET resource also accepts additional query parameters:
* ``sort_key=KEY``
Results will be ordered by the specified image attribute ``KEY``. Accepted
values include ``namespace``, ``created_at`` (default) and ``updated_at``.
* ``sort_dir=DIR``
Results will be sorted in the direction ``DIR``. Accepted values are ``asc``
for ascending or ``desc`` (default) for descending.
* ``marker=NAMESPACE``
A namespace identifier marker may be specified. When present only
namespaces which occur after the identifier ``NAMESPACE`` will be listed,
i.e. the namespaces which have a `sort_key` later than that of the marker
``NAMESPACE`` in the `sort_dir` direction.
* ``limit=LIMIT``
When present the maximum number of results returned will not exceed ``LIMIT``.
.. note::
If the specified ``LIMIT`` exceeds the operator defined limit (api_limit_max)
then the number of results returned may be less than ``LIMIT``.
* ``visibility=PUBLIC``
An admin user may use the `visibility` parameter to control which results are
returned (PRIVATE or PUBLIC).
Retrieve Namespace
******************
We want to see a more detailed information about a namespace that the
authenticated user has access to. The detail includes the properties, objects,
and resource type associations.
We issue a ``GET`` request to ``http://glance.example.com/v2/metadefs/namespaces/{namespace}``
to retrieve the namespace details.
The data is returned as a JSON-encoded mapping in the following format::
{
"namespace": "MyNamespace",
"display_name": "My User Friendly Namespace",
"description": "My description",
"property_count": 2,
"object_count": 2,
"resource_types": [
{
"name": "OS::Glance::Image",
"prefix": "hw_"
},
{
"name": "OS::Cinder::Volume",
"prefix": "hw_",
"properties_target": "image"
},
{
"name": "OS::Nova::Flavor",
"prefix": "filter1:"
}
],
"properties": {
"nsprop1": {
"title": "My namespace property1",
"description": "More info here",
"type": "boolean",
"default": true
},
"nsprop2": {
"title": "My namespace property2",
"description": "More info here",
"type": "string",
"default": "value1"
}
},
"objects": [
{
"name": "object1",
"description": "my-description",
"properties": {
"prop1": {
"title": "My object1 property1",
"description": "More info here",
"type": "array",
"items": {
"type": "string"
},
"readonly": false
}
}
},
{
"name": "object2",
"description": "my-description",
"properties": {
"prop1": {
"title": "My object2 property1",
"description": "More info here",
"type": "integer",
"default": 20
}
}
}
],
"visibility": "public",
"protected": true,
"owner": "The Test Owner"
}
Retrieve available Resource Types
*********************************
We want to see the list of all resource types that are available in Glance
We issue a ``GET`` request to ``http://glance.example.com/v2/metadefs/resource_types``
to retrieve all resource types.
The data is returned as a JSON-encoded mapping in the following format::
[
"OS::Cinder::Volume",
"OS::Glance::Image",
"OS::Nova::Flavor",
"OS::Neutron::Subnet"
]
Retrieve Resource Types associated with a Namespace
***************************************************
We want to see the list of resource types that are associated for a specific
namespace
We issue a ``GET`` request to ``http://glance.example.com/v2/metadefs/namespaces/{namespace}/resource_types``
to retrieve resource types.
The data is returned as a JSON-encoded mapping in the following format::
{
"resource_types" : [
{
"name" : "OS::Glance::Image",
"prefix" : "hw_"
},
{
"name" :"OS::Cinder::Volume",
"prefix" : "hw_",
"properties_target" : "image"
},
{
"name" : "OS::Nova::Flavor",
"prefix" : "hw:"
}
]
}
Add Namespace
*************
We want to create a new namespace that can contain the properties, objects,
etc.
We issue a ``POST`` request to add an namespace to Glance::
POST http://glance.example.com/v2/metadefs/namespaces/
The input data is an JSON-encoded mapping in the following format::
{
"namespace": "MyNamespace",
"display_name": "My User Friendly Namespace",
"description": "My description",
"visibility": "public",
"protected": true
}
.. note::
Optionally properties, objects and resource types could be added in the
same input. See GET Namespace output above.
Update Namespace
****************
We want to update an existing namespace
We issue a ``PUT`` request to update an namespace to Glance::
PUT http://glance.example.com/v2/metadefs/namespaces/{namespace}
The input data is similar to Add Namespace
Delete Namespace
****************
We want to delete an existing namespace including all its objects,
properties etc.
We issue a ``DELETE`` request to delete an namespace to Glance::
DELETE http://glance.example.com/v2/metadefs/namespaces/{namespace}
Remove Resource Type associated with a Namespace
************************************************
We want to de-associate namespace from a resource type
We issue a ``DELETE`` request to de-associate namespace resource type to
Glance::
DELETE http://glance.example.com/v2//metadefs/namespaces/{namespace}/resource_types/{resource_type}
List Objects in Namespace
*************************
We want to see the list of meta definition objects in a specific namespace
We issue a ``GET`` request to ``http://glance.example.com/v2/metadefs/namespaces/{namespace}/objects``
to retrieve objects.
The data is returned as a JSON-encoded mapping in the following format::
{
"objects": [
{
"name": "object1",
"description": "object1-description",
"properties": {
"prop1": {
"title": "My Property",
"description": "More info here",
"type": "boolean",
"default": true
}
}
},
{
"name": "object2",
"description": "object2-description",
"properties": {
"prop1": {
"title": "My Property",
"description": "More info here",
"type": "boolean",
"default": true
}
}
}
],
"schema": "/schema/metadefs/objects"
}
Add object in a specific namespace
**********************************
We want to create a new object which can group the properties
We issue a ``POST`` request to add object to a namespace in Glance::
POST http://glance.example.com/v2/metadefs/namespaces/{namespace}/objects
The input data is an JSON-encoded mapping in the following format::
{
"name": "StorageQOS",
"description": "Our available storage QOS.",
"required": [
"minIOPS"
],
"properties": {
"minIOPS": {
"type": "integer",
"description": "The minimum IOPs required",
"default": 100,
"minimum": 100,
"maximum": 30000369
},
"burstIOPS": {
"type": "integer",
"description": "The expected burst IOPs",
"default": 1000,
"minimum": 100,
"maximum": 30000377
}
}
}
Update Object in a specific namespace
*************************************
We want to update an existing object
We issue a ``PUT`` request to update an object to Glance::
PUT http://glance.example.com/v2/metadefs/namespaces/{namespace}/objects/{object_name}
The input data is similar to Add Object
Delete Object in a specific namespace
*************************************
We want to delete an existing object.
We issue a ``DELETE`` request to delete object in a namespace to Glance::
DELETE http://glance.example.com/v2/metadefs/namespaces/{namespace}/objects/{object_name}
Add property definition in a specific namespace
***********************************************
We want to create a new property definition in a namespace
We issue a ``POST`` request to add property definition to a namespace in
Glance::
POST http://glance.example.com/v2/metadefs/namespaces/{namespace}/properties
The input data is an JSON-encoded mapping in the following format::
{
"name": "hypervisor_type",
"title" : "Hypervisor",
"type": "array",
"description": "The type of hypervisor required",
"items": {
"type": "string",
"enum": [
"hyperv",
"qemu",
"kvm"
]
}
}
Update property definition in a specific namespace
**************************************************
We want to update an existing object
We issue a ``PUT`` request to update an property definition in a namespace to
Glance::
PUT http://glance.example.com/v2/metadefs/namespaces/{namespace}/properties/{property_name}
The input data is similar to Add property definition
Delete property definition in a specific namespace
**************************************************
We want to delete an existing object.
We issue a ``DELETE`` request to delete property definition in a namespace to
Glance::
DELETE http://glance.example.com/v2/metadefs/namespaces/{namespace}/properties/{property_name}
API Message Localization
------------------------
Glance supports HTTP message localization. For example, an HTTP client can
receive API messages in Chinese even if the locale language of the server is
English.
How to use it
*************
To receive localized API messages, the HTTP client needs to specify the
**Accept-Language** header to indicate the language to use to translate the
message. For more info about Accept-Language, please refer http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
A typical curl API request will be like below::
curl -i -X GET -H 'Accept-Language: zh' -H 'Content-Type: application/json'
http://127.0.0.1:9292/v2/images/aaa
Then the response will be like the following::
HTTP/1.1 404 Not Found
Content-Length: 234
Content-Type: text/html; charset=UTF-8
X-Openstack-Request-Id: req-54d403a0-064e-4544-8faf-4aeef086f45a
Date: Sat, 22 Feb 2014 06:26:26 GMT
<html>
<head>
<title>404 Not Found</title>
</head>
<body>
<h1>404 Not Found</h1>
&#25214;&#19981;&#21040;&#20219;&#20309;&#20855;&#26377;&#26631;&#35782; aaa &#30340;&#26144;&#20687;<br /><br />
</body>
</html>
.. note::
Be sure there is the language package under /usr/share/locale-langpack/ on
the target Glance server.

View File

@ -81,3 +81,4 @@ Using Glance
glanceapi
glanceclient
glancemetadefcatalogapi

View File

@ -28,5 +28,24 @@
"get_task": "",
"get_tasks": "",
"add_task": "",
"modify_task": ""
"modify_task": "",
"get_metadef_namespace": "",
"get_metadef_namespaces":"",
"modify_metadef_namespace":"",
"add_metadef_namespace":"",
"get_metadef_object":"",
"get_metadef_objects":"",
"modify_metadef_object":"",
"add_metadef_object":"",
"list_metadef_resource_types":"",
"add_metadef_resource_type_association":"",
"get_metadef_property":"",
"get_metadef_properties":"",
"modify_metadef_property":"",
"add_metadef_property":""
}

View File

@ -455,3 +455,356 @@ class TaskStubRepoProxy(glance.domain.proxy.TaskStubRepo):
def list(self, *args, **kwargs):
task_stubs = self.task_stub_repo.list(*args, **kwargs)
return [proxy_task_stub(self.context, t) for t in task_stubs]
#Metadef Namespace classes
def is_namespace_mutable(context, namespace):
"""Return True if the namespace is mutable in this context."""
if context.is_admin:
return True
if context.owner is None:
return False
return namespace.owner == context.owner
def proxy_namespace(context, namespace):
if is_namespace_mutable(context, namespace):
return namespace
else:
return ImmutableMetadefNamespaceProxy(namespace)
class ImmutableMetadefNamespaceProxy(object):
def __init__(self, base):
self.base = base
self.resource_name = 'namespace'
namespace_id = _immutable_attr('base', 'namespace_id')
namespace = _immutable_attr('base', 'namespace')
display_name = _immutable_attr('base', 'display_name')
description = _immutable_attr('base', 'description')
owner = _immutable_attr('base', 'owner')
visibility = _immutable_attr('base', 'visibility')
protected = _immutable_attr('base', 'protected')
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 namespace.")
raise exception.Forbidden(message)
def save(self):
message = _("You are not permitted to update this namespace.")
raise exception.Forbidden(message)
class MetadefNamespaceProxy(glance.domain.proxy.MetadefNamespace):
def __init__(self, namespace):
self.namespace_input = namespace
super(MetadefNamespaceProxy, self).__init__(namespace)
class MetadefNamespaceFactoryProxy(
glance.domain.proxy.MetadefNamespaceFactory):
def __init__(self, meta_namespace_factory, context):
self.meta_namespace_factory = meta_namespace_factory
self.context = context
super(MetadefNamespaceFactoryProxy, self).__init__(
meta_namespace_factory,
meta_namespace_proxy_class=MetadefNamespaceProxy)
def new_namespace(self, **kwargs):
owner = kwargs.pop('owner', self.context.owner)
if not self.context.is_admin:
if owner is None or owner != self.context.owner:
message = _("You are not permitted to create namespace "
"owned by '%s'")
raise exception.Forbidden(message % (owner))
return super(MetadefNamespaceFactoryProxy, self).new_namespace(
owner=owner, **kwargs)
class MetadefNamespaceRepoProxy(glance.domain.proxy.MetadefNamespaceRepo):
def __init__(self, namespace_repo, context):
self.namespace_repo = namespace_repo
self.context = context
super(MetadefNamespaceRepoProxy, self).__init__(namespace_repo)
def get(self, namespace):
namespace_obj = self.namespace_repo.get(namespace)
return proxy_namespace(self.context, namespace_obj)
def list(self, *args, **kwargs):
namespaces = self.namespace_repo.list(*args, **kwargs)
return [proxy_namespace(self.context, namespace) for
namespace in namespaces]
#Metadef Object classes
def is_object_mutable(context, object):
"""Return True if the object is mutable in this context."""
if context.is_admin:
return True
if context.owner is None:
return False
return object.namespace.owner == context.owner
def proxy_object(context, object):
if is_object_mutable(context, object):
return object
else:
return ImmutableMetadefObjectProxy(object)
class ImmutableMetadefObjectProxy(object):
def __init__(self, base):
self.base = base
self.resource_name = 'object'
object_id = _immutable_attr('base', 'object_id')
name = _immutable_attr('base', 'name')
required = _immutable_attr('base', 'required')
description = _immutable_attr('base', 'description')
properties = _immutable_attr('base', 'properties')
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 object.")
raise exception.Forbidden(message)
def save(self):
message = _("You are not permitted to update this object.")
raise exception.Forbidden(message)
class MetadefObjectProxy(glance.domain.proxy.MetadefObject):
def __init__(self, meta_object):
self.meta_object = meta_object
super(MetadefObjectProxy, self).__init__(meta_object)
class MetadefObjectFactoryProxy(glance.domain.proxy.MetadefObjectFactory):
def __init__(self, meta_object_factory, context):
self.meta_object_factory = meta_object_factory
self.context = context
super(MetadefObjectFactoryProxy, self).__init__(
meta_object_factory,
meta_object_proxy_class=MetadefObjectProxy)
def new_object(self, **kwargs):
owner = kwargs.pop('owner', self.context.owner)
if not self.context.is_admin:
if owner is None or owner != self.context.owner:
message = _("You are not permitted to create object "
"owned by '%s'")
raise exception.Forbidden(message % (owner))
return super(MetadefObjectFactoryProxy, self).new_object(**kwargs)
class MetadefObjectRepoProxy(glance.domain.proxy.MetadefObjectRepo):
def __init__(self, object_repo, context):
self.object_repo = object_repo
self.context = context
super(MetadefObjectRepoProxy, self).__init__(object_repo)
def get(self, namespace, object_name):
meta_object = self.object_repo.get(namespace, object_name)
return proxy_object(self.context, meta_object)
def list(self, *args, **kwargs):
objects = self.object_repo.list(*args, **kwargs)
return [proxy_object(self.context, meta_object) for
meta_object in objects]
#Metadef ResourceType classes
def is_meta_resource_type_mutable(context, meta_resource_type):
"""Return True if the meta_resource_type is mutable in this context."""
if context.is_admin:
return True
if context.owner is None:
return False
#(lakshmiS): resource type can exist without an association with
# namespace and resource type cannot be created/update/deleted directly(
# they have to be associated/de-associated from namespace)
if meta_resource_type.namespace:
return meta_resource_type.namespace.owner == context.owner
else:
return False
def proxy_meta_resource_type(context, meta_resource_type):
if is_meta_resource_type_mutable(context, meta_resource_type):
return meta_resource_type
else:
return ImmutableMetadefResourceTypeProxy(meta_resource_type)
class ImmutableMetadefResourceTypeProxy(object):
def __init__(self, base):
self.base = base
self.resource_name = 'meta_resource_type'
namespace = _immutable_attr('base', 'namespace')
name = _immutable_attr('base', 'name')
prefix = _immutable_attr('base', 'prefix')
properties_target = _immutable_attr('base', 'properties_target')
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 meta_resource_type.")
raise exception.Forbidden(message)
class MetadefResourceTypeProxy(glance.domain.proxy.MetadefResourceType):
def __init__(self, meta_resource_type):
self.meta_resource_type = meta_resource_type
super(MetadefResourceTypeProxy, self).__init__(meta_resource_type)
class MetadefResourceTypeFactoryProxy(
glance.domain.proxy.MetadefResourceTypeFactory):
def __init__(self, resource_type_factory, context):
self.meta_resource_type_factory = resource_type_factory
self.context = context
super(MetadefResourceTypeFactoryProxy, self).__init__(
resource_type_factory,
resource_type_proxy_class=MetadefResourceTypeProxy)
def new_resource_type(self, **kwargs):
owner = kwargs.pop('owner', self.context.owner)
if not self.context.is_admin:
if owner is None or owner != self.context.owner:
message = _("You are not permitted to create resource_type "
"owned by '%s'")
raise exception.Forbidden(message % (owner))
return super(MetadefResourceTypeFactoryProxy, self).new_resource_type(
**kwargs)
class MetadefResourceTypeRepoProxy(
glance.domain.proxy.MetadefResourceTypeRepo):
def __init__(self, meta_resource_type_repo, context):
self.meta_resource_type_repo = meta_resource_type_repo
self.context = context
super(MetadefResourceTypeRepoProxy, self).__init__(
meta_resource_type_repo)
def list(self, *args, **kwargs):
meta_resource_types = self.meta_resource_type_repo.list(
*args, **kwargs)
return [proxy_meta_resource_type(self.context, meta_resource_type) for
meta_resource_type in meta_resource_types]
#Metadef namespace properties classes
def is_namespace_property_mutable(context, namespace_property):
"""Return True if the object is mutable in this context."""
if context.is_admin:
return True
if context.owner is None:
return False
return namespace_property.namespace.owner == context.owner
def proxy_namespace_property(context, namespace_property):
if is_namespace_property_mutable(context, namespace_property):
return namespace_property
else:
return ImmutableMetadefPropertyProxy(namespace_property)
class ImmutableMetadefPropertyProxy(object):
def __init__(self, base):
self.base = base
self.resource_name = 'namespace_property'
property_id = _immutable_attr('base', 'property_id')
name = _immutable_attr('base', 'name')
schema = _immutable_attr('base', 'schema')
def delete(self):
message = _("You are not permitted to delete this property.")
raise exception.Forbidden(message)
def save(self):
message = _("You are not permitted to update this property.")
raise exception.Forbidden(message)
class MetadefPropertyProxy(glance.domain.proxy.MetadefProperty):
def __init__(self, namespace_property):
self.meta_object = namespace_property
super(MetadefPropertyProxy, self).__init__(namespace_property)
class MetadefPropertyFactoryProxy(glance.domain.proxy.MetadefPropertyFactory):
def __init__(self, namespace_property_factory, context):
self.meta_object_factory = namespace_property_factory
self.context = context
super(MetadefPropertyFactoryProxy, self).__init__(
namespace_property_factory,
property_proxy_class=MetadefPropertyProxy)
def new_namespace_property(self, **kwargs):
owner = kwargs.pop('owner', self.context.owner)
if not self.context.is_admin:
if owner is None or owner != self.context.owner:
message = _("You are not permitted to create property "
"owned by '%s'")
raise exception.Forbidden(message % (owner))
return super(MetadefPropertyFactoryProxy, self).\
new_namespace_property(**kwargs)
class MetadefPropertyRepoProxy(glance.domain.proxy.MetadefPropertyRepo):
def __init__(self, namespace_property_repo, context):
self.namespace_property_repo = namespace_property_repo
self.context = context
super(MetadefPropertyRepoProxy, self).__init__(namespace_property_repo)
def get(self, namespace, object_name):
namespace_property = self.namespace_property_repo.get(namespace,
object_name)
return proxy_namespace_property(self.context, namespace_property)
def list(self, *args, **kwargs):
namespace_properties = self.namespace_property_repo.list(
*args, **kwargs)
return [proxy_namespace_property(self.context, namespace_property) for
namespace_property in namespace_properties]

View File

@ -446,3 +446,211 @@ class ImageTarget(object):
return getattr(self.image, key)
else:
return self.image.extra_properties[key]
#Metadef Namespace classes
class MetadefNamespaceProxy(glance.domain.proxy.MetadefNamespace):
def __init__(self, namespace, context, policy):
self.namespace_input = namespace
self.context = context
self.policy = policy
super(MetadefNamespaceProxy, self).__init__(namespace)
class MetadefNamespaceRepoProxy(glance.domain.proxy.MetadefNamespaceRepo):
def __init__(self, namespace_repo, context, namespace_policy):
self.context = context
self.policy = namespace_policy
self.namespace_repo = namespace_repo
proxy_kwargs = {'context': self.context, 'policy': self.policy}
super(MetadefNamespaceRepoProxy,
self).__init__(namespace_repo,
namespace_proxy_class=MetadefNamespaceProxy,
namespace_proxy_kwargs=proxy_kwargs)
def get(self, namespace):
self.policy.enforce(self.context, 'get_metadef_namespace', {})
return super(MetadefNamespaceRepoProxy, self).get(namespace)
def list(self, *args, **kwargs):
self.policy.enforce(self.context, 'get_metadef_namespaces', {})
return super(MetadefNamespaceRepoProxy, self).list(*args, **kwargs)
def save(self, namespace):
self.policy.enforce(self.context, 'modify_metadef_namespace', {})
return super(MetadefNamespaceRepoProxy, self).save(namespace)
def add(self, namespace):
self.policy.enforce(self.context, 'add_metadef_namespace', {})
return super(MetadefNamespaceRepoProxy, self).add(namespace)
class MetadefNamespaceFactoryProxy(
glance.domain.proxy.MetadefNamespaceFactory):
def __init__(self, meta_namespace_factory, context, policy):
self.meta_namespace_factory = meta_namespace_factory
self.context = context
self.policy = policy
proxy_kwargs = {'context': self.context, 'policy': self.policy}
super(MetadefNamespaceFactoryProxy, self).__init__(
meta_namespace_factory,
meta_namespace_proxy_class=MetadefNamespaceProxy,
meta_namespace_proxy_kwargs=proxy_kwargs)
#Metadef Object classes
class MetadefObjectProxy(glance.domain.proxy.MetadefObject):
def __init__(self, meta_object, context, policy):
self.meta_object = meta_object
self.context = context
self.policy = policy
super(MetadefObjectProxy, self).__init__(meta_object)
class MetadefObjectRepoProxy(glance.domain.proxy.MetadefObjectRepo):
def __init__(self, object_repo, context, object_policy):
self.context = context
self.policy = object_policy
self.object_repo = object_repo
proxy_kwargs = {'context': self.context, 'policy': self.policy}
super(MetadefObjectRepoProxy,
self).__init__(object_repo,
object_proxy_class=MetadefObjectProxy,
object_proxy_kwargs=proxy_kwargs)
def get(self, namespace, object_name):
self.policy.enforce(self.context, 'get_metadef_object', {})
return super(MetadefObjectRepoProxy, self).get(namespace, object_name)
def list(self, *args, **kwargs):
self.policy.enforce(self.context, 'get_metadef_objects', {})
return super(MetadefObjectRepoProxy, self).list(*args, **kwargs)
def save(self, meta_object):
self.policy.enforce(self.context, 'modify_metadef_object', {})
return super(MetadefObjectRepoProxy, self).save(meta_object)
def add(self, meta_object):
self.policy.enforce(self.context, 'add_metadef_object', {})
return super(MetadefObjectRepoProxy, self).add(meta_object)
class MetadefObjectFactoryProxy(glance.domain.proxy.MetadefObjectFactory):
def __init__(self, meta_object_factory, context, policy):
self.meta_object_factory = meta_object_factory
self.context = context
self.policy = policy
proxy_kwargs = {'context': self.context, 'policy': self.policy}
super(MetadefObjectFactoryProxy, self).__init__(
meta_object_factory,
meta_object_proxy_class=MetadefObjectProxy,
meta_object_proxy_kwargs=proxy_kwargs)
#Metadef ResourceType classes
class MetadefResourceTypeProxy(glance.domain.proxy.MetadefResourceType):
def __init__(self, meta_resource_type, context, policy):
self.meta_resource_type = meta_resource_type
self.context = context
self.policy = policy
super(MetadefResourceTypeProxy, self).__init__(meta_resource_type)
class MetadefResourceTypeRepoProxy(
glance.domain.proxy.MetadefResourceTypeRepo):
def __init__(self, resource_type_repo, context, resource_type_policy):
self.context = context
self.policy = resource_type_policy
self.resource_type_repo = resource_type_repo
proxy_kwargs = {'context': self.context, 'policy': self.policy}
super(MetadefResourceTypeRepoProxy, self).__init__(
resource_type_repo,
resource_type_proxy_class=MetadefResourceTypeProxy,
resource_type_proxy_kwargs=proxy_kwargs)
def list(self, *args, **kwargs):
self.policy.enforce(self.context, 'list_metadef_resource_types', {})
return super(MetadefResourceTypeRepoProxy, self).list(*args, **kwargs)
def add(self, resource_type):
self.policy.enforce(self.context,
'add_metadef_resource_type_association', {})
return super(MetadefResourceTypeRepoProxy, self).add(resource_type)
class MetadefResourceTypeFactoryProxy(
glance.domain.proxy.MetadefResourceTypeFactory):
def __init__(self, resource_type_factory, context, policy):
self.resource_type_factory = resource_type_factory
self.context = context
self.policy = policy
proxy_kwargs = {'context': self.context, 'policy': self.policy}
super(MetadefResourceTypeFactoryProxy, self).__init__(
resource_type_factory,
resource_type_proxy_class=MetadefResourceTypeProxy,
resource_type_proxy_kwargs=proxy_kwargs)
#Metadef namespace properties classes
class MetadefPropertyProxy(glance.domain.proxy.MetadefProperty):
def __init__(self, namespace_property, context, policy):
self.namespace_property = namespace_property
self.context = context
self.policy = policy
super(MetadefPropertyProxy, self).__init__(namespace_property)
class MetadefPropertyRepoProxy(glance.domain.proxy.MetadefPropertyRepo):
def __init__(self, property_repo, context, object_policy):
self.context = context
self.policy = object_policy
self.property_repo = property_repo
proxy_kwargs = {'context': self.context, 'policy': self.policy}
super(MetadefPropertyRepoProxy, self).__init__(
property_repo,
property_proxy_class=MetadefPropertyProxy,
property_proxy_kwargs=proxy_kwargs)
def get(self, namespace, property_name):
self.policy.enforce(self.context, 'get_metadef_property', {})
return super(MetadefPropertyRepoProxy, self).get(namespace,
property_name)
def list(self, *args, **kwargs):
self.policy.enforce(self.context, 'get_metadef_properties', {})
return super(MetadefPropertyRepoProxy, self).list(
*args, **kwargs)
def save(self, namespace_property):
self.policy.enforce(self.context, 'modify_metadef_property', {})
return super(MetadefPropertyRepoProxy, self).save(
namespace_property)
def add(self, namespace_property):
self.policy.enforce(self.context, 'add_metadef_property', {})
return super(MetadefPropertyRepoProxy, self).add(
namespace_property)
class MetadefPropertyFactoryProxy(glance.domain.proxy.MetadefPropertyFactory):
def __init__(self, namespace_property_factory, context, policy):
self.namespace_property_factory = namespace_property_factory
self.context = context
self.policy = policy
proxy_kwargs = {'context': self.context, 'policy': self.policy}
super(MetadefPropertyFactoryProxy, self).__init__(
namespace_property_factory,
property_proxy_class=MetadefPropertyProxy,
property_proxy_kwargs=proxy_kwargs)

View File

@ -0,0 +1,744 @@
# 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
import six
import six.moves.urllib.parse as urlparse
import webob.exc
from wsme.rest.json import fromjson
from wsme.rest.json import tojson
from glance.api import policy
from glance.api.v2.model.metadef_namespace import Namespace
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.common import exception
from glance.common import utils
from glance.common import wsgi
from glance.common import wsme_utils
import glance.db
import glance.gateway
from glance import i18n
import glance.notifier
from glance.openstack.common import jsonutils as json
import glance.openstack.common.log as logging
import glance.schema
import glance.store
LOG = logging.getLogger(__name__)
_LE = i18n._LE
_LW = i18n._LW
_LI = i18n._LI
CONF = cfg.CONF
class NamespaceController(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.ns_schema_link = '/v2/schemas/metadefs/namespace'
self.obj_schema_link = '/v2/schemas/metadefs/object'
def index(self, req, marker=None, limit=None, sort_key='created_at',
sort_dir='desc', filters=None):
try:
ns_repo = self.gateway.get_metadef_namespace_repo(req.context)
# Get namespace id
if marker:
namespace_obj = ns_repo.get(marker)
marker = namespace_obj.namespace_id
database_ns_list = ns_repo.list(
marker=marker, limit=limit, sort_key=sort_key,
sort_dir=sort_dir, filters=filters)
for db_namespace in database_ns_list:
# Get resource type associations
filters = dict()
filters['namespace'] = db_namespace.namespace
rs_repo = (
self.gateway.get_metadef_resource_type_repo(req.context))
repo_rs_type_list = rs_repo.list(filters=filters)
resource_type_list = [ResourceTypeAssociation.to_wsme_model(
resource_type) for resource_type in repo_rs_type_list]
if resource_type_list:
db_namespace.resource_type_associations = (
resource_type_list)
namespace_list = [Namespace.to_wsme_model(
db_namespace,
get_namespace_href(db_namespace),
self.ns_schema_link) for db_namespace in database_ns_list]
namespaces = Namespaces()
namespaces.namespaces = namespace_list
if len(namespace_list) != 0 and len(namespace_list) == limit:
namespaces.next = namespace_list[-1].namespace
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 namespaces
@utils.mutating
def create(self, req, namespace):
try:
namespace_created = False
# Create Namespace
ns_factory = self.gateway.get_metadef_namespace_factory(
req.context)
ns_repo = self.gateway.get_metadef_namespace_repo(req.context)
new_namespace = ns_factory.new_namespace(**namespace.to_dict())
ns_repo.add(new_namespace)
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:
for resource_type in namespace.resource_type_associations:
new_resource = rs_factory.new_resource_type(
namespace=namespace.namespace,
**resource_type.to_dict())
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:
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 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:
for (name, value) in namespace.properties.items():
new_property_type = (
prop_factory.new_namespace_property(
namespace=namespace.namespace,
**self._to_property_dict(name, value)
))
prop_repo.add(new_property_type)
except exception.Forbidden as e:
self._cleanup_namespace(ns_repo, namespace, namespace_created)
raise webob.exc.HTTPForbidden(explanation=e.msg)
except exception.NotFound as e:
self._cleanup_namespace(ns_repo, namespace, namespace_created)
raise webob.exc.HTTPNotFound(explanation=e.msg)
except exception.Duplicate as e:
self._cleanup_namespace(ns_repo, namespace, namespace_created)
raise webob.exc.HTTPConflict(explanation=e.msg)
except Exception as e:
LOG.error(utils.exception_to_str(e))
raise webob.exc.HTTPInternalServerError()
# Return the user namespace as we don't expose the id to user
new_namespace.properties = namespace.properties
new_namespace.objects = namespace.objects
new_namespace.resource_type_associations = (
namespace.resource_type_associations)
return Namespace.to_wsme_model(new_namespace,
get_namespace_href(new_namespace),
self.ns_schema_link)
def _to_property_dict(self, name, value):
# Convert the model PropertyTypes dict to a JSON string
json_data = tojson(PropertyType, value)
db_property_type_dict = dict()
db_property_type_dict['schema'] = json.dumps(json_data)
db_property_type_dict['name'] = name
return db_property_type_dict
def _cleanup_namespace(self, namespace_repo, namespace, namespace_created):
if namespace_created:
try:
namespace_obj = namespace_repo.get(namespace.namespace)
namespace_obj.delete()
namespace_repo.remove(namespace_obj)
msg = ("Cleaned up namespace %(namespace)s "
% {'namespace': namespace.namespace})
LOG.debug(msg)
except exception:
msg = (_LE("Failed to delete namespace %(namespace)s ") %
{'namespace': namespace.namespace})
LOG.error(msg)
def show(self, req, namespace, filters=None):
try:
# Get namespace
ns_repo = self.gateway.get_metadef_namespace_repo(req.context)
namespace_obj = ns_repo.get(namespace)
namespace_detail = Namespace.to_wsme_model(
namespace_obj,
get_namespace_href(namespace_obj),
self.ns_schema_link)
ns_filters = dict()
ns_filters['namespace'] = namespace
# Get objects
object_repo = self.gateway.get_metadef_object_repo(req.context)
db_metaobject_list = object_repo.list(filters=ns_filters)
object_list = [MetadefObject.to_wsme_model(
db_metaobject,
get_object_href(namespace, db_metaobject),
self.obj_schema_link) for db_metaobject in db_metaobject_list]
if object_list:
namespace_detail.objects = object_list
# Get resource type associations
rs_repo = self.gateway.get_metadef_resource_type_repo(req.context)
db_resource_type_list = rs_repo.list(filters=ns_filters)
resource_type_list = [ResourceTypeAssociation.to_wsme_model(
resource_type) for resource_type in db_resource_type_list]
if resource_type_list:
namespace_detail.resource_type_associations = (
resource_type_list)
# Get properties
prop_repo = self.gateway.get_metadef_property_repo(req.context)
db_properties = prop_repo.list(filters=ns_filters)
property_list = Namespace.to_model_properties(db_properties)
if property_list:
namespace_detail.properties = property_list
if filters and filters['resource_type']:
namespace_detail = self._prefix_property_name(
namespace_detail, filters['resource_type'])
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 namespace_detail
def update(self, req, user_ns, namespace):
namespace_repo = self.gateway.get_metadef_namespace_repo(req.context)
try:
ns_obj = namespace_repo.get(namespace)
ns_obj.namespace = wsme_utils._get_value(user_ns.namespace)
ns_obj.display_name = wsme_utils._get_value(user_ns.display_name)
ns_obj.description = wsme_utils._get_value(user_ns.description)
# Following optional fields will default to same values as in
# create namespace if not specified
ns_obj.visibility = (
wsme_utils._get_value(user_ns.visibility) or 'private')
ns_obj.protected = (
wsme_utils._get_value(user_ns.protected) or False)
ns_obj.owner = (
wsme_utils._get_value(user_ns.owner) or req.context.owner)
updated_namespace = namespace_repo.save(ns_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.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 Namespace.to_wsme_model(updated_namespace,
get_namespace_href(updated_namespace),
self.ns_schema_link)
def delete(self, req, namespace):
namespace_repo = self.gateway.get_metadef_namespace_repo(req.context)
try:
namespace_obj = namespace_repo.get(namespace)
namespace_obj.delete()
namespace_repo.remove(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_objects(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_objects(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:
namespace_obj = ns_repo.get(namespace)
namespace_obj.delete()
ns_repo.remove_properties(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 _prefix_property_name(self, namespace_detail, user_resource_type):
prefix = None
if user_resource_type and namespace_detail.resource_type_associations:
for resource_type in namespace_detail.resource_type_associations:
if resource_type.name == user_resource_type:
prefix = resource_type.prefix
break
if prefix:
if namespace_detail.properties:
new_property_dict = dict()
for (key, value) in namespace_detail.properties.items():
new_property_dict[prefix + key] = value
namespace_detail.properties = new_property_dict
if namespace_detail.objects:
for object in namespace_detail.objects:
new_object_property_dict = dict()
for (key, value) in object.properties.items():
new_object_property_dict[prefix + key] = value
object.properties = new_object_property_dict
if object.required and len(object.required) > 0:
required = [prefix + name for name in object.required]
object.required = required
return namespace_detail
class RequestDeserializer(wsgi.JSONRequestDeserializer):
_disallowed_properties = ['self', 'schema', 'created_at', 'updated_at']
def __init__(self, schema=None):
super(RequestDeserializer, self).__init__()
self.schema = schema or get_schema()
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']
@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)
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')
if limit is None:
limit = CONF.limit_param_default
limit = min(CONF.api_limit_max, int(limit))
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 is not None:
query_params['marker'] = marker
if limit is not None:
query_params['limit'] = self._validate_limit(limit)
return query_params
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']:
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 show(self, request):
params = request.params.copy()
query_params = {
'filters': self._get_filters(params)
}
return query_params
def create(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)
namespace = fromjson(Namespace, body)
return dict(namespace=namespace)
def 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)
namespace = fromjson(Namespace, body)
return dict(user_ns=namespace)
class ResponseSerializer(wsgi.JSONResponseSerializer):
def __init__(self, schema=None):
super(ResponseSerializer, self).__init__()
self.schema = schema
def create(self, response, namespace):
ns_json = tojson(Namespace, namespace)
response = self.__render(ns_json, response, 201)
response.location = get_namespace_href(namespace)
def show(self, response, namespace):
ns_json = tojson(Namespace, namespace)
response = self.__render(ns_json, response)
def index(self, response, result):
params = dict(response.request.params)
params.pop('marker', None)
query = urlparse.urlencode(params)
result.first = "/v2/metadefs/namespaces"
result.schema = "/v2/schemas/metadefs/namespaces"
if query:
result.first = '%s?%s' % (result.first, query)
if result.next:
params['marker'] = result.next
next_query = urlparse.urlencode(params)
result.next = '/v2/metadefs/namespaces?%s' % next_query
ns_json = tojson(Namespaces, result)
response = self.__render(ns_json, response)
def update(self, response, namespace):
ns_json = tojson(Namespace, namespace)
response = self.__render(ns_json, response, 200)
def delete(self, response, result):
response.status_int = 204
def delete_objects(self, response, result):
response.status_int = 204
def delete_properties(self, response, result):
response.status_int = 204
def __render(self, json_data, response, response_status=None):
body = json.dumps(json_data, ensure_ascii=False)
response.unicode_body = six.text_type(body)
response.content_type = 'application/json'
if response_status:
response.status_int = response_status
return response
def _get_base_definitions():
return get_schema_definitions()
def get_schema_definitions():
return {
"positiveInteger": {
"type": "integer",
"minimum": 0
},
"positiveIntegerDefault0": {
"allOf": [
{"$ref": "#/definitions/positiveInteger"},
{"default": 0}
]
},
"stringArray": {
"type": "array",
"items": {"type": "string"},
# "minItems": 1,
"uniqueItems": True
},
"property": {
"type": "object",
"additionalProperties": {
"type": "object",
"required": ["title", "type"],
"properties": {
"name": {
"type": "string"
},
"title": {
"type": "string"
},
"description": {
"type": "string"
},
"type": {
"type": "string",
"enum": [
"array",
"boolean",
"integer",
"number",
"object",
"string",
None
]
},
"required": {
"$ref": "#/definitions/stringArray"
},
"minimum": {
"type": "number"
},
"maximum": {
"type": "number"
},
"maxLength": {
"$ref": "#/definitions/positiveInteger"
},
"minLength": {
"$ref": "#/definitions/positiveIntegerDefault0"
},
"pattern": {
"type": "string",
"format": "regex"
},
"enum": {
"type": "array"
},
"readonly": {
"type": "boolean"
},
"default": {},
"items": {
"type": "object",
"properties": {
"type": {
"type": "string",
"enum": [
"array",
"boolean",
"integer",
"number",
"object",
"string",
None
]
},
"enum": {
"type": "array"
}
}
},
"maxItems": {
"$ref": "#/definitions/positiveInteger"
},
"minItems": {
"$ref": "#/definitions/positiveIntegerDefault0"
},
"uniqueItems": {
"type": "boolean",
"default": False
},
"additionalItems": {
"type": "boolean"
},
}
}
}
}
def _get_base_properties():
return {
"namespace": {
"type": "string",
"description": _("The unique namespace text."),
"maxLength": 80,
},
"display_name": {
"type": "string",
"description": _("The user friendly name for the namespace. Used "
"by UI if available."),
"maxLength": 80,
},
"description": {
"type": "string",
"description": _("Provides a user friendly description of the "
"namespace."),
"maxLength": 500,
},
"visibility": {
"type": "string",
"description": _("Scope of namespace accessibility."),
"enum": ["public", "private"],
},
"protected": {
"type": "boolean",
"description": _("If true, namespace will not be deletable."),
},
"owner": {
"type": "string",
"description": _("Owner of the namespace."),
"maxLength": 255,
},
"created_at": {
"type": "string",
"description": _("Date and time of namespace creation"
" (READ-ONLY)"),
"format": "date-time"
},
"updated_at": {
"type": "string",
"description": _("Date and time of the last namespace modification"
" (READ-ONLY)"),
"format": "date-time"
},
"schema": {
"type": "string"
},
"self": {
"type": "string"
},
"resource_type_associations": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"prefix": {
"type": "string"
},
"properties_target": {
"type": "string"
}
}
}
},
"properties": {
"$ref": "#/definitions/property"
},
"objects": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"required": {
"$ref": "#/definitions/stringArray"
},
"properties": {
"$ref": "#/definitions/property"
},
}
}
}
}
def get_schema():
properties = _get_base_properties()
definitions = _get_base_definitions()
mandatory_attrs = Namespace.get_mandatory_attrs()
schema = glance.schema.Schema(
'namespace',
properties,
required=mandatory_attrs,
definitions=definitions
)
return schema
def get_collection_schema():
namespace_schema = get_schema()
return glance.schema.CollectionSchema('namespaces', namespace_schema)
def get_namespace_href(namespace):
base_href = '/v2/metadefs/namespaces/%s' % namespace.namespace
return base_href
def get_object_href(namespace_name, metadef_object):
base_href = ('/v2/metadefs/namespaces/%s/objects/%s' %
(namespace_name, metadef_object.name))
return base_href
def create_resource():
"""Namespaces resource factory method"""
schema = get_schema()
deserializer = RequestDeserializer(schema)
serializer = ResponseSerializer(schema)
controller = NamespaceController()
return wsgi.Resource(controller, deserializer, serializer)

View File

@ -0,0 +1,335 @@
# 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
import six
import webob.exc
from wsme.rest.json import fromjson
from wsme.rest.json import tojson
from glance.api import policy
from glance.api.v2 import metadef_namespaces as namespaces
from glance.api.v2.model.metadef_object import MetadefObject
from glance.api.v2.model.metadef_object import MetadefObjects
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
from glance.openstack.common import jsonutils as json
import glance.openstack.common.log as logging
import glance.schema
LOG = logging.getLogger(__name__)
_LE = i18n._LE
_LI = i18n._LI
CONF = cfg.CONF
class MetadefObjectsController(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.obj_schema_link = '/v2/schemas/metadefs/object'
def create(self, req, metadata_object, namespace):
object_factory = self.gateway.get_metadef_object_factory(req.context)
object_repo = self.gateway.get_metadef_object_repo(req.context)
try:
new_meta_object = object_factory.new_object(
namespace=namespace,
**metadata_object.to_dict())
object_repo.add(new_meta_object)
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 MetadefObject.to_wsme_model(
new_meta_object,
get_object_href(namespace, new_meta_object),
self.obj_schema_link)
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
object_repo = self.gateway.get_metadef_object_repo(req.context)
db_metaobject_list = object_repo.list(
marker=marker, limit=limit, sort_key=sort_key,
sort_dir=sort_dir, filters=filters)
object_list = [MetadefObject.to_wsme_model(
db_metaobject,
get_object_href(namespace, db_metaobject),
self.obj_schema_link) for db_metaobject in db_metaobject_list]
metadef_objects = MetadefObjects()
metadef_objects.objects = object_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_objects
def show(self, req, namespace, object_name):
meta_object_repo = self.gateway.get_metadef_object_repo(
req.context)
try:
metadef_object = meta_object_repo.get(namespace, object_name)
return MetadefObject.to_wsme_model(
metadef_object,
get_object_href(namespace, metadef_object),
self.obj_schema_link)
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_object, namespace, object_name):
meta_repo = self.gateway.get_metadef_object_repo(req.context)
try:
metadef_object = meta_repo.get(namespace, object_name)
metadef_object.name = wsme_utils._get_value(
metadata_object.name)
metadef_object.description = wsme_utils._get_value(
metadata_object.description)
metadef_object.required = wsme_utils._get_value(
metadata_object.required)
metadef_object.properties = wsme_utils._get_value(
metadata_object.properties)
updated_metadata_obj = meta_repo.save(metadef_object)
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 MetadefObject.to_wsme_model(
updated_metadata_obj,
get_object_href(namespace, updated_metadata_obj),
self.obj_schema_link)
def delete(self, req, namespace, object_name):
meta_repo = self.gateway.get_metadef_object_repo(req.context)
try:
metadef_object = meta_repo.get(namespace, object_name)
metadef_object.delete()
meta_repo.remove(metadef_object)
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 namespaces.get_schema_definitions()
def _get_base_properties():
return {
"name": {
"type": "string"
},
"description": {
"type": "string"
},
"required": {
"$ref": "#/definitions/stringArray"
},
"properties": {
"$ref": "#/definitions/property"
},
"schema": {
"type": "string"
},
"self": {
"type": "string"
},
"created_at": {
"type": "string",
"description": _("Date and time of object creation"
" (READ-ONLY)"),
"format": "date-time"
},
"updated_at": {
"type": "string",
"description": _("Date and time of the last object modification"
" (READ-ONLY)"),
"format": "date-time"
}
}
def get_schema():
definitions = _get_base_definitions()
properties = _get_base_properties()
mandatory_attrs = MetadefObject.get_mandatory_attrs()
schema = glance.schema.Schema(
'object',
properties,
required=mandatory_attrs,
definitions=definitions,
)
return schema
def get_collection_schema():
object_schema = get_schema()
return glance.schema.CollectionSchema('objects', object_schema)
class RequestDeserializer(wsgi.JSONRequestDeserializer):
_disallowed_properties = ['self', 'schema', 'created_at', 'updated_at']
def __init__(self, schema=None):
super(RequestDeserializer, self).__init__()
self.schema = schema or get_schema()
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 create(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_object = fromjson(MetadefObject, body)
return dict(metadata_object=metadata_object)
def 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_object = fromjson(MetadefObject, body)
return dict(metadata_object=metadata_object)
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 is not None:
query_params['marker'] = marker
if limit is not None:
query_params['limit'] = self._validate_limit(limit)
return query_params
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
@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_object):
response.status_int = 201
self.show(response, metadata_object)
def show(self, response, metadata_object):
metadata_object_json = tojson(MetadefObject, metadata_object)
body = json.dumps(metadata_object_json, ensure_ascii=False)
response.unicode_body = six.text_type(body)
response.content_type = 'application/json'
def update(self, response, metadata_object):
response.status_int = 200
self.show(response, metadata_object)
def index(self, response, result):
result.schema = "v2/schemas/metadefs/objects"
metadata_objects_json = tojson(MetadefObjects, result)
body = json.dumps(metadata_objects_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_object_href(namespace_name, metadef_object):
base_href = ('/v2/metadefs/namespaces/%s/objects/%s' %
(namespace_name, metadef_object.name))
return base_href
def create_resource():
"""Metadef objects resource factory method"""
schema = get_schema()
deserializer = RequestDeserializer(schema)
serializer = ResponseSerializer(schema)
controller = MetadefObjectsController()
return wsgi.Resource(controller, deserializer, serializer)

View File

@ -0,0 +1,275 @@
# 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 six
import webob.exc
from wsme.rest.json import fromjson
from wsme.rest.json import tojson
from glance.api import policy
from glance.api.v2 import metadef_namespaces as namespaces
from glance.api.v2.model.metadef_namespace import Namespace
from glance.api.v2.model.metadef_property_type import PropertyType
from glance.api.v2.model.metadef_property_type import PropertyTypes
from glance.common import exception
from glance.common import utils
from glance.common import wsgi
import glance.db
import glance.gateway
from glance import i18n
import glance.notifier
from glance.openstack.common import jsonutils as json
import glance.openstack.common.log as logging
import glance.schema
import glance.store
LOG = logging.getLogger(__name__)
_LE = i18n._LE
_LI = i18n._LI
class NamespacePropertiesController(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)
def _to_dict(self, model_property_type):
# Convert the model PropertyTypes dict to a JSON string
json_data = tojson(PropertyType, model_property_type)
db_property_type_dict = dict()
db_property_type_dict['schema'] = json.dumps(json_data)
db_property_type_dict['name'] = model_property_type.name
return db_property_type_dict
def _to_model(self, db_property_type):
# Convert the persisted json schema to a dict of PropertyTypes
json_props = json.loads(db_property_type.schema)
property_type = fromjson(PropertyType, json_props)
property_type.name = db_property_type.name
return property_type
def index(self, req, namespace):
try:
filters = dict()
filters['namespace'] = namespace
prop_repo = self.gateway.get_metadef_property_repo(req.context)
db_properties = prop_repo.list(filters=filters)
property_list = Namespace.to_model_properties(db_properties)
namespace_properties = PropertyTypes()
namespace_properties.properties = property_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 namespace_properties
def show(self, req, namespace, property_name):
try:
prop_repo = self.gateway.get_metadef_property_repo(req.context)
db_property = prop_repo.get(namespace, property_name)
property = self._to_model(db_property)
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 property
def create(self, req, namespace, property_type):
prop_factory = self.gateway.get_metadef_property_factory(req.context)
prop_repo = self.gateway.get_metadef_property_repo(req.context)
try:
new_property_type = prop_factory.new_namespace_property(
namespace=namespace, **self._to_dict(property_type))
prop_repo.add(new_property_type)
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 self._to_model(new_property_type)
def update(self, req, namespace, property_name, property_type):
prop_repo = self.gateway.get_metadef_property_repo(req.context)
try:
db_property_type = prop_repo.get(namespace, property_name)
db_property_type.name = property_type.name
db_property_type.schema = (self._to_dict(property_type))['schema']
updated_property_type = prop_repo.save(db_property_type)
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 self._to_model(updated_property_type)
def delete(self, req, namespace, property_name):
prop_repo = self.gateway.get_metadef_property_repo(req.context)
try:
property_type = prop_repo.get(namespace, property_name)
property_type.delete()
prop_repo.remove(property_type)
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()
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()
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']
@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)
def create(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)
property_type = fromjson(PropertyType, body)
return dict(property_type=property_type)
def 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)
property_type = fromjson(PropertyType, body)
return dict(property_type=property_type)
class ResponseSerializer(wsgi.JSONResponseSerializer):
def __init__(self, schema=None):
super(ResponseSerializer, self).__init__()
self.schema = schema
def show(self, response, result):
property_type_json = tojson(PropertyType, result)
body = json.dumps(property_type_json, ensure_ascii=False)
response.unicode_body = six.text_type(body)
response.content_type = 'application/json'
def index(self, response, result):
property_type_json = tojson(PropertyTypes, result)
body = json.dumps(property_type_json, ensure_ascii=False)
response.unicode_body = six.text_type(body)
response.content_type = 'application/json'
def create(self, response, result):
response.status_int = 201
self.show(response, result)
def update(self, response, result):
response.status_int = 200
self.show(response, result)
def delete(self, response, result):
response.status_int = 204
def _get_base_definitions():
return {
"positiveInteger": {
"type": "integer",
"minimum": 0
},
"positiveIntegerDefault0": {
"allOf": [
{"$ref": "#/definitions/positiveInteger"},
{"default": 0}
]
},
"stringArray": {
"type": "array",
"items": {"type": "string"},
"minItems": 1,
"uniqueItems": True
}
}
def _get_base_properties():
base_def = namespaces.get_schema_definitions()
return base_def['property']['additionalProperties']['properties']
def get_schema():
definitions = _get_base_definitions()
properties = _get_base_properties()
mandatory_attrs = PropertyType.get_mandatory_attrs()
# name is required attribute when use as single property type
mandatory_attrs.append('name')
schema = glance.schema.Schema(
'property',
properties,
required=mandatory_attrs,
definitions=definitions
)
return schema
def get_collection_schema():
namespace_properties_schema = get_schema()
# Property name is a dict key and not a required attribute in
# individual property schema inside property collections
namespace_properties_schema.required.remove('name')
return glance.schema.DictCollectionSchema('properties',
namespace_properties_schema)
def create_resource():
"""NamespaceProperties resource factory method"""
schema = get_schema()
deserializer = RequestDeserializer(schema)
serializer = ResponseSerializer(schema)
controller = NamespacePropertiesController()
return wsgi.Resource(controller, deserializer, serializer)

View File

@ -0,0 +1,264 @@
# 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 six
import webob.exc
from wsme.rest.json import fromjson
from wsme.rest.json import tojson
from glance.api import policy
from glance.api.v2.model.metadef_resource_type import ResourceType
from glance.api.v2.model.metadef_resource_type import ResourceTypeAssociation
from glance.api.v2.model.metadef_resource_type import ResourceTypeAssociations
from glance.api.v2.model.metadef_resource_type import ResourceTypes
from glance.common import exception
from glance.common import utils
from glance.common import wsgi
import glance.db
import glance.gateway
from glance import i18n
import glance.notifier
from glance.openstack.common import jsonutils as json
import glance.openstack.common.log as logging
import glance.schema
import glance.store
LOG = logging.getLogger(__name__)
_LE = i18n._LE
_LI = i18n._LI
class ResourceTypeController(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)
def index(self, req):
try:
filters = {}
filters['namespace'] = None
rs_type_repo = self.gateway.get_metadef_resource_type_repo(
req.context)
db_resource_type_list = rs_type_repo.list(filters=filters)
resource_type_list = [ResourceType.to_wsme_model(
resource_type) for resource_type in db_resource_type_list]
resource_types = ResourceTypes()
resource_types.resource_types = resource_type_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(e)
return resource_types
def show(self, req, namespace):
try:
filters = {}
filters['namespace'] = namespace
rs_type_repo = self.gateway.get_metadef_resource_type_repo(
req.context)
db_resource_type_list = rs_type_repo.list(filters=filters)
resource_type_list = [ResourceTypeAssociation.to_wsme_model(
resource_type) for resource_type in db_resource_type_list]
resource_types = ResourceTypeAssociations()
resource_types.resource_type_associations = resource_type_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(e)
return resource_types
def create(self, req, resource_type, namespace):
rs_type_factory = self.gateway.get_metadef_resource_type_factory(
req.context)
rs_type_repo = self.gateway.get_metadef_resource_type_repo(req.context)
try:
new_resource_type = rs_type_factory.new_resource_type(
namespace=namespace, **resource_type.to_dict())
rs_type_repo.add(new_resource_type)
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 ResourceTypeAssociation.to_wsme_model(new_resource_type)
def delete(self, req, namespace, resource_type):
rs_type_repo = self.gateway.get_metadef_resource_type_repo(req.context)
try:
filters = {}
found = False
filters['namespace'] = namespace
db_resource_type_list = rs_type_repo.list(filters=filters)
for db_resource_type in db_resource_type_list:
if db_resource_type.name == resource_type:
db_resource_type.delete()
rs_type_repo.remove(db_resource_type)
found = True
if not found:
raise exception.NotFound()
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=e.msg)
except exception.NotFound as e:
msg = (_LE("Failed to find resource type %(resourcetype)s to "
"delete") % {'resourcetype': resource_type})
LOG.error(msg)
raise webob.exc.HTTPNotFound(explanation=msg)
except Exception as e:
LOG.error(utils.exception_to_str(e))
raise webob.exc.HTTPInternalServerError()
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()
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']
@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)
def create(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)
resource_type = fromjson(ResourceTypeAssociation, body)
return dict(resource_type=resource_type)
class ResponseSerializer(wsgi.JSONResponseSerializer):
def __init__(self, schema=None):
super(ResponseSerializer, self).__init__()
self.schema = schema
def show(self, response, result):
resource_type_json = tojson(ResourceTypeAssociations, result)
body = json.dumps(resource_type_json, ensure_ascii=False)
response.unicode_body = six.text_type(body)
response.content_type = 'application/json'
def index(self, response, result):
resource_type_json = tojson(ResourceTypes, result)
body = json.dumps(resource_type_json, ensure_ascii=False)
response.unicode_body = six.text_type(body)
response.content_type = 'application/json'
def create(self, response, result):
resource_type_json = tojson(ResourceTypeAssociation, result)
response.status_int = 201
body = json.dumps(resource_type_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_base_properties():
return {
'name': {
'type': 'string',
'description': _('Resource type names should be aligned with Heat '
'resource types whenever possible: '
'http://docs.openstack.org/developer/heat/'
'template_guide/openstack.html'),
'maxLength': 80,
},
'prefix': {
'type': 'string',
'description': _('Specifies the prefix to use for the given '
'resource type. Any properties in the namespace '
'should be prefixed with this prefix when being '
'applied to the specified resource type. Must '
'include prefix separator (e.g. a colon :).'),
'maxLength': 80,
},
'properties_target': {
'type': 'string',
'description': _('Some resource types allow more than one key / '
'value pair per instance. For example, Cinder '
'allows user and image metadata on volumes. Only '
'the image properties metadata is evaluated by '
'Nova (scheduling or drivers). This property '
'allows a namespace target to remove the '
'ambiguity.'),
'maxLength': 80,
},
"created_at": {
"type": "string",
"description": _("Date and time of resource type association"
" (READ-ONLY)"),
"format": "date-time"
},
"updated_at": {
"type": "string",
"description": _("Date and time of the last resource type "
"association modification (READ-ONLY)"),
"format": "date-time"
}
}
def get_schema():
properties = _get_base_properties()
mandatory_attrs = ResourceTypeAssociation.get_mandatory_attrs()
schema = glance.schema.Schema(
'resource_type_association',
properties,
required=mandatory_attrs,
)
return schema
def get_collection_schema():
resource_type_schema = get_schema()
return glance.schema.CollectionSchema('resource_type_associations',
resource_type_schema)
def create_resource():
"""ResourceTypeAssociation resource factory method"""
schema = get_schema()
deserializer = RequestDeserializer(schema)
serializer = ResponseSerializer(schema)
controller = ResourceTypeController()
return wsgi.Resource(controller, deserializer, serializer)

View File

View File

@ -0,0 +1,79 @@
# 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.rest.json import fromjson
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.common.wsme_utils import WSMEModelTransformer
from glance.openstack.common import jsonutils as json
class Namespace(types.Base, WSMEModelTransformer):
#Base fields
namespace = wsme.wsattr(types.text, mandatory=True)
display_name = wsme.wsattr(types.text, mandatory=False)
description = wsme.wsattr(types.text, mandatory=False)
visibility = wsme.wsattr(types.text, mandatory=False)
protected = wsme.wsattr(bool, mandatory=False)
owner = wsme.wsattr(types.text, mandatory=False)
#Not using datetime since time format has to be
#in glance.openstack.common.timeutils.isotime() format
created_at = wsme.wsattr(types.text, mandatory=False)
updated_at = wsme.wsattr(types.text, mandatory=False)
#Contained fields
resource_type_associations = wsme.wsattr([ResourceTypeAssociation],
mandatory=False)
properties = wsme.wsattr({types.text: PropertyType}, mandatory=False)
objects = wsme.wsattr([MetadefObject], mandatory=False)
#Generated fields
self = wsme.wsattr(types.text, mandatory=False)
schema = wsme.wsattr(types.text, mandatory=False)
def __init__(cls, **kwargs):
super(Namespace, cls).__init__(**kwargs)
@staticmethod
def to_model_properties(db_property_types):
property_types = {}
for db_property_type in db_property_types:
# Convert the persisted json schema to a dict of PropertyTypes
json_props = json.loads(db_property_type.schema)
property_type = fromjson(PropertyType, json_props)
property_type_name = db_property_type.name
property_types[property_type_name] = property_type
return property_types
class Namespaces(types.Base, WSMEModelTransformer):
namespaces = wsme.wsattr([Namespace], mandatory=False)
#Pagination
next = wsme.wsattr(types.text, mandatory=False)
schema = wsme.wsattr(types.text, mandatory=True)
first = wsme.wsattr(types.text, mandatory=True)
def __init__(self, **kwargs):
super(Namespaces, self).__init__(**kwargs)

View File

@ -0,0 +1,49 @@
# 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.api.v2.model.metadef_property_type import PropertyType
from glance.common.wsme_utils import WSMEModelTransformer
class MetadefObject(types.Base, WSMEModelTransformer):
name = wsme.wsattr(types.text, mandatory=True)
required = wsme.wsattr([types.text], mandatory=False)
description = wsme.wsattr(types.text, mandatory=False)
properties = wsme.wsattr({types.text: PropertyType}, mandatory=False)
#Not using datetime since time format has to be
#in glance.openstack.common.timeutils.isotime() format
created_at = wsme.wsattr(types.text, mandatory=False)
updated_at = wsme.wsattr(types.text, mandatory=False)
#Generated fields
self = wsme.wsattr(types.text, mandatory=False)
schema = wsme.wsattr(types.text, mandatory=False)
def __init__(cls, **kwargs):
super(MetadefObject, cls).__init__(**kwargs)
class MetadefObjects(types.Base, WSMEModelTransformer):
objects = wsme.wsattr([MetadefObject], mandatory=False)
schema = wsme.wsattr(types.text, mandatory=True)
def __init__(self, **kwargs):
super(MetadefObjects, self).__init__(**kwargs)

View File

@ -0,0 +1,27 @@
# 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
class ItemType(types.Base):
type = wsme.wsattr(types.text, mandatory=True)
enum = wsme.wsattr([types.text], mandatory=False)
_wsme_attr_order = ('type', 'enum')
def __init__(self, **kwargs):
super(ItemType, self).__init__(**kwargs)

View File

@ -0,0 +1,59 @@
# 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.api.v2.model.metadef_property_item_type import ItemType
from glance.common.wsme_utils import WSMEModelTransformer
class PropertyType(types.Base, WSMEModelTransformer):
#When used in collection of PropertyTypes, name is a dictionary key
#and not included as separate field.
name = wsme.wsattr(types.text, mandatory=False)
type = wsme.wsattr(types.text, mandatory=True)
title = wsme.wsattr(types.text, mandatory=True)
description = wsme.wsattr(types.text, mandatory=False)
default = wsme.wsattr(types.bytes, mandatory=False)
# fields for type = string
minimum = wsme.wsattr(int, mandatory=False)
maximum = wsme.wsattr(int, mandatory=False)
enum = wsme.wsattr([types.text], mandatory=False)
pattern = wsme.wsattr(types.text, mandatory=False)
# fields for type = integer, number
minLength = wsme.wsattr(int, mandatory=False)
maxLength = wsme.wsattr(int, mandatory=False)
confidential = wsme.wsattr(bool, mandatory=False)
# fields for type = array
items = wsme.wsattr(ItemType, mandatory=False)
uniqueItems = wsme.wsattr(bool, mandatory=False)
minItems = wsme.wsattr(int, mandatory=False)
maxItems = wsme.wsattr(int, mandatory=False)
additionalItems = wsme.wsattr(bool, mandatory=False)
def __init__(self, **kwargs):
super(PropertyType, self).__init__(**kwargs)
class PropertyTypes(types.Base, WSMEModelTransformer):
properties = wsme.wsattr({types.text: PropertyType}, mandatory=False)
def __init__(self, **kwargs):
super(PropertyTypes, self).__init__(**kwargs)

View File

@ -0,0 +1,62 @@
# 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.wsme_utils import WSMEModelTransformer
class ResourceTypeAssociation(types.Base, WSMEModelTransformer):
name = wsme.wsattr(types.text, mandatory=True)
prefix = wsme.wsattr(types.text, mandatory=False)
properties_target = wsme.wsattr(types.text, mandatory=False)
#Not using datetime since time format has to be
#in glance.openstack.common.timeutils.isotime() format
created_at = wsme.wsattr(types.text, mandatory=False)
updated_at = wsme.wsattr(types.text, mandatory=False)
def __init__(self, **kwargs):
super(ResourceTypeAssociation, self).__init__(**kwargs)
class ResourceTypeAssociations(types.Base, WSMEModelTransformer):
resource_type_associations = wsme.wsattr([ResourceTypeAssociation],
mandatory=False)
def __init__(self, **kwargs):
super(ResourceTypeAssociations, self).__init__(**kwargs)
class ResourceType(types.Base, WSMEModelTransformer):
name = wsme.wsattr(types.text, mandatory=True)
#Not using datetime since time format has to be
#in glance.openstack.common.timeutils.isotime() format
created_at = wsme.wsattr(types.text, mandatory=False)
updated_at = wsme.wsattr(types.text, mandatory=False)
def __init__(self, **kwargs):
super(ResourceType, self).__init__(**kwargs)
class ResourceTypes(types.Base, WSMEModelTransformer):
resource_types = wsme.wsattr([ResourceType], mandatory=False)
def __init__(self, **kwargs):
super(ResourceTypes, self).__init__(**kwargs)

View File

@ -17,6 +17,10 @@ from glance.api.v2 import image_data
from glance.api.v2 import image_members
from glance.api.v2 import image_tags
from glance.api.v2 import images
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 schemas
from glance.api.v2 import tasks
from glance.common import wsgi
@ -94,6 +98,256 @@ class API(wsgi.Router):
conditions={'method': ['POST', 'PUT', 'DELETE',
'PATCH', 'HEAD']})
mapper.connect('/schemas/metadefs/namespace',
controller=schemas_resource,
action='metadef_namespace',
conditions={'method': ['GET']})
mapper.connect('/schemas/metadefs/namespace',
controller=reject_method_resource,
action='reject',
allowed_methods='GET',
conditions={'method': ['POST', 'PUT', 'DELETE',
'PATCH', 'HEAD']})
mapper.connect('/schemas/metadefs/namespaces',
controller=schemas_resource,
action='metadef_namespaces',
conditions={'method': ['GET']})
mapper.connect('/schemas/metadefs/namespaces',
controller=reject_method_resource,
action='reject',
allowed_methods='GET',
conditions={'method': ['POST', 'PUT', 'DELETE',
'PATCH', 'HEAD']})
mapper.connect('/schemas/metadefs/resource_type',
controller=schemas_resource,
action='metadef_resource_type',
conditions={'method': ['GET']})
mapper.connect('/schemas/metadefs/resource_type',
controller=reject_method_resource,
action='reject',
allowed_methods='GET',
conditions={'method': ['POST', 'PUT', 'DELETE',
'PATCH', 'HEAD']})
mapper.connect('/schemas/metadefs/resource_types',
controller=schemas_resource,
action='metadef_resource_types',
conditions={'method': ['GET']})
mapper.connect('/schemas/metadefs/resource_types',
controller=reject_method_resource,
action='reject',
allowed_methods='GET',
conditions={'method': ['POST', 'PUT', 'DELETE',
'PATCH', 'HEAD']})
mapper.connect('/schemas/metadefs/property',
controller=schemas_resource,
action='metadef_property',
conditions={'method': ['GET']})
mapper.connect('/schemas/metadefs/property',
controller=reject_method_resource,
action='reject',
allowed_methods='GET',
conditions={'method': ['POST', 'PUT', 'DELETE',
'PATCH', 'HEAD']})
mapper.connect('/schemas/metadefs/properties',
controller=schemas_resource,
action='metadef_properties',
conditions={'method': ['GET']})
mapper.connect('/schemas/metadefs/properties',
controller=reject_method_resource,
action='reject',
allowed_methods='GET',
conditions={'method': ['POST', 'PUT', 'DELETE',
'PATCH', 'HEAD']})
mapper.connect('/schemas/metadefs/object',
controller=schemas_resource,
action='metadef_object',
conditions={'method': ['GET']})
mapper.connect('/schemas/metadefs/object',
controller=reject_method_resource,
action='reject',
allowed_methods='GET',
conditions={'method': ['POST', 'PUT', 'DELETE',
'PATCH', 'HEAD']})
mapper.connect('/schemas/metadefs/objects',
controller=schemas_resource,
action='metadef_objects',
conditions={'method': ['GET']})
mapper.connect('/schemas/metadefs/objects',
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())
mapper.connect('/metadefs/resource_types',
controller=metadef_resource_types_resource,
action='index',
conditions={'method': ['GET']})
mapper.connect('/metadefs/resource_types',
controller=reject_method_resource,
action='reject',
allowed_methods='GET',
conditions={'method': ['POST', 'PUT', 'DELETE',
'PATCH', 'HEAD']})
mapper.connect('/metadefs/namespaces/{namespace}/resource_types',
controller=metadef_resource_types_resource,
action='show',
conditions={'method': ['GET']})
mapper.connect('/metadefs/namespaces/{namespace}/resource_types',
controller=metadef_resource_types_resource,
action='create',
conditions={'method': ['POST']})
mapper.connect('/metadefs/namespaces/{namespace}/resource_types',
controller=reject_method_resource,
action='reject',
allowed_methods='GET, POST',
conditions={'method': ['PUT', 'DELETE',
'PATCH', 'HEAD']})
mapper.connect('/metadefs/namespaces/{namespace}/resource_types/'
'{resource_type}',
controller=metadef_resource_types_resource,
action='delete',
conditions={'method': ['DELETE']})
mapper.connect('/metadefs/namespaces/{namespace}/resource_types/'
'{resource_type}',
controller=reject_method_resource,
action='reject',
allowed_methods='DELETE',
conditions={'method': ['GET', 'POST', 'PUT',
'PATCH', 'HEAD']})
# Metadef Namespaces
metadef_namespace_resource = metadef_namespaces.create_resource()
mapper.connect('/metadefs/namespaces',
controller=metadef_namespace_resource,
action='index',
conditions={'method': ['GET']})
mapper.connect('/metadefs/namespaces',
controller=metadef_namespace_resource,
action='create',
conditions={'method': ['POST']})
mapper.connect('/metadefs/namespaces',
controller=reject_method_resource,
action='reject',
allowed_methods='GET, POST',
conditions={'method': ['PUT', 'DELETE',
'PATCH', 'HEAD']})
mapper.connect('/metadefs/namespaces/{namespace}',
controller=metadef_namespace_resource,
action='show',
conditions={'method': ['GET']})
mapper.connect('/metadefs/namespaces/{namespace}',
controller=metadef_namespace_resource,
action='update',
conditions={'method': ['PUT']})
mapper.connect('/metadefs/namespaces/{namespace}',
controller=metadef_namespace_resource,
action='delete',
conditions={'method': ['DELETE']})
mapper.connect('/metadefs/namespaces/{namespace}',
controller=reject_method_resource,
action='reject',
allowed_methods='GET, PUT, DELETE',
conditions={'method': ['POST', 'PATCH', 'HEAD']})
# Metadef namespace properties
metadef_properties_resource = metadef_properties.create_resource()
mapper.connect('/metadefs/namespaces/{namespace}/properties',
controller=metadef_properties_resource,
action='index',
conditions={'method': ['GET']})
mapper.connect('/metadefs/namespaces/{namespace}/properties',
controller=metadef_properties_resource,
action='create',
conditions={'method': ['POST']})
mapper.connect('/metadefs/namespaces/{namespace}/properties',
controller=metadef_namespace_resource,
action='delete_properties',
conditions={'method': ['DELETE']})
mapper.connect('/metadefs/namespaces/{namespace}/properties',
controller=reject_method_resource,
action='reject',
allowed_methods='GET, POST, DELETE',
conditions={'method': ['PUT', 'PATCH', 'HEAD']})
mapper.connect('/metadefs/namespaces/{namespace}/properties/{'
'property_name}',
controller=metadef_properties_resource,
action='show',
conditions={'method': ['GET']})
mapper.connect('/metadefs/namespaces/{namespace}/properties/{'
'property_name}',
controller=metadef_properties_resource,
action='update',
conditions={'method': ['PUT']})
mapper.connect('/metadefs/namespaces/{namespace}/properties/{'
'property_name}',
controller=metadef_properties_resource,
action='delete',
conditions={'method': ['DELETE']})
mapper.connect('/metadefs/namespaces/{namespace}/properties/{'
'property_name}',
controller=reject_method_resource,
action='reject',
allowed_methods='GET, PUT, DELETE',
conditions={'method': ['POST', 'PATCH', 'HEAD']})
# Metadef objects
metadef_objects_resource = metadef_objects.create_resource()
mapper.connect('/metadefs/namespaces/{namespace}/objects',
controller=metadef_objects_resource,
action='index',
conditions={'method': ['GET']})
mapper.connect('/metadefs/namespaces/{namespace}/objects',
controller=metadef_objects_resource,
action='create',
conditions={'method': ['POST']})
mapper.connect('/metadefs/namespaces/{namespace}/objects',
controller=metadef_namespace_resource,
action='delete_objects',
conditions={'method': ['DELETE']})
mapper.connect('/metadefs/namespaces/{namespace}/objects',
controller=reject_method_resource,
action='reject',
allowed_methods='GET, POST, DELETE',
conditions={'method': ['PUT', 'PATCH', 'HEAD']})
mapper.connect('/metadefs/namespaces/{namespace}/objects/{'
'object_name}',
controller=metadef_objects_resource,
action='show',
conditions={'method': ['GET']})
mapper.connect('/metadefs/namespaces/{namespace}/objects/{'
'object_name}',
controller=metadef_objects_resource,
action='update',
conditions={'method': ['PUT']})
mapper.connect('/metadefs/namespaces/{namespace}/objects/{'
'object_name}',
controller=metadef_objects_resource,
action='delete',
conditions={'method': ['DELETE']})
mapper.connect('/metadefs/namespaces/{namespace}/objects/{'
'object_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

@ -15,6 +15,10 @@
from glance.api.v2 import image_members
from glance.api.v2 import images
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 tasks
from glance.common import wsgi
@ -29,6 +33,23 @@ class Controller(object):
self.task_schema = tasks.get_task_schema()
self.task_collection_schema = tasks.get_collection_schema()
#Metadef schemas
self.metadef_namespace_schema = metadef_namespaces.get_schema()
self.metadef_namespace_collection_schema = \
metadef_namespaces.get_collection_schema()
self.metadef_resource_type_schema = metadef_resource_types.get_schema()
self.metadef_resource_type_collection_schema = \
metadef_resource_types.get_collection_schema()
self.metadef_property_schema = metadef_properties.get_schema()
self.metadef_property_collection_schema = \
metadef_properties.get_collection_schema()
self.metadef_object_schema = metadef_objects.get_schema()
self.metadef_object_collection_schema = \
metadef_objects.get_collection_schema()
def image(self, req):
return self.image_schema.raw()
@ -47,6 +68,30 @@ class Controller(object):
def tasks(self, req):
return self.task_collection_schema.minimal()
def metadef_namespace(self, req):
return self.metadef_namespace_schema.raw()
def metadef_namespaces(self, req):
return self.metadef_namespace_collection_schema.raw()
def metadef_resource_type(self, req):
return self.metadef_resource_type_schema.raw()
def metadef_resource_types(self, req):
return self.metadef_resource_type_collection_schema.raw()
def metadef_property(self, req):
return self.metadef_property_schema.raw()
def metadef_properties(self, req):
return self.metadef_property_collection_schema.raw()
def metadef_object(self, req):
return self.metadef_object_schema.raw()
def metadef_objects(self, req):
return self.metadef_object_collection_schema.raw()
def create_resource(custom_image_properties=None):
controller = Controller(custom_image_properties)

View File

@ -0,0 +1,71 @@
# 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 datetime import datetime
from wsme import types as wsme_types
from glance.openstack.common import timeutils
class WSMEModelTransformer():
def to_dict(self):
# Return the wsme_attributes names:values as a dict
my_dict = {}
for attribute in self._wsme_attributes:
value = getattr(self, attribute.name)
if value is not wsme_types.Unset:
my_dict.update({attribute.name: value})
return my_dict
@classmethod
def to_wsme_model(model, db_entity, self_link=None, schema=None):
# Return the wsme_attributes names:values as a dict
names = []
for attribute in model._wsme_attributes:
names.append(attribute.name)
values = {}
for name in names:
value = getattr(db_entity, name, None)
if value is not None:
if type(value) == datetime:
iso_datetime_value = timeutils.isotime(value)
values.update({name: iso_datetime_value})
else:
values.update({name: value})
if schema:
values['schema'] = schema
model_object = model(**values)
# 'self' kwarg is used in wsme.types.Base.__init__(self, ..) and
# conflicts during initialization. self_link is a proxy field to self.
if self_link:
model_object.self = self_link
return model_object
@classmethod
def get_mandatory_attrs(cls):
return [attr.name for attr in cls._wsme_attributes if attr.mandatory]
def _get_value(obj):
if obj is not wsme_types.Unset:
return obj
else:
return None

View File

@ -17,14 +17,17 @@
# under the License.
from oslo.config import cfg
from wsme.rest.json import fromjson
from wsme.rest.json import tojson
from glance.api.v2.model.metadef_property_type import PropertyType
from glance.common import crypt
from glance.common import exception
from glance.common import location_strategy
import glance.domain
import glance.domain.proxy
from glance.openstack.common import importutils
from glance.openstack.common import jsonutils as json
CONF = cfg.CONF
CONF.import_opt('image_size_cap', 'glance.common.config')
@ -380,3 +383,365 @@ class TaskRepo(object):
raise exception.NotFound(msg)
task.updated_at = updated_values['updated_at']
task.deleted_at = updated_values['deleted_at']
class MetadefNamespaceRepo(object):
def __init__(self, context, db_api):
self.context = context
self.db_api = db_api
def _format_namespace_from_db(self, namespace_obj):
return glance.domain.MetadefNamespace(
namespace_id=namespace_obj['id'],
namespace=namespace_obj['namespace'],
display_name=namespace_obj['display_name'],
description=namespace_obj['description'],
owner=namespace_obj['owner'],
visibility=namespace_obj['visibility'],
protected=namespace_obj['protected'],
created_at=namespace_obj['created_at'],
updated_at=namespace_obj['updated_at']
)
def _format_namespace_to_db(self, namespace_obj):
namespace = {
'namespace': namespace_obj.namespace,
'display_name': namespace_obj.display_name,
'description': namespace_obj.description,
'visibility': namespace_obj.visibility,
'protected': namespace_obj.protected,
'owner': namespace_obj.owner
}
return namespace
def add(self, namespace):
self.db_api.metadef_namespace_create(
self.context,
self._format_namespace_to_db(namespace)
)
def get(self, namespace):
try:
db_api_namespace = self.db_api.metadef_namespace_get(
self.context, namespace)
except (exception.NotFound, exception.Forbidden):
msg = _('Could not find namespace %s') % namespace
raise exception.NotFound(msg)
return self._format_namespace_from_db(db_api_namespace)
def list(self, marker=None, limit=None, sort_key='created_at',
sort_dir='desc', filters=None):
db_namespaces = self.db_api.metadef_namespace_get_all(
self.context,
marker=marker,
limit=limit,
sort_key=sort_key,
sort_dir=sort_dir,
filters=filters
)
return [self._format_namespace_from_db(namespace_obj)
for namespace_obj in db_namespaces]
def remove(self, namespace):
try:
self.db_api.metadef_namespace_delete(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 remove_objects(self, namespace):
try:
self.db_api.metadef_object_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 remove_properties(self, namespace):
try:
self.db_api.metadef_property_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,
namespace_name
)
def property_count(self, namespace_name):
return self.db_api.metadef_property_count(
self.context,
namespace_name
)
def save(self, namespace):
try:
self.db_api.metadef_namespace_update(
self.context, namespace.namespace_id,
self._format_namespace_to_db(namespace)
)
except exception.NotFound as e:
raise exception.NotFound(explanation=e.msg)
return namespace
class MetadefObjectRepo(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_object_from_db(self, metadata_object,
namespace_entity):
required_str = metadata_object['required']
required_list = required_str.split(",") if required_str else []
# Convert the persisted json schema to a dict of PropertyTypes
property_types = {}
json_props = json.loads(metadata_object['schema'])
for id in json_props:
property_types[id] = fromjson(PropertyType, json_props[id])
return glance.domain.MetadefObject(
namespace=namespace_entity,
object_id=metadata_object['id'],
name=metadata_object['name'],
required=required_list,
description=metadata_object['description'],
properties=property_types,
created_at=metadata_object['created_at'],
updated_at=metadata_object['updated_at']
)
def _format_metadef_object_to_db(self, metadata_object):
required_str = (",".join(metadata_object.required) if
metadata_object.required else None)
# Convert the model PropertyTypes dict to a JSON string
properties = metadata_object.properties
db_schema = {}
if properties:
for k, v in properties.items():
json_data = tojson(PropertyType, v)
db_schema[k] = json_data
property_schema = json.dumps(db_schema)
db_metadata_object = {
'name': metadata_object.name,
'required': required_str,
'description': metadata_object.description,
'schema': property_schema
}
return db_metadata_object
def add(self, metadata_object):
self.db_api.metadef_object_create(
self.context,
metadata_object.namespace,
self._format_metadef_object_to_db(metadata_object)
)
def get(self, namespace, object_name):
try:
namespace_entity = self.meta_namespace_repo.get(namespace)
db_metadata_object = self.db_api.metadef_object_get(
self.context,
namespace,
object_name)
except (exception.NotFound, exception.Forbidden):
msg = _('Could not find metadata object %s') % object_name
raise exception.NotFound(msg)
return self._format_metadef_object_from_db(db_metadata_object,
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_objects = self.db_api.metadef_object_get_all(
self.context, namespace)
return [self._format_metadef_object_from_db(metadata_object,
namespace_entity)
for metadata_object in db_metadata_objects]
def remove(self, metadata_object):
try:
self.db_api.metadef_object_delete(
self.context,
metadata_object.namespace.namespace,
metadata_object.name
)
except (exception.NotFound, exception.Forbidden):
msg = _("The specified metadata object %s could not be found")
raise exception.NotFound(msg % metadata_object.name)
def save(self, metadata_object):
try:
self.db_api.metadef_object_update(
self.context, metadata_object.namespace.namespace,
metadata_object.object_id,
self._format_metadef_object_to_db(metadata_object))
except exception.NotFound as e:
raise exception.NotFound(explanation=e.msg)
return metadata_object
class MetadefResourceTypeRepo(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_resource_type_from_db(self, resource_type, namespace):
return glance.domain.MetadefResourceType(
namespace=namespace,
name=resource_type['name'],
prefix=resource_type['prefix'],
properties_target=resource_type['properties_target'],
created_at=resource_type['created_at'],
updated_at=resource_type['updated_at']
)
def _format_resource_type_to_db(self, resource_type):
db_resource_type = {
'name': resource_type.name,
'prefix': resource_type.prefix,
'properties_target': resource_type.properties_target
}
return db_resource_type
def add(self, resource_type):
self.db_api.metadef_resource_type_association_create(
self.context, resource_type.namespace,
self._format_resource_type_to_db(resource_type)
)
def list(self, filters=None):
namespace = filters['namespace']
if namespace:
namespace_entity = self.meta_namespace_repo.get(namespace)
db_resource_types = (
self.db_api.
metadef_resource_type_association_get_all_by_namespace(
self.context,
namespace
)
)
return [self._format_resource_type_from_db(resource_type,
namespace_entity)
for resource_type in db_resource_types]
else:
db_resource_types = (
self.db_api.
metadef_resource_type_get_all(self.context)
)
return [glance.domain.MetadefResourceType(
namespace=None,
name=resource_type['name'],
prefix=None,
properties_target=None,
created_at=resource_type['created_at'],
updated_at=resource_type['updated_at']
) for resource_type in db_resource_types]
def remove(self, resource_type):
try:
self.db_api.metadef_resource_type_association_delete(
self.context, resource_type.namespace.namespace,
resource_type.name)
except (exception.NotFound, exception.Forbidden):
msg = _("The specified resource type %s could not be found ")
raise exception.NotFound(msg % resource_type.name)
class MetadefPropertyRepo(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_property_from_db(
self,
property,
namespace_entity):
return glance.domain.MetadefProperty(
namespace=namespace_entity,
property_id=property['id'],
name=property['name'],
schema=property['schema']
)
def _format_metadef_property_to_db(self, property):
db_metadata_object = {
'name': property.name,
'schema': property.schema
}
return db_metadata_object
def add(self, property):
self.db_api.metadef_property_create(
self.context,
property.namespace,
self._format_metadef_property_to_db(property)
)
def get(self, namespace, property_name):
try:
namespace_entity = self.meta_namespace_repo.get(namespace)
db_property_type = self.db_api.metadef_property_get(
self.context,
namespace,
property_name
)
except (exception.NotFound, exception.Forbidden):
msg = _('Could not find property %s') % property_name
raise exception.NotFound(msg)
return self._format_metadef_property_from_db(
db_property_type, 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_properties = self.db_api.metadef_property_get_all(
self.context, namespace)
return (
[self._format_metadef_property_from_db(
property, namespace_entity) for property in db_properties]
)
def remove(self, property):
try:
self.db_api.metadef_property_delete(
self.context, property.namespace.namespace, property.name)
except (exception.NotFound, exception.Forbidden):
msg = _("The specified property %s could not be found")
raise exception.NotFound(msg % property.name)
def save(self, property):
try:
self.db_api.metadef_property_update(
self.context, property.namespace.namespace,
property.property_id,
self._format_metadef_property_to_db(property)
)
except exception.NotFound as e:
raise exception.NotFound(explanation=e.msg)
return property

View File

@ -443,3 +443,136 @@ class TaskFactory(object):
kwargs.get('result'),
task_time_to_live
)
class MetadefNamespace(object):
def __init__(self, namespace_id, namespace, display_name, description,
owner, visibility, protected, created_at, updated_at):
self.namespace_id = namespace_id
self.namespace = namespace
self.display_name = display_name
self.description = description
self.owner = owner
self.visibility = visibility or "private"
self.protected = protected or False
self.created_at = created_at
self.updated_at = updated_at
def delete(self):
if self.protected:
raise exception.ProtectedMetadefNamespaceDelete(
namespace=self.namespace)
class MetadefNamespaceFactory(object):
def new_namespace(self, namespace, owner, **kwargs):
namespace_id = str(uuid.uuid4())
created_at = timeutils.utcnow()
updated_at = created_at
return MetadefNamespace(
namespace_id,
namespace,
kwargs.get('display_name'),
kwargs.get('description'),
owner,
kwargs.get('visibility'),
kwargs.get('protected'),
created_at,
updated_at
)
class MetadefObject(object):
def __init__(self, namespace, object_id, name, created_at, updated_at,
required, description, properties):
self.namespace = namespace
self.object_id = object_id
self.name = name
self.created_at = created_at
self.updated_at = updated_at
self.required = required
self.description = description
self.properties = properties
def delete(self):
if self.namespace.protected:
raise exception.ProtectedMetadefObjectDelete(object_name=self.name)
class MetadefObjectFactory(object):
def new_object(self, namespace, name, **kwargs):
object_id = str(uuid.uuid4())
created_at = timeutils.utcnow()
updated_at = created_at
return MetadefObject(
namespace,
object_id,
name,
created_at,
updated_at,
kwargs.get('required'),
kwargs.get('description'),
kwargs.get('properties')
)
class MetadefResourceType(object):
def __init__(self, namespace, name, prefix, properties_target,
created_at, updated_at):
self.namespace = namespace
self.name = name
self.prefix = prefix
self.properties_target = properties_target
self.created_at = created_at
self.updated_at = updated_at
def delete(self):
if self.namespace.protected:
raise exception.ProtectedMetadefResourceTypeAssociationDelete(
resource_type=self.name)
class MetadefResourceTypeFactory(object):
def new_resource_type(self, namespace, name, **kwargs):
created_at = timeutils.utcnow()
updated_at = created_at
return MetadefResourceType(
namespace,
name,
kwargs.get('prefix'),
kwargs.get('properties_target'),
created_at,
updated_at
)
class MetadefProperty(object):
def __init__(self, namespace, property_id, name, schema):
self.namespace = namespace
self.property_id = property_id
self.name = name
self.schema = schema
def delete(self):
if self.namespace.protected:
raise exception.ProtectedMetadefNamespacePropDelete(
property_name=self.name)
class MetadefPropertyFactory(object):
def new_namespace_property(self, namespace, name, schema, **kwargs):
property_id = str(uuid.uuid4())
return MetadefProperty(
namespace,
property_id,
name,
schema
)

View File

@ -219,3 +219,245 @@ class TaskFactory(object):
def new_task(self, **kwargs):
t = self.base.new_task(**kwargs)
return self.task_helper.proxy(t)
#Metadef Namespace classes
class MetadefNamespaceRepo(object):
def __init__(self, base,
namespace_proxy_class=None, namespace_proxy_kwargs=None):
self.base = base
self.namespace_proxy_helper = Helper(namespace_proxy_class,
namespace_proxy_kwargs)
def get(self, namespace):
namespace_obj = self.base.get(namespace)
return self.namespace_proxy_helper.proxy(namespace_obj)
def add(self, namespace):
self.base.add(self.namespace_proxy_helper.unproxy(namespace))
def list(self, *args, **kwargs):
namespaces = self.base.list(*args, **kwargs)
return [self.namespace_proxy_helper.proxy(namespace) for namespace
in namespaces]
def remove(self, item):
base_item = self.namespace_proxy_helper.unproxy(item)
result = self.base.remove(base_item)
return self.namespace_proxy_helper.proxy(result)
def remove_objects(self, item):
base_item = self.namespace_proxy_helper.unproxy(item)
result = self.base.remove_objects(base_item)
return self.namespace_proxy_helper.proxy(result)
def remove_properties(self, item):
base_item = self.namespace_proxy_helper.unproxy(item)
result = self.base.remove_properties(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)
return self.namespace_proxy_helper.proxy(result)
class MetadefNamespace(object):
def __init__(self, base):
self.base = base
namespace_id = _proxy('base', 'namespace_id')
namespace = _proxy('base', 'namespace')
display_name = _proxy('base', 'display_name')
description = _proxy('base', 'description')
owner = _proxy('base', 'owner')
visibility = _proxy('base', 'visibility')
protected = _proxy('base', 'protected')
created_at = _proxy('base', 'created_at')
updated_at = _proxy('base', 'updated_at')
def delete(self):
self.base.delete()
class MetadefNamespaceFactory(object):
def __init__(self,
base,
meta_namespace_proxy_class=None,
meta_namespace_proxy_kwargs=None):
self.meta_namespace_helper = Helper(meta_namespace_proxy_class,
meta_namespace_proxy_kwargs)
self.base = base
def new_namespace(self, **kwargs):
t = self.base.new_namespace(**kwargs)
return self.meta_namespace_helper.proxy(t)
#Metadef object classes
class MetadefObjectRepo(object):
def __init__(self, base,
object_proxy_class=None, object_proxy_kwargs=None):
self.base = base
self.object_proxy_helper = Helper(object_proxy_class,
object_proxy_kwargs)
def get(self, namespace, object_name):
meta_object = self.base.get(namespace, object_name)
return self.object_proxy_helper.proxy(meta_object)
def add(self, meta_object):
self.base.add(self.object_proxy_helper.unproxy(meta_object))
def list(self, *args, **kwargs):
objects = self.base.list(*args, **kwargs)
return [self.object_proxy_helper.proxy(meta_object) for meta_object
in objects]
def remove(self, item):
base_item = self.object_proxy_helper.unproxy(item)
result = self.base.remove(base_item)
return self.object_proxy_helper.proxy(result)
def save(self, item):
base_item = self.object_proxy_helper.unproxy(item)
result = self.base.save(base_item)
return self.object_proxy_helper.proxy(result)
class MetadefObject(object):
def __init__(self, base):
self.base = base
namespace = _proxy('base', 'namespace')
object_id = _proxy('base', 'object_id')
name = _proxy('base', 'name')
required = _proxy('base', 'required')
description = _proxy('base', 'description')
properties = _proxy('base', 'properties')
created_at = _proxy('base', 'created_at')
updated_at = _proxy('base', 'updated_at')
def delete(self):
self.base.delete()
class MetadefObjectFactory(object):
def __init__(self,
base,
meta_object_proxy_class=None,
meta_object_proxy_kwargs=None):
self.meta_object_helper = Helper(meta_object_proxy_class,
meta_object_proxy_kwargs)
self.base = base
def new_object(self, **kwargs):
t = self.base.new_object(**kwargs)
return self.meta_object_helper.proxy(t)
#Metadef ResourceType classes
class MetadefResourceTypeRepo(object):
def __init__(self, base, resource_type_proxy_class=None,
resource_type_proxy_kwargs=None):
self.base = base
self.resource_type_proxy_helper = Helper(resource_type_proxy_class,
resource_type_proxy_kwargs)
def add(self, meta_resource_type):
self.base.add(self.resource_type_proxy_helper.unproxy(
meta_resource_type))
def list(self, *args, **kwargs):
resource_types = self.base.list(*args, **kwargs)
return [self.resource_type_proxy_helper.proxy(resource_type)
for resource_type in resource_types]
def remove(self, item):
base_item = self.resource_type_proxy_helper.unproxy(item)
result = self.base.remove(base_item)
return self.resource_type_proxy_helper.proxy(result)
class MetadefResourceType(object):
def __init__(self, base):
self.base = base
namespace = _proxy('base', 'namespace')
name = _proxy('base', 'name')
prefix = _proxy('base', 'prefix')
properties_target = _proxy('base', 'properties_target')
created_at = _proxy('base', 'created_at')
updated_at = _proxy('base', 'updated_at')
def delete(self):
self.base.delete()
class MetadefResourceTypeFactory(object):
def __init__(self,
base,
resource_type_proxy_class=None,
resource_type_proxy_kwargs=None):
self.resource_type_helper = Helper(resource_type_proxy_class,
resource_type_proxy_kwargs)
self.base = base
def new_resource_type(self, **kwargs):
t = self.base.new_resource_type(**kwargs)
return self.resource_type_helper.proxy(t)
#Metadef namespace property classes
class MetadefPropertyRepo(object):
def __init__(self, base,
property_proxy_class=None, property_proxy_kwargs=None):
self.base = base
self.property_proxy_helper = Helper(property_proxy_class,
property_proxy_kwargs)
def get(self, namespace, property_name):
property = self.base.get(namespace, property_name)
return self.property_proxy_helper.proxy(property)
def add(self, property):
self.base.add(self.property_proxy_helper.unproxy(property))
def list(self, *args, **kwargs):
properties = self.base.list(*args, **kwargs)
return [self.property_proxy_helper.proxy(property) for property
in properties]
def remove(self, item):
base_item = self.property_proxy_helper.unproxy(item)
result = self.base.remove(base_item)
return self.property_proxy_helper.proxy(result)
def save(self, item):
base_item = self.property_proxy_helper.unproxy(item)
result = self.base.save(base_item)
return self.property_proxy_helper.proxy(result)
class MetadefProperty(object):
def __init__(self, base):
self.base = base
namespace = _proxy('base', 'namespace')
property_id = _proxy('base', 'property_id')
name = _proxy('base', 'name')
schema = _proxy('base', 'schema')
def delete(self):
self.base.delete()
class MetadefPropertyFactory(object):
def __init__(self,
base,
property_proxy_class=None,
property_proxy_kwargs=None):
self.meta_object_helper = Helper(property_proxy_class,
property_proxy_kwargs)
self.base = base
def new_namespace_property(self, **kwargs):
t = self.base.new_namespace_property(**kwargs)
return self.meta_object_helper.proxy(t)

View File

@ -120,3 +120,70 @@ class Gateway(object):
authorized_task_stub_repo = authorization.TaskStubRepoProxy(
notifier_task_stub_repo, context)
return authorized_task_stub_repo
def get_metadef_namespace_factory(self, context):
ns_factory = glance.domain.MetadefNamespaceFactory()
policy_ns_factory = policy.MetadefNamespaceFactoryProxy(
ns_factory, context, self.policy)
authorized_ns_factory = authorization.MetadefNamespaceFactoryProxy(
policy_ns_factory, context)
return authorized_ns_factory
def get_metadef_namespace_repo(self, context):
ns_repo = glance.db.MetadefNamespaceRepo(context, self.db_api)
policy_ns_repo = policy.MetadefNamespaceRepoProxy(
ns_repo, context, self.policy)
authorized_ns_repo = authorization.MetadefNamespaceRepoProxy(
policy_ns_repo, context)
return authorized_ns_repo
def get_metadef_object_factory(self, context):
object_factory = glance.domain.MetadefObjectFactory()
policy_object_factory = policy.MetadefObjectFactoryProxy(
object_factory, context, self.policy)
authorized_object_factory = authorization.MetadefObjectFactoryProxy(
policy_object_factory, context)
return authorized_object_factory
def get_metadef_object_repo(self, context):
object_repo = glance.db.MetadefObjectRepo(context, self.db_api)
policy_object_repo = policy.MetadefObjectRepoProxy(
object_repo, context, self.policy)
authorized_object_repo = authorization.MetadefObjectRepoProxy(
policy_object_repo, context)
return authorized_object_repo
def get_metadef_resource_type_factory(self, context):
resource_type_factory = glance.domain.MetadefResourceTypeFactory()
policy_resource_type_factory = policy.MetadefResourceTypeFactoryProxy(
resource_type_factory, context, self.policy)
authorized_resource_type_factory = \
authorization.MetadefResourceTypeFactoryProxy(
policy_resource_type_factory, context)
return authorized_resource_type_factory
def get_metadef_resource_type_repo(self, context):
resource_type_repo = glance.db.MetadefResourceTypeRepo(
context, self.db_api)
policy_object_repo = policy.MetadefResourceTypeRepoProxy(
resource_type_repo, context, self.policy)
authorized_resource_type_repo = \
authorization.MetadefResourceTypeRepoProxy(policy_object_repo,
context)
return authorized_resource_type_repo
def get_metadef_property_factory(self, context):
prop_factory = glance.domain.MetadefPropertyFactory()
policy_prop_factory = policy.MetadefPropertyFactoryProxy(
prop_factory, context, self.policy)
authorized_prop_factory = authorization.MetadefPropertyFactoryProxy(
policy_prop_factory, context)
return authorized_prop_factory
def get_metadef_property_repo(self, context):
prop_repo = glance.db.MetadefPropertyRepo(context, self.db_api)
policy_prop_repo = policy.MetadefPropertyRepoProxy(
prop_repo, context, self.policy)
authorized_prop_repo = authorization.MetadefPropertyRepoProxy(
policy_prop_repo, context)
return authorized_prop_repo

View File

@ -22,12 +22,15 @@ from glance.common import utils
class Schema(object):
def __init__(self, name, properties=None, links=None):
def __init__(self, name, properties=None, links=None, required=None,
definitions=None):
self.name = name
if properties is None:
properties = {}
self.properties = properties
self.links = links
self.required = required
self.definitions = definitions
def validate(self, obj):
try:
@ -68,6 +71,10 @@ class Schema(object):
'properties': self.properties,
'additionalProperties': False,
}
if self.definitions:
raw['definitions'] = self.definitions
if self.required:
raw['required'] = self.required
if self.links:
raw['links'] = self.links
return raw
@ -77,6 +84,10 @@ class Schema(object):
'name': self.name,
'properties': self.properties
}
if self.definitions:
minimal['definitions'] = self.definitions
if self.required:
minimal['required'] = self.required
return minimal
@ -102,7 +113,11 @@ class CollectionSchema(object):
self.item_schema = item_schema
def raw(self):
return {
definitions = None
if self.item_schema.definitions:
definitions = self.item_schema.definitions
self.item_schema.definitions = None
raw = {
'name': self.name,
'properties': {
self.name: {
@ -119,9 +134,18 @@ class CollectionSchema(object):
{'rel': 'describedby', 'href': '{schema}'},
],
}
if definitions:
raw['definitions'] = definitions
self.item_schema.definitions = definitions
return raw
def minimal(self):
return {
definitions = None
if self.item_schema.definitions:
definitions = self.item_schema.definitions
self.item_schema.definitions = None
minimal = {
'name': self.name,
'properties': {
self.name: {
@ -134,3 +158,66 @@ class CollectionSchema(object):
{'rel': 'describedby', 'href': '{schema}'},
],
}
if definitions:
minimal['definitions'] = definitions
self.item_schema.definitions = definitions
return minimal
class DictCollectionSchema(Schema):
def __init__(self, name, item_schema):
self.name = name
self.item_schema = item_schema
def raw(self):
definitions = None
if self.item_schema.definitions:
definitions = self.item_schema.definitions
self.item_schema.definitions = None
raw = {
'name': self.name,
'properties': {
self.name: {
'type': 'object',
'additionalProperties': self.item_schema.raw(),
},
'first': {'type': 'string'},
'next': {'type': 'string'},
'schema': {'type': 'string'},
},
'links': [
{'rel': 'first', 'href': '{first}'},
{'rel': 'next', 'href': '{next}'},
{'rel': 'describedby', 'href': '{schema}'},
],
}
if definitions:
raw['definitions'] = definitions
self.item_schema.definitions = definitions
return raw
def minimal(self):
definitions = None
if self.item_schema.definitions:
definitions = self.item_schema.definitions
self.item_schema.definitions = None
minimal = {
'name': self.name,
'properties': {
self.name: {
'type': 'object',
'additionalProperties': self.item_schema.minimal(),
},
'schema': {'type': 'string'},
},
'links': [
{'rel': 'describedby', 'href': '{schema}'},
],
}
if definitions:
minimal['definitions'] = definitions
self.item_schema.definitions = definitions
return minimal

View File

@ -0,0 +1,177 @@
# 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
import requests
from glance.openstack.common import jsonutils
from glance.tests import functional
TENANT1 = str(uuid.uuid4())
TENANT2 = str(uuid.uuid4())
class TestNamespaces(functional.FunctionalTest):
def setUp(self):
super(TestNamespaces, 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_namespace_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"
}
)
response = requests.post(path, headers=headers, data=data)
self.assertEqual(201, response.status_code)
namespace_loc_header = response.headers['Location']
# Returned namespace should match the created namespace with default
# values of visibility=private, protected=False and owner=Context
# Tenant
namespace = jsonutils.loads(response.text)
checked_keys = set([
u'namespace',
u'display_name',
u'description',
u'visibility',
u'self',
u'schema',
u'protected',
u'owner',
u'created_at',
u'updated_at'
])
self.assertEqual(set(namespace.keys()), checked_keys)
expected_namespace = {
"namespace": namespace_name,
"display_name": "My User Friendly Namespace",
"description": "My description",
"visibility": "private",
"protected": False,
"owner": TENANT1,
"self": "/v2/metadefs/namespaces/%s" % namespace_name,
"schema": "/v2/schemas/metadefs/namespace"
}
for key, value in expected_namespace.items():
self.assertEqual(namespace[key], value, key)
# Get the namespace using the returned Location header
response = requests.get(namespace_loc_header, headers=self._headers())
self.assertEqual(200, response.status_code)
namespace = jsonutils.loads(response.text)
self.assertEqual(namespace_name, namespace['namespace'])
self.assertNotIn('object', namespace)
self.assertEqual(TENANT1, namespace['owner'])
self.assertEqual('private', namespace['visibility'])
self.assertEqual(False, namespace['protected'])
# The namespace should be mutable
path = self._url('/v2/metadefs/namespaces/%s' % namespace_name)
media_type = 'application/json'
headers = self._headers({'content-type': media_type})
namespace_name = "MyNamespace-UPDATED"
data = jsonutils.dumps(
{
"namespace": namespace_name,
"display_name": "display_name-UPDATED",
"description": "description-UPDATED",
"visibility": "private", # Not changed
"protected": True,
"owner": TENANT2
}
)
response = requests.put(path, headers=headers, data=data)
self.assertEqual(200, response.status_code, response.text)
# Returned namespace should reflect the changes
namespace = jsonutils.loads(response.text)
self.assertEqual('MyNamespace-UPDATED', namespace_name)
self.assertEqual('display_name-UPDATED', namespace['display_name'])
self.assertEqual('description-UPDATED', namespace['description'])
self.assertEqual('private', namespace['visibility'])
self.assertTrue(namespace['protected'])
self.assertEqual(TENANT2, namespace['owner'])
# Updates should persist across requests
path = self._url('/v2/metadefs/namespaces/%s' % namespace_name)
response = requests.get(path, headers=self._headers())
self.assertEqual(200, response.status_code)
namespace = jsonutils.loads(response.text)
self.assertEqual('MyNamespace-UPDATED', namespace['namespace'])
self.assertEqual('display_name-UPDATED', namespace['display_name'])
self.assertEqual('description-UPDATED', namespace['description'])
self.assertEqual('private', namespace['visibility'])
self.assertTrue(namespace['protected'])
self.assertEqual(TENANT2, namespace['owner'])
# Deletion should not work on protected namespaces
path = self._url('/v2/metadefs/namespaces/%s' % namespace_name)
response = requests.delete(path, headers=self._headers())
self.assertEqual(403, response.status_code)
# Unprotect namespace for deletion
path = self._url('/v2/metadefs/namespaces/%s' % namespace_name)
media_type = 'application/json'
headers = self._headers({'content-type': media_type})
doc = {
"namespace": namespace_name,
"display_name": "My User Friendly Namespace",
"description": "My description",
"visibility": "public",
"protected": False,
"owner": TENANT2
}
data = jsonutils.dumps(doc)
response = requests.put(path, headers=headers, data=data)
self.assertEqual(200, response.status_code, response.text)
# Deletion should work. Deleting namespace MyNamespace
path = self._url('/v2/metadefs/namespaces/%s' % namespace_name)
response = requests.delete(path, headers=self._headers())
self.assertEqual(204, response.status_code)
# Namespace should not exist
path = self._url('/v2/metadefs/namespaces/MyNamespace')
response = requests.get(path, headers=self._headers())
self.assertEqual(404, response.status_code)

View File

@ -0,0 +1,263 @@
# 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
import requests
from glance.openstack.common import jsonutils
from glance.tests import functional
TENANT1 = str(uuid.uuid4())
class TestMetadefObjects(functional.FunctionalTest):
def setUp(self):
super(TestMetadefObjects, 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_objects_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 objects should not exist
path = self._url('/v2/metadefs/namespaces/MyNamespace/objects/object1')
response = requests.get(path, headers=self._headers())
self.assertEqual(404, response.status_code)
# Create a object
path = self._url('/v2/metadefs/namespaces/MyNamespace/objects')
headers = self._headers({'content-type': 'application/json'})
metadata_object_name = "object1"
data = jsonutils.dumps(
{
"name": metadata_object_name,
"description": "object1 description.",
"required": [
"property1"
],
"properties": {
"property1": {
"type": "integer",
"title": "property1",
"description": "property1 description",
"default": 100,
"minimum": 100,
"maximum": 30000369
},
"property2": {
"type": "string",
"title": "property2",
"description": "property2 description ",
"default": "value2",
"minLength": 2,
"maxLength": 50
}
}
}
)
response = requests.post(path, headers=headers, data=data)
self.assertEqual(201, response.status_code)
# Get the metadata object created above
path = self._url('/v2/metadefs/namespaces/%s/objects/%s' %
(namespace_name, metadata_object_name))
response = requests.get(path,
headers=self._headers())
self.assertEqual(200, response.status_code)
metadata_object = jsonutils.loads(response.text)
self.assertEqual("object1", metadata_object['name'])
# Returned object should match the created object
metadata_object = jsonutils.loads(response.text)
checked_keys = set([
u'name',
u'description',
u'properties',
u'required',
u'self',
u'schema',
u'created_at',
u'updated_at'
])
self.assertEqual(set(metadata_object.keys()), checked_keys)
expected_metadata_object = {
"name": metadata_object_name,
"description": "object1 description.",
"required": [
"property1"
],
"properties": {
'property1': {
'type': 'integer',
"title": "property1",
'description': 'property1 description',
'default': 100,
'minimum': 100,
'maximum': 30000369
},
"property2": {
"type": "string",
"title": "property2",
"description": "property2 description ",
"default": "value2",
"minLength": 2,
"maxLength": 50
}
},
"self": "/v2/metadefs/namespaces/%("
"namespace)s/objects/%(object)s" %
{'namespace': namespace_name,
'object': metadata_object_name},
"schema": "v2/schemas/metadefs/object"
}
#Simple key values
checked_values = set([
u'name',
u'description',
])
for key, value in expected_metadata_object.items():
if(key in checked_values):
self.assertEqual(metadata_object[key], value, key)
#Complex key values - properties
for key, value in \
expected_metadata_object["properties"]['property2'].items():
self.assertEqual(
metadata_object["properties"]["property2"][key],
value, key
)
# The metadata_object should be mutable
path = self._url('/v2/metadefs/namespaces/%s/objects/%s' %
(namespace_name, metadata_object_name))
media_type = 'application/json'
headers = self._headers({'content-type': media_type})
metadata_object_name = "object1-UPDATED"
data = jsonutils.dumps(
{
"name": metadata_object_name,
"description": "desc-UPDATED",
"required": [
"property2"
],
"properties": {
'property1': {
'type': 'integer',
"title": "property1",
'description': 'p1 desc-UPDATED',
'default': 500,
'minimum': 500,
'maximum': 1369
},
"property2": {
"type": "string",
"title": "property2",
"description": "p2 desc-UPDATED",
"default": "value2-UPDATED",
"minLength": 5,
"maxLength": 150
}
}
}
)
response = requests.put(path, headers=headers, data=data)
self.assertEqual(200, response.status_code, response.text)
# Returned metadata_object should reflect the changes
metadata_object = jsonutils.loads(response.text)
self.assertEqual('object1-UPDATED', metadata_object['name'])
self.assertEqual('desc-UPDATED', metadata_object['description'])
self.assertEqual('property2', metadata_object['required'][0])
updated_property1 = metadata_object['properties']['property1']
updated_property2 = metadata_object['properties']['property2']
self.assertEqual('integer', updated_property1['type'])
self.assertEqual('p1 desc-UPDATED', updated_property1['description'])
self.assertEqual('500', updated_property1['default'])
self.assertEqual(500, updated_property1['minimum'])
self.assertEqual(1369, updated_property1['maximum'])
self.assertEqual('string', updated_property2['type'])
self.assertEqual('p2 desc-UPDATED', updated_property2['description'])
self.assertEqual('value2-UPDATED', updated_property2['default'])
self.assertEqual(5, updated_property2['minLength'])
self.assertEqual(150, updated_property2['maxLength'])
# Updates should persist across requests
path = self._url('/v2/metadefs/namespaces/%s/objects/%s' %
(namespace_name, metadata_object_name))
response = requests.get(path, headers=self._headers())
self.assertEqual(200, response.status_code)
self.assertEqual('object1-UPDATED', metadata_object['name'])
self.assertEqual('desc-UPDATED', metadata_object['description'])
self.assertEqual('property2', metadata_object['required'][0])
updated_property1 = metadata_object['properties']['property1']
updated_property2 = metadata_object['properties']['property2']
self.assertEqual('integer', updated_property1['type'])
self.assertEqual('p1 desc-UPDATED', updated_property1['description'])
self.assertEqual('500', updated_property1['default'])
self.assertEqual(500, updated_property1['minimum'])
self.assertEqual(1369, updated_property1['maximum'])
self.assertEqual('string', updated_property2['type'])
self.assertEqual('p2 desc-UPDATED', updated_property2['description'])
self.assertEqual('value2-UPDATED', updated_property2['default'])
self.assertEqual(5, updated_property2['minLength'])
self.assertEqual(150, updated_property2['maxLength'])
# Deletion of metadata_object object1
path = self._url('/v2/metadefs/namespaces/%s/objects/%s' %
(namespace_name, metadata_object_name))
response = requests.delete(path, headers=self._headers())
self.assertEqual(204, response.status_code)
# metadata_object object1 should not exist
path = self._url('/v2/metadefs/namespaces/%s/objects/%s' %
(namespace_name, metadata_object_name))
response = requests.get(path, headers=self._headers())
self.assertEqual(404, response.status_code)

View File

@ -0,0 +1,180 @@
# 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
import requests
from glance.openstack.common import jsonutils
from glance.tests import functional
TENANT1 = str(uuid.uuid4())
class TestNamespaceProperties(functional.FunctionalTest):
def setUp(self):
super(TestNamespaceProperties, 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_properties_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)
# Property1 should not exist
path = self._url('/v2/metadefs/namespaces/MyNamespace/properties'
'/property1')
response = requests.get(path, headers=self._headers())
self.assertEqual(404, response.status_code)
# Create a property
path = self._url('/v2/metadefs/namespaces/MyNamespace/properties')
headers = self._headers({'content-type': 'application/json'})
property_name = "property1"
data = jsonutils.dumps(
{
"name": property_name,
"type": "integer",
"title": "property1",
"description": "property1 description",
"default": 100,
"minimum": 100,
"maximum": 30000369
}
)
response = requests.post(path, headers=headers, data=data)
self.assertEqual(201, response.status_code)
# Get the property created above
path = self._url('/v2/metadefs/namespaces/%s/properties/%s' %
(namespace_name, property_name))
response = requests.get(path, headers=self._headers())
self.assertEqual(200, response.status_code)
property_object = jsonutils.loads(response.text)
self.assertEqual("integer", property_object['type'])
self.assertEqual("property1", property_object['title'])
self.assertEqual("property1 description", property_object[
'description'])
self.assertEqual('100', property_object['default'])
self.assertEqual(100, property_object['minimum'])
self.assertEqual(30000369, property_object['maximum'])
# Returned property should match the created property
property_object = jsonutils.loads(response.text)
checked_keys = set([
u'name',
u'type',
u'title',
u'description',
u'default',
u'minimum',
u'maximum'
])
self.assertEqual(set(property_object.keys()), checked_keys)
expected_metadata_property = {
"type": "integer",
"title": "property1",
"description": "property1 description",
"default": '100',
"minimum": 100,
"maximum": 30000369
}
for key, value in expected_metadata_property.items():
self.assertEqual(property_object[key], value, key)
# The property should be mutable
path = self._url('/v2/metadefs/namespaces/%s/properties/%s' %
(namespace_name, property_name))
media_type = 'application/json'
headers = self._headers({'content-type': media_type})
property_name = "property1-UPDATED"
data = jsonutils.dumps(
{
"name": property_name,
"type": "string",
"title": "string property",
"description": "desc-UPDATED",
"default": "value-UPDATED",
"minLength": 5,
"maxLength": 10
}
)
response = requests.put(path, headers=headers, data=data)
self.assertEqual(200, response.status_code, response.text)
# Returned property should reflect the changes
property_object = jsonutils.loads(response.text)
self.assertEqual('string', property_object['type'])
self.assertEqual('desc-UPDATED', property_object['description'])
self.assertEqual('value-UPDATED', property_object['default'])
self.assertEqual(5, property_object['minLength'])
self.assertEqual(10, property_object['maxLength'])
# Updates should persist across requests
path = self._url('/v2/metadefs/namespaces/%s/properties/%s' %
(namespace_name, property_name))
response = requests.get(path, headers=self._headers())
self.assertEqual('string', property_object['type'])
self.assertEqual('desc-UPDATED', property_object['description'])
self.assertEqual('value-UPDATED', property_object['default'])
self.assertEqual(5, property_object['minLength'])
self.assertEqual(10, property_object['maxLength'])
# Deletion of property property1
path = self._url('/v2/metadefs/namespaces/%s/properties/%s' %
(namespace_name, property_name))
response = requests.delete(path, headers=self._headers())
self.assertEqual(204, response.status_code)
# property1 should not exist
path = self._url('/v2/metadefs/namespaces/%s/properties/%s' %
(namespace_name, property_name))
response = requests.get(path, headers=self._headers())
self.assertEqual(404, response.status_code)

View File

@ -0,0 +1,268 @@
# 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 six
import webob.exc
from wsme.rest.json import fromjson
from wsme.rest.json import tojson
from glance.api import policy
from glance.api.v2.model.metadef_resource_type import ResourceType
from glance.api.v2.model.metadef_resource_type import ResourceTypeAssociation
from glance.api.v2.model.metadef_resource_type import ResourceTypeAssociations
from glance.api.v2.model.metadef_resource_type import ResourceTypes
from glance.common import exception
from glance.common import utils
from glance.common import wsgi
import glance.db
import glance.gateway
from glance import i18n
import glance.notifier
from glance.openstack.common import jsonutils as json
import glance.openstack.common.log as logging
import glance.schema
import glance.store
LOG = logging.getLogger(__name__)
_LE = i18n._LE
_LI = i18n._LI
class ResourceTypeController(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)
def index(self, req):
try:
filters = {}
filters['namespace'] = None
rs_type_repo = self.gateway.get_metadef_resource_type_repo(
req.context)
db_resource_type_list = rs_type_repo.list(filters=filters)
resource_type_list = [ResourceType.to_wsme_model(
resource_type) for resource_type in db_resource_type_list]
resource_types = ResourceTypes()
resource_types.resource_types = resource_type_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(e)
raise webob.exc.HTTPInternalServerError(e)
return resource_types
def show(self, req, namespace):
try:
filters = {}
filters['namespace'] = namespace
rs_type_repo = self.gateway.get_metadef_resource_type_repo(
req.context)
db_resource_type_list = rs_type_repo.list(filters=filters)
resource_type_list = [ResourceTypeAssociation.to_wsme_model(
resource_type) for resource_type in db_resource_type_list]
resource_types = ResourceTypeAssociations()
resource_types.resource_type_associations = resource_type_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(e)
raise webob.exc.HTTPInternalServerError(e)
return resource_types
def create(self, req, resource_type, namespace):
rs_type_factory = self.gateway.get_metadef_resource_type_factory(
req.context)
rs_type_repo = self.gateway.get_metadef_resource_type_repo(req.context)
try:
new_resource_type = rs_type_factory.new_resource_type(
namespace=namespace, **resource_type.to_dict())
rs_type_repo.add(new_resource_type)
except exception.Forbidden as e:
msg = (_LE("Forbidden to create resource type. Reason: %("
"reason)s") % {'reason': utils.exception_to_str(e)})
LOG.error(msg)
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(e)
raise webob.exc.HTTPInternalServerError()
return ResourceTypeAssociation.to_wsme_model(new_resource_type)
def delete(self, req, namespace, resource_type):
rs_type_repo = self.gateway.get_metadef_resource_type_repo(req.context)
try:
filters = {}
found = False
filters['namespace'] = namespace
db_resource_type_list = rs_type_repo.list(filters=filters)
for db_resource_type in db_resource_type_list:
if db_resource_type.name == resource_type:
db_resource_type.delete()
rs_type_repo.remove(db_resource_type)
found = True
if not found:
raise exception.NotFound()
except exception.Forbidden as e:
raise webob.exc.HTTPForbidden(explanation=e.msg)
except exception.NotFound as e:
msg = (_LE("Failed to find resource type %(resourcetype)s to "
"delete") % {'resourcetype': resource_type})
LOG.error(msg)
raise webob.exc.HTTPNotFound(explanation=msg)
except Exception as e:
LOG.error(e)
raise webob.exc.HTTPInternalServerError()
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()
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']
@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=
utils.exception_to_str(msg))
def create(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)
resource_type = fromjson(ResourceTypeAssociation, body)
return dict(resource_type=resource_type)
class ResponseSerializer(wsgi.JSONResponseSerializer):
def __init__(self, schema=None):
super(ResponseSerializer, self).__init__()
self.schema = schema
def show(self, response, result):
resource_type_json = tojson(ResourceTypeAssociations, result)
body = json.dumps(resource_type_json, ensure_ascii=False)
response.unicode_body = six.text_type(body)
response.content_type = 'application/json'
def index(self, response, result):
resource_type_json = tojson(ResourceTypes, result)
body = json.dumps(resource_type_json, ensure_ascii=False)
response.unicode_body = six.text_type(body)
response.content_type = 'application/json'
def create(self, response, result):
resource_type_json = tojson(ResourceTypeAssociation, result)
response.status_int = 201
body = json.dumps(resource_type_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_base_properties():
return {
'name': {
'type': 'string',
'description': _('Resource type names should be aligned with Heat '
'resource types whenever possible: '
'http://docs.openstack.org/developer/heat/'
'template_guide/openstack.html'),
'maxLength': 80,
},
'prefix': {
'type': 'string',
'description': _('Specifies the prefix to use for the given '
'resource type. Any properties in the namespace '
'should be prefixed with this prefix when being '
'applied to the specified resource type. Must '
'include prefix separator (e.g. a colon :).'),
'maxLength': 80,
},
'properties_target': {
'type': 'string',
'description': _('Some resource types allow more than one key / '
'value pair per instance. For example, Cinder '
'allows user and image metadata on volumes. Only '
'the image properties metadata is evaluated by '
'Nova (scheduling or drivers). This property '
'allows a namespace target to remove the '
'ambiguity.'),
'maxLength': 80,
},
"created_at": {
"type": "string",
"description": _("Date and time of resource type association"
" (READ-ONLY)"),
"format": "date-time"
},
"updated_at": {
"type": "string",
"description": _("Date and time of the last resource type "
"association modification (READ-ONLY)"),
"format": "date-time"
}
}
def get_schema():
properties = _get_base_properties()
mandatory_attrs = ResourceTypeAssociation.get_mandatory_attrs()
schema = glance.schema.Schema(
'resource_type_association',
properties,
required=mandatory_attrs,
)
return schema
def get_collection_schema():
resource_type_schema = get_schema()
return glance.schema.CollectionSchema('resource_type_associations',
resource_type_schema)
def create_resource():
"""ResourceTypeAssociation resource factory method"""
schema = get_schema()
deserializer = RequestDeserializer(schema)
serializer = ResponseSerializer(schema)
controller = ResourceTypeController()
return wsgi.Resource(controller, deserializer, serializer)

View File

@ -0,0 +1,428 @@
# Copyright 2012 OpenStack Foundation.
# Copyright 2014 Intel Corporation
# All Rights Reserved.
#
# 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 glance.common import exception
from glance.common import utils
import glance.context
import glance.db
import glance.tests.unit.utils as unit_test_utils
import glance.tests.utils as test_utils
TENANT1 = '6838eb7b-6ded-434a-882c-b344c77fe8df'
TENANT2 = '2c014f32-55eb-467d-8fcb-4bd706012f81'
TENANT3 = '5a3e60e8-cfa9-4a9e-a90a-62b42cea92b8'
TENANT4 = 'c6c87f25-8a94-47ed-8c83-053c25f42df4'
USER1 = '54492ba0-f4df-4e4e-be62-27f4d76b29cf'
NAMESPACE1 = 'namespace1'
NAMESPACE2 = 'namespace2'
NAMESPACE3 = 'namespace3'
NAMESPACE4 = 'namespace4'
PROPERTY1 = 'Property1'
PROPERTY2 = 'Property2'
PROPERTY3 = 'Property3'
OBJECT1 = 'Object1'
OBJECT2 = 'Object2'
OBJECT3 = 'Object3'
RESOURCE_TYPE1 = 'ResourceType1'
RESOURCE_TYPE2 = 'ResourceType2'
RESOURCE_TYPE3 = 'ResourceType3'
def _db_namespace_fixture(**kwargs):
namespace = {
'namespace': None,
'display_name': None,
'description': None,
'visibility': True,
'protected': False,
'owner': None
}
namespace.update(kwargs)
return namespace
def _db_property_fixture(name, **kwargs):
property = {
'name': name,
'schema': '{"type": "string", "title": "title"}',
}
property.update(kwargs)
return property
def _db_object_fixture(name, **kwargs):
obj = {
'name': name,
'description': None,
'schema': '{}',
'required': '[]',
}
obj.update(kwargs)
return obj
def _db_resource_type_fixture(name, **kwargs):
obj = {
'name': name,
'protected': False,
}
obj.update(kwargs)
return obj
def _db_namespace_resource_type_fixture(name, **kwargs):
obj = {
'name': name,
'properties_target': None,
'prefix': None,
}
obj.update(kwargs)
return obj
class TestMetadefRepo(test_utils.BaseTestCase):
def setUp(self):
super(TestMetadefRepo, self).setUp()
self.db = unit_test_utils.FakeDB()
self.db.reset()
self.context = glance.context.RequestContext(user=USER1,
tenant=TENANT1)
self.namespace_repo = glance.db.MetadefNamespaceRepo(self.context,
self.db)
self.property_repo = glance.db.MetadefPropertyRepo(self.context,
self.db)
self.object_repo = glance.db.MetadefObjectRepo(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.resource_type_factory = glance.domain.MetadefResourceTypeFactory()
self._create_namespaces()
self._create_properties()
self._create_objects()
self._create_resource_types()
def _create_namespaces(self):
self.db.reset()
self.namespaces = [
_db_namespace_fixture(namespace=NAMESPACE1,
display_name='1',
description='desc1',
visibility='private',
protected=True,
owner=TENANT1),
_db_namespace_fixture(namespace=NAMESPACE2,
display_name='2',
description='desc2',
visibility='public',
protected=False,
owner=TENANT1),
_db_namespace_fixture(namespace=NAMESPACE3,
display_name='3',
description='desc3',
visibility='private',
protected=True,
owner=TENANT3),
_db_namespace_fixture(namespace=NAMESPACE4,
display_name='4',
description='desc4',
visibility='public',
protected=True,
owner=TENANT3)
]
[self.db.metadef_namespace_create(None, namespace)
for namespace in self.namespaces]
def _create_properties(self):
self.properties = [
_db_property_fixture(name=PROPERTY1),
_db_property_fixture(name=PROPERTY2),
_db_property_fixture(name=PROPERTY3)
]
[self.db.metadef_property_create(self.context, NAMESPACE1, property)
for property in self.properties]
[self.db.metadef_property_create(self.context, NAMESPACE4, property)
for property in self.properties]
def _create_objects(self):
self.objects = [
_db_object_fixture(name=OBJECT1,
description='desc1'),
_db_object_fixture(name=OBJECT2,
description='desc2'),
_db_object_fixture(name=OBJECT3,
description='desc3'),
]
[self.db.metadef_object_create(self.context, NAMESPACE1, object)
for object in self.objects]
[self.db.metadef_object_create(self.context, NAMESPACE4, object)
for object in self.objects]
def _create_resource_types(self):
self.resource_types = [
_db_resource_type_fixture(name=RESOURCE_TYPE1,
protected=False),
_db_resource_type_fixture(name=RESOURCE_TYPE2,
protected=False),
_db_resource_type_fixture(name=RESOURCE_TYPE3,
protected=True),
]
[self.db.metadef_resource_type_create(self.context, resource_type)
for resource_type in self.resource_types]
def test_get_namespace(self):
namespace = self.namespace_repo.get(NAMESPACE1)
self.assertEqual(namespace.namespace, NAMESPACE1)
self.assertEqual(namespace.description, 'desc1')
self.assertEqual(namespace.display_name, '1')
self.assertEqual(namespace.owner, TENANT1)
self.assertEqual(namespace.protected, True)
self.assertEqual(namespace.visibility, 'private')
def test_get_namespace_not_found(self):
fake_namespace = "fake_namespace"
exc = self.assertRaises(exception.NotFound,
self.namespace_repo.get,
fake_namespace)
self.assertIn(fake_namespace, utils.exception_to_str(exc))
def test_get_namespace_forbidden(self):
self.assertRaises(exception.NotFound,
self.namespace_repo.get,
NAMESPACE3)
def test_list_namespace(self):
namespaces = self.namespace_repo.list()
namespace_names = set([n.namespace for n in namespaces])
self.assertEqual(set([NAMESPACE1, NAMESPACE2, NAMESPACE4]),
namespace_names)
def test_list_private_namespaces(self):
filters = {'visibility': 'private'}
namespaces = self.namespace_repo.list(filters=filters)
namespace_names = set([n.namespace for n in namespaces])
self.assertEqual(set([NAMESPACE1]), namespace_names)
def test_add_namespace(self):
# NOTE(pawel-koniszewski): Change db_namespace_fixture to
# namespace_factory when namespace primary key in DB
# will be changed from Integer to UUID
namespace = _db_namespace_fixture(namespace='added_namespace',
display_name='fake',
description='fake_desc',
visibility='public',
protected=True,
owner=TENANT1)
self.assertEqual(namespace['namespace'], 'added_namespace')
self.db.metadef_namespace_create(None, namespace)
retrieved_namespace = self.namespace_repo.get(namespace['namespace'])
self.assertEqual(retrieved_namespace.namespace, 'added_namespace')
def test_save_namespace(self):
namespace = self.namespace_repo.get(NAMESPACE1)
namespace.display_name = 'save_name'
namespace.description = 'save_desc'
self.namespace_repo.save(namespace)
namespace = self.namespace_repo.get(NAMESPACE1)
self.assertEqual(namespace.display_name, 'save_name')
self.assertEqual(namespace.description, 'save_desc')
def test_remove_namespace(self):
namespace = self.namespace_repo.get(NAMESPACE1)
self.namespace_repo.remove(namespace)
self.assertRaises(exception.NotFound, self.namespace_repo.get,
NAMESPACE1)
def test_remove_namespace_not_found(self):
fake_name = 'fake_name'
namespace = self.namespace_repo.get(NAMESPACE1)
namespace.namespace = fake_name
exc = self.assertRaises(exception.NotFound, self.namespace_repo.remove,
namespace)
self.assertIn(fake_name, utils.exception_to_str(exc))
def test_get_property(self):
property = self.property_repo.get(NAMESPACE1, PROPERTY1)
namespace = self.namespace_repo.get(NAMESPACE1)
self.assertEqual(property.name, PROPERTY1)
self.assertEqual(property.namespace.namespace, namespace.namespace)
def test_get_property_not_found(self):
exc = self.assertRaises(exception.NotFound,
self.property_repo.get,
NAMESPACE2, PROPERTY1)
self.assertIn(PROPERTY1, utils.exception_to_str(exc))
def test_list_property(self):
properties = self.property_repo.list(filters={'namespace': NAMESPACE1})
property_names = set([p.name for p in properties])
self.assertEqual(set([PROPERTY1, PROPERTY2, PROPERTY3]),
property_names)
def test_list_property_empty_result(self):
properties = self.property_repo.list(filters={'namespace': NAMESPACE2})
property_names = set([p.name for p in properties])
self.assertEqual(set([]),
property_names)
def test_list_property_namespace_not_found(self):
exc = self.assertRaises(exception.NotFound, self.property_repo.list,
filters={'namespace': 'not-a-namespace'})
self.assertIn('not-a-namespace', utils.exception_to_str(exc))
def test_add_property(self):
# NOTE(pawel-koniszewski): Change db_property_fixture to
# property_factory when property primary key in DB
# will be changed from Integer to UUID
property = _db_property_fixture(name='added_property')
self.assertEqual(property['name'], 'added_property')
self.db.metadef_property_create(self.context, NAMESPACE1, property)
retrieved_property = self.property_repo.get(NAMESPACE1,
'added_property')
self.assertEqual(retrieved_property.name, 'added_property')
def test_add_property_namespace_forbidden(self):
# NOTE(pawel-koniszewski): Change db_property_fixture to
# property_factory when property primary key in DB
# will be changed from Integer to UUID
property = _db_property_fixture(name='added_property')
self.assertEqual(property['name'], 'added_property')
self.assertRaises(exception.Forbidden, self.db.metadef_property_create,
self.context, NAMESPACE3, property)
def test_add_property_namespace_not_found(self):
# NOTE(pawel-koniszewski): Change db_property_fixture to
# property_factory when property primary key in DB
# will be changed from Integer to UUID
property = _db_property_fixture(name='added_property')
self.assertEqual(property['name'], 'added_property')
self.assertRaises(exception.NotFound, self.db.metadef_property_create,
self.context, 'not_a_namespace', property)
def test_save_property(self):
property = self.property_repo.get(NAMESPACE1, PROPERTY1)
property.schema = '{"save": "schema"}'
self.property_repo.save(property)
property = self.property_repo.get(NAMESPACE1, PROPERTY1)
self.assertEqual(property.name, PROPERTY1)
self.assertEqual(property.schema, '{"save": "schema"}')
def test_remove_property(self):
property = self.property_repo.get(NAMESPACE1, PROPERTY1)
self.property_repo.remove(property)
self.assertRaises(exception.NotFound, self.property_repo.get,
NAMESPACE1, PROPERTY1)
def test_remove_property_not_found(self):
fake_name = 'fake_name'
property = self.property_repo.get(NAMESPACE1, PROPERTY1)
property.name = fake_name
self.assertRaises(exception.NotFound, self.property_repo.remove,
property)
def test_get_object(self):
object = self.object_repo.get(NAMESPACE1, OBJECT1)
namespace = self.namespace_repo.get(NAMESPACE1)
self.assertEqual(object.name, OBJECT1)
self.assertEqual(object.description, 'desc1')
self.assertEqual(object.required, ['[]'])
self.assertEqual(object.properties, {})
self.assertEqual(object.namespace.namespace, namespace.namespace)
def test_get_object_not_found(self):
exc = self.assertRaises(exception.NotFound, self.object_repo.get,
NAMESPACE2, OBJECT1)
self.assertIn(OBJECT1, utils.exception_to_str(exc))
def test_list_object(self):
objects = self.object_repo.list(filters={'namespace': NAMESPACE1})
object_names = set([o.name for o in objects])
self.assertEqual(set([OBJECT1, OBJECT2, OBJECT3]), object_names)
def test_list_object_empty_result(self):
objects = self.object_repo.list(filters={'namespace': NAMESPACE2})
object_names = set([o.name for o in objects])
self.assertEqual(set([]), object_names)
def test_list_object_namespace_not_found(self):
exc = self.assertRaises(exception.NotFound, self.object_repo.list,
filters={'namespace': 'not-a-namespace'})
self.assertIn('not-a-namespace', utils.exception_to_str(exc))
def test_add_object(self):
# NOTE(pawel-koniszewski): Change db_object_fixture to
# object_factory when object primary key in DB
# will be changed from Integer to UUID
object = _db_object_fixture(name='added_object')
self.assertEqual(object['name'], 'added_object')
self.db.metadef_object_create(self.context, NAMESPACE1, object)
retrieved_object = self.object_repo.get(NAMESPACE1,
'added_object')
self.assertEqual(retrieved_object.name, 'added_object')
def test_add_object_namespace_forbidden(self):
# NOTE(pawel-koniszewski): Change db_object_fixture to
# object_factory when object primary key in DB
# will be changed from Integer to UUID
object = _db_object_fixture(name='added_object')
self.assertEqual(object['name'], 'added_object')
self.assertRaises(exception.Forbidden, self.db.metadef_object_create,
self.context, NAMESPACE3, object)
def test_add_object_namespace_not_found(self):
# NOTE(pawel-koniszewski): Change db_object_fixture to
# object_factory when object primary key in DB
# will be changed from Integer to UUID
object = _db_object_fixture(name='added_object')
self.assertEqual(object['name'], 'added_object')
self.assertRaises(exception.NotFound, self.db.metadef_object_create,
self.context, 'not-a-namespace', object)
def test_save_object(self):
object = self.object_repo.get(NAMESPACE1, OBJECT1)
object.required = ['save_req']
object.description = 'save_desc'
self.object_repo.save(object)
object = self.object_repo.get(NAMESPACE1, OBJECT1)
self.assertEqual(object.name, OBJECT1)
self.assertEqual(object.required, ['save_req'])
self.assertEqual(object.description, 'save_desc')
def test_remove_object(self):
object = self.object_repo.get(NAMESPACE1, OBJECT1)
self.object_repo.remove(object)
self.assertRaises(exception.NotFound, self.object_repo.get,
NAMESPACE1, OBJECT1)
def test_remove_object_not_found(self):
fake_name = 'fake_name'
object = self.object_repo.get(NAMESPACE1, OBJECT1)
object.name = fake_name
self.assertRaises(exception.NotFound, self.object_repo.remove,
object)
def test_list_resource_type(self):
resource_type = self.resource_type_repo.list(filters=
{'namespace': NAMESPACE1})
self.assertEqual(len(resource_type), 0)

File diff suppressed because it is too large Load Diff

View File

@ -24,6 +24,7 @@ oslo.config>=1.4.0.0a3
stevedore>=0.14
netaddr>=0.7.6
keystonemiddleware>=1.0.0
WSME>=0.6
# For openstack/common/lockutils
posix_ipc