Implement v2 API image tags
* Add image_tags table and new db methods to manage it * Add appropriate API resource classes * Implements bp api-v2-image-tags Change-Id: I5f3748b15239de8da000e7b3ff537c1cfc8e2f0d
This commit is contained in:
parent
5e85329ed1
commit
793bb61005
73
glance/api/v2/image_tags.py
Normal file
73
glance/api/v2/image_tags.py
Normal file
@ -0,0 +1,73 @@
|
||||
# Copyright 2012 OpenStack, LLC
|
||||
# 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 json
|
||||
|
||||
import webob.exc
|
||||
|
||||
from glance.api.v2 import base
|
||||
from glance.common import exception
|
||||
from glance.common import wsgi
|
||||
import glance.registry.db.api
|
||||
|
||||
|
||||
class Controller(base.Controller):
|
||||
def __init__(self, conf, db=None):
|
||||
super(Controller, self).__init__(conf)
|
||||
self.db_api = db or glance.registry.db.api
|
||||
self.db_api.configure_db(conf)
|
||||
|
||||
@staticmethod
|
||||
def _build_tag(image_tag):
|
||||
return {
|
||||
'value': image_tag['value'],
|
||||
'image_id': image_tag['image_id'],
|
||||
}
|
||||
|
||||
def index(self, req, image_id):
|
||||
tags = self.db_api.image_tag_get_all(req.context, image_id)
|
||||
return [self._build_tag(t) for t in tags]
|
||||
|
||||
def update(self, req, image_id, tag_value):
|
||||
self.db_api.image_tag_create(req.context, image_id, tag_value)
|
||||
|
||||
def delete(self, req, image_id, tag_value):
|
||||
try:
|
||||
self.db_api.image_tag_delete(req.context, image_id, tag_value)
|
||||
except exception.NotFound:
|
||||
raise webob.exc.HTTPNotFound()
|
||||
|
||||
|
||||
class ResponseSerializer(wsgi.JSONResponseSerializer):
|
||||
@staticmethod
|
||||
def _format_tag(tag):
|
||||
return tag['value']
|
||||
|
||||
def index(self, response, tags):
|
||||
response.content_type = 'application/json'
|
||||
response.body = json.dumps([self._format_tag(t) for t in tags])
|
||||
|
||||
def update(self, response, result):
|
||||
response.status_int = 204
|
||||
|
||||
def delete(self, response, result):
|
||||
response.status_int = 204
|
||||
|
||||
|
||||
def create_resource(conf):
|
||||
"""Images resource factory method"""
|
||||
serializer = ResponseSerializer()
|
||||
controller = Controller(conf)
|
||||
return wsgi.Resource(controller, serializer=serializer)
|
@ -20,6 +20,7 @@ import logging
|
||||
import routes
|
||||
|
||||
from glance.api.v2 import image_data
|
||||
from glance.api.v2 import image_tags
|
||||
from glance.api.v2 import images
|
||||
from glance.api.v2 import root
|
||||
from glance.api.v2 import schemas
|
||||
@ -85,4 +86,18 @@ class API(wsgi.Router):
|
||||
action='upload',
|
||||
conditions={'method': ['PUT']})
|
||||
|
||||
image_tags_resource = image_tags.create_resource(conf)
|
||||
mapper.connect('/images/{image_id}/tags',
|
||||
controller=image_tags_resource,
|
||||
action='index',
|
||||
conditions={'method': ['GET']})
|
||||
mapper.connect('/images/{image_id}/tags/{tag_value}',
|
||||
controller=image_tags_resource,
|
||||
action='update',
|
||||
conditions={'method': ['PUT']})
|
||||
mapper.connect('/images/{image_id}/tags/{tag_value}',
|
||||
controller=image_tags_resource,
|
||||
action='delete',
|
||||
conditions={'method': ['DELETE']})
|
||||
|
||||
super(API, self).__init__(mapper)
|
||||
|
@ -768,3 +768,36 @@ def can_show_deleted(context):
|
||||
if not hasattr(context, 'get'):
|
||||
return False
|
||||
return context.get('deleted', False)
|
||||
|
||||
|
||||
def image_tag_create(context, image_id, value):
|
||||
"""Create an image tag."""
|
||||
session = get_session()
|
||||
tag_ref = models.ImageTag(image_id=image_id, value=value)
|
||||
tag_ref.save(session=session)
|
||||
return tag_ref
|
||||
|
||||
|
||||
def image_tag_delete(context, image_id, value):
|
||||
"""Delete an image tag."""
|
||||
session = get_session()
|
||||
query = session.query(models.ImageTag).\
|
||||
filter_by(image_id=image_id).\
|
||||
filter_by(value=value).\
|
||||
filter_by(deleted=False)
|
||||
try:
|
||||
tag_ref = query.one()
|
||||
except exc.NoResultFound:
|
||||
raise exception.NotFound()
|
||||
|
||||
tag_ref.delete(session=session)
|
||||
|
||||
|
||||
def image_tag_get_all(context, image_id):
|
||||
"""Get a list of tags for a specific image."""
|
||||
session = get_session()
|
||||
tags = session.query(models.ImageTag).\
|
||||
filter_by(image_id=image_id).\
|
||||
filter_by(deleted=False).\
|
||||
all()
|
||||
return tags
|
||||
|
@ -0,0 +1,54 @@
|
||||
# Copyright 2012 OpenStack, LLC
|
||||
# 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 sqlalchemy import schema
|
||||
|
||||
from glance.registry.db.migrate_repo import schema as glance_schema
|
||||
|
||||
|
||||
def define_image_tags_table(meta):
|
||||
# Load the images table so the foreign key can be set up properly
|
||||
schema.Table('images', meta, autoload=True)
|
||||
|
||||
image_tags = schema.Table('image_tags', meta,
|
||||
schema.Column('id', glance_schema.Integer(),
|
||||
primary_key=True, nullable=False),
|
||||
schema.Column('image_id', glance_schema.String(36),
|
||||
schema.ForeignKey('images.id'), nullable=False),
|
||||
schema.Column('value', glance_schema.String(255), nullable=False),
|
||||
mysql_engine='InnoDB')
|
||||
|
||||
schema.Index('ix_image_tags_image_id',
|
||||
image_tags.c.image_id)
|
||||
|
||||
schema.Index('ix_image_tags_image_id_tag_value',
|
||||
image_tags.c.image_id,
|
||||
image_tags.c.value)
|
||||
|
||||
return image_tags
|
||||
|
||||
|
||||
def upgrade(migrate_engine):
|
||||
meta = schema.MetaData()
|
||||
meta.bind = migrate_engine
|
||||
tables = [define_image_tags_table(meta)]
|
||||
glance_schema.create_tables(tables)
|
||||
|
||||
|
||||
def downgrade(migrate_engine):
|
||||
meta = schema.MetaData()
|
||||
meta.bind = migrate_engine
|
||||
tables = [define_image_tags_table(meta)]
|
||||
glance_schema.drop_tables(tables)
|
@ -131,6 +131,15 @@ class ImageProperty(BASE, ModelBase):
|
||||
value = Column(Text)
|
||||
|
||||
|
||||
class ImageTag(BASE, ModelBase):
|
||||
"""Represents an image tag in the datastore"""
|
||||
__tablename__ = 'image_tags'
|
||||
|
||||
id = Column(Integer, primary_key=True, nullable=False)
|
||||
image_id = Column(String(36), ForeignKey('images.id'), nullable=False)
|
||||
value = Column(String(255), nullable=False)
|
||||
|
||||
|
||||
class ImageMember(BASE, ModelBase):
|
||||
"""Represents an image members in the datastore"""
|
||||
__tablename__ = 'image_members'
|
||||
|
@ -53,14 +53,19 @@ def runs_sql(func):
|
||||
@functools.wraps(func)
|
||||
def wrapped(*a, **kwargs):
|
||||
test_obj = a[0]
|
||||
orig_sql_connection = test_obj.registry_server.sql_connection
|
||||
orig_reg_sql_connection = test_obj.registry_server.sql_connection
|
||||
orig_api_sql_connection = test_obj.api_server.sql_connection
|
||||
try:
|
||||
if orig_sql_connection.startswith('sqlite'):
|
||||
if orig_reg_sql_connection.startswith('sqlite'):
|
||||
test_obj.registry_server.sql_connection =\
|
||||
"sqlite:///tests.sqlite"
|
||||
if orig_api_sql_connection.startswith('sqlite'):
|
||||
test_obj.api_server.sql_connection =\
|
||||
"sqlite:///tests.sqlite"
|
||||
func(*a, **kwargs)
|
||||
finally:
|
||||
test_obj.registry_server.sql_connection = orig_sql_connection
|
||||
test_obj.registry_server.sql_connection = orig_reg_sql_connection
|
||||
test_obj.api_server.sql_connection = orig_api_sql_connection
|
||||
return wrapped
|
||||
|
||||
|
||||
@ -212,6 +217,11 @@ class ApiServer(Server):
|
||||
self.policy_file = policy_file
|
||||
self.policy_default_rule = 'default'
|
||||
self.server_control_options = '--capture-output'
|
||||
|
||||
default_sql_connection = 'sqlite:///'
|
||||
self.sql_connection = os.environ.get('GLANCE_TEST_SQL_CONNECTION',
|
||||
default_sql_connection)
|
||||
|
||||
self.conf_base = """[DEFAULT]
|
||||
verbose = %(verbose)s
|
||||
debug = %(debug)s
|
||||
@ -248,6 +258,7 @@ image_cache_dir = %(image_cache_dir)s
|
||||
image_cache_driver = %(image_cache_driver)s
|
||||
policy_file = %(policy_file)s
|
||||
policy_default_rule = %(policy_default_rule)s
|
||||
sql_connection = %(sql_connection)s
|
||||
[paste_deploy]
|
||||
flavor = %(deployment_flavor)s
|
||||
"""
|
||||
@ -448,6 +459,7 @@ class FunctionalTest(unittest.TestCase):
|
||||
# and recreate it, which ensures that we have no side-effects
|
||||
# from the tests
|
||||
self._reset_database(self.registry_server.sql_connection)
|
||||
self._reset_database(self.api_server.sql_connection)
|
||||
|
||||
def set_policy_rules(self, rules):
|
||||
fap = open(self.policy_file, 'w')
|
||||
|
103
glance/tests/functional/v2/test_image_tags.py
Normal file
103
glance/tests/functional/v2/test_image_tags.py
Normal file
@ -0,0 +1,103 @@
|
||||
# Copyright 2012 OpenStack, LLC
|
||||
# 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 json
|
||||
|
||||
import requests
|
||||
|
||||
from glance.tests import functional
|
||||
|
||||
|
||||
class TestImageTags(functional.FunctionalTest):
|
||||
|
||||
def setUp(self):
|
||||
super(TestImageTags, self).setUp()
|
||||
self.cleanup()
|
||||
self.api_server.deployment_flavor = 'noauth'
|
||||
self.start_servers(**self.__dict__.copy())
|
||||
|
||||
# Create an image for our tests
|
||||
path = 'http://0.0.0.0:%d/v2/images' % self.api_port
|
||||
headers = self._headers({'Content-Type': 'application/json'})
|
||||
data = json.dumps({'name': 'image-1'})
|
||||
response = requests.post(path, headers=headers, data=data)
|
||||
self.assertEqual(200, response.status_code)
|
||||
self.image_url = response.headers['Location']
|
||||
|
||||
def _url(self, path):
|
||||
return '%s%s' % (self.image_url, path)
|
||||
|
||||
def _headers(self, custom_headers=None):
|
||||
base_headers = {
|
||||
'X-Identity-Status': 'Confirmed',
|
||||
'X-Auth-Token': '932c5c84-02ac-4fe5-a9ba-620af0e2bb96',
|
||||
'X-User-Id': 'f9a41d13-0c13-47e9-bee2-ce4e8bfe958e',
|
||||
'X-Tenant-Id': '38b7149a-b564-48dd-a0a5-aa7e643368c0',
|
||||
'X-Roles': 'member',
|
||||
}
|
||||
base_headers.update(custom_headers or {})
|
||||
return base_headers
|
||||
|
||||
@functional.runs_sql
|
||||
def test_image_tag_lifecycle(self):
|
||||
# List of image tags should be empty
|
||||
path = self._url('/tags')
|
||||
response = requests.get(path, headers=self._headers())
|
||||
self.assertEqual(200, response.status_code)
|
||||
tags = json.loads(response.text)
|
||||
self.assertEqual([], tags)
|
||||
|
||||
# Create a tag
|
||||
path = self._url('/tags/sniff')
|
||||
response = requests.put(path, headers=self._headers())
|
||||
self.assertEqual(204, response.status_code)
|
||||
|
||||
# List should now have an entry
|
||||
path = self._url('/tags')
|
||||
response = requests.get(path, headers=self._headers())
|
||||
self.assertEqual(200, response.status_code)
|
||||
tags = json.loads(response.text)
|
||||
self.assertEqual(['sniff'], tags)
|
||||
|
||||
# Create a more complex tag
|
||||
path = self._url('/tags/someone%40example.com')
|
||||
response = requests.put(path, headers=self._headers())
|
||||
self.assertEqual(204, response.status_code)
|
||||
|
||||
# List should reflect our new tag
|
||||
path = self._url('/tags')
|
||||
response = requests.get(path, headers=self._headers())
|
||||
self.assertEqual(200, response.status_code)
|
||||
tags = json.loads(response.text)
|
||||
self.assertEqual(['sniff', 'someone@example.com'], tags)
|
||||
|
||||
# The tag should be deletable
|
||||
path = self._url('/tags/someone%40example.com')
|
||||
response = requests.delete(path, headers=self._headers())
|
||||
self.assertEqual(204, response.status_code)
|
||||
|
||||
# List should reflect the deletion
|
||||
path = self._url('/tags')
|
||||
response = requests.get(path, headers=self._headers())
|
||||
self.assertEqual(200, response.status_code)
|
||||
tags = json.loads(response.text)
|
||||
self.assertEqual(['sniff'], tags)
|
||||
|
||||
# Deleting the same tag should return a 404
|
||||
path = self._url('/tags/someone%40example.com')
|
||||
response = requests.delete(path, headers=self._headers())
|
||||
self.assertEqual(404, response.status_code)
|
||||
|
||||
self.stop_servers()
|
@ -44,6 +44,7 @@ class TestImages(functional.FunctionalTest):
|
||||
base_headers.update(custom_headers or {})
|
||||
return base_headers
|
||||
|
||||
@functional.runs_sql
|
||||
def test_image_lifecycle(self):
|
||||
# Image list should be empty
|
||||
path = self._url('/v2/images')
|
||||
|
@ -48,6 +48,26 @@ CONF = {'sql_connection': 'sqlite://',
|
||||
'debug': False}
|
||||
|
||||
|
||||
class BaseDBTestCase(base.IsolatedUnitTest):
|
||||
|
||||
def setUp(self):
|
||||
super(BaseDBTestCase, self).setUp()
|
||||
conf = test_utils.TestConfigOpts(CONF)
|
||||
self.adm_context = context.RequestContext(is_admin=True)
|
||||
self.context = context.RequestContext(is_admin=False)
|
||||
db_api.configure_db(conf)
|
||||
self.destroy_fixtures()
|
||||
self.create_fixtures()
|
||||
|
||||
def create_fixtures(self):
|
||||
pass
|
||||
|
||||
def destroy_fixtures(self):
|
||||
# Easiest to just drop the models and re-create them...
|
||||
db_models.unregister_models(db_api._ENGINE)
|
||||
db_models.register_models(db_api._ENGINE)
|
||||
|
||||
|
||||
def build_fixtures(t1, t2):
|
||||
return [
|
||||
{'id': UUID1,
|
||||
@ -84,17 +104,7 @@ def build_fixtures(t1, t2):
|
||||
'properties': {}}]
|
||||
|
||||
|
||||
class TestRegistryDb(base.IsolatedUnitTest):
|
||||
|
||||
def setUp(self):
|
||||
"""Establish a clean test environment"""
|
||||
super(TestRegistryDb, self).setUp()
|
||||
conf = test_utils.TestConfigOpts(CONF)
|
||||
self.adm_context = context.RequestContext(is_admin=True)
|
||||
self.context = context.RequestContext(is_admin=False)
|
||||
db_api.configure_db(conf)
|
||||
self.destroy_fixtures()
|
||||
self.create_fixtures()
|
||||
class TestRegistryDb(BaseDBTestCase):
|
||||
|
||||
def create_fixtures(self):
|
||||
self.fixtures = self.build_fixtures()
|
||||
@ -106,11 +116,6 @@ class TestRegistryDb(base.IsolatedUnitTest):
|
||||
t2 = t1 + datetime.timedelta(microseconds=1)
|
||||
return build_fixtures(t1, t2)
|
||||
|
||||
def destroy_fixtures(self):
|
||||
# Easiest to just drop the models and re-create them...
|
||||
db_models.unregister_models(db_api._ENGINE)
|
||||
db_models.register_models(db_api._ENGINE)
|
||||
|
||||
def test_image_get(self):
|
||||
image = db_api.image_get(self.context, UUID1)
|
||||
self.assertEquals(image['id'], self.fixtures[0]['id'])
|
||||
@ -160,6 +165,48 @@ class TestRegistryDb(base.IsolatedUnitTest):
|
||||
self.assertEquals(len(images), 0)
|
||||
|
||||
|
||||
class TestDBImageTags(BaseDBTestCase):
|
||||
|
||||
def create_fixtures(self):
|
||||
fixtures = [
|
||||
{'id': UUID1, 'status': 'queued'},
|
||||
{'id': UUID2, 'status': 'queued'},
|
||||
]
|
||||
for fixture in fixtures:
|
||||
db_api.image_create(self.adm_context, fixture)
|
||||
|
||||
def test_image_tag_create(self):
|
||||
tag_ref = db_api.image_tag_create(self.context, UUID1, 'snap')
|
||||
self.assertEqual(UUID1, tag_ref.image_id)
|
||||
self.assertEqual('snap', tag_ref.value)
|
||||
|
||||
def test_image_tag_get_all(self):
|
||||
db_api.image_tag_create(self.context, UUID1, 'snap')
|
||||
db_api.image_tag_create(self.context, UUID1, 'snarf')
|
||||
db_api.image_tag_create(self.context, UUID2, 'snarf')
|
||||
|
||||
# Check the tags for the first image
|
||||
tag_refs = db_api.image_tag_get_all(self.context, UUID1)
|
||||
tags = [(t.image_id, t.value) for t in tag_refs]
|
||||
expected = [(UUID1, 'snap'), (UUID1, 'snarf')]
|
||||
self.assertEqual(expected, tags)
|
||||
|
||||
# Check the tags for the second image
|
||||
tag_refs = db_api.image_tag_get_all(self.context, UUID2)
|
||||
tags = [(t.image_id, t.value) for t in tag_refs]
|
||||
expected = [(UUID2, 'snarf')]
|
||||
self.assertEqual(expected, tags)
|
||||
|
||||
def test_image_tag_get_all_no_tags(self):
|
||||
self.assertEqual([], db_api.image_tag_get_all(self.context, UUID1))
|
||||
|
||||
def test_image_tag_delete(self):
|
||||
db_api.image_tag_create(self.context, UUID1, 'snap')
|
||||
db_api.image_tag_delete(self.context, UUID1, 'snap')
|
||||
self.assertRaises(exception.NotFound, db_api.image_tag_delete,
|
||||
self.context, UUID1, 'snap')
|
||||
|
||||
|
||||
class TestRegistryDbWithSameTime(TestRegistryDb):
|
||||
|
||||
def build_fixtures(self):
|
||||
|
@ -62,10 +62,18 @@ class FakeDB(object):
|
||||
],
|
||||
UUID2: [],
|
||||
}
|
||||
self.tags = {
|
||||
UUID1: {
|
||||
'ping': {'image_id': UUID1, 'value': 'ping'},
|
||||
'pong': {'image_id': UUID1, 'value': 'pong'},
|
||||
},
|
||||
UUID2: [],
|
||||
}
|
||||
|
||||
def reset(self):
|
||||
self.images = {}
|
||||
self.members = {}
|
||||
self.tags = {}
|
||||
|
||||
def configure_db(*args, **kwargs):
|
||||
pass
|
||||
@ -145,6 +153,29 @@ class FakeDB(object):
|
||||
LOG.info('Image %s updated to %s' % (image_id, str(image)))
|
||||
return image
|
||||
|
||||
def image_tag_get_all(self, context, image_id):
|
||||
return [
|
||||
{'image_id': image_id, 'value': 'ping'},
|
||||
{'image_id': image_id, 'value': 'pong'},
|
||||
]
|
||||
|
||||
def image_tag_get(self, context, image_id, value):
|
||||
try:
|
||||
return self.tags[image_id][value]
|
||||
except KeyError:
|
||||
raise exception.NotFound()
|
||||
|
||||
def image_tag_create(self, context, image_id, value):
|
||||
tag = {'image_id': image_id, 'value': value}
|
||||
self.tags[image_id][value] = tag.copy()
|
||||
return tag
|
||||
|
||||
def image_tag_delete(self, context, image_id, value):
|
||||
try:
|
||||
del self.tags[image_id][value]
|
||||
except KeyError:
|
||||
raise exception.NotFound()
|
||||
|
||||
|
||||
class FakeStoreAPI(object):
|
||||
def __init__(self):
|
||||
|
79
glance/tests/unit/v2/test_image_tags_resource.py
Normal file
79
glance/tests/unit/v2/test_image_tags_resource.py
Normal file
@ -0,0 +1,79 @@
|
||||
# Copyright 2012 OpenStack, LLC
|
||||
# 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 json
|
||||
import unittest
|
||||
|
||||
import webob
|
||||
|
||||
import glance.api.v2.image_tags
|
||||
import glance.tests.unit.utils as test_utils
|
||||
|
||||
|
||||
class TestImageTagsController(unittest.TestCase):
|
||||
def setUp(self):
|
||||
super(TestImageTagsController, self).setUp()
|
||||
self.db = test_utils.FakeDB()
|
||||
conf = {}
|
||||
self.controller = glance.api.v2.image_tags.Controller(conf, self.db)
|
||||
|
||||
def test_list_tags(self):
|
||||
request = test_utils.FakeRequest()
|
||||
tags = self.controller.index(request, test_utils.UUID1)
|
||||
expected = [
|
||||
{'value': 'ping', 'image_id': test_utils.UUID1},
|
||||
{'value': 'pong', 'image_id': test_utils.UUID1},
|
||||
]
|
||||
self.assertEqual(expected, tags)
|
||||
|
||||
def test_create_tag(self):
|
||||
request = test_utils.FakeRequest()
|
||||
self.controller.update(request, test_utils.UUID1, 'dink')
|
||||
|
||||
def test_delete_tag(self):
|
||||
request = test_utils.FakeRequest()
|
||||
self.controller.delete(request, test_utils.UUID1, 'ping')
|
||||
|
||||
def test_delete_tag_not_found(self):
|
||||
request = test_utils.FakeRequest()
|
||||
self.assertRaises(webob.exc.HTTPNotFound, self.controller.delete,
|
||||
request, test_utils.UUID1, 'what')
|
||||
|
||||
|
||||
class TestImagesSerializer(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.serializer = glance.api.v2.image_tags.ResponseSerializer()
|
||||
|
||||
def test_list_tags(self):
|
||||
fixtures = [
|
||||
{'value': 'ping', 'image_id': test_utils.UUID1},
|
||||
{'value': 'pong', 'image_id': test_utils.UUID1},
|
||||
]
|
||||
expected = ['ping', 'pong']
|
||||
response = webob.Response()
|
||||
self.serializer.index(response, fixtures)
|
||||
self.assertEqual(200, response.status_int)
|
||||
self.assertEqual('application/json', response.content_type)
|
||||
self.assertEqual(expected, json.loads(response.body))
|
||||
|
||||
def test_create_tag(self):
|
||||
response = webob.Response()
|
||||
self.serializer.update(response, None)
|
||||
self.assertEqual(204, response.status_int)
|
||||
|
||||
def test_delete_tag(self):
|
||||
response = webob.Response()
|
||||
self.serializer.delete(response, None)
|
||||
self.assertEqual(204, response.status_int)
|
Loading…
Reference in New Issue
Block a user