cinder/cinder/tests/unit/api/contrib/test_volume_image_metadata.py

447 lines
17 KiB
Python

# Copyright 2012 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.
import mock
import uuid
from oslo_policy import policy as oslo_policy
from oslo_serialization import jsonutils
from oslo_utils import timeutils
from six.moves import http_client
import webob
from cinder.api.contrib import volume_image_metadata
from cinder import context
from cinder import db
from cinder import exception
from cinder import objects
from cinder.objects import fields
from cinder.policies import base as base_policy
from cinder.policies import volume_metadata as metadata_policy
from cinder import policy
from cinder import test
from cinder.tests.unit.api import fakes
from cinder.tests.unit import fake_constants as fake
from cinder.tests.unit import fake_volume
from cinder import volume
def fake_db_volume_get(*args, **kwargs):
return {
'id': kwargs.get('volume_id') or fake.VOLUME_ID,
'host': 'host001',
'status': 'available',
'size': 5,
'availability_zone': 'somewhere',
'created_at': timeutils.utcnow(),
'display_name': 'anothervolume',
'display_description': 'Just another volume!',
'volume_type_id': None,
'snapshot_id': None,
'project_id': fake.PROJECT_ID,
'migration_status': None,
'_name_id': fake.VOLUME2_ID,
'attach_status': fields.VolumeAttachStatus.DETACHED,
}
def fake_volume_api_get(*args, **kwargs):
ctx = context.RequestContext(fake.USER_ID, fake.PROJECT_ID, True)
db_volume = fake_db_volume_get(volume_id=kwargs.get('volume_id'))
return fake_volume.fake_volume_obj(ctx, **db_volume)
def fake_volume_get_all(*args, **kwargs):
return objects.VolumeList(objects=[fake_volume_api_get(),
fake_volume_api_get(
volume_id=fake.VOLUME2_ID)])
def fake_volume_get_all_empty(*args, **kwargs):
return objects.VolumeList(objects=[])
fake_image_metadata = {
'image_id': fake.IMAGE_ID,
'image_name': 'fake',
'kernel_id': 'somekernel',
'ramdisk_id': 'someramdisk',
}
def fake_get_volume_image_metadata(*args, **kwargs):
return fake_image_metadata
def fake_get_volumes_image_metadata(*args, **kwargs):
return {'fake': fake_image_metadata}
def return_empty_image_metadata(*args, **kwargs):
return {}
def volume_metadata_delete(context, volume_id, key, meta_type):
pass
def fake_create_volume_metadata(context, volume_id, metadata,
delete, meta_type):
return fake_get_volume_image_metadata()
def return_volume_nonexistent(*args, **kwargs):
raise exception.VolumeNotFound('bogus test message')
class VolumeImageMetadataTest(test.TestCase):
content_type = 'application/json'
def setUp(self):
super(VolumeImageMetadataTest, self).setUp()
self.mock_object(volume.api.API, 'get', fake_volume_api_get)
self.mock_object(volume.api.API, 'get_all', fake_volume_get_all)
self.mock_object(volume.api.API, 'get_volume_image_metadata',
fake_get_volume_image_metadata)
self.mock_object(volume.api.API, 'get_volumes_image_metadata',
fake_get_volumes_image_metadata)
self.UUID = uuid.uuid4()
self.controller = (volume_image_metadata.
VolumeImageMetadataController())
self.user_ctxt = context.RequestContext(
fake.USER_ID, fake.PROJECT_ID, auth_token=True)
def _make_request(self, url):
req = webob.Request.blank(url)
req.accept = self.content_type
res = req.get_response(fakes.wsgi_app(
fake_auth_context=self.user_ctxt))
return res
def _get_image_metadata(self, body):
return jsonutils.loads(body)['volume']['volume_image_metadata']
def _get_image_metadata_list(self, body):
return [
volume['volume_image_metadata']
for volume in jsonutils.loads(body)['volumes']
if volume.get('volume_image_metadata')
]
def _create_volume_and_glance_metadata(self):
ctxt = context.get_admin_context()
# create a bootable volume
db.volume_create(ctxt, {'id': fake.VOLUME_ID, 'status': 'available',
'host': 'test', 'provider_location': '',
'size': 1})
db.volume_glance_metadata_create(ctxt, fake.VOLUME_ID,
'image_id', fake.IMAGE_ID)
db.volume_glance_metadata_create(ctxt, fake.VOLUME_ID,
'image_name', 'fake')
db.volume_glance_metadata_create(ctxt, fake.VOLUME_ID, 'kernel_id',
'somekernel')
db.volume_glance_metadata_create(ctxt, fake.VOLUME_ID, 'ramdisk_id',
'someramdisk')
# create an unbootable volume
db.volume_create(ctxt, {'id': fake.VOLUME2_ID, 'status': 'available',
'host': 'test', 'provider_location': '',
'size': 1})
def test_get_volume(self):
self._create_volume_and_glance_metadata()
res = self._make_request('/v2/%s/volumes/%s' % (
fake.PROJECT_ID, self.UUID))
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual(fake_image_metadata,
self._get_image_metadata(res.body))
def test_list_detail_volumes(self):
self._create_volume_and_glance_metadata()
res = self._make_request('/v2/%s/volumes/detail' % fake.PROJECT_ID)
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual(fake_image_metadata,
self._get_image_metadata_list(res.body)[0])
def test_list_detail_empty_volumes(self):
def fake_dont_call_this(*args, **kwargs):
fake_dont_call_this.called = True
fake_dont_call_this.called = False
self.mock_object(volume.api.API, 'get_list_volumes_image_metadata',
fake_dont_call_this)
self.mock_object(volume.api.API, 'get_all',
fake_volume_get_all_empty)
res = self._make_request('/v2/%s/volumes/detail' % fake.PROJECT_ID)
self.assertEqual(http_client.OK, res.status_int)
self.assertFalse(fake_dont_call_this.called)
def test_list_detail_volumes_with_limit(self):
ctxt = context.get_admin_context()
db.volume_create(ctxt, {'id': fake.VOLUME_ID, 'status': 'available',
'host': 'test', 'provider_location': '',
'size': 1})
db.volume_glance_metadata_create(ctxt, fake.VOLUME_ID,
'key1', 'value1')
db.volume_glance_metadata_create(ctxt, fake.VOLUME_ID,
'key2', 'value2')
res = self._make_request('/v2/%s/volumes/detail?limit=1'
% fake.PROJECT_ID)
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual({'key1': 'value1', 'key2': 'value2'},
self._get_image_metadata_list(res.body)[0])
@mock.patch('cinder.objects.Volume.get_by_id')
def test_create_image_metadata(self, fake_get):
self.mock_object(volume.api.API, 'get_volume_image_metadata',
return_empty_image_metadata)
self.mock_object(db, 'volume_metadata_update',
fake_create_volume_metadata)
body = {"os-set_image_metadata": {"metadata": fake_image_metadata}}
req = webob.Request.blank('/v2/%s/volumes/%s/action' % (
fake.PROJECT_ID, fake.VOLUME_ID))
req.method = "POST"
req.body = jsonutils.dump_as_bytes(body)
req.headers["content-type"] = "application/json"
fake_get.return_value = {}
res = req.get_response(fakes.wsgi_app(
fake_auth_context=self.user_ctxt))
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual(fake_image_metadata,
jsonutils.loads(res.body)["metadata"])
@mock.patch('cinder.objects.Volume.get_by_id')
def test_create_image_metadata_policy_not_authorized(self, fake_get):
rules = {
metadata_policy.IMAGE_METADATA_POLICY: base_policy.RULE_ADMIN_API
}
policy.set_rules(oslo_policy.Rules.from_dict(rules))
self.addCleanup(policy.reset)
fake_get.return_value = {}
req = fakes.HTTPRequest.blank('/v2/%s/volumes/%s/action' % (
fake.PROJECT_ID, fake.VOLUME_ID), use_admin_context=False)
req.method = 'POST'
req.content_type = "application/json"
body = {"os-set_image_metadata": {
"metadata": {"image_name": "fake"}}
}
req.body = jsonutils.dump_as_bytes(body)
self.assertRaises(exception.PolicyNotAuthorized,
self.controller.create, req, fake.VOLUME_ID,
body=body)
@mock.patch('cinder.objects.Volume.get_by_id')
def test_create_with_keys_case_insensitive(self, fake_get):
# If the keys in uppercase_and_lowercase, should return the one
# which server added
self.mock_object(volume.api.API, 'get_volume_image_metadata',
return_empty_image_metadata)
self.mock_object(db, 'volume_metadata_update',
fake_create_volume_metadata)
fake_get.return_value = {}
body = {
"os-set_image_metadata": {
"metadata": {
"Image_Id": "someid",
"image_name": "fake",
"Kernel_id": "somekernel",
"ramdisk_id": "someramdisk"
},
},
}
req = webob.Request.blank('/v2/%s/volumes/%s/action' % (
fake.PROJECT_ID, fake.VOLUME_ID))
req.method = 'POST'
req.body = jsonutils.dump_as_bytes(body)
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app(
fake_auth_context=self.user_ctxt))
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual(fake_image_metadata,
jsonutils.loads(res.body)["metadata"])
@mock.patch('cinder.objects.Volume.get_by_id')
def test_create_empty_body(self, fake_get):
req = fakes.HTTPRequest.blank('/v2/%s/volumes/%s/action' % (
fake.PROJECT_ID, fake.VOLUME_ID))
req.method = 'POST'
req.headers["content-type"] = "application/json"
fake_get.return_value = {}
self.assertRaises(exception.ValidationError,
self.controller.create, req, fake.VOLUME_ID,
body=None)
def test_create_nonexistent_volume(self):
self.mock_object(volume.api.API, 'get', return_volume_nonexistent)
req = fakes.HTTPRequest.blank('/v2/%s/volumes/%s/action' % (
fake.PROJECT_ID, fake.VOLUME_ID))
req.method = 'POST'
req.content_type = "application/json"
body = {"os-set_image_metadata": {
"metadata": {"image_name": "fake"}}
}
req.body = jsonutils.dump_as_bytes(body)
self.assertRaises(exception.VolumeNotFound,
self.controller.create, req, fake.VOLUME_ID,
body=body)
@mock.patch('cinder.objects.Volume.get_by_id')
def test_invalid_metadata_items_on_create(self, fake_get):
self.mock_object(db, 'volume_metadata_update',
fake_create_volume_metadata)
req = fakes.HTTPRequest.blank('/v2/%s/volumes/%s/action' % (
fake.PROJECT_ID, fake.VOLUME_ID))
req.method = 'POST'
req.headers["content-type"] = "application/json"
data = {"os-set_image_metadata": {
"metadata": {"a" * 260: "value1"}}
}
fake_get.return_value = {}
# Test for long key
req.body = jsonutils.dump_as_bytes(data)
self.assertRaises(exception.ValidationError,
self.controller.create, req, fake.VOLUME_ID,
body=data)
# Test for long value
data = {"os-set_image_metadata": {
"metadata": {"key": "v" * 260}}
}
req.body = jsonutils.dump_as_bytes(data)
self.assertRaises(exception.ValidationError,
self.controller.create, req, fake.VOLUME_ID,
body=data)
# Test for empty key.
data = {"os-set_image_metadata": {
"metadata": {"": "value1"}}
}
req.body = jsonutils.dump_as_bytes(data)
self.assertRaises(exception.ValidationError,
self.controller.create, req, fake.VOLUME_ID,
body=data)
@mock.patch('cinder.objects.Volume.get_by_id')
def test_delete(self, fake_get):
self.mock_object(db, 'volume_metadata_delete',
volume_metadata_delete)
body = {"os-unset_image_metadata": {
"key": "ramdisk_id"}
}
req = webob.Request.blank('/v2/%s/volumes/%s/action' % (
fake.PROJECT_ID, fake.VOLUME_ID))
req.method = 'POST'
req.body = jsonutils.dump_as_bytes(body)
req.headers["content-type"] = "application/json"
fake_get.return_value = {}
res = req.get_response(fakes.wsgi_app(
fake_auth_context=self.user_ctxt))
self.assertEqual(http_client.OK, res.status_int)
@mock.patch('cinder.objects.Volume.get_by_id')
def test_delete_image_metadata_policy_not_authorized(self, fake_get):
rules = {
metadata_policy.IMAGE_METADATA_POLICY: base_policy.RULE_ADMIN_API
}
policy.set_rules(oslo_policy.Rules.from_dict(rules))
self.addCleanup(policy.reset)
fake_get.return_value = {}
req = fakes.HTTPRequest.blank('/v2/%s/volumes/%s/action' % (
fake.PROJECT_ID, fake.VOLUME_ID), use_admin_context=False)
req.method = 'POST'
req.content_type = "application/json"
body = {"os-unset_image_metadata": {
"metadata": {"image_name": "fake"}}
}
req.body = jsonutils.dump_as_bytes(body)
self.assertRaises(exception.ValidationError,
self.controller.delete, req, fake.VOLUME_ID,
body=None)
@mock.patch('cinder.objects.Volume.get_by_id')
def test_delete_meta_not_found(self, fake_get):
data = {"os-unset_image_metadata": {
"key": "invalid_id"}
}
req = fakes.HTTPRequest.blank('/v2/%s/volumes/%s/action' % (
fake.PROJECT_ID, fake.VOLUME_ID))
req.method = 'POST'
req.body = jsonutils.dump_as_bytes(data)
req.headers["content-type"] = "application/json"
fake_get.return_value = {}
self.assertRaises(exception.GlanceMetadataNotFound,
self.controller.delete, req, fake.VOLUME_ID,
body=data)
@mock.patch('cinder.objects.Volume.get_by_id')
def test_delete_nonexistent_volume(self, fake_get):
self.mock_object(db, 'volume_metadata_delete',
return_volume_nonexistent)
body = {"os-unset_image_metadata": {
"key": "fake"}
}
req = fakes.HTTPRequest.blank('/v2/%s/volumes/%s/action' % (
fake.PROJECT_ID, fake.VOLUME_ID))
req.method = 'POST'
req.body = jsonutils.dump_as_bytes(body)
req.headers["content-type"] = "application/json"
fake_get.return_value = {}
self.assertRaises(exception.GlanceMetadataNotFound,
self.controller.delete, req, fake.VOLUME_ID,
body=body)
def test_delete_empty_body(self):
req = fakes.HTTPRequest.blank('/v2/%s/volumes/%s/action' % (
fake.PROJECT_ID, fake.VOLUME_ID))
req.method = 'POST'
req.headers["content-type"] = "application/json"
self.assertRaises(exception.ValidationError,
self.controller.delete, req, fake.VOLUME_ID,
body=None)
def test_show_image_metadata(self):
body = {"os-show_image_metadata": None}
req = webob.Request.blank('/v2/%s/volumes/%s/action' % (
fake.PROJECT_ID, fake.VOLUME_ID))
req.method = 'POST'
req.body = jsonutils.dump_as_bytes(body)
req.headers["content-type"] = "application/json"
res = req.get_response(fakes.wsgi_app(
fake_auth_context=self.user_ctxt))
self.assertEqual(http_client.OK, res.status_int)
self.assertEqual(fake_image_metadata,
jsonutils.loads(res.body)["metadata"])