Glare 0.2.0 release
-----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQEcBAABAgAGBQJX89giAAoJEGvNeA1vV2/Yrg4IAK1icFWXWE7ikgtiFWsIQMZf ZRPGkNyG4kKuruFibJFUBZnlYgYYRV6QnyhrL6QH2bQmSD9s1ES6yV3UyxQP3Em3 tFyi/CIUIyppkYsEA+BlBxoKpY8oXE/OIEDvo0Pi4SGmbKTMdidkvDGK7deUKsOl rftPBjTTlJXGX97ACnu9AY1yhIHYsfugTSXyVVfLIcZ2Jjxr+PglopRSnyoo9ejl sabjSQGF5cl2mtHmdXGbA5gBdWQN2A9wW6IjtClorkbDYJI7U3TbLBSP5C+Xkq7K 2DKAJsSpv7N7YhixTH8ZVH/3N+tv7IPxu3PCwiu7VkfE6xF9H/tOASiOV9Sx0H0= =YFJg -----END PGP SIGNATURE----- Merge tag '0.2.0' into debian/newton Glare 0.2.0 release Change-Id: I8ac5fc4af3367bdf2946c9733711cfb78ebc4d24
This commit is contained in:
commit
6789bdd57a
|
@ -1,4 +1,4 @@
|
|||
[gerrit]
|
||||
host=review.openstack.org
|
||||
port=29418
|
||||
project=openstack/glare.git
|
||||
project=openstack/deb-glare.git
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
glare (0.2.0-1) UNRELEASED; urgency=medium
|
||||
|
||||
* New upstream release.
|
||||
|
||||
-- Ivan Udovichenko <iudovichenko@mirantis.com> Thu, 06 Oct 2016 12:42:54 +0300
|
||||
|
||||
glare (0.1.0-1) unstable; urgency=medium
|
||||
|
||||
* Initial release. (Closes: #839231)
|
||||
|
|
|
@ -43,7 +43,7 @@ Build-Depends-Indep: python-alembic (>= 0.8.4),
|
|||
python-oslo.log (>= 3.11.0),
|
||||
python-oslo.messaging (>= 5.2.0),
|
||||
python-oslo.middleware (>= 3.0.0),
|
||||
python-oslo.policy (>= 1.9.0),
|
||||
python-oslo.policy (>= 1.14.0),
|
||||
python-oslo.serialization (>= 1.10.0),
|
||||
python-oslo.service (>= 1.10.0),
|
||||
python-oslo.utils (>= 3.16.0),
|
||||
|
@ -101,7 +101,7 @@ Depends: python-alembic (>= 0.8.4),
|
|||
python-oslo.log (>= 3.11.0),
|
||||
python-oslo.messaging (>= 5.2.0),
|
||||
python-oslo.middleware (>= 3.0.0),
|
||||
python-oslo.policy (>= 1.9.0),
|
||||
python-oslo.policy (>= 1.14.0),
|
||||
python-oslo.serialization (>= 1.10.0),
|
||||
python-oslo.service (>= 1.10.0),
|
||||
python-oslo.utils (>= 3.16.0),
|
||||
|
|
|
@ -72,6 +72,9 @@ function configure_glare {
|
|||
iniset $GLARE_CONF_FILE oslo_messaging_rabbit rabbit_userid $RABBIT_USERID
|
||||
iniset $GLARE_CONF_FILE oslo_messaging_rabbit rabbit_password $RABBIT_PASSWORD
|
||||
|
||||
# Enable notifications support
|
||||
iniset $GLARE_CONF_FILE oslo_messaging_notifications driver messaging
|
||||
|
||||
# Configure the database.
|
||||
iniset $GLARE_CONF_FILE database connection `database_connection_url glare`
|
||||
iniset $GLARE_CONF_FILE database max_overflow -1
|
||||
|
|
|
@ -70,6 +70,7 @@ def main():
|
|||
config.parse_args()
|
||||
wsgi.set_eventlet_hub()
|
||||
logging.setup(CONF, 'glare')
|
||||
notification.set_defaults()
|
||||
|
||||
if cfg.CONF.profiler.enabled:
|
||||
_notifier = osprofiler.notifier.create(
|
||||
|
|
|
@ -33,8 +33,6 @@ artifact_policy_rules = [
|
|||
'is_admin:True or project_id:%(owner)s'),
|
||||
policy.RuleDefault("artifact:type_list", "",
|
||||
"Policy to request list of artifact types"),
|
||||
policy.RuleDefault("artifact:type_get", "",
|
||||
"Policy to request artifact type definition"),
|
||||
policy.RuleDefault("artifact:create", "", "Policy to create artifact."),
|
||||
policy.RuleDefault("artifact:update_public",
|
||||
"'public':%(visibility)s and rule:context_is_admin "
|
||||
|
|
|
@ -40,11 +40,13 @@ from oslo_log import log as logging
|
|||
from oslo_utils import encodeutils
|
||||
from oslo_utils import excutils
|
||||
from oslo_utils import timeutils
|
||||
from oslo_versionedobjects import fields
|
||||
import six
|
||||
from webob import exc
|
||||
|
||||
from glare.common import exception
|
||||
from glare.i18n import _, _LE, _LW
|
||||
from glare.objects.meta import fields as glare_fields
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
@ -553,6 +555,54 @@ class error_handler(object):
|
|||
return new_function
|
||||
|
||||
|
||||
def get_schema_type(attr):
|
||||
if isinstance(attr, fields.IntegerField):
|
||||
return 'integer'
|
||||
elif isinstance(attr, fields.FloatField):
|
||||
return 'number'
|
||||
elif isinstance(attr, fields.BooleanField):
|
||||
return 'boolean'
|
||||
elif isinstance(attr, glare_fields.List):
|
||||
return 'array'
|
||||
elif isinstance(attr, (glare_fields.Dict, glare_fields.BlobField)):
|
||||
return 'object'
|
||||
return 'string'
|
||||
|
||||
|
||||
def get_glare_type(attr):
|
||||
if isinstance(attr, fields.IntegerField):
|
||||
return 'Integer'
|
||||
elif isinstance(attr, fields.FloatField):
|
||||
return 'Float'
|
||||
elif isinstance(attr, fields.FlexibleBooleanField):
|
||||
return 'Boolean'
|
||||
elif isinstance(attr, fields.DateTimeField):
|
||||
return 'DateTime'
|
||||
elif isinstance(attr, glare_fields.BlobField):
|
||||
return 'Blob'
|
||||
elif isinstance(attr, glare_fields.Link):
|
||||
return 'Link'
|
||||
elif isinstance(attr, glare_fields.List):
|
||||
return _get_element_type(attr.element_type) + 'List'
|
||||
elif isinstance(attr, glare_fields.Dict):
|
||||
return _get_element_type(attr.element_type) + 'Dict'
|
||||
return 'String'
|
||||
|
||||
|
||||
def _get_element_type(element_type):
|
||||
if element_type is fields.FlexibleBooleanField:
|
||||
return 'Boolean'
|
||||
elif element_type is fields.Integer:
|
||||
return 'Integer'
|
||||
elif element_type is fields.Float:
|
||||
return 'Float'
|
||||
elif element_type is glare_fields.BlobFieldType:
|
||||
return 'Blob'
|
||||
elif element_type is glare_fields.LinkFieldType:
|
||||
return 'Link'
|
||||
return 'String'
|
||||
|
||||
|
||||
class DictDiffer(object):
|
||||
"""
|
||||
Calculate the difference between two dictionaries as:
|
||||
|
|
|
@ -57,7 +57,8 @@ class ArtifactAPI(base_api.BaseDBAPI):
|
|||
|
||||
def list(self, context, filters, marker, limit, sort, latest):
|
||||
session = api.get_session()
|
||||
filters.append(('type_name', None, 'eq', None, self.type))
|
||||
if self.type != 'all':
|
||||
filters.append(('type_name', None, 'eq', None, self.type))
|
||||
return api.get_all(context=context, session=session, filters=filters,
|
||||
marker=marker, limit=limit, sort=sort,
|
||||
latest=latest)
|
||||
|
|
|
@ -113,7 +113,7 @@ class Engine(object):
|
|||
|
||||
@classmethod
|
||||
def show_type_schema(cls, context, type_name):
|
||||
policy.authorize("artifact:type_get", {}, context)
|
||||
policy.authorize("artifact:type_list", {}, context)
|
||||
schemas = cls._get_schemas(cls.registry)
|
||||
if type_name not in schemas:
|
||||
msg = _("Artifact type %s does not exist") % type_name
|
||||
|
|
|
@ -15,7 +15,6 @@
|
|||
from oslo_config import cfg
|
||||
from oslo_log import log as logging
|
||||
import oslo_messaging
|
||||
from oslo_messaging import serializer
|
||||
|
||||
CONF = cfg.CONF
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
@ -31,19 +30,8 @@ def get_transport():
|
|||
return oslo_messaging.get_notification_transport(CONF)
|
||||
|
||||
|
||||
class RequestSerializer(serializer.Serializer):
|
||||
|
||||
def serialize_entity(self, context, entity):
|
||||
return entity.to_notification()
|
||||
|
||||
def deserialize_entity(self, context, entity):
|
||||
return entity
|
||||
|
||||
def serialize_context(self, context):
|
||||
return context.to_dict()
|
||||
|
||||
def deserialize_context(self, context):
|
||||
return context.from_dict(context)
|
||||
def set_defaults(control_exchange='glare'):
|
||||
oslo_messaging.set_transport_defaults(control_exchange)
|
||||
|
||||
|
||||
class Notifier(object):
|
||||
|
@ -59,8 +47,7 @@ class Notifier(object):
|
|||
if cls.GLARE_NOTIFIER is None:
|
||||
cls.GLARE_NOTIFIER = oslo_messaging.Notifier(
|
||||
get_transport(),
|
||||
publisher_id=CONF.glare_publisher_id,
|
||||
serializer=RequestSerializer())
|
||||
publisher_id=CONF.glare_publisher_id)
|
||||
return cls.GLARE_NOTIFIER
|
||||
|
||||
@classmethod
|
||||
|
@ -74,7 +61,8 @@ class Notifier(object):
|
|||
"""
|
||||
af_notifier = cls._get_notifier()
|
||||
method = getattr(af_notifier, level.lower())
|
||||
method(context, "%s.%s" % (cls.SERVICE_NAME, event_type), body)
|
||||
method({}, "%s.%s" % (cls.SERVICE_NAME, event_type),
|
||||
body.to_notification())
|
||||
LOG.debug('Notification event %(event)s send successfully for '
|
||||
'request %(request)s', {'event': event_type,
|
||||
'request': context.request_id})
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
# Copyright (c) 2016 Mirantis, Inc.
|
||||
#
|
||||
# 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_versionedobjects import fields
|
||||
|
||||
from glare.objects import base
|
||||
from glare.objects.meta import attribute
|
||||
|
||||
|
||||
Field = attribute.Attribute.init
|
||||
|
||||
|
||||
class All(base.ReadOnlyMixin, base.BaseArtifact):
|
||||
"""Artifact type that allows to get artifacts regardless of their type"""
|
||||
|
||||
fields = {
|
||||
'type_name': Field(fields.StringField,
|
||||
description="Name of artifact type."),
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def get_type_name(cls):
|
||||
return "all"
|
|
@ -434,24 +434,24 @@ class BaseArtifact(base.VersionedObject):
|
|||
else:
|
||||
action = cls.activate
|
||||
|
||||
# check updates for dependencies and validate them
|
||||
# check updates for links and validate them
|
||||
try:
|
||||
for key, value in six.iteritems(updates):
|
||||
if cls.fields.get(key) is glare_fields.Dependency \
|
||||
if cls.fields.get(key) is glare_fields.Link \
|
||||
and value is not None:
|
||||
# check format
|
||||
glare_fields.DependencyFieldType.coerce(None, key, value)
|
||||
glare_fields.LinkFieldType.coerce(None, key, value)
|
||||
# check containment
|
||||
if glare_fields.DependencyFieldType.is_external(value):
|
||||
# validate external dependency
|
||||
cls._validate_external_dependency(value)
|
||||
if glare_fields.LinkFieldType.is_external(value):
|
||||
# validate external link
|
||||
cls._validate_external_link(value)
|
||||
else:
|
||||
type_name = (glare_fields.DependencyFieldType.
|
||||
type_name = (glare_fields.LinkFieldType.
|
||||
get_type_name(value))
|
||||
af_type = registry.get_artifact_type(type_name)
|
||||
cls._validate_soft_dependency(context, value, af_type)
|
||||
cls._validate_soft_link(context, value, af_type)
|
||||
except Exception as e:
|
||||
msg = (_("Bad dependency in artifact %(af)s: %(msg)s")
|
||||
msg = (_("Bad link in artifact %(af)s: %(msg)s")
|
||||
% {"af": artifact.id, "msg": str(e)})
|
||||
raise exception.BadRequest(msg)
|
||||
|
||||
|
@ -461,12 +461,12 @@ class BaseArtifact(base.VersionedObject):
|
|||
return action
|
||||
|
||||
@classmethod
|
||||
def _validate_external_dependency(cls, link):
|
||||
def _validate_external_link(cls, link):
|
||||
with urlrequest.urlopen(link) as data:
|
||||
data.read(1)
|
||||
|
||||
@classmethod
|
||||
def _validate_soft_dependency(cls, context, link, af_type):
|
||||
def _validate_soft_link(cls, context, link, af_type):
|
||||
af_id = link.split('/')[3]
|
||||
af_type.get(context, af_id)
|
||||
|
||||
|
@ -1087,23 +1087,9 @@ class BaseArtifact(base.VersionedObject):
|
|||
res[key] = val
|
||||
return res
|
||||
|
||||
@staticmethod
|
||||
def schema_type(attr):
|
||||
if isinstance(attr, fields.IntegerField):
|
||||
return 'integer'
|
||||
elif isinstance(attr, fields.FloatField):
|
||||
return 'number'
|
||||
elif isinstance(attr, fields.BooleanField):
|
||||
return 'boolean'
|
||||
elif isinstance(attr, glare_fields.List):
|
||||
return 'array'
|
||||
elif isinstance(attr, (glare_fields.Dict, glare_fields.BlobField)):
|
||||
return 'object'
|
||||
return 'string'
|
||||
|
||||
@classmethod
|
||||
def schema_attr(cls, attr, attr_name=''):
|
||||
attr_type = cls.schema_type(attr)
|
||||
attr_type = utils.get_schema_type(attr)
|
||||
schema = {}
|
||||
|
||||
# generate schema for validators
|
||||
|
@ -1112,6 +1098,7 @@ class BaseArtifact(base.VersionedObject):
|
|||
|
||||
schema['type'] = (attr_type
|
||||
if not attr.nullable else [attr_type, 'null'])
|
||||
schema['glareType'] = utils.get_glare_type(attr)
|
||||
output_blob_schema = {
|
||||
'type': ['object', 'null'],
|
||||
'properties': {
|
||||
|
@ -1134,7 +1121,7 @@ class BaseArtifact(base.VersionedObject):
|
|||
schema['readOnly'] = True
|
||||
|
||||
if isinstance(attr, glare_fields.Dict):
|
||||
element_type = (cls.schema_type(attr.element_type)
|
||||
element_type = (utils.get_schema_type(attr.element_type)
|
||||
if hasattr(attr, 'element_type')
|
||||
else 'string')
|
||||
|
||||
|
@ -1156,7 +1143,7 @@ class BaseArtifact(base.VersionedObject):
|
|||
|
||||
if attr_type == 'array':
|
||||
schema['items'] = {
|
||||
'type': (cls.schema_type(attr.element_type)
|
||||
'type': (utils.get_schema_type(attr.element_type)
|
||||
if hasattr(attr, 'element_type')
|
||||
else 'string')}
|
||||
|
||||
|
@ -1193,9 +1180,64 @@ class BaseArtifact(base.VersionedObject):
|
|||
attr_name=attr_name)
|
||||
schemas = {'properties': schemas_prop,
|
||||
'name': cls.get_type_name(),
|
||||
'version': cls.VERSION,
|
||||
'title': 'Artifact type %s of version %s' %
|
||||
(cls.get_type_name(), cls.VERSION),
|
||||
'type': 'object',
|
||||
'required': ['name']}
|
||||
|
||||
return schemas
|
||||
|
||||
|
||||
class ReadOnlyMixin(object):
|
||||
"""Mixin that disables all modifying actions on artifacts."""
|
||||
|
||||
@classmethod
|
||||
def create(cls, context, values):
|
||||
raise exception.Forbidden("This type is read only.")
|
||||
|
||||
@classmethod
|
||||
def update(cls, context, af, values):
|
||||
raise exception.Forbidden("This type is read only.")
|
||||
|
||||
@classmethod
|
||||
def get_action_for_updates(cls, context, artifact, updates, registry):
|
||||
raise exception.Forbidden("This type is read only.")
|
||||
|
||||
@classmethod
|
||||
def delete(cls, context, af):
|
||||
raise exception.Forbidden("This type is read only.")
|
||||
|
||||
@classmethod
|
||||
def activate(cls, context, af, values):
|
||||
raise exception.Forbidden("This type is read only.")
|
||||
|
||||
@classmethod
|
||||
def reactivate(cls, context, af, values):
|
||||
raise exception.Forbidden("This type is read only.")
|
||||
|
||||
@classmethod
|
||||
def deactivate(cls, context, af, values):
|
||||
raise exception.Forbidden("This type is read only.")
|
||||
|
||||
@classmethod
|
||||
def publish(cls, context, af, values):
|
||||
raise exception.Forbidden("This type is read only.")
|
||||
|
||||
@classmethod
|
||||
def upload_blob(cls, context, af, field_name, fd, content_type):
|
||||
raise exception.Forbidden("This type is read only.")
|
||||
|
||||
@classmethod
|
||||
def upload_blob_dict(cls, context, af, field_name, blob_key, fd,
|
||||
content_type):
|
||||
raise exception.Forbidden("This type is read only.")
|
||||
|
||||
@classmethod
|
||||
def add_blob_location(cls, context, af, field_name, location, blob_meta):
|
||||
raise exception.Forbidden("This type is read only.")
|
||||
|
||||
@classmethod
|
||||
def add_blob_dict_location(cls, context, af, field_name,
|
||||
blob_key, location, blob_meta):
|
||||
raise exception.Forbidden("This type is read only.")
|
||||
|
|
|
@ -29,7 +29,7 @@ BlobDict = attribute.BlobDictAttribute.init
|
|||
class HeatTemplate(base.BaseArtifact):
|
||||
|
||||
fields = {
|
||||
'environments': Dict(glare_fields.Dependency,
|
||||
'environments': Dict(glare_fields.LinkFieldType,
|
||||
mutable=True,
|
||||
description="References to Heat Environments "
|
||||
"that can be used with current "
|
||||
|
|
|
@ -112,8 +112,8 @@ class BlobField(fields.AutoTypedField):
|
|||
AUTO_TYPE = BlobFieldType()
|
||||
|
||||
|
||||
class DependencyFieldType(fields.FieldType):
|
||||
"""Dependency field specifies Artifact dependency on other artifact or some
|
||||
class LinkFieldType(fields.FieldType):
|
||||
"""Link field specifies Artifact dependency on other artifact or some
|
||||
external resource. From technical perspective it is just soft link to Glare
|
||||
Artifact or https/http resource. So Artifact users can download the
|
||||
referenced file by that link.
|
||||
|
@ -134,7 +134,7 @@ class DependencyFieldType(fields.FieldType):
|
|||
|
||||
@staticmethod
|
||||
def coerce(obj, attr, value):
|
||||
# to remove the existing dependency user sets its value to None,
|
||||
# to remove the existing link user sets its value to None,
|
||||
# we have to consider this case.
|
||||
if value is None:
|
||||
return value
|
||||
|
@ -144,7 +144,7 @@ class DependencyFieldType(fields.FieldType):
|
|||
'not a %(type)s') %
|
||||
{'attr': attr, 'type': type(value).__name__})
|
||||
# determine if link is external or internal
|
||||
external = DependencyFieldType.is_external(value)
|
||||
external = LinkFieldType.is_external(value)
|
||||
# validate link itself
|
||||
if external:
|
||||
link = urlparse.urlparse(value)
|
||||
|
@ -155,7 +155,7 @@ class DependencyFieldType(fields.FieldType):
|
|||
result = value.split('/')
|
||||
if len(result) != 4 or result[1] != 'artifacts':
|
||||
raise ValueError(
|
||||
_('Dependency link %(link)s is not valid in field '
|
||||
_('Link %(link)s is not valid in field '
|
||||
'%(attr)s. The link must be either valid url or '
|
||||
'reference to artifact. Example: '
|
||||
'/artifacts/<artifact_type>/<artifact_id>'
|
||||
|
@ -163,8 +163,8 @@ class DependencyFieldType(fields.FieldType):
|
|||
return value
|
||||
|
||||
|
||||
class Dependency(fields.AutoTypedField):
|
||||
AUTO_TYPE = DependencyFieldType()
|
||||
class Link(fields.AutoTypedField):
|
||||
AUTO_TYPE = LinkFieldType()
|
||||
|
||||
|
||||
class List(fields.AutoTypedField):
|
||||
|
|
|
@ -103,7 +103,7 @@ class ArtifactRegistry(vo_base.VersionedObjectRegistry):
|
|||
supported_types = []
|
||||
for module in modules:
|
||||
supported_types.extend(get_subclasses(module, base.BaseArtifact))
|
||||
for type_name in CONF.glare.enabled_artifact_types:
|
||||
for type_name in set(CONF.glare.enabled_artifact_types + ['all']):
|
||||
for af_type in supported_types:
|
||||
if type_name == af_type.get_type_name():
|
||||
cls._validate_artifact_type(af_type)
|
||||
|
|
|
@ -211,7 +211,7 @@ class MinNumberSize(SizeValidator):
|
|||
return fields.IntegerField, fields.FloatField
|
||||
|
||||
def to_jsonschema(self):
|
||||
return {'minumum': self.size}
|
||||
return {'minimum': self.size}
|
||||
|
||||
|
||||
class Unique(Validator):
|
||||
|
|
|
@ -50,7 +50,7 @@ class MuranoPackage(base.BaseArtifact):
|
|||
"the package."),
|
||||
'inherits': Dict(fields.String),
|
||||
'keywords': List(fields.String, mutable=True),
|
||||
'dependencies': List(glare_fields.Dependency,
|
||||
'dependencies': List(glare_fields.LinkFieldType,
|
||||
required_on_activate=False,
|
||||
description="List of package dependencies for "
|
||||
"this package."),
|
||||
|
|
|
@ -0,0 +1,183 @@
|
|||
# Copyright (c) 2016 Mirantis, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import uuid
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
import requests
|
||||
|
||||
from glare.tests import functional
|
||||
|
||||
|
||||
def sort_results(lst, target='name'):
|
||||
return sorted(lst, key=lambda x: x[target])
|
||||
|
||||
|
||||
class TestArtifact(functional.FunctionalTest):
|
||||
enabled_types = (u'sample_artifact', u'images', u'heat_templates',
|
||||
u'heat_environments', u'tosca_templates',
|
||||
u'murano_packages', u'all')
|
||||
|
||||
users = {
|
||||
'user1': {
|
||||
'id': str(uuid.uuid4()),
|
||||
'tenant_id': str(uuid.uuid4()),
|
||||
'token': str(uuid.uuid4()),
|
||||
'role': 'member'
|
||||
},
|
||||
'user2': {
|
||||
'id': str(uuid.uuid4()),
|
||||
'tenant_id': str(uuid.uuid4()),
|
||||
'token': str(uuid.uuid4()),
|
||||
'role': 'member'
|
||||
},
|
||||
'admin': {
|
||||
'id': str(uuid.uuid4()),
|
||||
'tenant_id': str(uuid.uuid4()),
|
||||
'token': str(uuid.uuid4()),
|
||||
'role': 'admin'
|
||||
},
|
||||
'anonymous': {
|
||||
'id': None,
|
||||
'tenant_id': None,
|
||||
'token': None,
|
||||
'role': None
|
||||
}
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super(TestArtifact, self).setUp()
|
||||
|
||||
self.set_user('user1')
|
||||
self.glare_server.deployment_flavor = 'noauth'
|
||||
|
||||
self.glare_server.enabled_artifact_types = ','.join(
|
||||
self.enabled_types)
|
||||
self.glare_server.custom_artifact_types_modules = (
|
||||
'glare.tests.functional.sample_artifact')
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
def tearDown(self):
|
||||
self.stop_servers()
|
||||
self._reset_database(self.glare_server.sql_connection)
|
||||
super(TestArtifact, self).tearDown()
|
||||
|
||||
def _url(self, path):
|
||||
if 'schemas' in path:
|
||||
return 'http://127.0.0.1:%d%s' % (self.glare_port, path)
|
||||
else:
|
||||
return 'http://127.0.0.1:%d/artifacts%s' % (self.glare_port, path)
|
||||
|
||||
def set_user(self, username):
|
||||
if username not in self.users:
|
||||
raise KeyError
|
||||
self.current_user = username
|
||||
|
||||
def _headers(self, custom_headers=None):
|
||||
base_headers = {
|
||||
'X-Identity-Status': 'Confirmed',
|
||||
'X-Auth-Token': self.users[self.current_user]['token'],
|
||||
'X-User-Id': self.users[self.current_user]['id'],
|
||||
'X-Tenant-Id': self.users[self.current_user]['tenant_id'],
|
||||
'X-Project-Id': self.users[self.current_user]['tenant_id'],
|
||||
'X-Roles': self.users[self.current_user]['role'],
|
||||
}
|
||||
base_headers.update(custom_headers or {})
|
||||
return base_headers
|
||||
|
||||
def create_artifact(self, data=None, status=201,
|
||||
type_name='sample_artifact'):
|
||||
return self.post('/' + type_name, data or {}, status=status)
|
||||
|
||||
def _check_artifact_method(self, method, url, data=None, status=200,
|
||||
headers=None):
|
||||
if not headers:
|
||||
headers = self._headers()
|
||||
else:
|
||||
headers = self._headers(headers)
|
||||
headers.setdefault("Content-Type", "application/json")
|
||||
if 'application/json' in headers['Content-Type'] and data is not None:
|
||||
data = jsonutils.dumps(data)
|
||||
response = getattr(requests, method)(self._url(url), headers=headers,
|
||||
data=data)
|
||||
self.assertEqual(status, response.status_code, response.text)
|
||||
if status >= 400:
|
||||
return response.text
|
||||
if ("application/json" in response.headers["content-type"] or
|
||||
"application/schema+json" in response.headers["content-type"]):
|
||||
return jsonutils.loads(response.text)
|
||||
return response.text
|
||||
|
||||
def post(self, url, data=None, status=201, headers=None):
|
||||
return self._check_artifact_method("post", url, data, status=status,
|
||||
headers=headers)
|
||||
|
||||
def get(self, url, status=200, headers=None):
|
||||
return self._check_artifact_method("get", url, status=status,
|
||||
headers=headers)
|
||||
|
||||
def delete(self, url, status=204):
|
||||
response = requests.delete(self._url(url), headers=self._headers())
|
||||
self.assertEqual(status, response.status_code, response.text)
|
||||
return response.text
|
||||
|
||||
def patch(self, url, data, status=200, headers=None):
|
||||
if headers is None:
|
||||
headers = {}
|
||||
if 'Content-Type' not in headers:
|
||||
headers.update({'Content-Type': 'application/json-patch+json'})
|
||||
return self._check_artifact_method("patch", url, data, status=status,
|
||||
headers=headers)
|
||||
|
||||
def put(self, url, data=None, status=200, headers=None):
|
||||
return self._check_artifact_method("put", url, data, status=status,
|
||||
headers=headers)
|
||||
|
||||
# the test cases below are written in accordance with use cases
|
||||
# each test tries to cover separate use case in Glare
|
||||
# all code inside each test tries to cover all operators and data
|
||||
# involved in use case execution
|
||||
# each tests represents part of artifact lifecycle
|
||||
# so we can easily define where is the failed code
|
||||
|
||||
make_active = [{"op": "replace", "path": "/status", "value": "active"}]
|
||||
|
||||
def activate_with_admin(self, artifact_id, status=200):
|
||||
cur_user = self.current_user
|
||||
self.set_user('admin')
|
||||
url = '/sample_artifact/%s' % artifact_id
|
||||
af = self.patch(url=url, data=self.make_active, status=status)
|
||||
self.set_user(cur_user)
|
||||
return af
|
||||
|
||||
make_deactivated = [{"op": "replace", "path": "/status",
|
||||
"value": "deactivated"}]
|
||||
|
||||
def deactivate_with_admin(self, artifact_id, status=200):
|
||||
cur_user = self.current_user
|
||||
self.set_user('admin')
|
||||
url = '/sample_artifact/%s' % artifact_id
|
||||
af = self.patch(url=url, data=self.make_deactivated, status=status)
|
||||
self.set_user(cur_user)
|
||||
return af
|
||||
|
||||
make_public = [{"op": "replace", "path": "/visibility", "value": "public"}]
|
||||
|
||||
def publish_with_admin(self, artifact_id, status=200):
|
||||
cur_user = self.current_user
|
||||
self.set_user('admin')
|
||||
url = '/sample_artifact/%s' % artifact_id
|
||||
af = self.patch(url=url, data=self.make_public, status=status)
|
||||
self.set_user(cur_user)
|
||||
return af
|
|
@ -36,12 +36,12 @@ class SampleArtifact(base_artifact.BaseArtifact):
|
|||
description="I am Blob"),
|
||||
'small_blob': Blob(max_blob_size=10, required_on_activate=False,
|
||||
mutable=True, filter_ops=[]),
|
||||
'dependency1': Field(glare_fields.Dependency,
|
||||
required_on_activate=False,
|
||||
filter_ops=[]),
|
||||
'dependency2': Field(glare_fields.Dependency,
|
||||
required_on_activate=False,
|
||||
filter_ops=[]),
|
||||
'link1': Field(glare_fields.Link,
|
||||
required_on_activate=False,
|
||||
filter_ops=[]),
|
||||
'link2': Field(glare_fields.Link,
|
||||
required_on_activate=False,
|
||||
filter_ops=[]),
|
||||
'bool1': Field(fields.FlexibleBooleanField,
|
||||
required_on_activate=False,
|
||||
filter_ops=(attribute.FILTER_EQ,),
|
||||
|
|
|
@ -0,0 +1,96 @@
|
|||
# Copyright (c) 2016 Mirantis, Inc.
|
||||
#
|
||||
# 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 glare.tests.functional import base
|
||||
|
||||
|
||||
class TestAll(base.TestArtifact):
|
||||
|
||||
def test_all(self):
|
||||
for type_name in self.enabled_types:
|
||||
if type_name == 'all':
|
||||
continue
|
||||
for i in range(3):
|
||||
for j in range(3):
|
||||
self.create_artifact(
|
||||
data={'name': '%s_%d' % (type_name, i),
|
||||
'version': '%d' % j,
|
||||
'tags': ['tag%s' % i]},
|
||||
type_name=type_name)
|
||||
|
||||
# get all possible artifacts
|
||||
url = '/all?sort=name:asc&limit=100'
|
||||
res = self.get(url=url, status=200)['all']
|
||||
from pprint import pformat
|
||||
self.assertEqual(54, len(res), pformat(res))
|
||||
|
||||
# get artifacts with latest versions
|
||||
url = '/all?version=latest&sort=name:asc'
|
||||
res = self.get(url=url, status=200)['all']
|
||||
self.assertEqual(18, len(res))
|
||||
for art in res:
|
||||
self.assertEqual('2.0.0', art['version'])
|
||||
|
||||
# get images only
|
||||
url = '/all?type_name=images&sort=name:asc'
|
||||
res = self.get(url=url, status=200)['all']
|
||||
self.assertEqual(9, len(res))
|
||||
for art in res:
|
||||
self.assertEqual('images', art['type_name'])
|
||||
|
||||
# get images and heat_templates
|
||||
url = '/all?type_name=in:images,heat_templates&sort=name:asc'
|
||||
res = self.get(url=url, status=200)['all']
|
||||
self.assertEqual(18, len(res))
|
||||
for art in res:
|
||||
self.assertIn(art['type_name'], ('images', 'heat_templates'))
|
||||
|
||||
def test_all_readonlyness(self):
|
||||
self.create_artifact(data={'name': 'all'}, type_name='all', status=403)
|
||||
art = self.create_artifact(data={'name': 'image'}, type_name='images')
|
||||
|
||||
url = '/all/%s' % art['id']
|
||||
|
||||
headers = {'Content-Type': 'application/octet-stream'}
|
||||
# upload to 'all' is forbidden
|
||||
self.put(url=url + '/icon', data='data', status=403,
|
||||
headers=headers)
|
||||
|
||||
# update 'all' is forbidden
|
||||
data = [{
|
||||
"op": "replace",
|
||||
"path": "/description",
|
||||
"value": "text"
|
||||
}]
|
||||
self.patch(url=url, data=data, status=403)
|
||||
|
||||
# activation is forbidden
|
||||
data = [{
|
||||
"op": "replace",
|
||||
"path": "/status",
|
||||
"value": "active"
|
||||
}]
|
||||
self.patch(url=url, data=data, status=403)
|
||||
|
||||
# publishing is forbidden
|
||||
data = [{
|
||||
"op": "replace",
|
||||
"path": "/visibility",
|
||||
"value": "public"
|
||||
}]
|
||||
self.patch(url=url, data=data, status=403)
|
||||
|
||||
# get is okay
|
||||
new_art = self.get(url=url)
|
||||
self.assertEqual(new_art['id'], art['id'])
|
|
@ -17,168 +17,15 @@ import hashlib
|
|||
import uuid
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
import requests
|
||||
|
||||
from glare.tests import functional
|
||||
from glare.tests.functional import base
|
||||
|
||||
|
||||
def sort_results(lst, target='name'):
|
||||
return sorted(lst, key=lambda x: x[target])
|
||||
|
||||
|
||||
class TestArtifact(functional.FunctionalTest):
|
||||
|
||||
users = {
|
||||
'user1': {
|
||||
'id': str(uuid.uuid4()),
|
||||
'tenant_id': str(uuid.uuid4()),
|
||||
'token': str(uuid.uuid4()),
|
||||
'role': 'member'
|
||||
},
|
||||
'user2': {
|
||||
'id': str(uuid.uuid4()),
|
||||
'tenant_id': str(uuid.uuid4()),
|
||||
'token': str(uuid.uuid4()),
|
||||
'role': 'member'
|
||||
},
|
||||
'admin': {
|
||||
'id': str(uuid.uuid4()),
|
||||
'tenant_id': str(uuid.uuid4()),
|
||||
'token': str(uuid.uuid4()),
|
||||
'role': 'admin'
|
||||
},
|
||||
'anonymous': {
|
||||
'id': None,
|
||||
'tenant_id': None,
|
||||
'token': None,
|
||||
'role': None
|
||||
}
|
||||
}
|
||||
|
||||
def setUp(self):
|
||||
super(TestArtifact, self).setUp()
|
||||
self.set_user('user1')
|
||||
self.glare_server.deployment_flavor = 'noauth'
|
||||
self.glare_server.enabled_artifact_types = 'sample_artifact'
|
||||
self.glare_server.custom_artifact_types_modules = (
|
||||
'glare.tests.functional.sample_artifact')
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
def tearDown(self):
|
||||
self.stop_servers()
|
||||
self._reset_database(self.glare_server.sql_connection)
|
||||
super(TestArtifact, self).tearDown()
|
||||
|
||||
def _url(self, path):
|
||||
if 'schemas' in path:
|
||||
return 'http://127.0.0.1:%d%s' % (self.glare_port, path)
|
||||
else:
|
||||
return 'http://127.0.0.1:%d/artifacts%s' % (self.glare_port, path)
|
||||
|
||||
def set_user(self, username):
|
||||
if username not in self.users:
|
||||
raise KeyError
|
||||
self.current_user = username
|
||||
|
||||
def _headers(self, custom_headers=None):
|
||||
base_headers = {
|
||||
'X-Identity-Status': 'Confirmed',
|
||||
'X-Auth-Token': self.users[self.current_user]['token'],
|
||||
'X-User-Id': self.users[self.current_user]['id'],
|
||||
'X-Tenant-Id': self.users[self.current_user]['tenant_id'],
|
||||
'X-Project-Id': self.users[self.current_user]['tenant_id'],
|
||||
'X-Roles': self.users[self.current_user]['role'],
|
||||
}
|
||||
base_headers.update(custom_headers or {})
|
||||
return base_headers
|
||||
|
||||
def create_artifact(self, data=None, status=201):
|
||||
return self.post('/sample_artifact', data or {}, status=status)
|
||||
|
||||
def _check_artifact_method(self, method, url, data=None, status=200,
|
||||
headers=None):
|
||||
if not headers:
|
||||
headers = self._headers()
|
||||
else:
|
||||
headers = self._headers(headers)
|
||||
headers.setdefault("Content-Type", "application/json")
|
||||
if 'application/json' in headers['Content-Type'] and data is not None:
|
||||
data = jsonutils.dumps(data)
|
||||
response = getattr(requests, method)(self._url(url), headers=headers,
|
||||
data=data)
|
||||
self.assertEqual(status, response.status_code, response.text)
|
||||
if status >= 400:
|
||||
return response.text
|
||||
if ("application/json" in response.headers["content-type"] or
|
||||
"application/schema+json" in response.headers["content-type"]):
|
||||
return jsonutils.loads(response.text)
|
||||
return response.text
|
||||
|
||||
def post(self, url, data=None, status=201, headers=None):
|
||||
return self._check_artifact_method("post", url, data, status=status,
|
||||
headers=headers)
|
||||
|
||||
def get(self, url, status=200, headers=None):
|
||||
return self._check_artifact_method("get", url, status=status,
|
||||
headers=headers)
|
||||
|
||||
def delete(self, url, status=204):
|
||||
response = requests.delete(self._url(url), headers=self._headers())
|
||||
self.assertEqual(status, response.status_code, response.text)
|
||||
return response.text
|
||||
|
||||
def patch(self, url, data, status=200, headers=None):
|
||||
if headers is None:
|
||||
headers = {}
|
||||
if 'Content-Type' not in headers:
|
||||
headers.update({'Content-Type': 'application/json-patch+json'})
|
||||
return self._check_artifact_method("patch", url, data, status=status,
|
||||
headers=headers)
|
||||
|
||||
def put(self, url, data=None, status=200, headers=None):
|
||||
return self._check_artifact_method("put", url, data, status=status,
|
||||
headers=headers)
|
||||
|
||||
# the test cases below are written in accordance with use cases
|
||||
# each test tries to cover separate use case in Glare
|
||||
# all code inside each test tries to cover all operators and data
|
||||
# involved in use case execution
|
||||
# each tests represents part of artifact lifecycle
|
||||
# so we can easily define where is the failed code
|
||||
|
||||
make_active = [{"op": "replace", "path": "/status", "value": "active"}]
|
||||
|
||||
def activate_with_admin(self, artifact_id, status=200):
|
||||
cur_user = self.current_user
|
||||
self.set_user('admin')
|
||||
url = '/sample_artifact/%s' % artifact_id
|
||||
af = self.patch(url=url, data=self.make_active, status=status)
|
||||
self.set_user(cur_user)
|
||||
return af
|
||||
|
||||
make_deactivated = [{"op": "replace", "path": "/status",
|
||||
"value": "deactivated"}]
|
||||
|
||||
def deactivate_with_admin(self, artifact_id, status=200):
|
||||
cur_user = self.current_user
|
||||
self.set_user('admin')
|
||||
url = '/sample_artifact/%s' % artifact_id
|
||||
af = self.patch(url=url, data=self.make_deactivated, status=status)
|
||||
self.set_user(cur_user)
|
||||
return af
|
||||
|
||||
make_public = [{"op": "replace", "path": "/visibility", "value": "public"}]
|
||||
|
||||
def publish_with_admin(self, artifact_id, status=200):
|
||||
cur_user = self.current_user
|
||||
self.set_user('admin')
|
||||
url = '/sample_artifact/%s' % artifact_id
|
||||
af = self.patch(url=url, data=self.make_public, status=status)
|
||||
self.set_user(cur_user)
|
||||
return af
|
||||
|
||||
|
||||
class TestList(TestArtifact):
|
||||
class TestList(base.TestArtifact):
|
||||
def test_list_marker_and_limit(self):
|
||||
# Create artifacts
|
||||
art_list = [self.create_artifact({'name': 'name%s' % i,
|
||||
|
@ -806,7 +653,7 @@ class TestList(TestArtifact):
|
|||
self.assertEqual(response_url, result['first'])
|
||||
|
||||
|
||||
class TestBlobs(TestArtifact):
|
||||
class TestBlobs(base.TestArtifact):
|
||||
def test_blob_dicts(self):
|
||||
# Getting empty artifact list
|
||||
url = '/sample_artifact'
|
||||
|
@ -1005,7 +852,7 @@ class TestBlobs(TestArtifact):
|
|||
status=400, headers=headers)
|
||||
|
||||
|
||||
class TestTags(TestArtifact):
|
||||
class TestTags(base.TestArtifact):
|
||||
def test_tags(self):
|
||||
# Create artifact
|
||||
art = self.create_artifact({'name': 'name5',
|
||||
|
@ -1064,7 +911,7 @@ class TestTags(TestArtifact):
|
|||
self.patch(url=url, data=patch, status=400)
|
||||
|
||||
|
||||
class TestArtifactOps(TestArtifact):
|
||||
class TestArtifactOps(base.TestArtifact):
|
||||
def test_create(self):
|
||||
"""All tests related to artifact creation"""
|
||||
# check that cannot create artifact for non-existent artifact type
|
||||
|
@ -1136,7 +983,7 @@ class TestArtifactOps(TestArtifact):
|
|||
# (except blobs and system)
|
||||
expected = {
|
||||
"name": "test_big_create",
|
||||
"dependency1": "/artifacts/sample_artifact/%s" % some_af['id'],
|
||||
"link1": "/artifacts/sample_artifact/%s" % some_af['id'],
|
||||
"bool1": True,
|
||||
"int1": 2323,
|
||||
"float1": 0.1,
|
||||
|
@ -1252,14 +1099,14 @@ class TestArtifactOps(TestArtifact):
|
|||
url = '/sample_artifact/111111'
|
||||
self.delete(url=url, status=404)
|
||||
|
||||
# check that we can delete artifact with soft dependency
|
||||
# check that we can delete artifact with soft link
|
||||
art = self.create_artifact(
|
||||
data={"name": "test_af", "string_required": "test_str",
|
||||
"version": "0.0.1"})
|
||||
artd = self.create_artifact(
|
||||
data={"name": "test_afd", "string_required": "test_str",
|
||||
"version": "0.0.1",
|
||||
"dependency1": '/artifacts/sample_artifact/%s' % art['id']})
|
||||
"link1": '/artifacts/sample_artifact/%s' % art['id']})
|
||||
|
||||
url = '/sample_artifact/%s' % artd['id']
|
||||
self.delete(url=url, status=204)
|
||||
|
@ -1346,7 +1193,7 @@ class TestArtifactOps(TestArtifact):
|
|||
self.assertEqual("active", deactive_art["status"])
|
||||
|
||||
|
||||
class TestUpdate(TestArtifact):
|
||||
class TestUpdate(base.TestArtifact):
|
||||
def test_update_artifact_before_activate(self):
|
||||
"""Test updates for artifact before activation"""
|
||||
# create artifact to update
|
||||
|
@ -2164,24 +2011,121 @@ class TestUpdate(TestArtifact):
|
|||
url = '/sample_artifact/%s' % art1['id']
|
||||
self.patch(url=url, data=data, status=400)
|
||||
|
||||
def test_update_remove_properties(self):
|
||||
data = {
|
||||
"name": "test_big_create",
|
||||
"version": "1.0.0",
|
||||
"bool1": True,
|
||||
"int1": 2323,
|
||||
"float1": 0.1,
|
||||
"str1": "test",
|
||||
"list_of_str": ["test1", "test2"],
|
||||
"list_of_int": [0, 1, 2],
|
||||
"dict_of_str": {"test": "test"},
|
||||
"dict_of_int": {"test": 0},
|
||||
"string_mutable": "test",
|
||||
"string_required": "test",
|
||||
}
|
||||
art1 = self.create_artifact(data=data)
|
||||
|
||||
class TestDependencies(TestArtifact):
|
||||
def test_manage_dependencies(self):
|
||||
# remove the whole list of strings
|
||||
data = [{'op': 'replace',
|
||||
'path': '/list_of_str',
|
||||
'value': None}]
|
||||
url = '/sample_artifact/%s' % art1['id']
|
||||
result = self.patch(url=url, data=data)
|
||||
self.assertEqual([], result['list_of_str'])
|
||||
|
||||
# remove the whole list of ints
|
||||
data = [{'op': 'replace',
|
||||
'path': '/list_of_int',
|
||||
'value': None}]
|
||||
url = '/sample_artifact/%s' % art1['id']
|
||||
result = self.patch(url=url, data=data)
|
||||
self.assertEqual([], result['list_of_int'])
|
||||
|
||||
# remove the whole dict of strings
|
||||
data = [{'op': 'replace',
|
||||
'path': '/dict_of_str',
|
||||
'value': None}]
|
||||
url = '/sample_artifact/%s' % art1['id']
|
||||
result = self.patch(url=url, data=data)
|
||||
self.assertEqual({}, result['dict_of_str'])
|
||||
|
||||
# remove the whole dict of ints
|
||||
data = [{'op': 'replace',
|
||||
'path': '/dict_of_int',
|
||||
'value': None}]
|
||||
url = '/sample_artifact/%s' % art1['id']
|
||||
result = self.patch(url=url, data=data)
|
||||
self.assertEqual({}, result['dict_of_int'])
|
||||
|
||||
# remove bool1
|
||||
data = [{'op': 'replace',
|
||||
'path': '/bool1',
|
||||
'value': None}]
|
||||
url = '/sample_artifact/%s' % art1['id']
|
||||
result = self.patch(url=url, data=data)
|
||||
self.assertEqual(False, result['bool1'])
|
||||
|
||||
# remove int1
|
||||
data = [{'op': 'replace',
|
||||
'path': '/int1',
|
||||
'value': None}]
|
||||
url = '/sample_artifact/%s' % art1['id']
|
||||
result = self.patch(url=url, data=data)
|
||||
self.assertIsNone(result['int1'])
|
||||
|
||||
# remove float1
|
||||
data = [{'op': 'replace',
|
||||
'path': '/float1',
|
||||
'value': None}]
|
||||
url = '/sample_artifact/%s' % art1['id']
|
||||
result = self.patch(url=url, data=data)
|
||||
self.assertIsNone(result['float1'])
|
||||
|
||||
# cannot remove id
|
||||
data = [{'op': 'replace',
|
||||
'path': '/id',
|
||||
'value': None}]
|
||||
url = '/sample_artifact/%s' % art1['id']
|
||||
self.patch(url=url, data=data, status=403)
|
||||
|
||||
# cannot remove name
|
||||
data = [{'op': 'replace',
|
||||
'path': '/name',
|
||||
'value': None}]
|
||||
url = '/sample_artifact/%s' % art1['id']
|
||||
self.patch(url=url, data=data, status=409)
|
||||
|
||||
headers = {'Content-Type': 'application/octet-stream'}
|
||||
self.put(url=url + '/blob', data="d" * 1000, headers=headers)
|
||||
|
||||
# cannot remove id
|
||||
data = [{'op': 'replace',
|
||||
'path': '/blob',
|
||||
'value': None}]
|
||||
url = '/sample_artifact/%s' % art1['id']
|
||||
self.patch(url=url, data=data, status=400)
|
||||
|
||||
|
||||
class TestLinks(base.TestArtifact):
|
||||
def test_manage_links(self):
|
||||
some_af = self.create_artifact(data={"name": "test_af"})
|
||||
dep_af = self.create_artifact(data={"name": "test_dep_af"})
|
||||
dep_url = "/artifacts/sample_artifact/%s" % some_af['id']
|
||||
|
||||
# set valid dependency
|
||||
patch = [{"op": "replace", "path": "/dependency1", "value": dep_url}]
|
||||
# set valid link
|
||||
patch = [{"op": "replace", "path": "/link1", "value": dep_url}]
|
||||
url = '/sample_artifact/%s' % dep_af['id']
|
||||
af = self.patch(url=url, data=patch)
|
||||
self.assertEqual(af['dependency1'], dep_url)
|
||||
self.assertEqual(af['link1'], dep_url)
|
||||
|
||||
# remove dependency from artifact
|
||||
patch = [{"op": "replace", "path": "/dependency1", "value": None}]
|
||||
# remove link from artifact
|
||||
patch = [{"op": "replace", "path": "/link1", "value": None}]
|
||||
af = self.patch(url=url, data=patch)
|
||||
self.assertIsNone(af['dependency1'])
|
||||
self.assertIsNone(af['link1'])
|
||||
|
||||
# try to set invalid dependency
|
||||
patch = [{"op": "replace", "path": "/dependency1", "value": "Invalid"}]
|
||||
# try to set invalid link
|
||||
patch = [{"op": "replace", "path": "/link1", "value": "Invalid"}]
|
||||
self.patch(url=url, data=patch, status=400)
|
||||
|
|
|
@ -15,11 +15,8 @@
|
|||
|
||||
import jsonschema
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
import requests
|
||||
|
||||
from glare.common import utils
|
||||
from glare.tests import functional
|
||||
from glare.tests.functional import base
|
||||
|
||||
fixture_base_props = {
|
||||
u'activated_at': {
|
||||
|
@ -32,6 +29,7 @@ fixture_base_props = {
|
|||
u'lt',
|
||||
u'lte'],
|
||||
u'format': u'date-time',
|
||||
u'glareType': u'DateTime',
|
||||
u'readOnly': True,
|
||||
u'required_on_activate': False,
|
||||
u'sortable': True,
|
||||
|
@ -47,6 +45,7 @@ fixture_base_props = {
|
|||
u'lt',
|
||||
u'lte'],
|
||||
u'format': u'date-time',
|
||||
u'glareType': u'DateTime',
|
||||
u'readOnly': True,
|
||||
u'sortable': True,
|
||||
u'type': u'string'},
|
||||
|
@ -55,6 +54,7 @@ fixture_base_props = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 4096,
|
||||
u'mutable': True,
|
||||
u'required_on_activate': False,
|
||||
|
@ -63,6 +63,7 @@ fixture_base_props = {
|
|||
u'icon': {u'additionalProperties': False,
|
||||
u'description': u'Artifact icon.',
|
||||
u'filter_ops': [],
|
||||
u'glareType': u'Blob',
|
||||
u'properties': {u'md5': {u'type': [u'string', u'null']},
|
||||
u'sha1': {u'type': [u'string', u'null']},
|
||||
u'sha256': {u'type': [u'string', u'null']},
|
||||
|
@ -86,6 +87,7 @@ fixture_base_props = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'pattern': u'^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}'
|
||||
u'-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$',
|
||||
|
@ -96,6 +98,7 @@ fixture_base_props = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'string',
|
||||
|
@ -104,6 +107,7 @@ fixture_base_props = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'string',
|
||||
|
@ -114,6 +118,7 @@ fixture_base_props = {
|
|||
u'about an artifact.',
|
||||
u'filter_ops': [u'eq',
|
||||
u'neq'],
|
||||
u'glareType': u'StringDict',
|
||||
u'maxProperties': 255,
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'object',
|
||||
|
@ -122,6 +127,7 @@ fixture_base_props = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'required_on_activate': False,
|
||||
u'sortable': True,
|
||||
|
@ -130,6 +136,7 @@ fixture_base_props = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'readOnly': True,
|
||||
u'required_on_activate': False,
|
||||
|
@ -140,6 +147,7 @@ fixture_base_props = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'StringDict',
|
||||
u'maxProperties': 255,
|
||||
u'properties': {u'company': {u'type': u'string'},
|
||||
u'href': {u'type': u'string'},
|
||||
|
@ -154,6 +162,7 @@ fixture_base_props = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'StringList',
|
||||
u'items': {u'type': u'string'},
|
||||
u'maxItems': 255,
|
||||
u'required_on_activate': False,
|
||||
|
@ -169,6 +178,7 @@ fixture_base_props = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'String',
|
||||
u'sortable': True,
|
||||
u'type': u'string'},
|
||||
u'supported_by': {u'additionalProperties': {u'type': u'string'},
|
||||
|
@ -177,6 +187,7 @@ fixture_base_props = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'StringDict',
|
||||
u'maxProperties': 255,
|
||||
u'required': [u'name'],
|
||||
u'required_on_activate': False,
|
||||
|
@ -187,6 +198,7 @@ fixture_base_props = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'StringList',
|
||||
u'items': {u'type': u'string'},
|
||||
u'maxItems': 255,
|
||||
u'mutable': True,
|
||||
|
@ -203,6 +215,7 @@ fixture_base_props = {
|
|||
u'lt',
|
||||
u'lte'],
|
||||
u'format': u'date-time',
|
||||
u'glareType': u'DateTime',
|
||||
u'readOnly': True,
|
||||
u'sortable': True,
|
||||
u'type': u'string'},
|
||||
|
@ -215,6 +228,7 @@ fixture_base_props = {
|
|||
u'gte',
|
||||
u'lt',
|
||||
u'lte'],
|
||||
u'glareType': u'String',
|
||||
u'pattern': u'/^([0-9]+)\\.([0-9]+)\\.([0-9]+)(?:-'
|
||||
u'([0-9A-Za-z-]+(?:\\.[0-9A-Za-z-]+)*))?'
|
||||
u'(?:\\+[0-9A-Za-z-]+)?$/',
|
||||
|
@ -226,15 +240,12 @@ fixture_base_props = {
|
|||
u'artifact can be available to other '
|
||||
u'users.',
|
||||
u'filter_ops': [u'eq'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'sortable': True,
|
||||
u'type': u'string'}
|
||||
}
|
||||
|
||||
enabled_artifact_types = (
|
||||
u'sample_artifact', u'images', u'heat_templates',
|
||||
u'heat_environments', u'tosca_templates', u'murano_packages')
|
||||
|
||||
|
||||
def generate_type_props(props):
|
||||
props.update(fixture_base_props)
|
||||
|
@ -248,6 +259,7 @@ fixtures = {
|
|||
u'blob': {u'additionalProperties': False,
|
||||
u'description': u'I am Blob',
|
||||
u'filter_ops': [],
|
||||
u'glareType': u'Blob',
|
||||
u'mutable': True,
|
||||
u'properties': {
|
||||
u'md5': {u'type': [u'string', u'null']},
|
||||
|
@ -276,26 +288,30 @@ fixtures = {
|
|||
u'null']},
|
||||
u'bool1': {u'default': False,
|
||||
u'filter_ops': [u'eq'],
|
||||
u'glareType': u'Boolean',
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'string',
|
||||
u'null']},
|
||||
u'bool2': {u'default': False,
|
||||
u'filter_ops': [u'eq'],
|
||||
u'glareType': u'Boolean',
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'string',
|
||||
u'null']},
|
||||
u'link1': {u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'Link',
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'string',
|
||||
u'null']},
|
||||
u'link2': {u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'Link',
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'string',
|
||||
u'null']},
|
||||
u'dependency1': {u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'string',
|
||||
u'null']},
|
||||
u'dependency2': {u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'string',
|
||||
u'null']},
|
||||
u'dict_of_blobs': {
|
||||
u'additionalProperties': {
|
||||
u'additionalProperties': False,
|
||||
|
@ -326,6 +342,7 @@ fixtures = {
|
|||
u'null']},
|
||||
u'default': {},
|
||||
u'filter_ops': [],
|
||||
u'glareType': u'BlobDict',
|
||||
u'maxProperties': 255,
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'object',
|
||||
|
@ -335,6 +352,7 @@ fixtures = {
|
|||
u'type': u'string'},
|
||||
u'default': {},
|
||||
u'filter_ops': [u'eq'],
|
||||
u'glareType': u'IntegerDict',
|
||||
u'maxProperties': 255,
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'object',
|
||||
|
@ -344,6 +362,7 @@ fixtures = {
|
|||
u'type': u'string'},
|
||||
u'default': {},
|
||||
u'filter_ops': [u'eq'],
|
||||
u'glareType': u'StringDict',
|
||||
u'maxProperties': 255,
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'object',
|
||||
|
@ -353,6 +372,7 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'StringDict',
|
||||
u'maxProperties': 3,
|
||||
u'properties': {
|
||||
u'abc': {u'type': [u'string',
|
||||
|
@ -373,6 +393,7 @@ fixtures = {
|
|||
u'gte',
|
||||
u'lt',
|
||||
u'lte'],
|
||||
u'glareType': u'Float',
|
||||
u'required_on_activate': False,
|
||||
u'sortable': True,
|
||||
u'type': [u'number',
|
||||
|
@ -384,6 +405,7 @@ fixtures = {
|
|||
u'gte',
|
||||
u'lt',
|
||||
u'lte'],
|
||||
u'glareType': u'Float',
|
||||
u'required_on_activate': False,
|
||||
u'sortable': True,
|
||||
u'type': [u'number',
|
||||
|
@ -395,6 +417,7 @@ fixtures = {
|
|||
u'gte',
|
||||
u'lt',
|
||||
u'lte'],
|
||||
u'glareType': u'Integer',
|
||||
u'required_on_activate': False,
|
||||
u'sortable': True,
|
||||
u'type': [u'integer',
|
||||
|
@ -406,6 +429,7 @@ fixtures = {
|
|||
u'gte',
|
||||
u'lt',
|
||||
u'lte'],
|
||||
u'glareType': u'Integer',
|
||||
u'required_on_activate': False,
|
||||
u'sortable': True,
|
||||
u'type': [u'integer',
|
||||
|
@ -417,13 +441,15 @@ fixtures = {
|
|||
u'gte',
|
||||
u'lt',
|
||||
u'lte'],
|
||||
u'glareType': u'Integer',
|
||||
u'maximum': 20,
|
||||
u'minumum': 10,
|
||||
u'minimum': 10,
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'integer',
|
||||
u'null']},
|
||||
u'list_of_int': {u'default': [],
|
||||
u'filter_ops': [u'eq'],
|
||||
u'glareType': u'IntegerList',
|
||||
u'items': {
|
||||
u'type': u'string'},
|
||||
u'maxItems': 255,
|
||||
|
@ -432,6 +458,7 @@ fixtures = {
|
|||
u'null']},
|
||||
u'list_of_str': {u'default': [],
|
||||
u'filter_ops': [u'eq'],
|
||||
u'glareType': u'StringList',
|
||||
u'items': {
|
||||
u'type': u'string'},
|
||||
u'maxItems': 255,
|
||||
|
@ -443,6 +470,7 @@ fixtures = {
|
|||
u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'StringList',
|
||||
u'items': {
|
||||
u'type': u'string'},
|
||||
u'maxItems': 3,
|
||||
|
@ -452,6 +480,7 @@ fixtures = {
|
|||
u'unique': True},
|
||||
u'small_blob': {u'additionalProperties': False,
|
||||
u'filter_ops': [],
|
||||
u'glareType': u'Blob',
|
||||
u'mutable': True,
|
||||
u'properties': {
|
||||
u'md5': {u'type': [u'string', u'null']},
|
||||
|
@ -486,6 +515,7 @@ fixtures = {
|
|||
u'gte',
|
||||
u'lt',
|
||||
u'lte'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'required_on_activate': False,
|
||||
u'sortable': True,
|
||||
|
@ -498,6 +528,7 @@ fixtures = {
|
|||
u'gte',
|
||||
u'lt',
|
||||
u'lte'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'mutable': True,
|
||||
u'required_on_activate': False,
|
||||
|
@ -511,6 +542,7 @@ fixtures = {
|
|||
u'gte',
|
||||
u'lt',
|
||||
u'lte'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'type': [u'string',
|
||||
u'null']},
|
||||
|
@ -526,6 +558,7 @@ fixtures = {
|
|||
u'gte',
|
||||
u'lt',
|
||||
u'lte'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 10,
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'string',
|
||||
|
@ -534,6 +567,7 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'readOnly': True,
|
||||
u'sortable': True,
|
||||
|
@ -542,6 +576,7 @@ fixtures = {
|
|||
}),
|
||||
u'required': [u'name'],
|
||||
u'title': u'Artifact type sample_artifact of version 1.0',
|
||||
u'version': u'1.0',
|
||||
u'type': u'object'},
|
||||
u'tosca_templates': {
|
||||
u'name': u'tosca_templates',
|
||||
|
@ -550,6 +585,7 @@ fixtures = {
|
|||
u'additionalProperties': False,
|
||||
u'description': u'TOSCA template body.',
|
||||
u'filter_ops': [],
|
||||
u'glareType': u'Blob',
|
||||
u'properties': {
|
||||
u'md5': {u'type': [u'string', u'null']},
|
||||
u'sha1': {u'type': [u'string', u'null']},
|
||||
|
@ -574,11 +610,13 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'type': [u'string',
|
||||
u'null']},
|
||||
}),
|
||||
u'required': [u'name'],
|
||||
u'version': u'1.0',
|
||||
u'title': u'Artifact type tosca_templates of version 1.0',
|
||||
u'type': u'object'},
|
||||
u'murano_packages': {
|
||||
|
@ -591,6 +629,7 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'StringList',
|
||||
u'items': {u'type': u'string'},
|
||||
u'maxItems': 255,
|
||||
u'mutable': True,
|
||||
|
@ -603,6 +642,7 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'StringList',
|
||||
u'items': {u'type': u'string'},
|
||||
u'maxItems': 255,
|
||||
u'type': [u'array',
|
||||
|
@ -615,6 +655,7 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'LinkList',
|
||||
u'items': {u'type': u'string'},
|
||||
u'maxItems': 255,
|
||||
u'required_on_activate': False,
|
||||
|
@ -625,6 +666,7 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'mutable': True,
|
||||
u'type': [u'string',
|
||||
|
@ -635,6 +677,7 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'StringDict',
|
||||
u'maxProperties': 255,
|
||||
u'type': [u'object',
|
||||
u'null']},
|
||||
|
@ -642,6 +685,7 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'StringList',
|
||||
u'items': {u'type': u'string'},
|
||||
u'maxItems': 255,
|
||||
u'mutable': True,
|
||||
|
@ -651,6 +695,7 @@ fixtures = {
|
|||
u'additionalProperties': False,
|
||||
u'description': u'Murano Package binary.',
|
||||
u'filter_ops': [],
|
||||
u'glareType': u'Blob',
|
||||
u'properties': {u'md5': {u'type': [u'string', u'null']},
|
||||
u'sha1': {u'type': [u'string', u'null']},
|
||||
u'sha256': {u'type': [u'string', u'null']},
|
||||
|
@ -679,11 +724,13 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'type': [u'string',
|
||||
u'null']}
|
||||
}),
|
||||
u'required': [u'name'],
|
||||
u'version': u'1.0',
|
||||
u'title': u'Artifact type murano_packages of version 1.0',
|
||||
u'type': u'object'},
|
||||
u'images': {
|
||||
|
@ -697,6 +744,7 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'string',
|
||||
|
@ -705,6 +753,7 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'string', u'null']},
|
||||
|
@ -720,6 +769,7 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'type': [u'string',
|
||||
u'null']},
|
||||
|
@ -739,11 +789,13 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'type': [u'string', u'null']},
|
||||
u'image': {u'additionalProperties': False,
|
||||
u'description': u'Image binary.',
|
||||
u'filter_ops': [],
|
||||
u'glareType': u'Blob',
|
||||
u'properties': {
|
||||
u'md5': {u'type': [u'string', u'null']},
|
||||
u'sha1': {u'type': [u'string', u'null']},
|
||||
|
@ -773,6 +825,7 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'string', u'null']},
|
||||
|
@ -784,6 +837,7 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'string',
|
||||
|
@ -795,6 +849,7 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'pattern': u'^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-'
|
||||
u'([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-'
|
||||
|
@ -806,7 +861,8 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'minumum': 0,
|
||||
u'glareType': u'Integer',
|
||||
u'minimum': 0,
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'integer', u'null']},
|
||||
u'min_ram': {
|
||||
|
@ -814,7 +870,8 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'minumum': 0,
|
||||
u'glareType': u'Integer',
|
||||
u'minimum': 0,
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'integer', u'null']},
|
||||
u'os_distro': {
|
||||
|
@ -825,6 +882,7 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'string', u'null']},
|
||||
|
@ -834,6 +892,7 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'string', u'null']},
|
||||
|
@ -844,12 +903,14 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'pattern': u'^([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F])'
|
||||
u'{4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$',
|
||||
u'required_on_activate': False,
|
||||
u'type': [u'string', u'null']}}),
|
||||
u'required': [u'name'],
|
||||
u'version': u'1.0',
|
||||
u'title': u'Artifact type images of version 1.0',
|
||||
u'type': u'object'},
|
||||
u'heat_templates': {
|
||||
|
@ -864,6 +925,7 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'StringDict',
|
||||
u'maxProperties': 255,
|
||||
u'mutable': True,
|
||||
u'type': [u'object',
|
||||
|
@ -877,6 +939,7 @@ fixtures = {
|
|||
u'filter_ops': [u'eq',
|
||||
u'neq',
|
||||
u'in'],
|
||||
u'glareType': u'LinkDict',
|
||||
u'maxProperties': 255,
|
||||
u'mutable': True,
|
||||
u'type': [u'object',
|
||||
|
@ -909,6 +972,7 @@ fixtures = {
|
|||
u'name of template and value is nested '
|
||||
u'template body.',
|
||||
u'filter_ops': [],
|
||||
u'glareType': u'BlobDict',
|
||||
u'maxProperties': 255,
|
||||
u'type': [u'object',
|
||||
u'null']},
|
||||
|
@ -916,6 +980,7 @@ fixtures = {
|
|||
u'additionalProperties': False,
|
||||
u'description': u'Heat template body.',
|
||||
u'filter_ops': [],
|
||||
u'glareType': u'Blob',
|
||||
u'properties': {
|
||||
u'md5': {u'type': [u'string', u'null']},
|
||||
u'sha1': {u'type': [u'string', u'null']},
|
||||
|
@ -938,6 +1003,7 @@ fixtures = {
|
|||
u'null']},
|
||||
|
||||
}),
|
||||
u'version': u'1.0',
|
||||
u'required': [u'name'],
|
||||
u'title': u'Artifact type heat_templates of version 1.0',
|
||||
u'type': u'object'},
|
||||
|
@ -948,6 +1014,7 @@ fixtures = {
|
|||
u'additionalProperties': False,
|
||||
u'description': u'Heat Environment text body.',
|
||||
u'filter_ops': [],
|
||||
u'glareType': u'Blob',
|
||||
u'properties': {u'md5': {u'type': [u'string', u'null']},
|
||||
u'sha1': {u'type': [u'string', u'null']},
|
||||
u'sha256': {u'type': [u'string', u'null']},
|
||||
|
@ -969,70 +1036,40 @@ fixtures = {
|
|||
|
||||
}),
|
||||
u'required': [u'name'],
|
||||
u'version': u'1.0',
|
||||
u'title': u'Artifact type heat_environments of version 1.0',
|
||||
u'type': u'object'},
|
||||
u'all': {
|
||||
u'name': u'all',
|
||||
u'properties': generate_type_props({
|
||||
u'type_name': {u'description': u'Name of artifact type.',
|
||||
u'filter_ops': [u'eq', u'neq', u'in'],
|
||||
u'glareType': u'String',
|
||||
u'maxLength': 255,
|
||||
u'type': [u'string', u'null']},
|
||||
|
||||
}),
|
||||
u'required': [u'name'],
|
||||
u'version': u'1.0',
|
||||
u'title': u'Artifact type all of version 1.0',
|
||||
u'type': u'object'}
|
||||
}
|
||||
|
||||
|
||||
class TestSchemas(functional.FunctionalTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestSchemas, self).setUp()
|
||||
self.glare_server.deployment_flavor = 'noauth'
|
||||
|
||||
self.glare_server.enabled_artifact_types = ','.join(
|
||||
enabled_artifact_types)
|
||||
self.glare_server.custom_artifact_types_modules = (
|
||||
'glare.tests.functional.sample_artifact')
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
def tearDown(self):
|
||||
self.stop_servers()
|
||||
self._reset_database(self.glare_server.sql_connection)
|
||||
super(TestSchemas, self).tearDown()
|
||||
|
||||
def _url(self, path):
|
||||
return 'http://127.0.0.1:%d%s' % (self.glare_port, path)
|
||||
|
||||
def _check_artifact_method(self, url, status=200):
|
||||
headers = {
|
||||
'X-Identity-Status': 'Confirmed',
|
||||
}
|
||||
response = requests.get(self._url(url), headers=headers)
|
||||
self.assertEqual(status, response.status_code, response.text)
|
||||
if status >= 400:
|
||||
return response.text
|
||||
if ("application/json" in response.headers["content-type"] or
|
||||
"application/schema+json" in response.headers["content-type"]):
|
||||
return jsonutils.loads(response.text)
|
||||
return response.text
|
||||
|
||||
def get(self, url, status=200, headers=None):
|
||||
return self._check_artifact_method(url, status=status)
|
||||
|
||||
class TestSchemas(base.TestArtifact):
|
||||
def test_schemas(self):
|
||||
# Get schemas for specific artifact type
|
||||
for at in self.enabled_types:
|
||||
result = self.get(url='/schemas/%s' % at)
|
||||
self.assertEqual(fixtures[at], result['schemas'][at],
|
||||
utils.DictDiffer(
|
||||
fixtures[at]['properties'],
|
||||
result['schemas'][at]['properties']))
|
||||
|
||||
# Get list schemas of artifacts
|
||||
result = self.get(url='/schemas')
|
||||
self.assertEqual(fixtures, result['schemas'], utils.DictDiffer(
|
||||
result['schemas'], fixtures))
|
||||
|
||||
# Get schemas for specific artifact type
|
||||
for at in enabled_artifact_types:
|
||||
result = self.get(url='/schemas/%s' % at)
|
||||
self.assertEqual(fixtures[at], result['schemas'][at],
|
||||
utils.DictDiffer(
|
||||
result['schemas'][at]['properties'],
|
||||
fixtures[at]['properties']))
|
||||
|
||||
# Get schema of sample_artifact
|
||||
result = self.get(url='/schemas/sample_artifact')
|
||||
self.assertEqual(fixtures['sample_artifact'],
|
||||
result['schemas']['sample_artifact'],
|
||||
utils.DictDiffer(
|
||||
result['schemas']['sample_artifact'][
|
||||
'properties'],
|
||||
fixtures['sample_artifact']['properties']))
|
||||
fixtures, result['schemas']))
|
||||
|
||||
# Validation of schemas
|
||||
result = self.get(url='/schemas')['schemas']
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
# Copyright 2016 OpenStack Foundation
|
||||
# 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.
|
||||
|
||||
import oslo_i18n as i18n
|
||||
|
||||
|
||||
def fake_translate_msgid(msgid, domain, desired_locale=None):
|
||||
return msgid
|
||||
|
||||
i18n.enable_lazy()
|
||||
|
||||
# To ensure messages don't really get translated while running tests.
|
||||
# As there are lots of places where matching is expected when comparing
|
||||
# exception message(translated) with raw message.
|
||||
i18n._translate_msgid = fake_translate_msgid
|
|
@ -0,0 +1,134 @@
|
|||
# Copyright 2012 OpenStack Foundation.
|
||||
# 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.
|
||||
|
||||
import os
|
||||
import shutil
|
||||
|
||||
import fixtures
|
||||
import glance_store as store
|
||||
from glance_store import location
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_config import cfg
|
||||
from oslo_config import fixture as cfg_fixture
|
||||
from oslo_db import options
|
||||
from oslo_serialization import jsonutils
|
||||
import testtools
|
||||
|
||||
from glare.common import config
|
||||
from glare.common import utils
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
class BaseTestCase(testtools.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(BaseTestCase, self).setUp()
|
||||
|
||||
self._config_fixture = self.useFixture(cfg_fixture.Config())
|
||||
config.parse_args(args=[])
|
||||
self.addCleanup(CONF.reset)
|
||||
self.test_dir = self.useFixture(fixtures.TempDir()).path
|
||||
self.conf_dir = os.path.join(self.test_dir, 'etc')
|
||||
utils.safe_mkdirs(self.conf_dir)
|
||||
self.set_policy()
|
||||
|
||||
def set_policy(self):
|
||||
conf_file = "policy.json"
|
||||
self.policy_file = self._copy_data_file(conf_file, self.conf_dir)
|
||||
self.config(policy_file=self.policy_file, group='oslo_policy')
|
||||
|
||||
def _copy_data_file(self, file_name, dst_dir):
|
||||
src_file_name = os.path.join('glare/tests/etc', file_name)
|
||||
shutil.copy(src_file_name, dst_dir)
|
||||
dst_file_name = os.path.join(dst_dir, file_name)
|
||||
return dst_file_name
|
||||
|
||||
def set_property_protection_rules(self, rules):
|
||||
with open(self.property_file, 'w') as f:
|
||||
for rule_key in rules.keys():
|
||||
f.write('[%s]\n' % rule_key)
|
||||
for operation in rules[rule_key].keys():
|
||||
roles_str = ','.join(rules[rule_key][operation])
|
||||
f.write('%s = %s\n' % (operation, roles_str))
|
||||
|
||||
def config(self, **kw):
|
||||
"""
|
||||
Override some configuration values.
|
||||
|
||||
The keyword arguments are the names of configuration options to
|
||||
override and their values.
|
||||
|
||||
If a group argument is supplied, the overrides are applied to
|
||||
the specified configuration option group.
|
||||
|
||||
All overrides are automatically cleared at the end of the current
|
||||
test by the fixtures cleanup process.
|
||||
"""
|
||||
self._config_fixture.config(**kw)
|
||||
|
||||
|
||||
class StoreClearingUnitTest(BaseTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(StoreClearingUnitTest, self).setUp()
|
||||
# Ensure stores + locations cleared
|
||||
location.SCHEME_TO_CLS_MAP = {}
|
||||
|
||||
self._create_stores()
|
||||
self.addCleanup(setattr, location, 'SCHEME_TO_CLS_MAP', dict())
|
||||
|
||||
def _create_stores(self, passing_config=True):
|
||||
"""Create known stores. Mock out sheepdog's subprocess dependency
|
||||
on collie.
|
||||
|
||||
:param passing_config: making store driver passes basic configurations.
|
||||
:returns: the number of how many store drivers been loaded.
|
||||
"""
|
||||
store.register_opts(CONF)
|
||||
|
||||
self.config(default_store='filesystem',
|
||||
filesystem_store_datadir=self.test_dir,
|
||||
group="glance_store")
|
||||
|
||||
store.create_stores(CONF)
|
||||
|
||||
|
||||
class IsolatedUnitTest(StoreClearingUnitTest):
|
||||
|
||||
"""
|
||||
Unit test case that establishes a mock environment within
|
||||
a testing directory (in isolation)
|
||||
"""
|
||||
registry = None
|
||||
|
||||
def setUp(self):
|
||||
super(IsolatedUnitTest, self).setUp()
|
||||
options.set_defaults(CONF, connection='sqlite:////%s/tests.sqlite' %
|
||||
self.test_dir)
|
||||
lockutils.set_defaults(os.path.join(self.test_dir))
|
||||
|
||||
self.config(debug=False)
|
||||
|
||||
self.config(default_store='filesystem',
|
||||
filesystem_store_datadir=self.test_dir,
|
||||
group="glance_store")
|
||||
|
||||
store.create_stores()
|
||||
|
||||
def set_policy_rules(self, rules):
|
||||
fap = open(CONF.oslo_policy.policy_file, 'w')
|
||||
fap.write(jsonutils.dumps(rules))
|
||||
fap.close()
|
|
@ -0,0 +1,65 @@
|
|||
# Copyright 2016 OpenStack Foundation.
|
||||
# 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 oslo_serialization import jsonutils
|
||||
import webob
|
||||
|
||||
from glare.api import versions
|
||||
from glare.tests.unit import base
|
||||
|
||||
|
||||
class VersionsTest(base.IsolatedUnitTest):
|
||||
|
||||
"""Test the version information returned from the API service."""
|
||||
|
||||
def test_get_version_list(self):
|
||||
req = webob.Request.blank('/', base_url='http://127.0.0.1:9494/')
|
||||
req.accept = 'application/json'
|
||||
res = versions.Controller().index(req, is_multi=True)
|
||||
self.assertEqual(300, res.status_int)
|
||||
self.assertEqual('application/json', res.content_type)
|
||||
results = jsonutils.loads(res.body)['versions']
|
||||
expected = [
|
||||
{
|
||||
'id': 'v1.0',
|
||||
'status': 'EXPERIMENTAL',
|
||||
'links': [{'rel': 'self',
|
||||
'href': 'http://127.0.0.1:9494/'}],
|
||||
'min_version': '1.0',
|
||||
'version': '1.0'
|
||||
}
|
||||
]
|
||||
self.assertEqual(expected, results)
|
||||
|
||||
def test_get_version_list_public_endpoint(self):
|
||||
req = webob.Request.blank('/', base_url='http://127.0.0.1:9494/')
|
||||
req.accept = 'application/json'
|
||||
self.config(bind_host='127.0.0.1', bind_port=9494,
|
||||
public_endpoint='https://example.com:9494')
|
||||
res = versions.Controller().index(req, is_multi=True)
|
||||
self.assertEqual(300, res.status_int)
|
||||
self.assertEqual('application/json', res.content_type)
|
||||
results = jsonutils.loads(res.body)['versions']
|
||||
expected = [
|
||||
{
|
||||
'id': 'v1.0',
|
||||
'status': 'EXPERIMENTAL',
|
||||
'links': [{'rel': 'self',
|
||||
'href': 'https://example.com:9494/'}],
|
||||
'min_version': '1.0',
|
||||
'version': '1.0'
|
||||
}
|
||||
]
|
||||
self.assertEqual(expected, results)
|
|
@ -36,7 +36,7 @@ oslo.i18n>=2.1.0 # Apache-2.0
|
|||
oslo.log>=3.11.0 # Apache-2.0
|
||||
oslo.messaging>=5.2.0 # Apache-2.0
|
||||
oslo.middleware>=3.0.0 # Apache-2.0
|
||||
oslo.policy>=1.9.0 # Apache-2.0
|
||||
oslo.policy>=1.14.0 # Apache-2.0
|
||||
oslo.serialization>=1.10.0 # Apache-2.0
|
||||
oslo.versionedobjects>=1.13.0 # Apache-2.0
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ coverage>=3.6 # Apache-2.0
|
|||
fixtures>=3.0.0 # Apache-2.0/BSD
|
||||
mox3>=0.7.0 # Apache-2.0
|
||||
mock>=2.0 # BSD
|
||||
sphinx!=1.3b1,<1.3,>=1.2.1 # BSD
|
||||
sphinx!=1.3b1,<1.4,>=1.2.1 # BSD
|
||||
requests>=2.10.0 # Apache-2.0
|
||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||
testresources>=0.2.4 # Apache-2.0/BSD
|
||||
|
@ -22,7 +22,7 @@ testscenarios>=0.4 # Apache-2.0/BSD
|
|||
testtools>=1.4.0 # MIT
|
||||
psutil<2.0.0,>=1.1.1 # BSD
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
os-testr>=0.7.0 # Apache-2.0
|
||||
os-testr>=0.8.0 # Apache-2.0
|
||||
|
||||
# Optional packages that should be installed when testing
|
||||
PyMySQL!=0.7.7,>=0.6.2 # MIT License
|
||||
|
@ -34,5 +34,5 @@ python-swiftclient>=2.2.0 # Apache-2.0
|
|||
|
||||
# Documentation
|
||||
os-api-ref>=1.0.0 # Apache-2.0
|
||||
oslosphinx!=3.4.0,>=2.5.0 # Apache-2.0
|
||||
oslosphinx>=4.7.0 # Apache-2.0
|
||||
reno>=1.8.0 # Apache2
|
||||
|
|
Loading…
Reference in New Issue