[APIv2] Update registry images tagging

The endpoint of image tag should be changed to follow guidelines
on tag[*]. Main ideas are:
1. To add, remove, or change tags, a PUT request should be sent to
   the root tags URL, the server responds with a 200 status code.
2. To delete the entire tag list associated with a resource, a
   DELETE request must be sent to the root tags URL.
3. the resource identified by URL must expose its tags with root
   URL like: http://example.com:8386/servers/1234567890/tags.
4. There also add a get tags method and a dozen of unit test

*: http://specs.openstack.org/openstack/api-wg/guidelines/tags.html

Partial-Implements: bp v2-api-experimental-impl
Change-Id: Ia70322538f966ad032ae804c99f6f07463461e7d
This commit is contained in:
Shu Yingya 2017-01-18 10:42:03 +00:00
parent 5490475b69
commit 5fdba5c322
5 changed files with 106 additions and 10 deletions

View File

@ -32,6 +32,7 @@
"data-processing:images:register": "",
"data-processing:images:unregister": "",
"data-processing:images:add_tags": "",
"data-processing:images:set_tags": "",
"data-processing:images:remove_tags": "",
"data-processing:job-executions:get_all": "",

View File

@ -54,17 +54,25 @@ def images_unset(image_id):
return u.render()
@rest.post('/images/<image_id>/tag')
@acl.enforce("data-processing:images:add_tags")
@rest.get('/images/<image_id>/tags')
@acl.enforce("data-processing:images:get_tags")
@v.check_exists(api.get_image, id='image_id')
def image_tags_get(image_id):
return u.render(api.get_image_tags(image_id))
@rest.put('/images/<image_id>/tags', status_code=200)
@acl.enforce("data-processing:images:set_tags")
@v.check_exists(api.get_image, id='image_id')
@v.validate(v_images.image_tags_schema, v_images.check_tags)
def image_tags_add(image_id, data):
return u.render(api.add_image_tags(image_id, **data).wrapped_dict)
def image_tags_update(image_id, data):
return u.render(api.set_image_tags(image_id, **data).wrapped_dict)
@rest.post('/images/<image_id>/untag')
@rest.delete('/images/<image_id>/tags')
@acl.enforce("data-processing:images:remove_tags")
@v.check_exists(api.get_image, id='image_id')
@v.validate(v_images.image_tags_schema)
def image_tags_delete(image_id, data):
return u.render(api.remove_image_tags(image_id, **data).wrapped_dict)
def image_tags_delete(image_id):
api.remove_image_tags(image_id)
return u.render()

View File

@ -56,13 +56,32 @@ def unregister_image(image_id):
return b.execute_with_retries(manager.get, image_id)
def add_image_tags(image_id, tags):
def get_image_tags(image_id):
return b.execute_with_retries(
sahara_images.image_manager().get, image_id).tags
def set_image_tags(image_id, tags):
manager = sahara_images.image_manager()
b.execute_with_retries(manager.tag, image_id, tags)
image_obj = b.execute_with_retries(manager.get, image_id)
org_tags = frozenset(image_obj.tags)
new_tags = frozenset(tags)
to_add = list(new_tags - org_tags)
to_remove = list(org_tags - new_tags)
if to_add:
b.execute_with_retries(manager.tag, image_id, to_add)
if to_remove:
b.execute_with_retries(manager.untag, image_id, to_remove)
return b.execute_with_retries(manager.get, image_id)
def remove_image_tags(image_id, tags):
def remove_image_tags(image_id):
manager = sahara_images.image_manager()
image_obj = b.execute_with_retries(manager.get, image_id)
tags = image_obj.tags
b.execute_with_retries(manager.untag, image_id, tags)
return b.execute_with_retries(manager.get, image_id)

View File

@ -0,0 +1,68 @@
# Copyright (c) 2017 EasyStack 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 mock
from sahara.service.api.v2 import images
from sahara.tests.unit import base
class TestImageApi(base.SaharaTestCase):
def SetUp(self):
super(TestImageApi, self).SetUp()
@mock.patch('sahara.utils.openstack.images.SaharaImageManager')
def test_get_image_tags(self, mock_manager):
image = mock.Mock()
manager = mock.Mock()
manager.get.return_value = mock.Mock(tags=['foo', 'bar', 'baz'])
mock_manager.return_value = manager
self.assertEqual(['foo', 'bar', 'baz'], images.get_image_tags(image))
@mock.patch('sahara.utils.openstack.images.SaharaImageManager')
def test_set_image_tags(self, mock_manager):
def _tag(image, to_add):
return tags.append('qux')
def _untag(image, to_remove):
return tags.remove('bar')
expected_tags = ['foo', 'baz', 'qux']
tags = ['foo', 'bar', 'baz']
image = mock.Mock()
manager = mock.Mock()
manager.get.return_value = mock.Mock(tags=tags)
manager.tag.side_effect = _tag
manager.untag.side_effect = _untag
mock_manager.return_value = manager
self.assertEqual(expected_tags,
images.set_image_tags(image, expected_tags).tags)
@mock.patch('sahara.utils.openstack.images.SaharaImageManager')
def test_remove_image_tags(self, mock_manager):
def _untag(image, to_remove):
for i in range(len(to_remove)):
actual_tags.pop()
return actual_tags
actual_tags = ['foo', 'bar', 'baz']
image = mock.Mock()
manager = mock.Mock()
manager.get.return_value = mock.Mock(tags=actual_tags)
manager.untag.side_effect = _untag
mock_manager.return_value = manager
self.assertEqual([], images.remove_image_tags(image).tags)