From e52d3ec81a2447531b2f4d30c8cb6486ed77c3d9 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Tue, 27 Sep 2016 09:59:26 +0000 Subject: [PATCH 01/13] Updated from global requirements Change-Id: I2a6b8acdc89147e564d4813511832892138931fc --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 15deb57..3e1545a 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -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 From 5623830498af16f0aefe27e5714f8c8996d010b7 Mon Sep 17 00:00:00 2001 From: Mike Fedosin Date: Tue, 27 Sep 2016 16:40:09 +0300 Subject: [PATCH 02/13] Add functional tests on removing properties values Change-Id: Iad0c45a182f340df8a3466a31050df4779c60f38 --- .../tests/functional/test_sample_artifact.py | 97 +++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/glare/tests/functional/test_sample_artifact.py b/glare/tests/functional/test_sample_artifact.py index 4159310..67e5de7 100644 --- a/glare/tests/functional/test_sample_artifact.py +++ b/glare/tests/functional/test_sample_artifact.py @@ -2164,6 +2164,103 @@ 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) + + # 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 TestDependencies(TestArtifact): def test_manage_dependencies(self): From 63d836bd5ab93477c04fd91fdc05d46f89b917b3 Mon Sep 17 00:00:00 2001 From: Darja Malyavkina Date: Tue, 27 Sep 2016 17:06:56 +0300 Subject: [PATCH 03/13] Add 'version' in schemas Change-Id: Ifa0b6be3024f321da9e1d923a9f2e3db0fff3e03 --- glare/objects/base.py | 1 + glare/tests/functional/test_schemas.py | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/glare/objects/base.py b/glare/objects/base.py index c2f5c05..3ba6b06 100644 --- a/glare/objects/base.py +++ b/glare/objects/base.py @@ -1193,6 +1193,7 @@ 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', diff --git a/glare/tests/functional/test_schemas.py b/glare/tests/functional/test_schemas.py index 29b6cc0..4d5a358 100644 --- a/glare/tests/functional/test_schemas.py +++ b/glare/tests/functional/test_schemas.py @@ -542,6 +542,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', @@ -579,6 +580,7 @@ fixtures = { 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': { @@ -684,6 +686,7 @@ fixtures = { 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': { @@ -850,6 +853,7 @@ fixtures = { 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': { @@ -938,6 +942,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'}, @@ -969,6 +974,7 @@ 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'} } From 387e5c4ec40806806d3ada46de7f819fd4dd3ab8 Mon Sep 17 00:00:00 2001 From: Sergey Skripnick Date: Tue, 27 Sep 2016 18:49:38 +0300 Subject: [PATCH 04/13] Fix typo in schema Change-Id: I2406cfabcd913794b8f9e549338a43f754c9dd4b --- glare/objects/meta/validators.py | 2 +- glare/tests/functional/test_schemas.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/glare/objects/meta/validators.py b/glare/objects/meta/validators.py index f587b2e..7699c34 100644 --- a/glare/objects/meta/validators.py +++ b/glare/objects/meta/validators.py @@ -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): diff --git a/glare/tests/functional/test_schemas.py b/glare/tests/functional/test_schemas.py index 4d5a358..901deb0 100644 --- a/glare/tests/functional/test_schemas.py +++ b/glare/tests/functional/test_schemas.py @@ -418,7 +418,7 @@ fixtures = { u'lt', u'lte'], u'maximum': 20, - u'minumum': 10, + u'minimum': 10, u'required_on_activate': False, u'type': [u'integer', u'null']}, @@ -809,7 +809,7 @@ fixtures = { u'filter_ops': [u'eq', u'neq', u'in'], - u'minumum': 0, + u'minimum': 0, u'required_on_activate': False, u'type': [u'integer', u'null']}, u'min_ram': { @@ -817,7 +817,7 @@ fixtures = { u'filter_ops': [u'eq', u'neq', u'in'], - u'minumum': 0, + u'minimum': 0, u'required_on_activate': False, u'type': [u'integer', u'null']}, u'os_distro': { From fe10a943287ffbcd350295e7bff37f84a9edf25d Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Wed, 28 Sep 2016 16:52:40 +0000 Subject: [PATCH 05/13] Updated from global requirements Change-Id: Id590c448230de31fd61f4ac01992f0525033f942 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index 3e1545a..a6ec1b8 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -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 From 7e72800d01c8c0da2d89becc047e17af8ffd3351 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Thu, 29 Sep 2016 05:06:07 +0000 Subject: [PATCH 06/13] Updated from global requirements Change-Id: I29fe4d7dfa7ae63c28c2baf6c39d794781a1162d --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 56f53ec..2e19e56 100644 --- a/requirements.txt +++ b/requirements.txt @@ -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 From a4feb466da5fd64ce3fce87f9c748a92879b3aa9 Mon Sep 17 00:00:00 2001 From: OpenStack Proposal Bot Date: Fri, 30 Sep 2016 10:16:43 +0000 Subject: [PATCH 07/13] Updated from global requirements Change-Id: Ia555bb80787d64321ea45adc023ebd7257ed6b21 --- test-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test-requirements.txt b/test-requirements.txt index a6ec1b8..750eeb4 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -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 From e6ebad88acdf0804c58541d3fa0952e20325cd96 Mon Sep 17 00:00:00 2001 From: kairat_kushaev Date: Tue, 13 Sep 2016 12:23:26 +0300 Subject: [PATCH 08/13] Add base test case for unit test Add base test case for unit test. Other devs can start to write unit test and increase our test coverage. Also I wrote a simple test case related to versions. Change-Id: Id37bf0f42f5f276cf5a1b435e640a340453b707b --- glare/tests/unit/__init__.py | 27 ++++++ glare/tests/unit/base.py | 134 ++++++++++++++++++++++++++++++ glare/tests/unit/test_versions.py | 65 +++++++++++++++ 3 files changed, 226 insertions(+) create mode 100644 glare/tests/unit/base.py create mode 100644 glare/tests/unit/test_versions.py diff --git a/glare/tests/unit/__init__.py b/glare/tests/unit/__init__.py index e69de29..e48f2cf 100644 --- a/glare/tests/unit/__init__.py +++ b/glare/tests/unit/__init__.py @@ -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 diff --git a/glare/tests/unit/base.py b/glare/tests/unit/base.py new file mode 100644 index 0000000..7396713 --- /dev/null +++ b/glare/tests/unit/base.py @@ -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() diff --git a/glare/tests/unit/test_versions.py b/glare/tests/unit/test_versions.py new file mode 100644 index 0000000..3379a1f --- /dev/null +++ b/glare/tests/unit/test_versions.py @@ -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) From 692f945ce4d65bf4f67b0e9c8a1d5fb3257d26ae Mon Sep 17 00:00:00 2001 From: GeetikaBatra Date: Sat, 1 Oct 2016 14:30:04 +0530 Subject: [PATCH 09/13] Remove redundant policy Currently the policy.py contains a redundant policy "artifact:type_get"; this is not used any more. This code also replaces the occurrences of the redundant policy. Change-Id: I11bb26add045a69b022c0cc41fb7130cc3922a11 --- glare/common/policy.py | 2 -- glare/engine.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/glare/common/policy.py b/glare/common/policy.py index 8b65f8a..719cd87 100644 --- a/glare/common/policy.py +++ b/glare/common/policy.py @@ -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 " diff --git a/glare/engine.py b/glare/engine.py index 426432f..34cd562 100644 --- a/glare/engine.py +++ b/glare/engine.py @@ -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 From 0388587a782785597971fa3fce6d7602464a81f3 Mon Sep 17 00:00:00 2001 From: Mike Fedosin Date: Sat, 1 Oct 2016 15:36:16 +0300 Subject: [PATCH 10/13] Enable notification support Change-Id: I6ccd2a2779ebffa02b4947465d5bfeb6158d414a --- devstack/plugin.sh | 3 +++ glare/cmd/api.py | 1 + glare/notification.py | 22 +++++----------------- 3 files changed, 9 insertions(+), 17 deletions(-) diff --git a/devstack/plugin.sh b/devstack/plugin.sh index f6aa762..3211f4b 100644 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -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 diff --git a/glare/cmd/api.py b/glare/cmd/api.py index aed17a5..9e7be6a 100755 --- a/glare/cmd/api.py +++ b/glare/cmd/api.py @@ -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( diff --git a/glare/notification.py b/glare/notification.py index 6566efe..04a2e3c 100644 --- a/glare/notification.py +++ b/glare/notification.py @@ -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}) From ead97e6d3314f18142266815816d24a47b7200ee Mon Sep 17 00:00:00 2001 From: Mike Fedosin Date: Sat, 1 Oct 2016 14:17:06 +0300 Subject: [PATCH 11/13] Implement 'all' artifact type Change-Id: Ia3bbe4f76af29e269ce25e67a6d2324e1ec57927 --- glare/db/artifact_api.py | 3 +- glare/objects/all.py | 34 ++++ glare/objects/base.py | 54 ++++++ glare/objects/meta/registry.py | 2 +- glare/tests/functional/base.py | 183 ++++++++++++++++++ glare/tests/functional/test_all.py | 96 +++++++++ .../tests/functional/test_sample_artifact.py | 167 +--------------- glare/tests/functional/test_schemas.py | 72 ++----- 8 files changed, 398 insertions(+), 213 deletions(-) create mode 100644 glare/objects/all.py create mode 100644 glare/tests/functional/base.py create mode 100644 glare/tests/functional/test_all.py diff --git a/glare/db/artifact_api.py b/glare/db/artifact_api.py index b2b775d..b63fa16 100644 --- a/glare/db/artifact_api.py +++ b/glare/db/artifact_api.py @@ -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) diff --git a/glare/objects/all.py b/glare/objects/all.py new file mode 100644 index 0000000..b6da231 --- /dev/null +++ b/glare/objects/all.py @@ -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" diff --git a/glare/objects/base.py b/glare/objects/base.py index 3ba6b06..f990ed9 100644 --- a/glare/objects/base.py +++ b/glare/objects/base.py @@ -1200,3 +1200,57 @@ class BaseArtifact(base.VersionedObject): '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.") diff --git a/glare/objects/meta/registry.py b/glare/objects/meta/registry.py index 2c51558..5ff0413 100644 --- a/glare/objects/meta/registry.py +++ b/glare/objects/meta/registry.py @@ -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) diff --git a/glare/tests/functional/base.py b/glare/tests/functional/base.py new file mode 100644 index 0000000..e6a68e9 --- /dev/null +++ b/glare/tests/functional/base.py @@ -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') + + 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 diff --git a/glare/tests/functional/test_all.py b/glare/tests/functional/test_all.py new file mode 100644 index 0000000..b0a1240 --- /dev/null +++ b/glare/tests/functional/test_all.py @@ -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']) diff --git a/glare/tests/functional/test_sample_artifact.py b/glare/tests/functional/test_sample_artifact.py index 67e5de7..f4f6f8c 100644 --- a/glare/tests/functional/test_sample_artifact.py +++ b/glare/tests/functional/test_sample_artifact.py @@ -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 @@ -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 @@ -2262,7 +2109,7 @@ class TestUpdate(TestArtifact): self.patch(url=url, data=data, status=400) -class TestDependencies(TestArtifact): +class TestDependencies(base.TestArtifact): def test_manage_dependencies(self): some_af = self.create_artifact(data={"name": "test_af"}) dep_af = self.create_artifact(data={"name": "test_dep_af"}) diff --git a/glare/tests/functional/test_schemas.py b/glare/tests/functional/test_schemas.py index 901deb0..66eba33 100644 --- a/glare/tests/functional/test_schemas.py +++ b/glare/tests/functional/test_schemas.py @@ -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': { @@ -231,10 +228,6 @@ fixture_base_props = { 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) @@ -976,61 +969,38 @@ 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'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 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: + for at in self.enabled_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 list schemas of artifacts + result = self.get(url='/schemas') + self.assertEqual(fixtures, result['schemas'], utils.DictDiffer( + result['schemas'], fixtures)) + # Get schema of sample_artifact result = self.get(url='/schemas/sample_artifact') self.assertEqual(fixtures['sample_artifact'], From e0f63c887eb3cfc4cd51f93f5dbe78c260e21e91 Mon Sep 17 00:00:00 2001 From: Mike Fedosin Date: Mon, 3 Oct 2016 16:29:16 +0300 Subject: [PATCH 12/13] Rename 'dependency' to 'link' Change-Id: I68d9b8d23b5feae2e0fde4490ce0b544a180b96f --- glare/objects/base.py | 22 ++++++++-------- glare/objects/heat_template.py | 2 +- glare/objects/meta/fields.py | 14 +++++----- glare/objects/murano_package.py | 2 +- glare/tests/functional/sample_artifact.py | 12 ++++----- .../tests/functional/test_sample_artifact.py | 26 +++++++++---------- glare/tests/functional/test_schemas.py | 24 ++++++++--------- 7 files changed, 51 insertions(+), 51 deletions(-) diff --git a/glare/objects/base.py b/glare/objects/base.py index f990ed9..e14dbb6 100644 --- a/glare/objects/base.py +++ b/glare/objects/base.py @@ -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) diff --git a/glare/objects/heat_template.py b/glare/objects/heat_template.py index d52b090..83b03e1 100644 --- a/glare/objects/heat_template.py +++ b/glare/objects/heat_template.py @@ -29,7 +29,7 @@ BlobDict = attribute.BlobDictAttribute.init class HeatTemplate(base.BaseArtifact): fields = { - 'environments': Dict(glare_fields.Dependency, + 'environments': Dict(glare_fields.Link, mutable=True, description="References to Heat Environments " "that can be used with current " diff --git a/glare/objects/meta/fields.py b/glare/objects/meta/fields.py index 5109899..7e9bcdf 100644 --- a/glare/objects/meta/fields.py +++ b/glare/objects/meta/fields.py @@ -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//' @@ -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): diff --git a/glare/objects/murano_package.py b/glare/objects/murano_package.py index 85ae249..d9f0347 100644 --- a/glare/objects/murano_package.py +++ b/glare/objects/murano_package.py @@ -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.Link, required_on_activate=False, description="List of package dependencies for " "this package."), diff --git a/glare/tests/functional/sample_artifact.py b/glare/tests/functional/sample_artifact.py index eb2283a..1143109 100644 --- a/glare/tests/functional/sample_artifact.py +++ b/glare/tests/functional/sample_artifact.py @@ -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,), diff --git a/glare/tests/functional/test_sample_artifact.py b/glare/tests/functional/test_sample_artifact.py index f4f6f8c..6fe180b 100644 --- a/glare/tests/functional/test_sample_artifact.py +++ b/glare/tests/functional/test_sample_artifact.py @@ -983,7 +983,7 @@ class TestArtifactOps(base.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, @@ -1099,14 +1099,14 @@ class TestArtifactOps(base.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) @@ -2109,23 +2109,23 @@ class TestUpdate(base.TestArtifact): self.patch(url=url, data=data, status=400) -class TestDependencies(base.TestArtifact): - def test_manage_dependencies(self): +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) diff --git a/glare/tests/functional/test_schemas.py b/glare/tests/functional/test_schemas.py index 66eba33..2006ae7 100644 --- a/glare/tests/functional/test_schemas.py +++ b/glare/tests/functional/test_schemas.py @@ -277,18 +277,18 @@ fixtures = { 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'link1': {u'filter_ops': [u'eq', + u'neq', + u'in'], + u'required_on_activate': False, + u'type': [u'string', + u'null']}, + u'link2': {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, From e6d7af4cf8c9b2fb5b19b995532c252fd5b67cbc Mon Sep 17 00:00:00 2001 From: Mike Fedosin Date: Thu, 29 Sep 2016 18:05:05 +0300 Subject: [PATCH 13/13] Add glare type names in json-schema output Change-Id: Ic1bfb876f1d93a929c67724dcac2195effaf8f97 --- glare/common/utils.py | 50 +++++++++++++++ glare/objects/base.py | 21 ++----- glare/objects/heat_template.py | 2 +- glare/objects/murano_package.py | 2 +- glare/tests/functional/base.py | 2 +- glare/tests/functional/test_schemas.py | 85 ++++++++++++++++++++++---- 6 files changed, 130 insertions(+), 32 deletions(-) diff --git a/glare/common/utils.py b/glare/common/utils.py index cbd6aa1..155177a 100644 --- a/glare/common/utils.py +++ b/glare/common/utils.py @@ -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: diff --git a/glare/objects/base.py b/glare/objects/base.py index e14dbb6..0353aff 100644 --- a/glare/objects/base.py +++ b/glare/objects/base.py @@ -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')} diff --git a/glare/objects/heat_template.py b/glare/objects/heat_template.py index 83b03e1..e5cc9d5 100644 --- a/glare/objects/heat_template.py +++ b/glare/objects/heat_template.py @@ -29,7 +29,7 @@ BlobDict = attribute.BlobDictAttribute.init class HeatTemplate(base.BaseArtifact): fields = { - 'environments': Dict(glare_fields.Link, + 'environments': Dict(glare_fields.LinkFieldType, mutable=True, description="References to Heat Environments " "that can be used with current " diff --git a/glare/objects/murano_package.py b/glare/objects/murano_package.py index d9f0347..b97f233 100644 --- a/glare/objects/murano_package.py +++ b/glare/objects/murano_package.py @@ -50,7 +50,7 @@ class MuranoPackage(base.BaseArtifact): "the package."), 'inherits': Dict(fields.String), 'keywords': List(fields.String, mutable=True), - 'dependencies': List(glare_fields.Link, + 'dependencies': List(glare_fields.LinkFieldType, required_on_activate=False, description="List of package dependencies for " "this package."), diff --git a/glare/tests/functional/base.py b/glare/tests/functional/base.py index e6a68e9..18d40b4 100644 --- a/glare/tests/functional/base.py +++ b/glare/tests/functional/base.py @@ -27,7 +27,7 @@ def sort_results(lst, target='name'): class TestArtifact(functional.FunctionalTest): enabled_types = (u'sample_artifact', u'images', u'heat_templates', u'heat_environments', u'tosca_templates', - u'murano_packages') + u'murano_packages', u'all') users = { 'user1': { diff --git a/glare/tests/functional/test_schemas.py b/glare/tests/functional/test_schemas.py index 2006ae7..dcd838c 100644 --- a/glare/tests/functional/test_schemas.py +++ b/glare/tests/functional/test_schemas.py @@ -29,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, @@ -44,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'}, @@ -52,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, @@ -60,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']}, @@ -83,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}$', @@ -93,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', @@ -101,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', @@ -111,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', @@ -119,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, @@ -127,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, @@ -137,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'}, @@ -151,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, @@ -166,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'}, @@ -174,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, @@ -184,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, @@ -200,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'}, @@ -212,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-]+)?$/', @@ -223,6 +240,7 @@ 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'} @@ -241,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']}, @@ -269,23 +288,27 @@ 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']}, @@ -319,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', @@ -328,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', @@ -337,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', @@ -346,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', @@ -366,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', @@ -377,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', @@ -388,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', @@ -399,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', @@ -410,6 +441,7 @@ fixtures = { u'gte', u'lt', u'lte'], + u'glareType': u'Integer', u'maximum': 20, u'minimum': 10, u'required_on_activate': False, @@ -417,6 +449,7 @@ fixtures = { 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, @@ -425,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, @@ -436,6 +470,7 @@ fixtures = { u'eq', u'neq', u'in'], + u'glareType': u'StringList', u'items': { u'type': u'string'}, u'maxItems': 3, @@ -445,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']}, @@ -479,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, @@ -491,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, @@ -504,6 +542,7 @@ fixtures = { u'gte', u'lt', u'lte'], + u'glareType': u'String', u'maxLength': 255, u'type': [u'string', u'null']}, @@ -519,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', @@ -527,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, @@ -544,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']}, @@ -568,6 +610,7 @@ fixtures = { u'filter_ops': [u'eq', u'neq', u'in'], + u'glareType': u'String', u'maxLength': 255, u'type': [u'string', u'null']}, @@ -586,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, @@ -598,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', @@ -610,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, @@ -620,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', @@ -630,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']}, @@ -637,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, @@ -646,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']}, @@ -674,6 +724,7 @@ fixtures = { u'filter_ops': [u'eq', u'neq', u'in'], + u'glareType': u'String', u'maxLength': 255, u'type': [u'string', u'null']} @@ -693,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', @@ -701,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']}, @@ -716,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']}, @@ -735,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']}, @@ -769,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']}, @@ -780,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', @@ -791,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}-' @@ -802,6 +861,7 @@ fixtures = { u'filter_ops': [u'eq', u'neq', u'in'], + u'glareType': u'Integer', u'minimum': 0, u'required_on_activate': False, u'type': [u'integer', u'null']}, @@ -810,6 +870,7 @@ fixtures = { u'filter_ops': [u'eq', u'neq', u'in'], + u'glareType': u'Integer', u'minimum': 0, u'required_on_activate': False, u'type': [u'integer', u'null']}, @@ -821,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']}, @@ -830,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']}, @@ -840,6 +903,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}-([0-9a-fA-F])' u'{4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}$', @@ -861,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', @@ -874,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', @@ -906,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']}, @@ -913,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']}, @@ -946,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']}, @@ -975,6 +1044,7 @@ fixtures = { 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']}, @@ -993,22 +1063,13 @@ class TestSchemas(base.TestArtifact): result = self.get(url='/schemas/%s' % at) self.assertEqual(fixtures[at], result['schemas'][at], utils.DictDiffer( - result['schemas'][at]['properties'], - fixtures[at]['properties'])) + 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 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']