1 # -*- coding: utf-8 -*-
2 # pylint: disable=unused-argument
6 from enum
import IntEnum
13 from ..exceptions
import DashboardException
14 from ..plugins
.ttl_cache
import ttl_cache
, ttl_cache_invalidator
15 from ._paginate
import ListPaginator
16 from .ceph_service
import CephService
19 from typing
import List
, Optional
21 pass # For typing only
24 RBD_FEATURES_NAME_MAPPING
= {
25 rbd
.RBD_FEATURE_LAYERING
: "layering",
26 rbd
.RBD_FEATURE_STRIPINGV2
: "striping",
27 rbd
.RBD_FEATURE_EXCLUSIVE_LOCK
: "exclusive-lock",
28 rbd
.RBD_FEATURE_OBJECT_MAP
: "object-map",
29 rbd
.RBD_FEATURE_FAST_DIFF
: "fast-diff",
30 rbd
.RBD_FEATURE_DEEP_FLATTEN
: "deep-flatten",
31 rbd
.RBD_FEATURE_JOURNALING
: "journaling",
32 rbd
.RBD_FEATURE_DATA_POOL
: "data-pool",
33 rbd
.RBD_FEATURE_OPERATIONS
: "operations",
36 RBD_IMAGE_REFS_CACHE_REFERENCE
= 'rbd_image_refs'
37 GET_IOCTX_CACHE
= 'get_ioctx'
38 POOL_NAMESPACES_CACHE
= 'pool_namespaces'
41 class MIRROR_IMAGE_MODE(IntEnum
):
42 journal
= rbd
.RBD_MIRROR_IMAGE_MODE_JOURNAL
43 snapshot
= rbd
.RBD_MIRROR_IMAGE_MODE_SNAPSHOT
46 def _rbd_support_remote(method_name
: str, *args
, **kwargs
):
48 return mgr
.remote('rbd_support', method_name
, *args
, **kwargs
)
49 except ImportError as ie
:
50 raise DashboardException(f
'rbd_support module not found {ie}')
51 except RuntimeError as ie
:
52 raise DashboardException(f
'rbd_support.{method_name} error: {ie}')
55 def format_bitmask(features
):
59 @DISABLEDOCTEST: >>> format_bitmask(45)
60 ['deep-flatten', 'exclusive-lock', 'layering', 'object-map']
62 names
= [val
for key
, val
in RBD_FEATURES_NAME_MAPPING
.items()
63 if key
& features
== key
]
67 def format_features(features
):
69 Converts the features list to bitmask:
71 @DISABLEDOCTEST: >>> format_features(['deep-flatten', 'exclusive-lock',
72 'layering', 'object-map'])
75 @DISABLEDOCTEST: >>> format_features(None) is None
78 @DISABLEDOCTEST: >>> format_features('deep-flatten, exclusive-lock')
81 if isinstance(features
, str):
82 features
= features
.split(',')
84 if not isinstance(features
, list):
88 for key
, value
in RBD_FEATURES_NAME_MAPPING
.items():
94 def _sort_features(features
, enable
=True):
96 Sorts image features according to feature dependencies:
98 object-map depends on exclusive-lock
99 journaling depends on exclusive-lock
100 fast-diff depends on object-map
102 ORDER
= ['exclusive-lock', 'journaling', 'object-map', 'fast-diff'] # noqa: N806
106 return ORDER
.index(feat
)
110 features
.sort(key
=key_func
, reverse
=not enable
)
113 def get_image_spec(pool_name
, namespace
, rbd_name
):
114 namespace
= '{}/'.format(namespace
) if namespace
else ''
115 return '{}/{}{}'.format(pool_name
, namespace
, rbd_name
)
118 def parse_image_spec(image_spec
):
119 namespace_spec
, image_name
= image_spec
.rsplit('/', 1)
120 if '/' in namespace_spec
:
121 pool_name
, namespace
= namespace_spec
.rsplit('/', 1)
123 pool_name
, namespace
= namespace_spec
, None
124 return pool_name
, namespace
, image_name
127 def rbd_call(pool_name
, namespace
, func
, *args
, **kwargs
):
128 with mgr
.rados
.open_ioctx(pool_name
) as ioctx
:
129 ioctx
.set_namespace(namespace
if namespace
is not None else '')
130 return func(ioctx
, *args
, **kwargs
)
133 def rbd_image_call(pool_name
, namespace
, image_name
, func
, *args
, **kwargs
):
134 def _ioctx_func(ioctx
, image_name
, func
, *args
, **kwargs
):
135 with rbd
.Image(ioctx
, image_name
) as img
:
136 return func(ioctx
, img
, *args
, **kwargs
)
138 return rbd_call(pool_name
, namespace
, _ioctx_func
, image_name
, func
, *args
, **kwargs
)
141 class RbdConfiguration(object):
144 def __init__(self
, pool_name
: str = '', namespace
: str = '', image_name
: str = '',
145 pool_ioctx
: Optional
[rados
.Ioctx
] = None, image_ioctx
: Optional
[rbd
.Image
] = None):
146 assert bool(pool_name
) != bool(pool_ioctx
) # xor
147 self
._pool
_name
= pool_name
148 self
._namespace
= namespace
if namespace
is not None else ''
149 self
._image
_name
= image_name
150 self
._pool
_ioctx
= pool_ioctx
151 self
._image
_ioctx
= image_ioctx
154 def _ensure_prefix(option
):
156 return option
if option
.startswith('conf_') else 'conf_' + option
159 # type: () -> List[dict]
161 if self
._image
_name
: # image config
163 # No need to open the context of the image again
164 # if we already did open it.
165 if self
._image
_ioctx
:
166 result
= self
._image
_ioctx
.config_list()
168 with rbd
.Image(ioctx
, self
._image
_name
) as image
:
169 result
= image
.config_list()
170 except rbd
.ImageNotFound
:
173 pg_status
= list(CephService
.get_pool_pg_status(self
._pool
_name
).keys())
174 if len(pg_status
) == 1 and 'incomplete' in pg_status
[0]:
175 # If config_list would be called with ioctx if it's a bad pool,
176 # the dashboard would stop working, waiting for the response
177 # that would not happen.
179 # This is only a workaround for https://tracker.ceph.com/issues/43771 which
180 # already got rejected as not worth the effort.
182 # Are more complete workaround for the dashboard will be implemented with
183 # https://tracker.ceph.com/issues/44224
185 # @TODO: If #44224 is addressed remove this workaround
187 result
= self
._rbd
.config_list(ioctx
)
191 ioctx
= mgr
.rados
.open_ioctx(self
._pool
_name
)
192 ioctx
.set_namespace(self
._namespace
)
194 ioctx
= self
._pool
_ioctx
198 def get(self
, option_name
):
200 option_name
= self
._ensure
_prefix
(option_name
)
201 with mgr
.rados
.open_ioctx(self
._pool
_name
) as pool_ioctx
:
202 pool_ioctx
.set_namespace(self
._namespace
)
204 with rbd
.Image(pool_ioctx
, self
._image
_name
) as image
:
205 return image
.metadata_get(option_name
)
206 return self
._rbd
.pool_metadata_get(pool_ioctx
, option_name
)
208 def set(self
, option_name
, option_value
):
209 # type: (str, str) -> None
211 option_value
= str(option_value
)
212 option_name
= self
._ensure
_prefix
(option_name
)
214 pool_ioctx
= self
._pool
_ioctx
215 if self
._pool
_name
: # open ioctx
216 pool_ioctx
= mgr
.rados
.open_ioctx(self
._pool
_name
)
217 pool_ioctx
.__enter
__() # type: ignore
218 pool_ioctx
.set_namespace(self
._namespace
) # type: ignore
220 image_ioctx
= self
._image
_ioctx
222 image_ioctx
= rbd
.Image(pool_ioctx
, self
._image
_name
)
223 image_ioctx
.__enter
__() # type: ignore
226 image_ioctx
.metadata_set(option_name
, option_value
) # type: ignore
228 self
._rbd
.pool_metadata_set(pool_ioctx
, option_name
, option_value
)
230 if self
._image
_name
: # Name provided, so we opened it and now have to close it
231 image_ioctx
.__exit
__(None, None, None) # type: ignore
233 pool_ioctx
.__exit
__(None, None, None) # type: ignore
235 def remove(self
, option_name
):
237 Removes an option by name. Will not raise an error, if the option hasn't been found.
238 :type option_name str
243 with rbd
.Image(ioctx
, self
._image
_name
) as image
:
244 image
.metadata_remove(option_name
)
246 self
._rbd
.pool_metadata_remove(ioctx
, option_name
)
250 option_name
= self
._ensure
_prefix
(option_name
)
253 with mgr
.rados
.open_ioctx(self
._pool
_name
) as pool_ioctx
:
254 pool_ioctx
.set_namespace(self
._namespace
)
257 _remove(self
._pool
_ioctx
)
259 def set_configuration(self
, configuration
):
261 for option_name
, option_value
in configuration
.items():
262 if option_value
is not None:
263 self
.set(option_name
, option_value
)
265 self
.remove(option_name
)
268 class RbdService(object):
269 _rbd_inst
= rbd
.RBD()
271 # set of image features that can be enable on existing images
272 ALLOW_ENABLE_FEATURES
= {"exclusive-lock", "object-map", "fast-diff", "journaling"}
274 # set of image features that can be disabled on existing images
275 ALLOW_DISABLE_FEATURES
= {"exclusive-lock", "object-map", "fast-diff", "deep-flatten",
279 def _rbd_disk_usage(cls
, image
, snaps
, whole_object
=True):
280 class DUCallback(object):
284 def __call__(self
, offset
, length
, exists
):
286 self
.used_size
+= length
291 for _
, size
, name
in snaps
:
293 du_callb
= DUCallback()
294 image
.diff_iterate(0, size
, prev_snap
, du_callb
,
295 whole_object
=whole_object
)
296 snap_map
[name
] = du_callb
.used_size
297 total_used_size
+= du_callb
.used_size
300 return total_used_size
, snap_map
303 def _rbd_image(cls
, ioctx
, pool_name
, namespace
, image_name
): # pylint: disable=R0912
304 with rbd
.Image(ioctx
, image_name
) as img
:
306 mirror_info
= img
.mirror_image_get_info()
307 mirror_mode
= img
.mirror_image_get_mode()
308 if mirror_mode
== rbd
.RBD_MIRROR_IMAGE_MODE_JOURNAL
and mirror_info
['state'] != rbd
.RBD_MIRROR_IMAGE_DISABLED
: # noqa E501 #pylint: disable=line-too-long
309 stat
['mirror_mode'] = 'journal'
310 elif mirror_mode
== rbd
.RBD_MIRROR_IMAGE_MODE_SNAPSHOT
:
311 stat
['mirror_mode'] = 'snapshot'
312 schedule_status
= json
.loads(_rbd_support_remote(
313 'mirror_snapshot_schedule_status')[1])
314 for scheduled_image
in schedule_status
['scheduled_images']:
315 if scheduled_image
['image'] == get_image_spec(pool_name
, namespace
, image_name
):
316 stat
['schedule_info'] = scheduled_image
318 stat
['mirror_mode'] = 'Disabled'
320 stat
['name'] = image_name
322 stat
['primary'] = None
323 if mirror_info
['state'] == rbd
.RBD_MIRROR_IMAGE_ENABLED
:
324 stat
['primary'] = mirror_info
['primary']
327 stat
['unique_id'] = get_image_spec(pool_name
, namespace
, stat
['block_name_prefix'])
328 stat
['id'] = stat
['unique_id']
329 stat
['image_format'] = 1
331 stat
['unique_id'] = get_image_spec(pool_name
, namespace
, img
.id())
332 stat
['id'] = img
.id()
333 stat
['image_format'] = 2
335 stat
['pool_name'] = pool_name
336 stat
['namespace'] = namespace
337 features
= img
.features()
338 stat
['features'] = features
339 stat
['features_name'] = format_bitmask(features
)
341 # the following keys are deprecated
342 del stat
['parent_pool']
343 del stat
['parent_name']
345 stat
['timestamp'] = "{}Z".format(img
.create_timestamp()
348 stat
['stripe_count'] = img
.stripe_count()
349 stat
['stripe_unit'] = img
.stripe_unit()
351 data_pool_name
= CephService
.get_pool_name_from_id(
353 if data_pool_name
== pool_name
:
354 data_pool_name
= None
355 stat
['data_pool'] = data_pool_name
357 stat
['parent'] = cls
._rbd
_image
_stat
_parent
(img
)
360 stat
['snapshots'] = []
361 for snap
in img
.list_snaps():
363 snap
['mirror_mode'] = MIRROR_IMAGE_MODE(img
.mirror_image_get_mode()).name
364 except ValueError as ex
:
365 raise DashboardException(f
'Unknown RBD Mirror mode: {ex}')
367 snap
['timestamp'] = "{}Z".format(
368 img
.get_snap_timestamp(snap
['id']).isoformat())
370 snap
['is_protected'] = None
371 if mirror_mode
!= rbd
.RBD_MIRROR_IMAGE_MODE_SNAPSHOT
:
372 snap
['is_protected'] = img
.is_protected_snap(snap
['name'])
373 snap
['used_bytes'] = None
374 snap
['children'] = []
376 if mirror_mode
!= rbd
.RBD_MIRROR_IMAGE_MODE_SNAPSHOT
:
377 img
.set_snap(snap
['name'])
378 for child_pool_name
, child_image_name
in img
.list_children():
379 snap
['children'].append({
380 'pool_name': child_pool_name
,
381 'image_name': child_image_name
383 stat
['snapshots'].append(snap
)
386 img_flags
= img
.flags()
387 if 'fast-diff' in stat
['features_name'] and \
388 not rbd
.RBD_FLAG_FAST_DIFF_INVALID
& img_flags
and \
389 mirror_mode
!= rbd
.RBD_MIRROR_IMAGE_MODE_SNAPSHOT
:
390 snaps
= [(s
['id'], s
['size'], s
['name'])
391 for s
in stat
['snapshots']]
392 snaps
.sort(key
=lambda s
: s
[0])
393 snaps
+= [(snaps
[-1][0] + 1 if snaps
else 0, stat
['size'], None)]
394 total_prov_bytes
, snaps_prov_bytes
= cls
._rbd
_disk
_usage
(
396 stat
['total_disk_usage'] = total_prov_bytes
397 for snap
, prov_bytes
in snaps_prov_bytes
.items():
399 stat
['disk_usage'] = prov_bytes
401 for ss
in stat
['snapshots']:
402 if ss
['name'] == snap
:
403 ss
['disk_usage'] = prov_bytes
406 stat
['total_disk_usage'] = None
407 stat
['disk_usage'] = None
409 stat
['configuration'] = RbdConfiguration(
410 pool_ioctx
=ioctx
, image_name
=image_name
, image_ioctx
=img
).list()
412 stat
['metadata'] = RbdImageMetadataService(img
).list()
417 def _rbd_image_stat_parent(cls
, img
):
420 stat_parent
= img
.get_parent_image_spec()
421 except rbd
.ImageNotFound
:
427 @ttl_cache(10, label
=GET_IOCTX_CACHE
)
428 def get_ioctx(cls
, pool_name
, namespace
=''):
429 ioctx
= mgr
.rados
.open_ioctx(pool_name
)
430 ioctx
.set_namespace(namespace
)
434 @ttl_cache(30, label
=RBD_IMAGE_REFS_CACHE_REFERENCE
)
435 def _rbd_image_refs(cls
, pool_name
, namespace
=''):
436 # We add and set the namespace here so that we cache by ioctx and namespace.
438 ioctx
= cls
.get_ioctx(pool_name
, namespace
)
439 images
= cls
._rbd
_inst
.list2(ioctx
)
443 @ttl_cache(30, label
=POOL_NAMESPACES_CACHE
)
444 def _pool_namespaces(cls
, pool_name
, namespace
=None):
447 namespaces
= [namespace
]
449 ioctx
= cls
.get_ioctx(pool_name
, namespace
=rados
.LIBRADOS_ALL_NSPACES
)
450 namespaces
= cls
._rbd
_inst
.namespace_list(ioctx
)
451 # images without namespace
452 namespaces
.append('')
456 def _rbd_image_stat(cls
, ioctx
, pool_name
, namespace
, image_name
):
457 return cls
._rbd
_image
(ioctx
, pool_name
, namespace
, image_name
)
460 def _rbd_image_stat_removing(cls
, ioctx
, pool_name
, namespace
, image_id
):
461 img
= cls
._rbd
_inst
.trash_get(ioctx
, image_id
)
462 img_spec
= get_image_spec(pool_name
, namespace
, image_id
)
464 if img
['source'] == 'REMOVING':
465 img
['unique_id'] = img_spec
466 img
['pool_name'] = pool_name
467 img
['namespace'] = namespace
468 img
['deletion_time'] = "{}Z".format(img
['deletion_time'].isoformat())
469 img
['deferment_end_time'] = "{}Z".format(img
['deferment_end_time'].isoformat())
471 raise rbd
.ImageNotFound('No image {} in status `REMOVING` found.'.format(img_spec
),
475 def _rbd_pool_image_refs(cls
, pool_names
: List
[str], namespace
: Optional
[str] = None):
477 for pool
in pool_names
:
478 for current_namespace
in cls
._pool
_namespaces
(pool
, namespace
=namespace
):
479 image_refs
= cls
._rbd
_image
_refs
(pool
, current_namespace
)
480 for image
in image_refs
:
481 image
['namespace'] = current_namespace
482 image
['pool_name'] = pool
483 joint_refs
.append(image
)
487 def rbd_pool_list(cls
, pool_names
: List
[str], namespace
: Optional
[str] = None, offset
: int = 0,
488 limit
: int = 5, search
: str = '', sort
: str = ''):
489 image_refs
= cls
._rbd
_pool
_image
_refs
(pool_names
, namespace
)
490 params
= ['name', 'pool_name', 'namespace']
491 paginator
= ListPaginator(offset
, limit
, sort
, search
, image_refs
,
492 searchable_params
=params
, sortable_params
=params
,
493 default_sort
='+name')
496 for image_ref
in paginator
.list():
497 with mgr
.rados
.open_ioctx(image_ref
['pool_name']) as ioctx
:
498 ioctx
.set_namespace(image_ref
['namespace'])
499 # Check if the RBD has been deleted partially. This happens for example if
500 # the deletion process of the RBD has been started and was interrupted.
503 stat
= cls
._rbd
_image
_stat
(
504 ioctx
, image_ref
['pool_name'], image_ref
['namespace'], image_ref
['name'])
505 except rbd
.ImageNotFound
:
507 stat
= cls
._rbd
_image
_stat
_removing
(
508 ioctx
, image_ref
['pool_name'], image_ref
['namespace'], image_ref
['id'])
509 except rbd
.ImageNotFound
:
512 return result
, paginator
.get_count()
515 def get_image(cls
, image_spec
):
516 pool_name
, namespace
, image_name
= parse_image_spec(image_spec
)
517 ioctx
= mgr
.rados
.open_ioctx(pool_name
)
519 ioctx
.set_namespace(namespace
)
521 return cls
._rbd
_image
(ioctx
, pool_name
, namespace
, image_name
)
522 except rbd
.ImageNotFound
:
523 raise cherrypy
.HTTPError(404, 'Image not found')
526 @ttl_cache_invalidator(RBD_IMAGE_REFS_CACHE_REFERENCE
)
527 def create(cls
, name
, pool_name
, size
, namespace
=None,
528 obj_size
=None, features
=None, stripe_unit
=None, stripe_count
=None,
529 data_pool
=None, configuration
=None, metadata
=None):
533 rbd_inst
= cls
._rbd
_inst
537 if obj_size
and obj_size
> 0:
538 l_order
= int(round(math
.log(float(obj_size
), 2)))
541 feature_bitmask
= format_features(features
)
543 rbd_inst
.create(ioctx
, name
, size
, order
=l_order
, old_format
=False,
544 features
=feature_bitmask
, stripe_unit
=stripe_unit
,
545 stripe_count
=stripe_count
, data_pool
=data_pool
)
546 RbdConfiguration(pool_ioctx
=ioctx
, namespace
=namespace
,
547 image_name
=name
).set_configuration(configuration
)
549 with rbd
.Image(ioctx
, name
) as image
:
550 RbdImageMetadataService(image
).set_metadata(metadata
)
551 rbd_call(pool_name
, namespace
, _create
)
554 @ttl_cache_invalidator(RBD_IMAGE_REFS_CACHE_REFERENCE
)
555 def set(cls
, image_spec
, name
=None, size
=None, features
=None,
556 configuration
=None, metadata
=None, enable_mirror
=None, primary
=None,
557 force
=False, resync
=False, mirror_mode
=None, schedule_interval
='',
558 remove_scheduling
=False):
559 # pylint: disable=too-many-branches
560 pool_name
, namespace
, image_name
= parse_image_spec(image_spec
)
562 def _edit(ioctx
, image
):
563 rbd_inst
= cls
._rbd
_inst
565 if name
and name
!= image_name
:
566 rbd_inst
.rename(ioctx
, image_name
, name
)
569 if size
and size
!= image
.size():
572 mirror_image_info
= image
.mirror_image_get_info()
573 if enable_mirror
and mirror_image_info
['state'] == rbd
.RBD_MIRROR_IMAGE_DISABLED
:
574 RbdMirroringService
.enable_image(
575 image_name
, pool_name
, namespace
,
576 MIRROR_IMAGE_MODE
[mirror_mode
])
577 elif (enable_mirror
is False
578 and mirror_image_info
['state'] == rbd
.RBD_MIRROR_IMAGE_ENABLED
):
579 RbdMirroringService
.disable_image(
580 image_name
, pool_name
, namespace
)
582 # check enable/disable features
583 if features
is not None:
584 curr_features
= format_bitmask(image
.features())
585 # check disabled features
586 _sort_features(curr_features
, enable
=False)
587 for feature
in curr_features
:
588 if (feature
not in features
589 and feature
in cls
.ALLOW_DISABLE_FEATURES
590 and feature
in format_bitmask(image
.features())):
591 f_bitmask
= format_features([feature
])
592 image
.update_features(f_bitmask
, False)
593 # check enabled features
594 _sort_features(features
)
595 for feature
in features
:
596 if (feature
not in curr_features
597 and feature
in cls
.ALLOW_ENABLE_FEATURES
598 and feature
not in format_bitmask(image
.features())):
599 f_bitmask
= format_features([feature
])
600 image
.update_features(f_bitmask
, True)
602 RbdConfiguration(pool_ioctx
=ioctx
, image_name
=image_name
).set_configuration(
605 RbdImageMetadataService(image
).set_metadata(metadata
)
607 if primary
and not mirror_image_info
['primary']:
608 RbdMirroringService
.promote_image(
609 image_name
, pool_name
, namespace
, force
)
610 elif primary
is False and mirror_image_info
['primary']:
611 RbdMirroringService
.demote_image(
612 image_name
, pool_name
, namespace
)
615 RbdMirroringService
.resync_image(image_name
, pool_name
, namespace
)
617 if schedule_interval
:
618 RbdMirroringService
.snapshot_schedule_add(image_spec
, schedule_interval
)
620 if remove_scheduling
:
621 RbdMirroringService
.snapshot_schedule_remove(image_spec
)
623 return rbd_image_call(pool_name
, namespace
, image_name
, _edit
)
626 @ttl_cache_invalidator(RBD_IMAGE_REFS_CACHE_REFERENCE
)
627 def delete(cls
, image_spec
):
628 pool_name
, namespace
, image_name
= parse_image_spec(image_spec
)
630 image
= RbdService
.get_image(image_spec
)
631 snapshots
= image
['snapshots']
632 for snap
in snapshots
:
633 RbdSnapshotService
.remove_snapshot(image_spec
, snap
['name'], snap
['is_protected'])
636 return rbd_call(pool_name
, namespace
, rbd_inst
.remove
, image_name
)
639 @ttl_cache_invalidator(RBD_IMAGE_REFS_CACHE_REFERENCE
)
640 def copy(cls
, image_spec
, dest_pool_name
, dest_namespace
, dest_image_name
,
641 snapshot_name
=None, obj_size
=None, features
=None,
642 stripe_unit
=None, stripe_count
=None, data_pool
=None,
643 configuration
=None, metadata
=None):
644 pool_name
, namespace
, image_name
= parse_image_spec(image_spec
)
646 def _src_copy(s_ioctx
, s_img
):
650 if obj_size
and obj_size
> 0:
651 l_order
= int(round(math
.log(float(obj_size
), 2)))
654 feature_bitmask
= format_features(features
)
657 s_img
.set_snap(snapshot_name
)
659 s_img
.copy(d_ioctx
, dest_image_name
, feature_bitmask
, l_order
,
660 stripe_unit
, stripe_count
, data_pool
)
661 RbdConfiguration(pool_ioctx
=d_ioctx
, image_name
=dest_image_name
).set_configuration(
664 with rbd
.Image(d_ioctx
, dest_image_name
) as image
:
665 RbdImageMetadataService(image
).set_metadata(metadata
)
667 return rbd_call(dest_pool_name
, dest_namespace
, _copy
)
669 return rbd_image_call(pool_name
, namespace
, image_name
, _src_copy
)
672 @ttl_cache_invalidator(RBD_IMAGE_REFS_CACHE_REFERENCE
)
673 def flatten(cls
, image_spec
):
674 def _flatten(ioctx
, image
):
677 pool_name
, namespace
, image_name
= parse_image_spec(image_spec
)
678 return rbd_image_call(pool_name
, namespace
, image_name
, _flatten
)
681 def move_image_to_trash(cls
, image_spec
, delay
):
682 pool_name
, namespace
, image_name
= parse_image_spec(image_spec
)
683 rbd_inst
= cls
._rbd
_inst
684 return rbd_call(pool_name
, namespace
, rbd_inst
.trash_move
, image_name
, delay
)
687 class RbdSnapshotService(object):
690 def remove_snapshot(cls
, image_spec
, snapshot_name
, unprotect
=False):
691 def _remove_snapshot(ioctx
, img
, snapshot_name
, unprotect
):
693 img
.unprotect_snap(snapshot_name
)
694 img
.remove_snap(snapshot_name
)
696 pool_name
, namespace
, image_name
= parse_image_spec(image_spec
)
697 return rbd_image_call(pool_name
, namespace
, image_name
,
698 _remove_snapshot
, snapshot_name
, unprotect
)
701 class RBDSchedulerInterval
:
702 def __init__(self
, interval
: str):
703 self
.amount
= int(interval
[:-1])
704 self
.unit
= interval
[-1]
705 if self
.unit
not in 'mhd':
706 raise ValueError(f
'Invalid interval unit {self.unit}')
709 return f
'{self.amount}{self.unit}'
712 class RbdMirroringService
:
715 def enable_image(cls
, image_name
: str, pool_name
: str, namespace
: str, mode
: MIRROR_IMAGE_MODE
):
716 rbd_image_call(pool_name
, namespace
, image_name
,
717 lambda ioctx
, image
: image
.mirror_image_enable(mode
))
720 def disable_image(cls
, image_name
: str, pool_name
: str, namespace
: str, force
: bool = False):
721 rbd_image_call(pool_name
, namespace
, image_name
,
722 lambda ioctx
, image
: image
.mirror_image_disable(force
))
725 def promote_image(cls
, image_name
: str, pool_name
: str, namespace
: str, force
: bool = False):
726 rbd_image_call(pool_name
, namespace
, image_name
,
727 lambda ioctx
, image
: image
.mirror_image_promote(force
))
730 def demote_image(cls
, image_name
: str, pool_name
: str, namespace
: str):
731 rbd_image_call(pool_name
, namespace
, image_name
,
732 lambda ioctx
, image
: image
.mirror_image_demote())
735 def resync_image(cls
, image_name
: str, pool_name
: str, namespace
: str):
736 rbd_image_call(pool_name
, namespace
, image_name
,
737 lambda ioctx
, image
: image
.mirror_image_resync())
740 def snapshot_schedule_add(cls
, image_spec
: str, interval
: str):
741 _rbd_support_remote('mirror_snapshot_schedule_add', image_spec
,
742 str(RBDSchedulerInterval(interval
)))
745 def snapshot_schedule_remove(cls
, image_spec
: str):
746 _rbd_support_remote('mirror_snapshot_schedule_remove', image_spec
)
749 class RbdImageMetadataService(object):
750 def __init__(self
, image
):
754 result
= self
._image
.metadata_list()
755 # filter out configuration metadata
756 return {v
[0]: v
[1] for v
in result
if not v
[0].startswith('conf_')}
759 return self
._image
.metadata_get(name
)
761 def set(self
, name
, value
):
762 self
._image
.metadata_set(name
, value
)
764 def remove(self
, name
):
766 self
._image
.metadata_remove(name
)
770 def set_metadata(self
, metadata
):
771 for name
, value
in metadata
.items():
772 if value
is not None:
773 self
.set(name
, value
)