Support for Metadata Definition Catalog for Tags

This set provides API and shell commands support for:
- CRUD on metadef_tags;

Change-Id: I09bdf43edee6fff615d223f1a6df7c15a1e40565
Implements: blueprint metadefs-tags-cli
DocImpact
This commit is contained in:
Wayne Okuma
2014-12-02 14:32:58 -08:00
parent 16ab18b64d
commit c0e90fa2bd
5 changed files with 508 additions and 0 deletions

View File

@@ -0,0 +1,183 @@
# Copyright 2015 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 testtools
from glanceclient.tests import utils
from glanceclient.v2 import metadefs
NAMESPACE1 = 'Namespace1'
TAG1 = 'Tag1'
TAG2 = 'Tag2'
TAGNEW1 = 'TagNew1'
TAGNEW2 = 'TagNew2'
TAGNEW3 = 'TagNew3'
def _get_tag_fixture(tag_name, **kwargs):
tag = {
"name": tag_name
}
tag.update(kwargs)
return tag
data_fixtures = {
"/v2/metadefs/namespaces/%s/tags" % NAMESPACE1: {
"GET": (
{},
{
"tags": [
_get_tag_fixture(TAG1),
_get_tag_fixture(TAG2)
]
}
),
"POST": (
{},
{
'tags': [
_get_tag_fixture(TAGNEW2),
_get_tag_fixture(TAGNEW3)
]
}
),
"DELETE": (
{},
{}
)
},
"/v2/metadefs/namespaces/%s/tags/%s" % (NAMESPACE1, TAGNEW1): {
"POST": (
{},
_get_tag_fixture(TAGNEW1)
)
},
"/v2/metadefs/namespaces/%s/tags/%s" % (NAMESPACE1, TAG1): {
"GET": (
{},
_get_tag_fixture(TAG1)
),
"PUT": (
{},
_get_tag_fixture(TAG2)
),
"DELETE": (
{},
{}
)
},
"/v2/metadefs/namespaces/%s/tags/%s" % (NAMESPACE1, TAG2): {
"GET": (
{},
_get_tag_fixture(TAG2)
),
},
"/v2/metadefs/namespaces/%s/tags/%s" % (NAMESPACE1, TAGNEW2): {
"GET": (
{},
_get_tag_fixture(TAGNEW2)
),
},
"/v2/metadefs/namespaces/%s/tags/%s" % (NAMESPACE1, TAGNEW3): {
"GET": (
{},
_get_tag_fixture(TAGNEW3)
),
}
}
schema_fixtures = {
"metadefs/tag": {
"GET": (
{},
{
"additionalProperties": True,
"name": {
"type": "string"
},
"created_at": {
"type": "string",
"description": ("Date and time of tag creation"
" (READ-ONLY)"),
"format": "date-time"
},
"updated_at": {
"type": "string",
"description": ("Date and time of the last tag"
" modification (READ-ONLY)"),
"format": "date-time"
},
'properties': {}
}
)
}
}
class TestTagController(testtools.TestCase):
def setUp(self):
super(TestTagController, self).setUp()
self.api = utils.FakeAPI(data_fixtures)
self.schema_api = utils.FakeSchemaAPI(schema_fixtures)
self.controller = metadefs.TagController(self.api, self.schema_api)
def test_list_tag(self):
tags = list(self.controller.list(NAMESPACE1))
actual = [tag.name for tag in tags]
self.assertEqual([TAG1, TAG2], actual)
def test_get_tag(self):
tag = self.controller.get(NAMESPACE1, TAG1)
self.assertEqual(TAG1, tag.name)
def test_create_tag(self):
tag = self.controller.create(NAMESPACE1, TAGNEW1)
self.assertEqual(TAGNEW1, tag.name)
def test_create_multiple_tags(self):
properties = {
'tags': [TAGNEW2, TAGNEW3]
}
tags = self.controller.create_multiple(NAMESPACE1, **properties)
actual = [tag.name for tag in tags]
self.assertEqual([TAGNEW2, TAGNEW3], actual)
def test_update_tag(self):
properties = {
'name': TAG2
}
tag = self.controller.update(NAMESPACE1, TAG1, **properties)
self.assertEqual(TAG2, tag.name)
def test_delete_tag(self):
self.controller.delete(NAMESPACE1, TAG1)
expect = [
('DELETE',
'/v2/metadefs/namespaces/%s/tags/%s' % (NAMESPACE1, TAG1),
{},
None)]
self.assertEqual(expect, self.api.calls)
def test_delete_all_tags(self):
self.controller.delete_all(NAMESPACE1)
expect = [
('DELETE',
'/v2/metadefs/namespaces/%s/tags' % NAMESPACE1,
{},
None)]
self.assertEqual(expect, self.api.calls)

