]> git.proxmox.com Git - ceph.git/blame - ceph/src/pybind/mgr/dashboard/controllers/rbd.py
update ceph source to reef 18.1.2
[ceph.git] / ceph / src / pybind / mgr / dashboard / controllers / rbd.py
CommitLineData
11fdf7f2
TL
1# -*- coding: utf-8 -*-
2# pylint: disable=unused-argument
3# pylint: disable=too-many-statements,too-many-branches
11fdf7f2 4
1911f103 5import logging
11fdf7f2 6import math
11fdf7f2 7from datetime import datetime
f67539c2 8from functools import partial
11fdf7f2 9
2a845540 10import cherrypy
11fdf7f2
TL
11import rbd
12
11fdf7f2 13from .. import mgr
9f95a23c 14from ..exceptions import DashboardException
11fdf7f2
TL
15from ..security import Scope
16from ..services.ceph_service import CephService
f67539c2 17from ..services.exception import handle_rados_error, handle_rbd_error, serialize_dashboard_exception
2a845540 18from ..services.rbd import MIRROR_IMAGE_MODE, RbdConfiguration, \
1e59de90
TL
19 RbdImageMetadataService, RbdMirroringService, RbdService, \
20 RbdSnapshotService, format_bitmask, format_features, get_image_spec, \
21 parse_image_spec, rbd_call, rbd_image_call
11fdf7f2 22from ..tools import ViewCache, str_to_bool
2a845540
TL
23from . import APIDoc, APIRouter, BaseController, CreatePermission, \
24 DeletePermission, Endpoint, EndpointDoc, ReadPermission, RESTController, \
25 Task, UIRouter, UpdatePermission, allow_empty_body
26from ._version import APIVersion
11fdf7f2 27
1911f103
TL
28logger = logging.getLogger(__name__)
29
f67539c2 30RBD_SCHEMA = ([{
f67539c2
TL
31 "value": ([str], ''),
32 "pool_name": (str, 'pool name')
33}])
34
35RBD_TRASH_SCHEMA = [{
36 "status": (int, ''),
37 "value": ([str], ''),
38 "pool_name": (str, 'pool name')
39}]
40
11fdf7f2
TL
41
42# pylint: disable=not-callable
9f95a23c 43def RbdTask(name, metadata, wait_for): # noqa: N802
11fdf7f2
TL
44 def composed_decorator(func):
45 func = handle_rados_error('pool')(func)
46 func = handle_rbd_error()(func)
47 return Task("rbd/{}".format(name), metadata, wait_for,
48 partial(serialize_dashboard_exception, include_http_status=True))(func)
49 return composed_decorator
50
51
11fdf7f2
TL
52def _sort_features(features, enable=True):
53 """
54 Sorts image features according to feature dependencies:
55
56 object-map depends on exclusive-lock
57 journaling depends on exclusive-lock
58 fast-diff depends on object-map
59 """
9f95a23c 60 ORDER = ['exclusive-lock', 'journaling', 'object-map', 'fast-diff'] # noqa: N806
11fdf7f2
TL
61
62 def key_func(feat):
63 try:
64 return ORDER.index(feat)
65 except ValueError:
66 return id(feat)
67
68 features.sort(key=key_func, reverse=not enable)
69
70
a4b75251
TL
71@APIRouter('/block/image', Scope.RBD_IMAGE)
72@APIDoc("RBD Management API", "Rbd")
11fdf7f2
TL
73class Rbd(RESTController):
74
11fdf7f2
TL
75 # set of image features that can be enable on existing images
76 ALLOW_ENABLE_FEATURES = {"exclusive-lock", "object-map", "fast-diff", "journaling"}
77
78 # set of image features that can be disabled on existing images
79 ALLOW_DISABLE_FEATURES = {"exclusive-lock", "object-map", "fast-diff", "deep-flatten",
80 "journaling"}
81
2a845540
TL
82 DEFAULT_LIMIT = 5
83
84 def _rbd_list(self, pool_name=None, offset=0, limit=DEFAULT_LIMIT, search='', sort=''):
11fdf7f2
TL
85 if pool_name:
86 pools = [pool_name]
87 else:
88 pools = [p['pool_name'] for p in CephService.get_pool_list('rbd')]
89
2a845540
TL
90 images, num_total_images = RbdService.rbd_pool_list(
91 pools, offset=offset, limit=limit, search=search, sort=sort)
92 cherrypy.response.headers['X-Total-Count'] = num_total_images
93 pool_result = {}
94 for i, image in enumerate(images):
95 pool = image['pool_name']
96 if pool not in pool_result:
97 pool_result[pool] = {'value': [], 'pool_name': image['pool_name']}
98 pool_result[pool]['value'].append(image)
99
100 images[i]['configuration'] = RbdConfiguration(
101 pool, image['namespace'], image['name']).list()
1e59de90
TL
102 images[i]['metadata'] = rbd_image_call(
103 pool, image['namespace'], image['name'],
104 lambda ioctx, image: RbdImageMetadataService(image).list())
105
2a845540 106 return list(pool_result.values())
11fdf7f2
TL
107
108 @handle_rbd_error()
109 @handle_rados_error('pool')
f67539c2
TL
110 @EndpointDoc("Display Rbd Images",
111 parameters={
112 'pool_name': (str, 'Pool Name'),
2a845540
TL
113 'limit': (int, 'limit'),
114 'offset': (int, 'offset'),
f67539c2
TL
115 },
116 responses={200: RBD_SCHEMA})
2a845540
TL
117 @RESTController.MethodMap(version=APIVersion(2, 0)) # type: ignore
118 def list(self, pool_name=None, offset: int = 0, limit: int = DEFAULT_LIMIT,
119 search: str = '', sort: str = ''):
39ae355f
TL
120 return self._rbd_list(pool_name, offset=int(offset), limit=int(limit),
121 search=search, sort=sort)
11fdf7f2
TL
122
123 @handle_rbd_error()
124 @handle_rados_error('pool')
9f95a23c
TL
125 def get(self, image_spec):
126 return RbdService.get_image(image_spec)
11fdf7f2
TL
127
128 @RbdTask('create',
9f95a23c 129 {'pool_name': '{pool_name}', 'namespace': '{namespace}', 'image_name': '{name}'}, 2.0)
2a845540
TL
130 def create(self, name, pool_name, size, namespace=None, schedule_interval='',
131 obj_size=None, features=None, stripe_unit=None, stripe_count=None,
1e59de90
TL
132 data_pool=None, configuration=None, metadata=None,
133 mirror_mode=None):
11fdf7f2
TL
134
135 size = int(size)
136
137 def _create(ioctx):
138 rbd_inst = rbd.RBD()
139
140 # Set order
141 l_order = None
142 if obj_size and obj_size > 0:
143 l_order = int(round(math.log(float(obj_size), 2)))
144
145 # Set features
146 feature_bitmask = format_features(features)
147
148 rbd_inst.create(ioctx, name, size, order=l_order, old_format=False,
149 features=feature_bitmask, stripe_unit=stripe_unit,
150 stripe_count=stripe_count, data_pool=data_pool)
9f95a23c
TL
151 RbdConfiguration(pool_ioctx=ioctx, namespace=namespace,
152 image_name=name).set_configuration(configuration)
1e59de90
TL
153 if metadata:
154 with rbd.Image(ioctx, name) as image:
155 RbdImageMetadataService(image).set_metadata(metadata)
9f95a23c
TL
156
157 rbd_call(pool_name, namespace, _create)
1e59de90 158
2a845540
TL
159 if mirror_mode:
160 RbdMirroringService.enable_image(name, pool_name, namespace,
161 MIRROR_IMAGE_MODE[mirror_mode])
162
163 if schedule_interval:
164 image_spec = get_image_spec(pool_name, namespace, name)
165 RbdMirroringService.snapshot_schedule_add(image_spec, schedule_interval)
9f95a23c
TL
166
167 @RbdTask('delete', ['{image_spec}'], 2.0)
168 def delete(self, image_spec):
169 pool_name, namespace, image_name = parse_image_spec(image_spec)
11fdf7f2 170
9f95a23c
TL
171 image = RbdService.get_image(image_spec)
172 snapshots = image['snapshots']
173 for snap in snapshots:
174 RbdSnapshotService.remove_snapshot(image_spec, snap['name'], snap['is_protected'])
11fdf7f2 175
11fdf7f2 176 rbd_inst = rbd.RBD()
9f95a23c
TL
177 return rbd_call(pool_name, namespace, rbd_inst.remove, image_name)
178
179 @RbdTask('edit', ['{image_spec}', '{name}'], 4.0)
2a845540 180 def set(self, image_spec, name=None, size=None, features=None,
1e59de90
TL
181 configuration=None, metadata=None, enable_mirror=None, primary=None,
182 force=False, resync=False, mirror_mode=None, schedule_interval='',
2a845540
TL
183 remove_scheduling=False):
184
9f95a23c 185 pool_name, namespace, image_name = parse_image_spec(image_spec)
11fdf7f2 186
11fdf7f2
TL
187 def _edit(ioctx, image):
188 rbd_inst = rbd.RBD()
189 # check rename image
190 if name and name != image_name:
191 rbd_inst.rename(ioctx, image_name, name)
192
193 # check resize
194 if size and size != image.size():
195 image.resize(size)
196
39ae355f
TL
197 mirror_image_info = image.mirror_image_get_info()
198 if enable_mirror and mirror_image_info['state'] == rbd.RBD_MIRROR_IMAGE_DISABLED:
199 RbdMirroringService.enable_image(
200 image_name, pool_name, namespace,
201 MIRROR_IMAGE_MODE[mirror_mode])
202 elif (enable_mirror is False
203 and mirror_image_info['state'] == rbd.RBD_MIRROR_IMAGE_ENABLED):
204 RbdMirroringService.disable_image(
205 image_name, pool_name, namespace)
206
11fdf7f2
TL
207 # check enable/disable features
208 if features is not None:
209 curr_features = format_bitmask(image.features())
210 # check disabled features
211 _sort_features(curr_features, enable=False)
212 for feature in curr_features:
1e59de90
TL
213 if (feature not in features
214 and feature in self.ALLOW_DISABLE_FEATURES
215 and feature in format_bitmask(image.features())):
11fdf7f2
TL
216 f_bitmask = format_features([feature])
217 image.update_features(f_bitmask, False)
218 # check enabled features
219 _sort_features(features)
220 for feature in features:
1e59de90
TL
221 if (feature not in curr_features
222 and feature in self.ALLOW_ENABLE_FEATURES
223 and feature not in format_bitmask(image.features())):
11fdf7f2
TL
224 f_bitmask = format_features([feature])
225 image.update_features(f_bitmask, True)
226
227 RbdConfiguration(pool_ioctx=ioctx, image_name=image_name).set_configuration(
228 configuration)
1e59de90
TL
229 if metadata:
230 RbdImageMetadataService(image).set_metadata(metadata)
11fdf7f2 231
2a845540
TL
232 if primary and not mirror_image_info['primary']:
233 RbdMirroringService.promote_image(
1e59de90 234 image_name, pool_name, namespace, force)
2a845540
TL
235 elif primary is False and mirror_image_info['primary']:
236 RbdMirroringService.demote_image(
237 image_name, pool_name, namespace)
238
239 if resync:
240 RbdMirroringService.resync_image(image_name, pool_name, namespace)
241
242 if schedule_interval:
243 RbdMirroringService.snapshot_schedule_add(image_spec, schedule_interval)
244
245 if remove_scheduling:
246 RbdMirroringService.snapshot_schedule_remove(image_spec)
247
9f95a23c 248 return rbd_image_call(pool_name, namespace, image_name, _edit)
11fdf7f2
TL
249
250 @RbdTask('copy',
9f95a23c 251 {'src_image_spec': '{image_spec}',
11fdf7f2 252 'dest_pool_name': '{dest_pool_name}',
9f95a23c 253 'dest_namespace': '{dest_namespace}',
11fdf7f2
TL
254 'dest_image_name': '{dest_image_name}'}, 2.0)
255 @RESTController.Resource('POST')
f91f0fd5 256 @allow_empty_body
9f95a23c
TL
257 def copy(self, image_spec, dest_pool_name, dest_namespace, dest_image_name,
258 snapshot_name=None, obj_size=None, features=None,
1e59de90
TL
259 stripe_unit=None, stripe_count=None, data_pool=None,
260 configuration=None, metadata=None):
9f95a23c 261 pool_name, namespace, image_name = parse_image_spec(image_spec)
11fdf7f2
TL
262
263 def _src_copy(s_ioctx, s_img):
264 def _copy(d_ioctx):
265 # Set order
266 l_order = None
267 if obj_size and obj_size > 0:
268 l_order = int(round(math.log(float(obj_size), 2)))
269
270 # Set features
271 feature_bitmask = format_features(features)
272
273 if snapshot_name:
274 s_img.set_snap(snapshot_name)
275
276 s_img.copy(d_ioctx, dest_image_name, feature_bitmask, l_order,
277 stripe_unit, stripe_count, data_pool)
278 RbdConfiguration(pool_ioctx=d_ioctx, image_name=dest_image_name).set_configuration(
279 configuration)
1e59de90
TL
280 if metadata:
281 with rbd.Image(d_ioctx, dest_image_name) as image:
282 RbdImageMetadataService(image).set_metadata(metadata)
11fdf7f2 283
9f95a23c 284 return rbd_call(dest_pool_name, dest_namespace, _copy)
11fdf7f2 285
9f95a23c 286 return rbd_image_call(pool_name, namespace, image_name, _src_copy)
11fdf7f2 287
9f95a23c 288 @RbdTask('flatten', ['{image_spec}'], 2.0)
11fdf7f2
TL
289 @RESTController.Resource('POST')
290 @UpdatePermission
f91f0fd5 291 @allow_empty_body
9f95a23c 292 def flatten(self, image_spec):
11fdf7f2
TL
293
294 def _flatten(ioctx, image):
295 image.flatten()
296
9f95a23c
TL
297 pool_name, namespace, image_name = parse_image_spec(image_spec)
298 return rbd_image_call(pool_name, namespace, image_name, _flatten)
11fdf7f2
TL
299
300 @RESTController.Collection('GET')
301 def default_features(self):
302 rbd_default_features = mgr.get('config')['rbd_default_features']
303 return format_bitmask(int(rbd_default_features))
304
f67539c2
TL
305 @RESTController.Collection('GET')
306 def clone_format_version(self):
307 """Return the RBD clone format version.
308 """
309 rbd_default_clone_format = mgr.get('config')['rbd_default_clone_format']
310 if rbd_default_clone_format != 'auto':
311 return int(rbd_default_clone_format)
312 osd_map = mgr.get_osdmap().dump()
313 min_compat_client = osd_map.get('min_compat_client', '')
314 require_min_compat_client = osd_map.get('require_min_compat_client', '')
315 if max(min_compat_client, require_min_compat_client) < 'mimic':
316 return 1
317
318 return 2
319
9f95a23c 320 @RbdTask('trash/move', ['{image_spec}'], 2.0)
11fdf7f2 321 @RESTController.Resource('POST')
f91f0fd5 322 @allow_empty_body
9f95a23c 323 def move_trash(self, image_spec, delay=0):
11fdf7f2
TL
324 """Move an image to the trash.
325 Images, even ones actively in-use by clones,
326 can be moved to the trash and deleted at a later time.
327 """
9f95a23c 328 pool_name, namespace, image_name = parse_image_spec(image_spec)
11fdf7f2 329 rbd_inst = rbd.RBD()
9f95a23c 330 return rbd_call(pool_name, namespace, rbd_inst.trash_move, image_name, delay)
11fdf7f2
TL
331
332
2a845540
TL
333@UIRouter('/block/rbd')
334class RbdStatus(BaseController):
335 @EndpointDoc("Display RBD Image feature status")
336 @Endpoint()
337 @ReadPermission
338 def status(self):
339 status = {'available': True, 'message': None}
340 if not CephService.get_pool_list('rbd'):
341 status['available'] = False
342 status['message'] = 'No RBD pools in the cluster. Please create a pool '\
343 'with the "rbd" application label.' # type: ignore
344 return status
345
346
a4b75251
TL
347@APIRouter('/block/image/{image_spec}/snap', Scope.RBD_IMAGE)
348@APIDoc("RBD Snapshot Management API", "RbdSnapshot")
11fdf7f2
TL
349class RbdSnapshot(RESTController):
350
351 RESOURCE_ID = "snapshot_name"
352
353 @RbdTask('snap/create',
1e59de90
TL
354 ['{image_spec}', '{snapshot_name}', '{mirrorImageSnapshot}'], 2.0)
355 def create(self, image_spec, snapshot_name, mirrorImageSnapshot):
9f95a23c
TL
356 pool_name, namespace, image_name = parse_image_spec(image_spec)
357
11fdf7f2 358 def _create_snapshot(ioctx, img, snapshot_name):
2a845540
TL
359 mirror_info = img.mirror_image_get_info()
360 mirror_mode = img.mirror_image_get_mode()
1e59de90 361 if (mirror_info['state'] == rbd.RBD_MIRROR_IMAGE_ENABLED and mirror_mode == rbd.RBD_MIRROR_IMAGE_MODE_SNAPSHOT) and mirrorImageSnapshot: # noqa E501 #pylint: disable=line-too-long
2a845540
TL
362 img.mirror_image_create_snapshot()
363 else:
364 img.create_snap(snapshot_name)
11fdf7f2 365
9f95a23c
TL
366 return rbd_image_call(pool_name, namespace, image_name, _create_snapshot,
367 snapshot_name)
11fdf7f2
TL
368
369 @RbdTask('snap/delete',
9f95a23c
TL
370 ['{image_spec}', '{snapshot_name}'], 2.0)
371 def delete(self, image_spec, snapshot_name):
372 return RbdSnapshotService.remove_snapshot(image_spec, snapshot_name)
11fdf7f2
TL
373
374 @RbdTask('snap/edit',
9f95a23c
TL
375 ['{image_spec}', '{snapshot_name}'], 4.0)
376 def set(self, image_spec, snapshot_name, new_snap_name=None,
11fdf7f2
TL
377 is_protected=None):
378 def _edit(ioctx, img, snapshot_name):
379 if new_snap_name and new_snap_name != snapshot_name:
380 img.rename_snap(snapshot_name, new_snap_name)
381 snapshot_name = new_snap_name
382 if is_protected is not None and \
383 is_protected != img.is_protected_snap(snapshot_name):
384 if is_protected:
385 img.protect_snap(snapshot_name)
386 else:
387 img.unprotect_snap(snapshot_name)
388
9f95a23c
TL
389 pool_name, namespace, image_name = parse_image_spec(image_spec)
390 return rbd_image_call(pool_name, namespace, image_name, _edit, snapshot_name)
11fdf7f2
TL
391
392 @RbdTask('snap/rollback',
9f95a23c 393 ['{image_spec}', '{snapshot_name}'], 5.0)
11fdf7f2
TL
394 @RESTController.Resource('POST')
395 @UpdatePermission
f91f0fd5 396 @allow_empty_body
9f95a23c 397 def rollback(self, image_spec, snapshot_name):
11fdf7f2
TL
398 def _rollback(ioctx, img, snapshot_name):
399 img.rollback_to_snap(snapshot_name)
9f95a23c
TL
400
401 pool_name, namespace, image_name = parse_image_spec(image_spec)
402 return rbd_image_call(pool_name, namespace, image_name, _rollback, snapshot_name)
11fdf7f2
TL
403
404 @RbdTask('clone',
9f95a23c 405 {'parent_image_spec': '{image_spec}',
11fdf7f2 406 'child_pool_name': '{child_pool_name}',
9f95a23c 407 'child_namespace': '{child_namespace}',
11fdf7f2
TL
408 'child_image_name': '{child_image_name}'}, 2.0)
409 @RESTController.Resource('POST')
f91f0fd5 410 @allow_empty_body
9f95a23c
TL
411 def clone(self, image_spec, snapshot_name, child_pool_name,
412 child_image_name, child_namespace=None, obj_size=None, features=None,
1e59de90
TL
413 stripe_unit=None, stripe_count=None, data_pool=None,
414 configuration=None, metadata=None):
11fdf7f2
TL
415 """
416 Clones a snapshot to an image
417 """
418
9f95a23c
TL
419 pool_name, namespace, image_name = parse_image_spec(image_spec)
420
11fdf7f2
TL
421 def _parent_clone(p_ioctx):
422 def _clone(ioctx):
423 # Set order
424 l_order = None
425 if obj_size and obj_size > 0:
426 l_order = int(round(math.log(float(obj_size), 2)))
427
428 # Set features
429 feature_bitmask = format_features(features)
430
431 rbd_inst = rbd.RBD()
432 rbd_inst.clone(p_ioctx, image_name, snapshot_name, ioctx,
433 child_image_name, feature_bitmask, l_order,
434 stripe_unit, stripe_count, data_pool)
435
436 RbdConfiguration(pool_ioctx=ioctx, image_name=child_image_name).set_configuration(
437 configuration)
1e59de90
TL
438 if metadata:
439 with rbd.Image(ioctx, child_image_name) as image:
440 RbdImageMetadataService(image).set_metadata(metadata)
11fdf7f2 441
9f95a23c 442 return rbd_call(child_pool_name, child_namespace, _clone)
11fdf7f2 443
9f95a23c 444 rbd_call(pool_name, namespace, _parent_clone)
11fdf7f2
TL
445
446
a4b75251
TL
447@APIRouter('/block/image/trash', Scope.RBD_IMAGE)
448@APIDoc("RBD Trash Management API", "RbdTrash")
11fdf7f2 449class RbdTrash(RESTController):
9f95a23c 450 RESOURCE_ID = "image_id_spec"
f67539c2
TL
451
452 def __init__(self):
453 super().__init__()
454 self.rbd_inst = rbd.RBD()
11fdf7f2
TL
455
456 @ViewCache()
457 def _trash_pool_list(self, pool_name):
458 with mgr.rados.open_ioctx(pool_name) as ioctx:
11fdf7f2 459 result = []
9f95a23c
TL
460 namespaces = self.rbd_inst.namespace_list(ioctx)
461 # images without namespace
462 namespaces.append('')
463 for namespace in namespaces:
464 ioctx.set_namespace(namespace)
465 images = self.rbd_inst.trash_list(ioctx)
466 for trash in images:
467 trash['pool_name'] = pool_name
468 trash['namespace'] = namespace
469 trash['deletion_time'] = "{}Z".format(trash['deletion_time'].isoformat())
470 trash['deferment_end_time'] = "{}Z".format(
471 trash['deferment_end_time'].isoformat())
472 result.append(trash)
11fdf7f2
TL
473 return result
474
475 def _trash_list(self, pool_name=None):
476 if pool_name:
477 pools = [pool_name]
478 else:
479 pools = [p['pool_name'] for p in CephService.get_pool_list('rbd')]
480
481 result = []
482 for pool in pools:
483 # pylint: disable=unbalanced-tuple-unpacking
484 status, value = self._trash_pool_list(pool)
485 result.append({'status': status, 'value': value, 'pool_name': pool})
486 return result
487
488 @handle_rbd_error()
489 @handle_rados_error('pool')
f67539c2
TL
490 @EndpointDoc("Get RBD Trash Details by pool name",
491 parameters={
492 'pool_name': (str, 'Name of the pool'),
493 },
494 responses={200: RBD_TRASH_SCHEMA})
11fdf7f2
TL
495 def list(self, pool_name=None):
496 """List all entries from trash."""
497 return self._trash_list(pool_name)
498
499 @handle_rbd_error()
500 @handle_rados_error('pool')
501 @RbdTask('trash/purge', ['{pool_name}'], 2.0)
502 @RESTController.Collection('POST', query_params=['pool_name'])
503 @DeletePermission
f91f0fd5 504 @allow_empty_body
11fdf7f2
TL
505 def purge(self, pool_name=None):
506 """Remove all expired images from trash."""
1911f103 507 now = "{}Z".format(datetime.utcnow().isoformat())
11fdf7f2
TL
508 pools = self._trash_list(pool_name)
509
510 for pool in pools:
511 for image in pool['value']:
512 if image['deferment_end_time'] < now:
1911f103
TL
513 logger.info('Removing trash image %s (pool=%s, namespace=%s, name=%s)',
514 image['id'], pool['pool_name'], image['namespace'], image['name'])
9f95a23c
TL
515 rbd_call(pool['pool_name'], image['namespace'],
516 self.rbd_inst.trash_remove, image['id'], 0)
11fdf7f2 517
9f95a23c 518 @RbdTask('trash/restore', ['{image_id_spec}', '{new_image_name}'], 2.0)
11fdf7f2
TL
519 @RESTController.Resource('POST')
520 @CreatePermission
f91f0fd5 521 @allow_empty_body
9f95a23c 522 def restore(self, image_id_spec, new_image_name):
11fdf7f2 523 """Restore an image from trash."""
9f95a23c
TL
524 pool_name, namespace, image_id = parse_image_spec(image_id_spec)
525 return rbd_call(pool_name, namespace, self.rbd_inst.trash_restore, image_id,
526 new_image_name)
11fdf7f2 527
9f95a23c
TL
528 @RbdTask('trash/remove', ['{image_id_spec}'], 2.0)
529 def delete(self, image_id_spec, force=False):
11fdf7f2
TL
530 """Delete an image from trash.
531 If image deferment time has not expired you can not removed it unless use force.
532 But an actively in-use by clones or has snapshots can not be removed.
533 """
9f95a23c
TL
534 pool_name, namespace, image_id = parse_image_spec(image_id_spec)
535 return rbd_call(pool_name, namespace, self.rbd_inst.trash_remove, image_id,
536 int(str_to_bool(force)))
537
538
a4b75251
TL
539@APIRouter('/block/pool/{pool_name}/namespace', Scope.RBD_IMAGE)
540@APIDoc("RBD Namespace Management API", "RbdNamespace")
9f95a23c 541class RbdNamespace(RESTController):
f67539c2
TL
542
543 def __init__(self):
544 super().__init__()
545 self.rbd_inst = rbd.RBD()
9f95a23c
TL
546
547 def create(self, pool_name, namespace):
548 with mgr.rados.open_ioctx(pool_name) as ioctx:
549 namespaces = self.rbd_inst.namespace_list(ioctx)
550 if namespace in namespaces:
551 raise DashboardException(
552 msg='Namespace already exists',
553 code='namespace_already_exists',
554 component='rbd')
555 return self.rbd_inst.namespace_create(ioctx, namespace)
556
557 def delete(self, pool_name, namespace):
558 with mgr.rados.open_ioctx(pool_name) as ioctx:
559 # pylint: disable=unbalanced-tuple-unpacking
2a845540 560 images, _ = RbdService.rbd_pool_list([pool_name], namespace=namespace)
9f95a23c
TL
561 if images:
562 raise DashboardException(
563 msg='Namespace contains images which must be deleted first',
564 code='namespace_contains_images',
565 component='rbd')
566 return self.rbd_inst.namespace_remove(ioctx, namespace)
567
568 def list(self, pool_name):
569 with mgr.rados.open_ioctx(pool_name) as ioctx:
570 result = []
571 namespaces = self.rbd_inst.namespace_list(ioctx)
572 for namespace in namespaces:
573 # pylint: disable=unbalanced-tuple-unpacking
2a845540 574 images, _ = RbdService.rbd_pool_list([pool_name], namespace=namespace)
9f95a23c
TL
575 result.append({
576 'namespace': namespace,
577 'num_images': len(images) if images else 0
578 })
579 return result