Added display_type_name field in BaseArtifact

display_type_name provides admin a facility to
define a more user friendly type_name for artifact.

Change-Id: I8b1b08e4fa647c87c65f7af94f8f49d6269e03cf
This commit is contained in:
Kushal Agrawal 2018-03-15 13:57:48 +05:30
parent d1de4d1c7b
commit a81783e682
21 changed files with 186 additions and 6 deletions

View File

@ -320,7 +320,7 @@ class ArtifactsController(api_versioning.VersionedResource):
if not values.get('name'):
msg = _("Name must be specified at creation.")
raise exc.BadRequest(msg)
for field in ('visibility', 'status'):
for field in ('visibility', 'status', 'display_type_name'):
if field in values:
msg = _("%s is not allowed in a request at creation.") % field
raise exc.BadRequest(msg)
@ -385,7 +385,8 @@ class ArtifactsController(api_versioning.VersionedResource):
artifacts = artifacts_data["artifacts"]
result = {'artifacts': artifacts,
'type_name': type_name,
'total_count': artifacts_data['total_count']}
'total_count': artifacts_data['total_count'],
'display_type_name': artifacts_data['display_type_name']}
if len(artifacts) != 0 and len(artifacts) == limit:
result['next_marker'] = artifacts[-1]['id']
return result
@ -545,7 +546,8 @@ class ResponseSerializer(api_versioning.VersionedResource,
'artifacts': af_list['artifacts'],
'first': '/artifacts/%s' % type_name,
'schema': '/schemas/%s' % type_name,
'total_count': af_list['total_count']
'total_count': af_list['total_count'],
'display_type_name': af_list['display_type_name']
}
if query:
body['first'] = '%s?%s' % (body['first'], query)

View File

@ -0,0 +1,45 @@
# Copyright 2018 OpenStack Foundation.
#
# 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.
"""added display name
Revision ID: 005
Revises: 004
Create Date: 2018-03-13 14:32:33.765690
"""
# revision identifiers, used by Alembic.
revision = '005'
down_revision = '004'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('glare_artifacts', sa.Column('display_type_name',
sa.String(255),
nullable=True))
op.create_index('ix_glare_artifact_display_name',
'glare_artifacts',
['display_type_name']
)
def downgrade():
with op.batch_alter_table('glare_artifacts') as batch_op:
batch_op.drop_index('ix_glare_artifact_display_name')
batch_op.drop_column('display_type_name')

View File

@ -50,7 +50,8 @@ options.set_defaults(CONF)
BASE_ARTIFACT_PROPERTIES = ('id', 'visibility', 'created_at', 'updated_at',
'activated_at', 'owner', 'status', 'description',
'name', 'type_name', 'version')
'name', 'type_name', 'version',
'display_type_name')
_FACADE = None
_LOCK = threading.Lock()

View File

@ -99,6 +99,7 @@ class Artifact(BASE, ArtifactBase):
Index('ix_glare_artifact_status', 'status'),
Index('ix_glare_artifact_owner', 'owner'),
Index('ix_glare_artifact_visibility', 'visibility'),
Index('ix_glare_artifact_display_name', 'display_type_name'),
{'mysql_engine': 'InnoDB', 'mysql_charset': 'utf8'})
__protected_attributes__ = set(["created_at", "updated_at"])
@ -122,6 +123,7 @@ class Artifact(BASE, ArtifactBase):
updated_at = Column(DateTime, default=lambda: timeutils.utcnow(),
nullable=False, onupdate=lambda: timeutils.utcnow())
activated_at = Column(DateTime)
display_type_name = Column(String(255), nullable=True)
def to_dict(self):
d = super(Artifact, self).to_dict()

View File

@ -349,6 +349,9 @@ class Engine(object):
context, filters, marker, limit, sort, latest, list_all_artifacts)
artifacts_data["artifacts"] = [af.to_dict()
for af in artifacts_data["artifacts"]]
artifacts_data['display_type_name'] = \
artifact_type.get_display_type_name()
return artifacts_data
@staticmethod

View File