View File

@@ -1096,3 +1096,107 @@ class ShellV2Test(testtools.TestCase):
['name', 'description'],
field_settings={
'description': {'align': 'l', 'max_width': 50}})
def test_do_md_tag_create(self):
args = self._make_args({'namespace': 'MyNamespace',
'name': 'MyTag'})
with mock.patch.object(self.gc.metadefs_tag,
'create') as mocked_create:
expect_tag = {}
expect_tag['namespace'] = 'MyNamespace'
expect_tag['name'] = 'MyTag'
mocked_create.return_value = expect_tag
test_shell.do_md_tag_create(self.gc, args)
mocked_create.assert_called_once_with('MyNamespace', 'MyTag')
utils.print_dict.assert_called_once_with(expect_tag)
def test_do_md_tag_update(self):
args = self._make_args({'namespace': 'MyNamespace',
'tag': 'MyTag',
'name': 'NewTag'})
with mock.patch.object(self.gc.metadefs_tag,
'update') as mocked_update:
expect_tag = {}
expect_tag['namespace'] = 'MyNamespace'
expect_tag['name'] = 'NewTag'
mocked_update.return_value = expect_tag
test_shell.do_md_tag_update(self.gc, args)
mocked_update.assert_called_once_with('MyNamespace', 'MyTag',
name='NewTag')
utils.print_dict.assert_called_once_with(expect_tag)
def test_do_md_tag_show(self):
args = self._make_args({'namespace': 'MyNamespace',
'tag': 'MyTag',
'sort_dir': 'desc'})
with mock.patch.object(self.gc.metadefs_tag, 'get') as mocked_get:
expect_tag = {}
expect_tag['namespace'] = 'MyNamespace'
expect_tag['tag'] = 'MyTag'
mocked_get.return_value = expect_tag
test_shell.do_md_tag_show(self.gc, args)
mocked_get.assert_called_once_with('MyNamespace', 'MyTag')
utils.print_dict.assert_called_once_with(expect_tag)
def test_do_md_tag_delete(self):
args = self._make_args({'namespace': 'MyNamespace',
'tag': 'MyTag'})
with mock.patch.object(self.gc.metadefs_tag,
'delete') as mocked_delete:
test_shell.do_md_tag_delete(self.gc, args)
mocked_delete.assert_called_once_with('MyNamespace', 'MyTag')
def test_do_md_namespace_tags_delete(self):
args = self._make_args({'namespace': 'MyNamespace'})
with mock.patch.object(self.gc.metadefs_tag,
'delete_all') as mocked_delete_all:
test_shell.do_md_namespace_tags_delete(self.gc, args)
mocked_delete_all.assert_called_once_with('MyNamespace')
def test_do_md_tag_list(self):
args = self._make_args({'namespace': 'MyNamespace'})
with mock.patch.object(self.gc.metadefs_tag, 'list') as mocked_list:
expect_tags = [{'namespace': 'MyNamespace',
'tag': 'MyTag'}]
mocked_list.return_value = expect_tags
test_shell.do_md_tag_list(self.gc, args)
mocked_list.assert_called_once_with('MyNamespace')
utils.print_list.assert_called_once_with(
expect_tags,
['name'],
field_settings={
'description': {'align': 'l', 'max_width': 50}})
def test_do_md_tag_create_multiple(self):
args = self._make_args({'namespace': 'MyNamespace',
'delim': ',',
'names': 'MyTag1, MyTag2'})
with mock.patch.object(
self.gc.metadefs_tag, 'create_multiple') as mocked_create_tags:
expect_tags = [{'tags': [{'name': 'MyTag1'}, {'name': 'MyTag2'}]}]
mocked_create_tags.return_value = expect_tags
test_shell.do_md_tag_create_multiple(self.gc, args)
mocked_create_tags.assert_called_once_with(
'MyNamespace', tags=['MyTag1', 'MyTag2'])
utils.print_list.assert_called_once_with(
expect_tags,
['name'],
field_settings={
'description': {'align': 'l', 'max_width': 50}})

