Implement 'all' artifact type
Change-Id: Ia3bbe4f76af29e269ce25e67a6d2324e1ec57927
This commit is contained in:
parent
a4feb466da
commit
ead97e6d33
@ -57,7 +57,8 @@ class ArtifactAPI(base_api.BaseDBAPI):
|
|||||||
|
|
||||||
def list(self, context, filters, marker, limit, sort, latest):
|
def list(self, context, filters, marker, limit, sort, latest):
|
||||||
session = api.get_session()
|
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,
|
return api.get_all(context=context, session=session, filters=filters,
|
||||||
marker=marker, limit=limit, sort=sort,
|
marker=marker, limit=limit, sort=sort,
|
||||||
latest=latest)
|
latest=latest)
|
||||||
|
34
glare/objects/all.py
Normal file
34
glare/objects/all.py
Normal file
@ -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"
|
@ -1200,3 +1200,57 @@ class BaseArtifact(base.VersionedObject):
|
|||||||
'required': ['name']}
|
'required': ['name']}
|
||||||
|
|
||||||
return schemas
|
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.")
|
||||||
|
@ -103,7 +103,7 @@ class ArtifactRegistry(vo_base.VersionedObjectRegistry):
|
|||||||
supported_types = []
|
supported_types = []
|
||||||
for module in modules:
|
for module in modules:
|
||||||
supported_types.extend(get_subclasses(module, base.BaseArtifact))
|
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:
|
for af_type in supported_types:
|
||||||
if type_name == af_type.get_type_name():
|
if type_name == af_type.get_type_name():
|
||||||
cls._validate_artifact_type(af_type)
|
cls._validate_artifact_type(af_type)
|
||||||
|
183
glare/tests/functional/base.py
Normal file
183
glare/tests/functional/base.py
Normal file
@ -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
|
96
glare/tests/functional/test_all.py
Normal file
96
glare/tests/functional/test_all.py
Normal file
@ -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
|
import uuid
|
||||||
|
|
||||||
from oslo_serialization import jsonutils
|
from oslo_serialization import jsonutils
|
||||||
import requests
|
|
||||||
|
|
||||||
from glare.tests import functional
|
from glare.tests.functional import base
|
||||||
|
|
||||||
|
|
||||||
def sort_results(lst, target='name'):
|
def sort_results(lst, target='name'):
|
||||||
return sorted(lst, key=lambda x: x[target])
|
return sorted(lst, key=lambda x: x[target])
|
||||||
|
|
||||||
|
|
||||||
class TestArtifact(functional.FunctionalTest):
|
class TestList(base.TestArtifact):
|
||||||
|
|
||||||
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):
|
|
||||||
def test_list_marker_and_limit(self):
|
def test_list_marker_and_limit(self):
|
||||||
# Create artifacts
|
# Create artifacts
|
||||||
art_list = [self.create_artifact({'name': 'name%s' % i,
|
art_list = [self.create_artifact({'name': 'name%s' % i,
|
||||||
@ -806,7 +653,7 @@ class TestList(TestArtifact):
|
|||||||
self.assertEqual(response_url, result['first'])
|
self.assertEqual(response_url, result['first'])
|
||||||
|
|
||||||
|
|
||||||
class TestBlobs(TestArtifact):
|
class TestBlobs(base.TestArtifact):
|
||||||
def test_blob_dicts(self):
|
def test_blob_dicts(self):
|
||||||
# Getting empty artifact list
|
# Getting empty artifact list
|
||||||
url = '/sample_artifact'
|
url = '/sample_artifact'
|
||||||
@ -1005,7 +852,7 @@ class TestBlobs(TestArtifact):
|
|||||||
status=400, headers=headers)
|
status=400, headers=headers)
|
||||||
|
|
||||||
|
|
||||||
class TestTags(TestArtifact):
|
class TestTags(base.TestArtifact):
|
||||||
def test_tags(self):
|
def test_tags(self):
|
||||||
# Create artifact
|
# Create artifact
|
||||||
art = self.create_artifact({'name': 'name5',
|
art = self.create_artifact({'name': 'name5',
|
||||||
@ -1064,7 +911,7 @@ class TestTags(TestArtifact):
|
|||||||
self.patch(url=url, data=patch, status=400)
|
self.patch(url=url, data=patch, status=400)
|
||||||
|
|
||||||
|
|
||||||
class TestArtifactOps(TestArtifact):
|
class TestArtifactOps(base.TestArtifact):
|
||||||
def test_create(self):
|
def test_create(self):
|
||||||
"""All tests related to artifact creation"""
|
"""All tests related to artifact creation"""
|
||||||
# check that cannot create artifact for non-existent artifact type
|
# check that cannot create artifact for non-existent artifact type
|
||||||
@ -1346,7 +1193,7 @@ class TestArtifactOps(TestArtifact):
|
|||||||
self.assertEqual("active", deactive_art["status"])
|
self.assertEqual("active", deactive_art["status"])
|
||||||
|
|
||||||
|
|
||||||
class TestUpdate(TestArtifact):
|
class TestUpdate(base.TestArtifact):
|
||||||
def test_update_artifact_before_activate(self):
|
def test_update_artifact_before_activate(self):
|
||||||
"""Test updates for artifact before activation"""
|
"""Test updates for artifact before activation"""
|
||||||
# create artifact to update
|
# create artifact to update
|
||||||
@ -2262,7 +2109,7 @@ class TestUpdate(TestArtifact):
|
|||||||
self.patch(url=url, data=data, status=400)
|
self.patch(url=url, data=data, status=400)
|
||||||
|
|
||||||
|
|
||||||
class TestDependencies(TestArtifact):
|
class TestDependencies(base.TestArtifact):
|
||||||
def test_manage_dependencies(self):
|
def test_manage_dependencies(self):
|
||||||
some_af = self.create_artifact(data={"name": "test_af"})
|
some_af = self.create_artifact(data={"name": "test_af"})
|
||||||
dep_af = self.create_artifact(data={"name": "test_dep_af"})
|
dep_af = self.create_artifact(data={"name": "test_dep_af"})
|
||||||
|
@ -15,11 +15,8 @@
|
|||||||
|
|
||||||
import jsonschema
|
import jsonschema
|
||||||
|
|
||||||
from oslo_serialization import jsonutils
|
|
||||||
import requests
|
|
||||||
|
|
||||||
from glare.common import utils
|
from glare.common import utils
|
||||||
from glare.tests import functional
|
from glare.tests.functional import base
|
||||||
|
|
||||||
fixture_base_props = {
|
fixture_base_props = {
|
||||||
u'activated_at': {
|
u'activated_at': {
|
||||||
@ -231,10 +228,6 @@ fixture_base_props = {
|
|||||||
u'type': u'string'}
|
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):
|
def generate_type_props(props):
|
||||||
props.update(fixture_base_props)
|
props.update(fixture_base_props)
|
||||||
@ -976,61 +969,38 @@ fixtures = {
|
|||||||
u'required': [u'name'],
|
u'required': [u'name'],
|
||||||
u'version': u'1.0',
|
u'version': u'1.0',
|
||||||
u'title': u'Artifact type heat_environments of version 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'}
|
u'type': u'object'}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
class TestSchemas(functional.FunctionalTest):
|
class TestSchemas(base.TestArtifact):
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
def test_schemas(self):
|
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
|
# 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)
|
result = self.get(url='/schemas/%s' % at)
|
||||||
self.assertEqual(fixtures[at], result['schemas'][at],
|
self.assertEqual(fixtures[at], result['schemas'][at],
|
||||||
utils.DictDiffer(
|
utils.DictDiffer(
|
||||||
result['schemas'][at]['properties'],
|
result['schemas'][at]['properties'],
|
||||||
fixtures[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
|
# Get schema of sample_artifact
|
||||||
result = self.get(url='/schemas/sample_artifact')
|
result = self.get(url='/schemas/sample_artifact')
|
||||||
self.assertEqual(fixtures['sample_artifact'],
|
self.assertEqual(fixtures['sample_artifact'],
|
||||||
|
Loading…
Reference in New Issue
Block a user