@ -30,6 +30,14 @@ class All(base.BaseArtifact):
'type_name': Field(fields.StringField,
description="Name of artifact type.",
sortable=True),
'display_type_name': Field(fields.StringField,
description="Display name of "
"artifact type.",
sortable=True,
filter_ops=(wrappers.FILTER_LIKE,
wrappers.FILTER_EQ,
wrappers.FILTER_NEQ,
wrappers.FILTER_IN))
}
@classmethod
@ -51,6 +59,10 @@ class All(base.BaseArtifact):
def get_type_name(cls):
return "all"
@classmethod
def get_display_type_name(cls):
return "All Artifacts"
def to_dict(self):
# Use specific method of artifact type to convert it to dict
values = self.obj_to_primitive()['versioned_object.data']

View File

@ -245,6 +245,16 @@ Possible values:
"""
raise NotImplementedError()
@classmethod
def get_display_type_name(cls):
"""
Provides verbose Artifact type name which any external user can
understand easily.
:return: general purpose name for Artifact
"""
return None
def create(self, context):
"""Create new artifact in Glare repo.
@ -253,6 +263,7 @@ Possible values:
"""
values = self.obj_changes_to_primitive()
values['type_name'] = self.get_type_name()
values['display_type_name'] = self.get_display_type_name()
LOG.debug("Sending request to create artifact of type '%(type_name)s'."
" New values are %(values)s",

View File

@ -29,3 +29,7 @@ class HeatEnvironment(base.BaseArtifact):
@classmethod
def get_type_name(cls):
return "heat_environments"
@classmethod
def get_display_type_name(cls):
return "Heat Environments"

View File

@ -47,3 +47,7 @@ class HeatTemplate(base.BaseArtifact):
@classmethod
def get_type_name(cls):
return "heat_templates"
@classmethod
def get_display_type_name(cls):
return "Heat Templates"

View File

@ -88,3 +88,7 @@ class Image(base.BaseArtifact):
@classmethod
def get_type_name(cls):
return "images"
@classmethod
def get_display_type_name(cls):
return "Images"

View File

@ -58,3 +58,7 @@ class MuranoPackage(base.BaseArtifact):
@classmethod
def get_type_name(cls):
return "murano_packages"
@classmethod
def get_display_type_name(cls):
return "Murano packages"

View File

@ -34,6 +34,10 @@ class Secret(base_artifact.BaseArtifact):
def get_type_name(cls):
return "secrets"
@classmethod
def get_display_type_name(cls):
return "Secrets"
fields = {
'payload': Blob( # The encrypted secret data
description="The secret's data to be stored"

View File

@ -33,3 +33,7 @@ class TOSCATemplate(base.BaseArtifact):
@classmethod
def get_type_name(cls):
return "tosca_templates"
@classmethod
def get_display_type_name(cls):
return "TOSCA Templates"

View File

@ -61,6 +61,30 @@ class TestAll(base.TestArtifact):
self.assertEqual(54, len(res))
self.assertEqual(sorted(res, key=lambda x: x['type_name']), res)
# get all artifacts Sorted in Asc order based on display_type_name
url = '/all?sort=display_type_name:asc&limit=100'
res = self.get(url=url, status=200)['artifacts']
self.assertEqual(54, len(res))
self.assertEqual(sorted(res, key=lambda x: x['display_type_name']),
res)
# get all artifacts sorted in desc order based on display_type_name
url = '/all?sort=display_type_name:desc&limit=100'
res = self.get(url=url, status=200)['artifacts']
self.assertEqual(54, len(res))
self.assertEqual(sorted(res, key=lambda x: x['display_type_name'],
reverse=True), res)
# get Heat Template like only
url = '/all?display_type_name=like:Heat%&sort=display_type_name:asc'
res = self.get(url=url, status=200)['artifacts']
self.assertEqual(18, len(res))
for art in res:
self.assertEqual('Heat', art['display_type_name'][:4])
# TODO(kushalagrawal): Need to Add test case for display_type_name with
# null once https://bugs.launchpad.net/glare/+bug/1741400 is resolved
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')

View File

@ -45,7 +45,8 @@ default_store = database
'artifacts': [],
'schema': '/schemas/sample_artifact',
'type_name': 'sample_artifact',
'total_count': 0}
'total_count': 0,
'display_type_name': 'Sample Artifact'}
self.assertEqual(expected, response)
# Create a test artifact

View File

@ -653,6 +653,12 @@ class TestList(base.TestArtifact):
self.assertEqual(art1, result['artifacts'][0])
self.assertEqual(response_url, result['first'])
def test_list_response_attributes(self):
url = '/sample_artifact'
res = self.get(url=url, status=200)
self.assertEqual(res['total_count'], 0)
self.assertEqual(res['display_type_name'], "Sample Artifact")
class TestBlobs(base.TestArtifact):
def test_blob_dicts(self):
@ -663,7 +669,8 @@ class TestBlobs(base.TestArtifact):
'artifacts': [],
'schema': '/schemas/sample_artifact',
'type_name': 'sample_artifact',
'total_count': 0}
'total_count': 0,
'display_type_name': 'Sample Artifact'}
self.assertEqual(expected, response)
# Create a test artifact
@ -1225,6 +1232,10 @@ class TestArtifactOps(base.TestArtifact):
self.create_artifact(data={"name": "test_af",
"string_required": "test_str"})
# Check we cannot create data with display_type_name.
self.create_artifact(data={"display_type_name": "Sample Artifact",
"name": "Invalid_data"}, status=400)
def test_activate(self):
# create artifact to update
private_art = self.create_artifact(

View File

@ -932,6 +932,11 @@ fixtures = {
u'maxLength': 255,
u'sortable': True,
u'type': [u'string', u'null']},
u'display_type_name': {
u'description': u'Display name of artifact type.',
u'filter_ops': [u'like', u'eq', u'neq', u'in'],
u'glareType': u'String', u'maxLength': 255,
u'sortable': True, u'type': [u'string', u'null']}
}),
u'required': [u'name'],

View File

@ -74,6 +74,10 @@ class HookChecker(base.BaseArtifact):
def get_type_name(cls):
return "hooks_artifact"
@classmethod
def get_display_type_name(cls):
return "Hooks Artifact"
@classmethod
def pre_create_hook(cls, context, af):
# create a temporary file and set the path to artifact field

View File

@ -139,6 +139,10 @@ class SampleArtifact(base_artifact.BaseArtifact):
def get_type_name(cls):
return "sample_artifact"
@classmethod
def get_display_type_name(cls):
return "Sample Artifact"
def to_dict(self):
res = self.obj_to_primitive()['versioned_object.data']
res['__some_meta_information__'] = res['name'].upper()

View File

@ -247,6 +247,33 @@ class GlareMigrationsCheckers(object):
self.assert_table(engine, 'glare_quotas', quota_indices,
quota_columns)
def _check_005(self, engine, data):
artifacts_indices = [('ix_glare_artifact_name_and_version',
['name', 'version_prefix', 'version_suffix']),
('ix_glare_artifact_type',
['type_name']),
('ix_glare_artifact_status', ['status']),
('ix_glare_artifact_visibility', ['visibility']),
('ix_glare_artifact_owner', ['owner']),
('ix_glare_artifact_display_name',
['display_type_name'])]
artifacts_columns = ['id',
'name',
'type_name',
'version_prefix',
'version_suffix',
'version_meta',
'description',
'visibility',
'status',
'owner',
'created_at',
'updated_at',
'activated_at',
'display_type_name']
self.assert_table(engine, 'glare_artifacts', artifacts_indices,
artifacts_columns)
class TestMigrationsMySQL(GlareMigrationsCheckers,
WalkVersionsMixin,

View File

@ -37,6 +37,10 @@ class Unpacker(base.BaseArtifact):
def get_type_name(cls):
return "unpacking_artifact"
@classmethod
def get_display_type_name(cls):
return "Unpacking Artifact"
@classmethod
def pre_upload_hook(cls, context, af, field_name, blob_key, fd):
flobj = io.BytesIO(fd.read(cls.MAX_BLOB_SIZE))