View File

@@ -56,5 +56,8 @@ class Client(object):
self.metadefs_object = (
metadefs.ObjectController(self.http_client, self.schemas))
self.metadefs_tag = (
metadefs.TagController(self.http_client, self.schemas))
self.metadefs_namespace = (
metadefs.NamespaceController(self.http_client, self.schemas))

View File

@@ -385,3 +385,107 @@ class ObjectController(object):
"""Delete all objects in a namespace."""
url = '/v2/metadefs/namespaces/{0}/objects'.format(namespace)
self.http_client.delete(url)
class TagController(object):
def __init__(self, http_client, schema_client):
self.http_client = http_client
self.schema_client = schema_client
@utils.memoized_property
def model(self):
schema = self.schema_client.get('metadefs/tag')
return warlock.model_factory(schema.raw(), schemas.SchemaBasedModel)
def create(self, namespace, tag_name):
"""Create a tag.
:param namespace: Name of a namespace the Tag belongs.
:param tag_name: The name of the new tag to create.
"""
url = ('/v2/metadefs/namespaces/{0}/tags/{1}'.format(namespace,
tag_name))
resp, body = self.http_client.post(url)
body.pop('self', None)
return self.model(**body)
def create_multiple(self, namespace, **kwargs):
"""Create the list of tags.
:param namespace: Name of a namespace to which the Tags belong.
:param kwargs: list of tags.
"""
tag_names = kwargs.pop('tags', [])
md_tag_list = []
for tag_name in tag_names:
try:
md_tag_list.append(self.model(name=tag_name))
except (warlock.InvalidOperation) as e:
raise TypeError(utils.exception_to_str(e))
tags = {'tags': md_tag_list}
url = '/v2/metadefs/namespaces/{0}/tags'.format(namespace)
resp, body = self.http_client.post(url, data=tags)
body.pop('self', None)
for tag in body['tags']:
yield self.model(tag)
def update(self, namespace, tag_name, **kwargs):
"""Update a tag.
:param namespace: Name of a namespace the Tag belongs.
:param prop_name: Name of the Tag (old one).
:param kwargs: Unpacked tag.
"""
tag = self.get(namespace, tag_name)
for (key, value) in kwargs.items():
try:
setattr(tag, key, value)
except warlock.InvalidOperation as e:
raise TypeError(utils.exception_to_str(e))
# Remove read-only parameters.
read_only = ['updated_at', 'created_at']
for elem in read_only:
if elem in tag:
del tag[elem]
url = '/v2/metadefs/namespaces/{0}/tags/{1}'.format(namespace,
tag_name)
self.http_client.put(url, data=tag)
return self.get(namespace, tag.name)
def get(self, namespace, tag_name):
url = '/v2/metadefs/namespaces/{0}/tags/{1}'.format(namespace,
tag_name)
resp, body = self.http_client.get(url)
body.pop('self', None)
return self.model(**body)
def list(self, namespace, **kwargs):
"""Retrieve a listing of metadata tags.
:returns generator over list of tags.
"""
url = '/v2/metadefs/namespaces/{0}/tags'.format(namespace)
resp, body = self.http_client.get(url)
for tag in body['tags']:
yield self.model(tag)
def delete(self, namespace, tag_name):
"""Delete a tag."""
url = '/v2/metadefs/namespaces/{0}/tags/{1}'.format(namespace,
tag_name)
self.http_client.delete(url)
def delete_all(self, namespace):
"""Delete all tags in a namespace."""
url = '/v2/metadefs/namespaces/{0}/tags'.format(namespace)
self.http_client.delete(url)

View File

