# pylint: disable=unused-argument
import errno
import json
+import math
from enum import IntEnum
import cherrypy
from .. import mgr
from ..exceptions import DashboardException
-from ..plugins.ttl_cache import ttl_cache
+from ..plugins.ttl_cache import ttl_cache, ttl_cache_invalidator
from ._paginate import ListPaginator
from .ceph_service import CephService
rbd.RBD_FEATURE_OPERATIONS: "operations",
}
+RBD_IMAGE_REFS_CACHE_REFERENCE = 'rbd_image_refs'
+GET_IOCTX_CACHE = 'get_ioctx'
+POOL_NAMESPACES_CACHE = 'pool_namespaces'
+
class MIRROR_IMAGE_MODE(IntEnum):
journal = rbd.RBD_MIRROR_IMAGE_MODE_JOURNAL
return res
+def _sort_features(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'] # noqa: N806
+
+ def key_func(feat):
+ try:
+ return ORDER.index(feat)
+ except ValueError:
+ return id(feat)
+
+ features.sort(key=key_func, reverse=not enable)
+
+
def get_image_spec(pool_name, namespace, rbd_name):
namespace = '{}/'.format(namespace) if namespace else ''
return '{}/{}{}'.format(pool_name, namespace, rbd_name)
class RbdService(object):
_rbd_inst = rbd.RBD()
+ # set of image features that can be enable on existing images
+ ALLOW_ENABLE_FEATURES = {"exclusive-lock", "object-map", "fast-diff", "journaling"}
+
+ # set of image features that can be disabled on existing images
+ ALLOW_DISABLE_FEATURES = {"exclusive-lock", "object-map", "fast-diff", "deep-flatten",
+ "journaling"}
+
@classmethod
def _rbd_disk_usage(cls, image, snaps, whole_object=True):
class DUCallback(object):
return stat_parent
@classmethod
- @ttl_cache(10)
+ @ttl_cache(10, label=GET_IOCTX_CACHE)
def get_ioctx(cls, pool_name, namespace=''):
ioctx = mgr.rados.open_ioctx(pool_name)
ioctx.set_namespace(namespace)
return ioctx
@classmethod
- @ttl_cache(30)
+ @ttl_cache(30, label=RBD_IMAGE_REFS_CACHE_REFERENCE)
def _rbd_image_refs(cls, pool_name, namespace=''):
# We add and set the namespace here so that we cache by ioctx and namespace.
images = []
return images
@classmethod
- @ttl_cache(30)
+ @ttl_cache(30, label=POOL_NAMESPACES_CACHE)
def _pool_namespaces(cls, pool_name, namespace=None):
namespaces = []
if namespace:
except rbd.ImageNotFound:
raise cherrypy.HTTPError(404, 'Image not found')
+ @classmethod
+ @ttl_cache_invalidator(RBD_IMAGE_REFS_CACHE_REFERENCE)
+ def create(cls, name, pool_name, size, namespace=None,
+ obj_size=None, features=None, stripe_unit=None, stripe_count=None,
+ data_pool=None, configuration=None, metadata=None):
+ size = int(size)
+
+ def _create(ioctx):
+ rbd_inst = cls._rbd_inst
+
+ # Set order
+ l_order = None
+ if obj_size and obj_size > 0:
+ l_order = int(round(math.log(float(obj_size), 2)))
+
+ # Set features
+ feature_bitmask = format_features(features)
+
+ 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)
+ RbdConfiguration(pool_ioctx=ioctx, namespace=namespace,
+ image_name=name).set_configuration(configuration)
+ if metadata:
+ with rbd.Image(ioctx, name) as image:
+ RbdImageMetadataService(image).set_metadata(metadata)
+ rbd_call(pool_name, namespace, _create)
+
+ @classmethod
+ @ttl_cache_invalidator(RBD_IMAGE_REFS_CACHE_REFERENCE)
+ def set(cls, image_spec, name=None, size=None, features=None,
+ configuration=None, metadata=None, enable_mirror=None, primary=None,
+ force=False, resync=False, mirror_mode=None, schedule_interval='',
+ remove_scheduling=False):
+ # pylint: disable=too-many-branches
+ pool_name, namespace, image_name = parse_image_spec(image_spec)
+
+ def _edit(ioctx, image):
+ rbd_inst = cls._rbd_inst
+ # check rename image
+ if name and name != image_name:
+ rbd_inst.rename(ioctx, image_name, name)
+
+ # check resize
+ if size and size != image.size():
+ image.resize(size)
+
+ mirror_image_info = image.mirror_image_get_info()
+ if enable_mirror and mirror_image_info['state'] == rbd.RBD_MIRROR_IMAGE_DISABLED:
+ RbdMirroringService.enable_image(
+ image_name, pool_name, namespace,
+ MIRROR_IMAGE_MODE[mirror_mode])
+ elif (enable_mirror is False
+ and mirror_image_info['state'] == rbd.RBD_MIRROR_IMAGE_ENABLED):
+ RbdMirroringService.disable_image(
+ image_name, pool_name, namespace)
+
+ # check enable/disable features
+ if features is not None:
+ curr_features = format_bitmask(image.features())
+ # check disabled features
+ _sort_features(curr_features, enable=False)
+ for feature in curr_features:
+ if (feature not in features
+ and feature in cls.ALLOW_DISABLE_FEATURES
+ and feature in format_bitmask(image.features())):
+ f_bitmask = format_features([feature])
+ image.update_features(f_bitmask, False)
+ # check enabled features
+ _sort_features(features)
+ for feature in features:
+ if (feature not in curr_features
+ and feature in cls.ALLOW_ENABLE_FEATURES
+ and feature not in format_bitmask(image.features())):
+ f_bitmask = format_features([feature])
+ image.update_features(f_bitmask, True)
+
+ RbdConfiguration(pool_ioctx=ioctx, image_name=image_name).set_configuration(
+ configuration)
+ if metadata:
+ RbdImageMetadataService(image).set_metadata(metadata)
+
+ if primary and not mirror_image_info['primary']:
+ RbdMirroringService.promote_image(
+ image_name, pool_name, namespace, force)
+ elif primary is False and mirror_image_info['primary']:
+ RbdMirroringService.demote_image(
+ image_name, pool_name, namespace)
+
+ if resync:
+ RbdMirroringService.resync_image(image_name, pool_name, namespace)
+
+ if schedule_interval:
+ RbdMirroringService.snapshot_schedule_add(image_spec, schedule_interval)
+
+ if remove_scheduling:
+ RbdMirroringService.snapshot_schedule_remove(image_spec)
+
+ return rbd_image_call(pool_name, namespace, image_name, _edit)
+
+ @classmethod
+ @ttl_cache_invalidator(RBD_IMAGE_REFS_CACHE_REFERENCE)
+ def delete(cls, image_spec):
+ pool_name, namespace, image_name = parse_image_spec(image_spec)
+
+ image = RbdService.get_image(image_spec)
+ snapshots = image['snapshots']
+ for snap in snapshots:
+ RbdSnapshotService.remove_snapshot(image_spec, snap['name'], snap['is_protected'])
+
+ rbd_inst = rbd.RBD()
+ return rbd_call(pool_name, namespace, rbd_inst.remove, image_name)
+
+ @classmethod
+ @ttl_cache_invalidator(RBD_IMAGE_REFS_CACHE_REFERENCE)
+ def copy(cls, image_spec, dest_pool_name, dest_namespace, dest_image_name,
+ snapshot_name=None, obj_size=None, features=None,
+ stripe_unit=None, stripe_count=None, data_pool=None,
+ configuration=None, metadata=None):
+ pool_name, namespace, image_name = parse_image_spec(image_spec)
+
+ def _src_copy(s_ioctx, s_img):
+ def _copy(d_ioctx):
+ # Set order
+ l_order = None
+ if obj_size and obj_size > 0:
+ l_order = int(round(math.log(float(obj_size), 2)))
+
+ # Set features
+ feature_bitmask = format_features(features)
+
+ if snapshot_name:
+ s_img.set_snap(snapshot_name)
+
+ s_img.copy(d_ioctx, dest_image_name, feature_bitmask, l_order,
+ stripe_unit, stripe_count, data_pool)
+ RbdConfiguration(pool_ioctx=d_ioctx, image_name=dest_image_name).set_configuration(
+ configuration)
+ if metadata:
+ with rbd.Image(d_ioctx, dest_image_name) as image:
+ RbdImageMetadataService(image).set_metadata(metadata)
+
+ return rbd_call(dest_pool_name, dest_namespace, _copy)
+
+ return rbd_image_call(pool_name, namespace, image_name, _src_copy)
+
+ @classmethod
+ @ttl_cache_invalidator(RBD_IMAGE_REFS_CACHE_REFERENCE)
+ def flatten(cls, image_spec):
+ def _flatten(ioctx, image):
+ image.flatten()
+
+ pool_name, namespace, image_name = parse_image_spec(image_spec)
+ return rbd_image_call(pool_name, namespace, image_name, _flatten)
+
+ @classmethod
+ def move_image_to_trash(cls, image_spec, delay):
+ pool_name, namespace, image_name = parse_image_spec(image_spec)
+ rbd_inst = cls._rbd_inst
+ return rbd_call(pool_name, namespace, rbd_inst.trash_move, image_name, delay)
+
class RbdSnapshotService(object):