Implement volume actions
The purpose of this patch set is to provide the end users with the following functionalities: 1. Extend the size of the specified volume. 2. Set attachment metadata of the specified volume. 3. Update state of the specified volume. 4. Set image metadata of the specified volume. 5. Unset image metadata of the specified volume. 6. Show image metadata of the specified volume. Change-Id: Ie9e4ca15a412c89a3c44f1b8526e6597eddf762c
This commit is contained in:
parent
122d2b1866
commit
7b6a6af766
tricircle
cinder_apigw/controllers
common
tests/unit/cinder_apigw/controllers
@ -18,6 +18,7 @@ import pecan
|
||||
import oslo_log.log as logging
|
||||
|
||||
from tricircle.cinder_apigw.controllers import volume
|
||||
from tricircle.cinder_apigw.controllers import volume_actions
|
||||
from tricircle.cinder_apigw.controllers import volume_metadata
|
||||
from tricircle.cinder_apigw.controllers import volume_type
|
||||
|
||||
@ -73,6 +74,7 @@ class V2Controller(object):
|
||||
|
||||
self.volumes_sub_controller = {
|
||||
'metadata': volume_metadata.VolumeMetaDataController,
|
||||
'action': volume_actions.VolumeActionController,
|
||||
}
|
||||
|
||||
@pecan.expose()
|
||||
|
234
tricircle/cinder_apigw/controllers/volume_actions.py
Normal file
234
tricircle/cinder_apigw/controllers/volume_actions.py
Normal file
@ -0,0 +1,234 @@
|
||||
# Copyright 2016 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 pecan
|
||||
from pecan import expose
|
||||
from pecan import rest
|
||||
|
||||
from oslo_log import log as logging
|
||||
|
||||
import tricircle.common.client as t_client
|
||||
from tricircle.common import constants
|
||||
import tricircle.common.context as t_context
|
||||
from tricircle.common.i18n import _
|
||||
from tricircle.common.i18n import _LE
|
||||
from tricircle.common import utils
|
||||
import tricircle.db.api as db_api
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class VolumeActionController(rest.RestController):
|
||||
|
||||
def __init__(self, project_id, volume_id):
|
||||
self.project_id = project_id
|
||||
self.volume_id = volume_id
|
||||
self.clients = {constants.TOP: t_client.Client()}
|
||||
self.handle_map = {
|
||||
'os-attach': self._attach,
|
||||
'os-extend': self._extend,
|
||||
'os-reset_status': self._reset_status,
|
||||
'os-set_image_metadata': self._set_image_metadata,
|
||||
'os-unset_image_metadata': self._unset_image_metadata,
|
||||
'os-show_image_metadata': self._show_image_metadata
|
||||
}
|
||||
|
||||
def _get_client(self, pod_name=constants.TOP):
|
||||
if pod_name not in self.clients:
|
||||
self.clients[pod_name] = t_client.Client(pod_name)
|
||||
return self.clients[pod_name]
|
||||
|
||||
def _action(self, context, pod_name, action, info=None, **kwargs):
|
||||
"""Perform a volume "action".
|
||||
|
||||
:param pod_name: the bottom pod name.
|
||||
:param action: volume action name.
|
||||
:param info: action parameters body.
|
||||
"""
|
||||
body = {action: info}
|
||||
url = '/volumes/%s/action' % self.volume_id
|
||||
api = self._get_client(pod_name).get_native_client('volume', context)
|
||||
return api.client.post(url, body=body)
|
||||
|
||||
def _attach(self, context, pod_name, kw):
|
||||
"""Add attachment metadata.
|
||||
|
||||
:param pod_name: the bottom pod name.
|
||||
:param kw: request body.
|
||||
"""
|
||||
try:
|
||||
mountpoint = None
|
||||
if 'mountpoint' in kw['os-attach']:
|
||||
mountpoint = kw['os-attach']['mountpoint']
|
||||
body = {'mountpoint': mountpoint}
|
||||
instance_uuid = None
|
||||
if 'instance_uuid' in kw['os-attach']:
|
||||
instance_uuid = kw['os-attach']['instance_uuid']
|
||||
host_name = None
|
||||
if 'host_name' in kw['os-attach']:
|
||||
host_name = kw['os-attach']['host_name']
|
||||
except (KeyError, ValueError, TypeError):
|
||||
msg = _('The server could not comply with the request since '
|
||||
'it is either malformed or otherwise incorrect.')
|
||||
return utils.format_cinder_error(400, msg)
|
||||
|
||||
if instance_uuid is not None:
|
||||
body.update({'instance_uuid': instance_uuid})
|
||||
if host_name is not None:
|
||||
body.update({'host_name': host_name})
|
||||
return self._action(context, pod_name, 'os-attach', body)
|
||||
|
||||
def _extend(self, context, pod_name, kw):
|
||||
"""Extend the size of the specified volume.
|
||||
|
||||
:param pod_name: the bottom pod name.
|
||||
:param kw: request body.
|
||||
"""
|
||||
try:
|
||||
new_size = int(kw['os-extend']['new_size'])
|
||||
except (KeyError, ValueError, TypeError):
|
||||
msg = _("New volume size must be specified as an integer.")
|
||||
return utils.format_cinder_error(400, msg)
|
||||
return self._action(context, pod_name, 'os-extend',
|
||||
{'new_size': new_size})
|
||||
|
||||
def _reset_status(self, context, pod_name, kw):
|
||||
"""Update the provided volume with the provided state.
|
||||
|
||||
:param pod_name: the bottom pod name.
|
||||
:param kw: request body.
|
||||
"""
|
||||
try:
|
||||
status = None
|
||||
if 'status' in kw['os-reset_status']:
|
||||
status = kw['os-reset_status']['status']
|
||||
attach_status = None
|
||||
if 'attach_status' in kw['os-reset_status']:
|
||||
attach_status = kw['os-reset_status']['attach_status']
|
||||
migration_status = None
|
||||
if 'migration_status' in kw['os-reset_status']:
|
||||
migration_status = kw['os-reset_status']['migration_status']
|
||||
except (TypeError, KeyError, ValueError):
|
||||
msg = _('The server has either erred or is incapable of '
|
||||
'performing the requested operation.')
|
||||
return utils.format_cinder_error(500, msg)
|
||||
|
||||
body = {'status': status} if status else {}
|
||||
if attach_status:
|
||||
body.update({'attach_status': attach_status})
|
||||
if migration_status:
|
||||
body.update({'migration_status': migration_status})
|
||||
return self._action(context, pod_name, 'os-reset_status', body)
|
||||
|
||||
def _set_image_metadata(self, context, pod_name, kw):
|
||||
"""Set a volume's image metadata.
|
||||
|
||||
:param pod_name: the bottom pod name.
|
||||
:param kw: request body.
|
||||
"""
|
||||
try:
|
||||
metadata = kw['os-set_image_metadata']['metadata']
|
||||
except (KeyError, TypeError):
|
||||
msg = _("Malformed request body.")
|
||||
return utils.format_cinder_error(400, msg)
|
||||
return self._action(context, pod_name, 'os-set_image_metadata',
|
||||
{'metadata': metadata})
|
||||
|
||||
def _unset_image_metadata(self, context, pod_name, kw):
|
||||
"""Unset specified keys from volume's image metadata.
|
||||
|
||||
:param pod_name: the bottom pod name.
|
||||
:param kw: request body.
|
||||
"""
|
||||
try:
|
||||
key = kw['os-unset_image_metadata']['key']
|
||||
except (KeyError, TypeError):
|
||||
msg = _("Malformed request body.")
|
||||
return utils.format_cinder_error(400, msg)
|
||||
return self._action(
|
||||
context, pod_name, 'os-unset_image_metadata', {'key': key})
|
||||
|
||||
def _show_image_metadata(self, context, pod_name, kw):
|
||||
"""Show a volume's image metadata.
|
||||
|
||||
:param pod_name: the bottom pod name.
|
||||
:param kw: request body.
|
||||
"""
|
||||
return self._action(context, pod_name, 'os-show_image_metadata')
|
||||
|
||||
@expose(generic=True, template='json')
|
||||
def post(self, **kw):
|
||||
context = t_context.extract_context_from_environ()
|
||||
|
||||
action_handle = None
|
||||
action_type = None
|
||||
for _type in self.handle_map:
|
||||
if _type in kw:
|
||||
action_handle = self.handle_map[_type]
|
||||
action_type = _type
|
||||
if not action_handle:
|
||||
return utils.format_cinder_error(
|
||||
400, _('Volume action not supported'))
|
||||
|
||||
volume_mappings = db_api.get_bottom_mappings_by_top_id(
|
||||
context, self.volume_id, constants.RT_VOLUME)
|
||||
if not volume_mappings:
|
||||
return utils.format_cinder_error(
|
||||
404, _('Volume %(volume_id)s could not be found.') % {
|
||||
'volume_id': self.volume_id
|
||||
})
|
||||
|
||||
pod_name = volume_mappings[0][0]['pod_name']
|
||||
|
||||
if action_type == 'os-attach':
|
||||
instance_uuid = kw['os-attach'].get('instance_uuid')
|
||||
if instance_uuid is not None:
|
||||
server_mappings = db_api.get_bottom_mappings_by_top_id(
|
||||
context, instance_uuid, constants.RT_SERVER)
|
||||
if not server_mappings:
|
||||
return utils.format_cinder_error(
|
||||
404, _('Server not found'))
|
||||
server_pod_name = server_mappings[0][0]['pod_name']
|
||||
if server_pod_name != pod_name:
|
||||
LOG.error(_LE('Server %(server)s is in pod %(server_pod)s'
|
||||
'and volume %(volume)s is in pod'
|
||||
'%(volume_pod)s, which '
|
||||
'are not the same.'),
|
||||
{'server': instance_uuid,
|
||||
'server_pod': server_pod_name,
|
||||
'volume': self.volume_id,
|
||||
'volume_pod': pod_name})
|
||||
return utils.format_cinder_error(
|
||||
400, _('Server and volume not in the same pod'))
|
||||
|
||||
try:
|
||||
resp, body = action_handle(context, pod_name, kw)
|
||||
pecan.response.status = resp.status_code
|
||||
if not body:
|
||||
return pecan.response
|
||||
else:
|
||||
return body
|
||||
except Exception as e:
|
||||
code = 500
|
||||
message = _('Action %(action)s on volume %(volume_id)s fails') % {
|
||||
'action': action_type,
|
||||
'volume_id': self.volume_id}
|
||||
if hasattr(e, 'code'):
|
||||
code = e.code
|
||||
ex_message = str(e)
|
||||
if ex_message:
|
||||
message = ex_message
|
||||
LOG.error(message)
|
||||
return utils.format_cinder_error(code, message)
|
@ -304,16 +304,16 @@ class NovaResourceHandle(ResourceHandle):
|
||||
|
||||
class CinderResourceHandle(ResourceHandle):
|
||||
service_type = cons.ST_CINDER
|
||||
support_resource = {'volume': GET | ACTION,
|
||||
support_resource = {'volume': LIST | CREATE | DELETE | GET | ACTION,
|
||||
'transfer': CREATE | ACTION}
|
||||
|
||||
def _get_client(self, cxt):
|
||||
cli = c_client.Client('2',
|
||||
auth_token=cxt.auth_token,
|
||||
auth_url=self.auth_url,
|
||||
timeout=cfg.CONF.client.cinder_timeout)
|
||||
cli.set_management_url(
|
||||
cli.client.set_management_url(
|
||||
self.endpoint_url.replace('$(tenant_id)s', cxt.tenant))
|
||||
cli.client.auth_token = cxt.auth_token
|
||||
return cli
|
||||
|
||||
def handle_get(self, cxt, resource, resource_id):
|
||||
|
@ -0,0 +1,285 @@
|
||||
# Copyright (c) 2015 Huawei Tech. Co., Ltd.
|
||||
# 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 mock import patch
|
||||
import pecan
|
||||
import unittest
|
||||
|
||||
from cinderclient.client import HTTPClient
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from tricircle.cinder_apigw.controllers import volume_actions as action
|
||||
from tricircle.common import constants
|
||||
from tricircle.common import context
|
||||
from tricircle.common import exceptions
|
||||
from tricircle.db import api
|
||||
from tricircle.db import core
|
||||
from tricircle.db import models
|
||||
|
||||
|
||||
class FakeResponse(object):
|
||||
def __new__(cls, code=500):
|
||||
cls.status = code
|
||||
cls.status_code = code
|
||||
return super(FakeResponse, cls).__new__(cls)
|
||||
|
||||
|
||||
class VolumeActionTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
core.initialize()
|
||||
core.ModelBase.metadata.create_all(core.get_engine())
|
||||
self.context = context.Context()
|
||||
self.project_id = 'test_project'
|
||||
self.context.tenant = self.project_id
|
||||
self.controller = action.VolumeActionController(self.project_id, '')
|
||||
|
||||
def _prepare_pod(self, bottom_pod_num=1):
|
||||
t_pod = {'pod_id': 't_pod_uuid', 'pod_name': 't_region',
|
||||
'az_name': ''}
|
||||
api.create_pod(self.context, t_pod)
|
||||
b_pods = []
|
||||
if bottom_pod_num == 1:
|
||||
b_pod = {'pod_id': 'b_pod_uuid', 'pod_name': 'b_region',
|
||||
'az_name': 'b_az'}
|
||||
api.create_pod(self.context, b_pod)
|
||||
b_pods.append(b_pod)
|
||||
else:
|
||||
for i in xrange(1, bottom_pod_num + 1):
|
||||
b_pod = {'pod_id': 'b_pod_%d_uuid' % i,
|
||||
'pod_name': 'b_region_%d' % i,
|
||||
'az_name': 'b_az_%d' % i}
|
||||
api.create_pod(self.context, b_pod)
|
||||
b_pods.append(b_pod)
|
||||
return t_pod, b_pods
|
||||
|
||||
def _prepare_pod_service(self, pod_id, service):
|
||||
config_dict = {'service_id': uuidutils.generate_uuid(),
|
||||
'pod_id': pod_id,
|
||||
'service_type': service,
|
||||
'service_url': 'fake_pod_service'}
|
||||
api.create_pod_service_configuration(self.context, config_dict)
|
||||
pass
|
||||
|
||||
def _prepare_volume(self, pod):
|
||||
t_volume_id = uuidutils.generate_uuid()
|
||||
b_volume_id = t_volume_id
|
||||
with self.context.session.begin():
|
||||
core.create_resource(
|
||||
self.context, models.ResourceRouting,
|
||||
{'top_id': t_volume_id, 'bottom_id': b_volume_id,
|
||||
'pod_id': pod['pod_id'], 'project_id': self.project_id,
|
||||
'resource_type': constants.RT_VOLUME})
|
||||
return t_volume_id
|
||||
|
||||
def _prepare_server(self, pod):
|
||||
t_server_id = uuidutils.generate_uuid()
|
||||
b_server_id = t_server_id
|
||||
with self.context.session.begin():
|
||||
core.create_resource(
|
||||
self.context, models.ResourceRouting,
|
||||
{'top_id': t_server_id, 'bottom_id': b_server_id,
|
||||
'pod_id': pod['pod_id'], 'project_id': self.project_id,
|
||||
'resource_type': constants.RT_SERVER})
|
||||
return t_server_id
|
||||
|
||||
@patch.object(pecan, 'response', new=FakeResponse)
|
||||
@patch.object(context, 'extract_context_from_environ')
|
||||
def test_action_not_supported(self, mock_context):
|
||||
mock_context.return_value = self.context
|
||||
|
||||
body = {'unsupported_action': ''}
|
||||
res = self.controller.post(**body)
|
||||
self.assertEqual('Volume action not supported',
|
||||
res['badRequest']['message'])
|
||||
self.assertEqual(400, res['badRequest']['code'])
|
||||
|
||||
@patch.object(pecan, 'response', new=FakeResponse)
|
||||
@patch.object(context, 'extract_context_from_environ')
|
||||
def test_action_volume_not_found(self, mock_context):
|
||||
mock_context.return_value = self.context
|
||||
|
||||
body = {'os-extend': ''}
|
||||
self.controller.volume_id = 'Fake_volume_id'
|
||||
res = self.controller.post(**body)
|
||||
self.assertEqual(
|
||||
'Volume %(volume_id)s could not be found.' % {
|
||||
'volume_id': self.controller.volume_id},
|
||||
res['itemNotFound']['message'])
|
||||
self.assertEqual(404, res['itemNotFound']['code'])
|
||||
|
||||
@patch.object(pecan, 'response', new=FakeResponse)
|
||||
@patch.object(HTTPClient, 'post')
|
||||
@patch.object(context, 'extract_context_from_environ')
|
||||
def test_action_exception(self, mock_context, mock_action):
|
||||
mock_context.return_value = self.context
|
||||
t_pod, b_pods = self._prepare_pod()
|
||||
self._prepare_pod_service(b_pods[0]['pod_id'], constants.ST_CINDER)
|
||||
t_volume_id = self._prepare_volume(b_pods[0])
|
||||
self.controller.volume_id = t_volume_id
|
||||
|
||||
mock_action.side_effect = exceptions.HTTPForbiddenError(
|
||||
msg='Volume operation forbidden')
|
||||
body = {'os-extend': {'new_size': 2}}
|
||||
res = self.controller.post(**body)
|
||||
# this is the message of HTTPForbiddenError exception
|
||||
self.assertEqual('Volume operation forbidden',
|
||||
res['forbidden']['message'])
|
||||
# this is the code of HTTPForbiddenError exception
|
||||
self.assertEqual(403, res['forbidden']['code'])
|
||||
|
||||
mock_action.side_effect = exceptions.ServiceUnavailable
|
||||
body = {'os-extend': {'new_size': 2}}
|
||||
res = self.controller.post(**body)
|
||||
# this is the message of ServiceUnavailable exception
|
||||
self.assertEqual('The service is unavailable',
|
||||
res['internalServerError']['message'])
|
||||
# code is 500 by default
|
||||
self.assertEqual(500, res['internalServerError']['code'])
|
||||
|
||||
mock_action.side_effect = Exception
|
||||
body = {'os-extend': {'new_size': 2}}
|
||||
res = self.controller.post(**body)
|
||||
# use default message if exception's message is empty
|
||||
self.assertEqual('Action os-extend on volume %s fails' % t_volume_id,
|
||||
res['internalServerError']['message'])
|
||||
# code is 500 by default
|
||||
self.assertEqual(500, res['internalServerError']['code'])
|
||||
|
||||
@patch.object(pecan, 'response', new=FakeResponse)
|
||||
@patch.object(HTTPClient, 'post')
|
||||
@patch.object(context, 'extract_context_from_environ')
|
||||
def test_extend_action(self, mock_context, mock_action):
|
||||
mock_context.return_value = self.context
|
||||
mock_action.return_value = (FakeResponse(202), None)
|
||||
t_pod, b_pods = self._prepare_pod()
|
||||
self._prepare_pod_service(b_pods[0]['pod_id'], constants.ST_CINDER)
|
||||
t_volume_id = self._prepare_volume(b_pods[0])
|
||||
self.controller.volume_id = t_volume_id
|
||||
|
||||
body = {'os-extend': {'new_size': 2}}
|
||||
res = self.controller.post(**body)
|
||||
url = '/volumes/%s/action' % t_volume_id
|
||||
mock_action.assert_called_once_with(url, body=body)
|
||||
self.assertEqual(202, res.status)
|
||||
|
||||
@patch.object(pecan, 'response', new=FakeResponse)
|
||||
@patch.object(HTTPClient, 'post')
|
||||
@patch.object(context, 'extract_context_from_environ')
|
||||
def test_attach_action(self, mock_context, mock_action):
|
||||
mock_context.return_value = self.context
|
||||
mock_action.return_value = (FakeResponse(202), None)
|
||||
|
||||
t_pod, b_pods = self._prepare_pod()
|
||||
self._prepare_pod_service(b_pods[0]['pod_id'], constants.ST_CINDER)
|
||||
t_volume_id = self._prepare_volume(b_pods[0])
|
||||
t_server_id = self._prepare_server(b_pods[0])
|
||||
self.controller.volume_id = t_volume_id
|
||||
|
||||
body = {'os-attach': {
|
||||
'instance_uuid': t_server_id,
|
||||
'mountpoint': '/dev/vdc'
|
||||
}}
|
||||
res = self.controller.post(**body)
|
||||
url = '/volumes/%s/action' % t_volume_id
|
||||
mock_action.assert_called_once_with(url, body=body)
|
||||
self.assertEqual(202, res.status)
|
||||
|
||||
@patch.object(pecan, 'response', new=FakeResponse)
|
||||
@patch.object(HTTPClient, 'post')
|
||||
@patch.object(context, 'extract_context_from_environ')
|
||||
def test_reset_status_action(self, mock_context, mock_action):
|
||||
mock_context.return_value = self.context
|
||||
mock_action.return_value = (FakeResponse(202), None)
|
||||
|
||||
t_pod, b_pods = self._prepare_pod()
|
||||
self._prepare_pod_service(b_pods[0]['pod_id'], constants.ST_CINDER)
|
||||
t_volume_id = self._prepare_volume(b_pods[0])
|
||||
self.controller.volume_id = t_volume_id
|
||||
|
||||
body = {"os-reset_status": {
|
||||
"status": "available",
|
||||
"attach_status": "detached",
|
||||
"migration_status": "migrating"
|
||||
}}
|
||||
res = self.controller.post(**body)
|
||||
url = '/volumes/%s/action' % t_volume_id
|
||||
mock_action.assert_called_once_with(url, body=body)
|
||||
self.assertEqual(202, res.status)
|
||||
|
||||
@patch.object(pecan, 'response', new=FakeResponse)
|
||||
@patch.object(HTTPClient, 'post')
|
||||
@patch.object(context, 'extract_context_from_environ')
|
||||
def test_set_image_metadata_action(self, mock_context, mock_action):
|
||||
mock_context.return_value = self.context
|
||||
mock_action.return_value = (FakeResponse(202), None)
|
||||
|
||||
t_pod, b_pods = self._prepare_pod()
|
||||
self._prepare_pod_service(b_pods[0]['pod_id'], constants.ST_CINDER)
|
||||
t_volume_id = self._prepare_volume(b_pods[0])
|
||||
self.controller.volume_id = t_volume_id
|
||||
|
||||
body = {"os-set_image_metadata": {
|
||||
"metadata": {
|
||||
"image_id": "521752a6-acf6-4b2d-bc7a-119f9148cd8c",
|
||||
"image_name": "image",
|
||||
"kernel_id": "155d900f-4e14-4e4c-a73d-069cbf4541e6",
|
||||
"ramdisk_id": "somedisk"
|
||||
}
|
||||
}}
|
||||
res = self.controller.post(**body)
|
||||
url = '/volumes/%s/action' % t_volume_id
|
||||
mock_action.assert_called_once_with(url, body=body)
|
||||
self.assertEqual(202, res.status)
|
||||
|
||||
@patch.object(pecan, 'response', new=FakeResponse)
|
||||
@patch.object(HTTPClient, 'post')
|
||||
@patch.object(context, 'extract_context_from_environ')
|
||||
def test_unset_image_metadata_action(self, mock_context, mock_action):
|
||||
mock_context.return_value = self.context
|
||||
mock_action.return_value = (FakeResponse(202), None)
|
||||
|
||||
t_pod, b_pods = self._prepare_pod()
|
||||
self._prepare_pod_service(b_pods[0]['pod_id'], constants.ST_CINDER)
|
||||
t_volume_id = self._prepare_volume(b_pods[0])
|
||||
self.controller.volume_id = t_volume_id
|
||||
|
||||
body = {"os-unset_image_metadata": {
|
||||
'key': 'image_name'
|
||||
}}
|
||||
res = self.controller.post(**body)
|
||||
url = '/volumes/%s/action' % t_volume_id
|
||||
mock_action.assert_called_once_with(url, body=body)
|
||||
self.assertEqual(202, res.status)
|
||||
|
||||
@patch.object(pecan, 'response', new=FakeResponse)
|
||||
@patch.object(HTTPClient, 'post')
|
||||
@patch.object(context, 'extract_context_from_environ')
|
||||
def test_show_image_metadata_action(self, mock_context, mock_action):
|
||||
mock_context.return_value = self.context
|
||||
mock_action.return_value = (FakeResponse(202), None)
|
||||
|
||||
t_pod, b_pods = self._prepare_pod()
|
||||
self._prepare_pod_service(b_pods[0]['pod_id'], constants.ST_CINDER)
|
||||
t_volume_id = self._prepare_volume(b_pods[0])
|
||||
self.controller.volume_id = t_volume_id
|
||||
|
||||
body = {"os-show_image_metadata": None}
|
||||
res = self.controller.post(**body)
|
||||
url = '/volumes/%s/action' % t_volume_id
|
||||
mock_action.assert_called_once_with(url, body=body)
|
||||
self.assertEqual(202, res.status)
|
||||
|
||||
def tearDown(self):
|
||||
core.ModelBase.metadata.drop_all(core.get_engine())
|
Loading…
x
Reference in New Issue
Block a user