@@ -417,6 +417,10 @@ def _namespace_show(namespace, max_column_width=None):
objects = [obj['name'] for obj in namespace['objects']]
namespace['objects'] = objects
if 'tags' in namespace:
tags = [tag['name'] for tag in namespace['tags']]
namespace['tags'] = tags
if max_column_width:
utils.print_dict(namespace, max_column_width)
else:
@@ -775,6 +779,116 @@ def do_md_object_list(gc, args):
utils.print_list(objects, columns, field_settings=column_settings)
def _tag_show(tag, max_column_width=None):
tag = dict(tag) # Warlock objects are compatible with dicts
if max_column_width:
utils.print_dict(tag, max_column_width)
else:
utils.print_dict(tag)
@utils.arg('namespace', metavar='<NAMESPACE>',
help='Name of the namespace the tag will belong to.')
@utils.arg('--name', metavar='<NAME>', required=True,
help='The name of the new tag to add.')
def do_md_tag_create(gc, args):
"""Add a new metadata definitions tag inside a namespace."""
name = args.name.strip()
if name:
new_tag = gc.metadefs_tag.create(args.namespace, name)
_tag_show(new_tag)
else:
utils.exit('Please supply at least one non-blank tag name.')
@utils.arg('namespace', metavar='<NAMESPACE>',
help='Name of the namespace the tags will belong to.')
@utils.arg('--names', metavar='<NAMES>', required=True,
help='A comma separated list of tag names.')
@utils.arg('--delim', metavar='<DELIM>', required=False,
help='The delimiter used to separate the names'
' (if none is provided then the default is a comma).')
def do_md_tag_create_multiple(gc, args):
"""Create new metadata definitions tags inside a namespace."""
delim = args.delim or ','
tags = []
names_list = args.names.split(delim)
for name in names_list:
name = name.strip()
if name:
tags.append(name)
if not tags:
utils.exit('Please supply at least one tag name. For example: '
'--names Tag1')
fields = {'tags': tags}
new_tags = gc.metadefs_tag.create_multiple(args.namespace, **fields)
columns = ['name']
column_settings = {
"description": {
"max_width": 50,
"align": "l"
}
}
utils.print_list(new_tags, columns, field_settings=column_settings)
@utils.arg('namespace', metavar='<NAMESPACE>',
help='Name of the namespace to which the tag belongs.')
@utils.arg('tag', metavar='<TAG>', help='Name of the old tag.')
@utils.arg('--name', metavar='<NAME>', default=None, required=True,
help='New name of the new tag.')
def do_md_tag_update(gc, args):
"""Rename a metadata definitions tag inside a namespace."""
name = args.name.strip()
if name:
fields = {'name': name}
new_tag = gc.metadefs_tag.update(args.namespace, args.tag,
**fields)
_tag_show(new_tag)
else:
utils.exit('Please supply at least one non-blank tag name.')
@utils.arg('namespace', metavar='<NAMESPACE>',
help='Name of the namespace to which the tag belongs.')
@utils.arg('tag', metavar='<TAG>', help='Name of the tag.')
def do_md_tag_show(gc, args):
"""Describe a specific metadata definitions tag inside a namespace."""
tag = gc.metadefs_tag.get(args.namespace, args.tag)
_tag_show(tag)
@utils.arg('namespace', metavar='<NAMESPACE>',
help='Name of the namespace to which the tag belongs.')
@utils.arg('tag', metavar='<TAG>', help='Name of the tag.')
def do_md_tag_delete(gc, args):
"""Delete a specific metadata definitions tag inside a namespace."""
gc.metadefs_tag.delete(args.namespace, args.tag)
@utils.arg('namespace', metavar='<NAMESPACE>', help='Name of namespace.')
def do_md_namespace_tags_delete(gc, args):
"""Delete all metadata definitions tags inside a specific namespace."""
gc.metadefs_tag.delete_all(args.namespace)
@utils.arg('namespace', metavar='<NAMESPACE>', help='Name of namespace.')
def do_md_tag_list(gc, args):
"""List metadata definitions tags inside a specific namespace."""
tags = gc.metadefs_tag.list(args.namespace)
columns = ['name']
column_settings = {
"description": {
"max_width": 50,
"align": "l"
}
}
utils.print_list(tags, columns, field_settings=column_settings)
@utils.arg('--sort-key', default='status',
choices=tasks.SORT_KEY_VALUES,
help='Sort task list by specified field.')