From e11ae2355ec037a6ab99642e8a296943981f2825 Mon Sep 17 00:00:00 2001 From: Ricardo Dias Date: Wed, 11 Apr 2018 16:17:14 +0100 Subject: [PATCH] mgr/dashboard: rbd: major refactoring for using the Task decorator Signed-off-by: Ricardo Dias --- qa/tasks/mgr/dashboard/helper.py | 44 ++-- qa/tasks/mgr/dashboard/test_rbd.py | 119 ++++----- src/pybind/mgr/dashboard/controllers/rbd.py | 274 ++++++++------------ 3 files changed, 189 insertions(+), 248 deletions(-) diff --git a/qa/tasks/mgr/dashboard/helper.py b/qa/tasks/mgr/dashboard/helper.py index 5709c457d40..ecfd697cad5 100644 --- a/qa/tasks/mgr/dashboard/helper.py +++ b/qa/tasks/mgr/dashboard/helper.py @@ -150,17 +150,18 @@ class DashboardTestCase(MgrTestCase): # pylint: disable=too-many-arguments @classmethod - def _task_request(cls, method, url, task_name, task_metadata, data, timeout): + def _task_request(cls, method, url, data, timeout): res = cls._request(url, method, data) - cls._assertIn(cls._resp.status_code, [200, 201, 204]) - cls._assertIsInst(res, dict) - cls._assertIn('status', res) - cls._assertIn(res['status'], ['done', 'executing']) - if res['status'] == 'done': - cls._assertIn('value', res) - log.info("task (%s, %s) finished immediately", task_name, - task_metadata) - return res['value'] + cls._assertIn(cls._resp.status_code, [200, 201, 202, 204, 409]) + + if cls._resp.status_code != 202: + log.info("task finished immediately") + return res + + cls._assertIn('name', res) + cls._assertIn('metadata', res) + task_name = res['name'] + task_metadata = res['metadata'] class Waiter(threading.Thread): def __init__(self, task_name, task_metadata): @@ -196,25 +197,26 @@ class DashboardTestCase(MgrTestCase): .format(task_name, task_metadata)) log.info("task (%s, %s) finished", task_name, task_metadata) if thread.res_task['success']: + if method == 'POST': + cls._resp.status_code = 201 + elif method == 'PUT': + cls._resp.status_code = 200 + elif method == 'DELETE': + cls._resp.status_code = 204 return thread.res_task['ret_value'] raise Exception(thread.res_task['exception']) @classmethod - def _task_post(cls, url, task_name, task_metadata, data=None, - timeout=60): - return cls._task_request('POST', url, task_name, task_metadata, data, - timeout) + def _task_post(cls, url, data=None, timeout=60): + return cls._task_request('POST', url, data, timeout) @classmethod - def _task_delete(cls, url, task_name, task_metadata, data=None, - timeout=60): - return cls._task_request('DELETE', url, task_name, task_metadata, data, - timeout) + def _task_delete(cls, url, timeout=60): + return cls._task_request('DELETE', url, None, timeout) @classmethod - def _task_put(cls, url, task_name, task_metadata, data=None, timeout=60): - return cls._task_request('PUT', url, task_name, task_metadata, data, - timeout) + def _task_put(cls, url, data=None, timeout=60): + return cls._task_request('PUT', url, data, timeout) @classmethod def cookies(cls): diff --git a/qa/tasks/mgr/dashboard/test_rbd.py b/qa/tasks/mgr/dashboard/test_rbd.py index 33342de9949..24095c06d51 100644 --- a/qa/tasks/mgr/dashboard/test_rbd.py +++ b/qa/tasks/mgr/dashboard/test_rbd.py @@ -29,42 +29,30 @@ class RbdTest(DashboardTestCase): def create_image(cls, pool, name, size, **kwargs): data = {'name': name, 'pool_name': pool, 'size': size} data.update(kwargs) - return cls._task_post('/api/rbd', 'rbd/create', - {'pool_name': pool, 'image_name': name}, data) + return cls._task_post('/api/rbd', data) @classmethod def remove_image(cls, pool, image): - return cls._task_delete('/api/rbd/{}/{}'.format(pool, image), - 'rbd/delete', - {'pool_name': pool, 'image_name': image}) + return cls._task_delete('/api/rbd/{}/{}'.format(pool, image)) # pylint: disable=too-many-arguments @classmethod def edit_image(cls, pool, image, name=None, size=None, features=None): - return cls._task_put('/api/rbd/{}/{}'.format(pool, image), 'rbd/edit', - {'pool_name': pool, 'image_name': image}, + return cls._task_put('/api/rbd/{}/{}'.format(pool, image), {'name': name, 'size': size, 'features': features}) @classmethod def create_snapshot(cls, pool, image, snapshot): - data = {'pool_name': pool, 'image_name': image, 'snapshot_name': snapshot} return cls._task_post('/api/rbd/{}/{}/snap'.format(pool, image), - 'rbd/snap/create', data, {'snapshot_name': snapshot}) @classmethod def remove_snapshot(cls, pool, image, snapshot): - return cls._task_delete('/api/rbd//{}/{}/snap/{}'.format(pool, image, snapshot), - 'rbd/snap/delete', - {'pool_name': pool, 'image_name': image, - 'snapshot_name': snapshot}) + return cls._task_delete('/api/rbd//{}/{}/snap/{}'.format(pool, image, snapshot)) @classmethod def update_snapshot(cls, pool, image, snapshot, new_name, is_protected): return cls._task_put('/api/rbd//{}/{}/snap/{}'.format(pool, image, snapshot), - 'rbd/snap/edit', - {'pool_name': pool, 'image_name': image, - 'snapshot_name': snapshot}, {'new_snap_name': new_name, 'is_protected': is_protected}) @classmethod @@ -198,8 +186,8 @@ class RbdTest(DashboardTestCase): def test_create(self): rbd_name = 'test_rbd' - res = self.create_image('rbd', rbd_name, 10240) - self.assertEqual(res, {"success": True}) + self.create_image('rbd', rbd_name, 10240) + self.assertStatus(201) img = self._get('/api/rbd/rbd/test_rbd') self.assertStatus(200) @@ -220,8 +208,8 @@ class RbdTest(DashboardTestCase): self.create_pool('data_pool', 12, 'erasure') rbd_name = 'test_rbd_in_data_pool' - res = self.create_image('rbd', rbd_name, 10240, data_pool='data_pool') - self.assertEqual(res, {"success": True}) + self.create_image('rbd', rbd_name, 10240, data_pool='data_pool') + self.assertStatus(201) img = self._get('/api/rbd/rbd/test_rbd_in_data_pool') self.assertStatus(200) @@ -235,6 +223,7 @@ class RbdTest(DashboardTestCase): 'object-map']) self.remove_image('rbd', rbd_name) + self.assertStatus(204) self._ceph_cmd(['osd', 'pool', 'delete', 'data_pool', 'data_pool', '--yes-i-really-really-mean-it']) @@ -242,9 +231,10 @@ class RbdTest(DashboardTestCase): res = self.create_image('rbd', 'test_rbd_twice', 10240) res = self.create_image('rbd', 'test_rbd_twice', 10240) - self.assertEqual(res, {"success": False, "errno": 17, + self.assertEqual(res, {"errno": 17, "detail": "[errno 17] error creating image"}) self.remove_image('rbd', 'test_rbd_twice') + self.assertStatus(204) def test_snapshots_and_clone_info(self): self.create_snapshot('rbd', 'img1', 'snap1') @@ -279,6 +269,7 @@ class RbdTest(DashboardTestCase): 'fast-diff', 'layering', 'object-map']) self.remove_image('rbd_iscsi', 'img1_clone') + self.assertStatus(204) def test_disk_usage(self): self._rbd_cmd(['bench', '--io-type', 'write', '--io-total', '50M', 'rbd/img2']) @@ -295,105 +286,105 @@ class RbdTest(DashboardTestCase): def test_delete_non_existent_image(self): res = self.remove_image('rbd', 'i_dont_exist') - self.assertEqual(res, {"success": False, "errno": 2, + self.assertEqual(res, {"errno": 2, "detail": "[errno 2] error removing image"}) def test_image_delete(self): - res = self.create_image('rbd', 'delete_me', 2**30) - self.assertTrue(res['success']) - res = self.create_snapshot('rbd', 'delete_me', 'snap1') - self.assertTrue(res['success']) - res = self.create_snapshot('rbd', 'delete_me', 'snap2') - self.assertTrue(res['success']) + self.create_image('rbd', 'delete_me', 2**30) + self.assertStatus(201) + self.create_snapshot('rbd', 'delete_me', 'snap1') + self.assertStatus(201) + self.create_snapshot('rbd', 'delete_me', 'snap2') + self.assertStatus(201) img = self._get('/api/rbd/rbd/delete_me') self.assertStatus(200) self._validate_image(img, name='delete_me', size=2**30) self.assertEqual(len(img['snapshots']), 2) - res = self.remove_snapshot('rbd', 'delete_me', 'snap1') - self.assertTrue(res['success']) - res = self.remove_snapshot('rbd', 'delete_me', 'snap2') - self.assertTrue(res['success']) + self.remove_snapshot('rbd', 'delete_me', 'snap1') + self.assertStatus(204) + self.remove_snapshot('rbd', 'delete_me', 'snap2') + self.assertStatus(204) img = self._get('/api/rbd/rbd/delete_me') self.assertStatus(200) self._validate_image(img, name='delete_me', size=2**30) self.assertEqual(len(img['snapshots']), 0) - res = self.remove_image('rbd', 'delete_me') - self.assertTrue(res['success']) + self.remove_image('rbd', 'delete_me') + self.assertStatus(204) def test_image_rename(self): - res = self.create_image('rbd', 'edit_img', 2**30) - self.assertTrue(res['success']) + self.create_image('rbd', 'edit_img', 2**30) + self.assertStatus(201) self._get('/api/rbd/rbd/edit_img') self.assertStatus(200) - res = self.edit_image('rbd', 'edit_img', 'new_edit_img') - self.assertTrue(res['success']) + self.edit_image('rbd', 'edit_img', 'new_edit_img') + self.assertStatus(200) self._get('/api/rbd/rbd/edit_img') self.assertStatus(404) self._get('/api/rbd/rbd/new_edit_img') self.assertStatus(200) - res = self.remove_image('rbd', 'new_edit_img') - self.assertTrue(res['success']) + self.remove_image('rbd', 'new_edit_img') + self.assertStatus(204) def test_image_resize(self): - res = self.create_image('rbd', 'edit_img', 2**30) - self.assertTrue(res['success']) + self.create_image('rbd', 'edit_img', 2**30) + self.assertStatus(201) img = self._get('/api/rbd/rbd/edit_img') self.assertStatus(200) self._validate_image(img, size=2**30) - res = self.edit_image('rbd', 'edit_img', size=2*2**30) - self.assertTrue(res['success']) + self.edit_image('rbd', 'edit_img', size=2*2**30) + self.assertStatus(200) img = self._get('/api/rbd/rbd/edit_img') self.assertStatus(200) self._validate_image(img, size=2*2**30) - res = self.remove_image('rbd', 'edit_img') - self.assertTrue(res['success']) + self.remove_image('rbd', 'edit_img') + self.assertStatus(204) def test_image_change_features(self): - res = self.create_image('rbd', 'edit_img', 2**30, features=["layering"]) - self.assertTrue(res['success']) + self.create_image('rbd', 'edit_img', 2**30, features=["layering"]) + self.assertStatus(201) img = self._get('/api/rbd/rbd/edit_img') self.assertStatus(200) self._validate_image(img, features_name=["layering"]) - res = self.edit_image('rbd', 'edit_img', - features=["fast-diff", "object-map", "exclusive-lock"]) + self.edit_image('rbd', 'edit_img', + features=["fast-diff", "object-map", "exclusive-lock"]) img = self._get('/api/rbd/rbd/edit_img') self.assertStatus(200) self._validate_image(img, features_name=['exclusive-lock', 'fast-diff', 'layering', 'object-map']) - res = self.edit_image('rbd', 'edit_img', - features=["journaling", "exclusive-lock"]) + self.edit_image('rbd', 'edit_img', + features=["journaling", "exclusive-lock"]) img = self._get('/api/rbd/rbd/edit_img') self.assertStatus(200) self._validate_image(img, features_name=['exclusive-lock', 'journaling', 'layering']) - res = self.remove_image('rbd', 'edit_img') - self.assertTrue(res['success']) + self.remove_image('rbd', 'edit_img') + self.assertStatus(204) def test_update_snapshot(self): - res = self.create_snapshot('rbd', 'img1', 'snap5') - self.assertTrue(res['success']) + self.create_snapshot('rbd', 'img1', 'snap5') + self.assertStatus(201) img = self._get('/api/rbd/rbd/img1') self._validate_snapshot_list(img['snapshots'], 'snap5', is_protected=False) - res = self.update_snapshot('rbd', 'img1', 'snap5', 'snap6', None) - self.assertTrue(res['success']) + self.update_snapshot('rbd', 'img1', 'snap5', 'snap6', None) + self.assertStatus(200) img = self._get('/api/rbd/rbd/img1') self._validate_snapshot_list(img['snapshots'], 'snap6', is_protected=False) - res = self.update_snapshot('rbd', 'img1', 'snap6', None, True) - self.assertTrue(res['success']) + self.update_snapshot('rbd', 'img1', 'snap6', None, True) + self.assertStatus(200) img = self._get('/api/rbd/rbd/img1') self._validate_snapshot_list(img['snapshots'], 'snap6', is_protected=True) - res = self.update_snapshot('rbd', 'img1', 'snap6', 'snap5', False) - self.assertTrue(res['success']) + self.update_snapshot('rbd', 'img1', 'snap6', 'snap5', False) + self.assertStatus(200) img = self._get('/api/rbd/rbd/img1') self._validate_snapshot_list(img['snapshots'], 'snap5', is_protected=False) - res = self.remove_snapshot('rbd', 'img1', 'snap5') - self.assertTrue(res['success']) + self.remove_snapshot('rbd', 'img1', 'snap5') + self.assertStatus(204) diff --git a/src/pybind/mgr/dashboard/controllers/rbd.py b/src/pybind/mgr/dashboard/controllers/rbd.py index f082b980f05..2da342308fc 100644 --- a/src/pybind/mgr/dashboard/controllers/rbd.py +++ b/src/pybind/mgr/dashboard/controllers/rbd.py @@ -1,15 +1,41 @@ # -*- coding: utf-8 -*- -# pylint: disable=too-many-arguments,too-many-locals +# pylint: disable=too-many-arguments,too-many-locals,unused-argument from __future__ import absolute_import import math import cherrypy import rbd -from . import ApiController, AuthRequired, RESTController +from . import ApiController, AuthRequired, RESTController, Task from .. import mgr from ..services.ceph_service import CephService -from ..tools import ViewCache, TaskManager +from ..tools import ViewCache + + +# pylint: disable=inconsistent-return-statements +def _rbd_exception_handler(ex): + if isinstance(ex, rbd.OSError): + cherrypy.response.status = 409 + return {'detail': str(ex), 'errno': ex.errno} + raise ex + + +def RbdTask(name, metadata, wait_for): + return Task("rbd/{}".format(name), metadata, wait_for, + _rbd_exception_handler) + + +def _rbd_call(pool_name, func, *args, **kwargs): + with mgr.rados.open_ioctx(pool_name) as ioctx: + func(ioctx, *args, **kwargs) + + +def _rbd_image_call(pool_name, image_name, func, *args, **kwargs): + def _ioctx_func(ioctx, image_name, func, *args, **kwargs): + with rbd.Image(ioctx, image_name) as img: + func(ioctx, img, *args, **kwargs) + + return _rbd_call(pool_name, _ioctx_func, image_name, func, *args, **kwargs) @ApiController('rbd') @@ -71,6 +97,25 @@ class Rbd(RESTController): res = key | res return res + @classmethod + def _sort_features(cls, features, enable=True): + """ + Sorts image features according to feature dependencies: + + object-map depends on exclusive-lock + journaling depends on exclusive-lock + fast-diff depends on object-map + """ + ORDER = ['exclusive-lock', 'journaling', 'object-map', 'fast-diff'] + + def key_func(feat): + try: + return ORDER.index(feat) + except ValueError: + return id(feat) + + features.sort(key=key_func, reverse=not enable) + @classmethod def _rbd_disk_usage(cls, image, snaps): class DUCallback(object): @@ -204,176 +249,93 @@ class Rbd(RESTController): except rbd.ImageNotFound: raise cherrypy.HTTPError(404) - @classmethod - def _create_image(cls, name, pool_name, size, obj_size=None, features=None, - stripe_unit=None, stripe_count=None, data_pool=None): - # pylint: disable=too-many-locals - rbd_inst = rbd.RBD() - - # Set order - order = None - if obj_size and obj_size > 0: - order = int(round(math.log(float(obj_size), 2))) - - # Set features - feature_bitmask = cls._format_features(features) - - ioctx = mgr.rados.open_ioctx(pool_name) - - try: - rbd_inst.create(ioctx, name, size, order=order, old_format=False, - features=feature_bitmask, stripe_unit=stripe_unit, - stripe_count=stripe_count, data_pool=data_pool) - except rbd.OSError as e: - return {'success': False, 'detail': str(e), 'errno': e.errno} - return {'success': True} - + @RbdTask('create', + {'pool_name': '{pool_name}', 'image_name': '{name}'}, 2.0) @RESTController.args_from_json def create(self, name, pool_name, size, obj_size=None, features=None, stripe_unit=None, stripe_count=None, data_pool=None): - task = TaskManager.run('rbd/create', - {'pool_name': pool_name, 'image_name': name}, - self._create_image, - [name, pool_name, size, obj_size, features, - stripe_unit, stripe_count, data_pool]) - status, value = task.wait(1.0) - return {'status': status, 'value': value} - - @classmethod - def _remove_image(cls, pool_name, image_name): - rbd_inst = rbd.RBD() - ioctx = mgr.rados.open_ioctx(pool_name) - try: - rbd_inst.remove(ioctx, image_name) - except rbd.OSError as e: - return {'success': False, 'detail': str(e), 'errno': e.errno} - return {'success': True} - - def delete(self, pool_name, image_name): - task = TaskManager.run('rbd/delete', - {'pool_name': pool_name, 'image_name': image_name}, - self._remove_image, [pool_name, image_name]) - status, value = task.wait(2.0) - cherrypy.response.status = 200 - return {'status': status, 'value': value} + def _create(ioctx): + rbd_inst = rbd.RBD() - @classmethod - def _sort_features(cls, features, enable=True): - """ - Sorts image features according to feature dependencies: + # Set order + l_order = None + if obj_size and obj_size > 0: + l_order = int(round(math.log(float(obj_size), 2))) - object-map depends on exclusive-lock - journaling depends on exclusive-lock - fast-diff depends on object-map - """ - ORDER = ['exclusive-lock', 'journaling', 'object-map', 'fast-diff'] + # Set features + feature_bitmask = self._format_features(features) - def key_func(feat): - try: - return ORDER.index(feat) - except ValueError: - return id(feat) + rbd_inst.create(ioctx, name, size, order=l_order, old_format=False, + features=feature_bitmask, stripe_unit=stripe_unit, + stripe_count=stripe_count, data_pool=data_pool) - features.sort(key=key_func, reverse=not enable) + return _rbd_call(pool_name, _create) - @classmethod - def _edit_image(cls, pool_name, image_name, name, size, features): + @RbdTask('delete', ['{pool_name}', '{image_name}'], 2.0) + def delete(self, pool_name, image_name): rbd_inst = rbd.RBD() - ioctx = mgr.rados.open_ioctx(pool_name) - image = rbd.Image(ioctx, image_name) + return _rbd_call(pool_name, rbd_inst.remove, image_name) - # check rename image - if name and name != image_name: - try: + @RbdTask('edit', ['{pool_name}', '{image_name}'], 4.0) + @RESTController.args_from_json + def set(self, pool_name, image_name, name=None, size=None, features=None): + def _edit(ioctx, image): + rbd_inst = rbd.RBD() + # check rename image + if name and name != image_name: rbd_inst.rename(ioctx, image_name, name) - except rbd.OSError as e: - return {'success': False, 'detail': str(e), 'errno': e.errno} - # check resize - if size and size != image.size(): - try: + # check resize + if size and size != image.size(): image.resize(size) - except rbd.OSError as e: - return {'success': False, 'detail': str(e), 'errno': e.errno} - - # check enable/disable features - if features is not None: - curr_features = cls._format_bitmask(image.features()) - # check disabled features - cls._sort_features(curr_features, enable=False) - for feature in curr_features: - if feature not in features and feature in cls.ALLOW_DISABLE_FEATURES: - f_bitmask = cls._format_features([feature]) - image.update_features(f_bitmask, False) - # check enabled features - cls._sort_features(features) - for feature in features: - if feature not in curr_features and feature in cls.ALLOW_ENABLE_FEATURES: - f_bitmask = cls._format_features([feature]) - image.update_features(f_bitmask, True) - - return {'success': True} - @RESTController.args_from_json - def set(self, pool_name, image_name, name=None, size=None, features=None): - task = TaskManager.run('rbd/edit', - {'pool_name': pool_name, 'image_name': image_name}, - self._edit_image, - [pool_name, image_name, name, size, features]) - status, value = task.wait(4.0) - return {'status': status, 'value': value} + # check enable/disable features + if features is not None: + curr_features = self._format_bitmask(image.features()) + # check disabled features + self._sort_features(curr_features, enable=False) + for feature in curr_features: + if feature not in features and feature in self.ALLOW_DISABLE_FEATURES: + f_bitmask = self._format_features([feature]) + image.update_features(f_bitmask, False) + # check enabled features + self._sort_features(features) + for feature in features: + if feature not in curr_features and feature in self.ALLOW_ENABLE_FEATURES: + f_bitmask = self._format_features([feature]) + image.update_features(f_bitmask, True) + + return _rbd_image_call(pool_name, image_name, _edit) @ApiController('rbd/:pool_name/:image_name/snap') class RbdSnapshot(RESTController): - @classmethod - def _create_snapshot(cls, pool_name, image_name, snapshot_name): - ioctx = mgr.rados.open_ioctx(pool_name) - img = rbd.Image(ioctx, image_name) - try: - img.create_snap(snapshot_name) - except rbd.OSError as e: - return {'success': False, 'detail': str(e), 'errno': e.errno} - return {'success': True} - + @RbdTask('snap/create', + ['{pool_name}', '{image_name}', '{snapshot_name}'], 2.0) @RESTController.args_from_json def create(self, pool_name, image_name, snapshot_name): - task = TaskManager.run('rbd/snap/create', - {'pool_name': pool_name, 'image_name': image_name, - 'snapshot_name': snapshot_name}, - self._create_snapshot, - [pool_name, image_name, snapshot_name]) - status, value = task.wait(1.0) - return {'status': status, 'value': value} + def _create_snapshot(ioctx, img, snapshot_name): + img.create_snap(snapshot_name) - @classmethod - def _remove_snapshot(cls, pool_name, image_name, snapshot_name): - ioctx = mgr.rados.open_ioctx(pool_name) - img = rbd.Image(ioctx, image_name) - try: - img.remove_snap(snapshot_name) - except rbd.OSError as e: - return {'success': False, 'detail': str(e), 'errno': e.errno} - return {'success': True} + return _rbd_image_call(pool_name, image_name, _create_snapshot, + snapshot_name) + @RbdTask('snap/delete', + ['{pool_name}', '{image_name}', '{snapshot_name}'], 2.0) def delete(self, pool_name, image_name, snapshot_name): - task = TaskManager.run('rbd/snap/delete', - {'pool_name': pool_name, - 'image_name': image_name, - 'snapshot_name': snapshot_name}, - self._remove_snapshot, - [pool_name, image_name, snapshot_name]) - status, value = task.wait(1.0) - cherrypy.response.status = 200 - return {'status': status, 'value': value} + def _remove_snapshot(ioctx, img, snapshot_name): + img.remove_snap(snapshot_name) - @classmethod - def _edit_snapshot(cls, pool_name, image_name, snapshot_name, - new_snap_name, is_protected): - ioctx = mgr.rados.open_ioctx(pool_name) - img = rbd.Image(ioctx, image_name) - try: + return _rbd_image_call(pool_name, image_name, _remove_snapshot, + snapshot_name) + + @RbdTask('snap/edit', + ['{pool_name}', '{image_name}', '{snapshot_name}'], 4.0) + @RESTController.args_from_json + def set(self, pool_name, image_name, snapshot_name, new_snap_name=None, + is_protected=None): + def _edit(ioctx, img, snapshot_name): if new_snap_name and new_snap_name != snapshot_name: img.rename_snap(snapshot_name, new_snap_name) snapshot_name = new_snap_name @@ -383,19 +345,5 @@ class RbdSnapshot(RESTController): img.protect_snap(snapshot_name) else: img.unprotect_snap(snapshot_name) - except rbd.OSError as e: - return {'success': False, 'detail': str(e), 'errno': e.errno} - return {'success': True} - @RESTController.args_from_json - def set(self, pool_name, image_name, snapshot_name, new_snap_name=None, - is_protected=None): - task = TaskManager.run('rbd/snap/edit', - {'pool_name': pool_name, - 'image_name': image_name, - 'snapshot_name': snapshot_name}, - self._edit_snapshot, - [pool_name, image_name, snapshot_name, - new_snap_name, is_protected]) - status, value = task.wait(1.0) - return {'status': status, 'value': value} + return _rbd_image_call(pool_name, image_name, _edit, snapshot_name) -- 2.39.5