summaryrefslogtreecommitdiffstats
path: root/src/pybind/mgr/dashboard/services/rbd.py
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 18:24:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-27 18:24:20 +0000
commit483eb2f56657e8e7f419ab1a4fab8dce9ade8609 (patch)
treee5d88d25d870d5dedacb6bbdbe2a966086a0a5cf /src/pybind/mgr/dashboard/services/rbd.py
parentInitial commit. (diff)
downloadceph-upstream.tar.xz
ceph-upstream.zip
Adding upstream version 14.2.21.upstream/14.2.21upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/pybind/mgr/dashboard/services/rbd.py')
-rw-r--r--src/pybind/mgr/dashboard/services/rbd.py177
1 files changed, 177 insertions, 0 deletions
diff --git a/src/pybind/mgr/dashboard/services/rbd.py b/src/pybind/mgr/dashboard/services/rbd.py
new file mode 100644
index 00000000..55c6f542
--- /dev/null
+++ b/src/pybind/mgr/dashboard/services/rbd.py
@@ -0,0 +1,177 @@
+# -*- coding: utf-8 -*-
+from __future__ import absolute_import
+
+import six
+
+import rbd
+
+from .. import mgr
+from .ceph_service import CephService
+
+
+RBD_FEATURES_NAME_MAPPING = {
+ rbd.RBD_FEATURE_LAYERING: "layering",
+ rbd.RBD_FEATURE_STRIPINGV2: "striping",
+ rbd.RBD_FEATURE_EXCLUSIVE_LOCK: "exclusive-lock",
+ rbd.RBD_FEATURE_OBJECT_MAP: "object-map",
+ rbd.RBD_FEATURE_FAST_DIFF: "fast-diff",
+ rbd.RBD_FEATURE_DEEP_FLATTEN: "deep-flatten",
+ rbd.RBD_FEATURE_JOURNALING: "journaling",
+ rbd.RBD_FEATURE_DATA_POOL: "data-pool",
+ rbd.RBD_FEATURE_OPERATIONS: "operations",
+}
+
+
+def format_bitmask(features):
+ """
+ Formats the bitmask:
+
+ >>> format_bitmask(45)
+ ['deep-flatten', 'exclusive-lock', 'layering', 'object-map']
+ """
+ names = [val for key, val in RBD_FEATURES_NAME_MAPPING.items()
+ if key & features == key]
+ return sorted(names)
+
+
+def format_features(features):
+ """
+ Converts the features list to bitmask:
+
+ >>> format_features(['deep-flatten', 'exclusive-lock', 'layering', 'object-map'])
+ 45
+
+ >>> format_features(None) is None
+ True
+
+ >>> format_features('deep-flatten, exclusive-lock')
+ 32
+ """
+ if isinstance(features, six.string_types):
+ features = features.split(',')
+
+ if not isinstance(features, list):
+ return None
+
+ res = 0
+ for key, value in RBD_FEATURES_NAME_MAPPING.items():
+ if value in features:
+ res = key | res
+ return res
+
+
+class RbdConfiguration(object):
+ _rbd = rbd.RBD()
+
+ def __init__(self, pool_name='', image_name='', pool_ioctx=None, image_ioctx=None):
+ # type: (str, str, object, object) -> None
+ assert bool(pool_name) != bool(pool_ioctx) # xor
+ self._pool_name = pool_name
+ self._image_name = image_name
+ self._pool_ioctx = pool_ioctx
+ self._image_ioctx = image_ioctx
+
+ @staticmethod
+ def _ensure_prefix(option):
+ # type: (str) -> str
+ return option if option.startswith('conf_') else 'conf_' + option
+
+ def list(self):
+ # type: () -> [dict]
+ def _list(ioctx):
+ if self._image_name: # image config
+ try:
+ with rbd.Image(ioctx, self._image_name) as image:
+ result = image.config_list()
+ except rbd.ImageNotFound:
+ result = []
+ else: # pool config
+ pg_status = list(CephService.get_pool_pg_status(self._pool_name).keys())
+ if len(pg_status) == 1 and 'incomplete' in pg_status[0]:
+ # If config_list would be called with ioctx if it's a bad pool,
+ # the dashboard would stop working, waiting for the response
+ # that would not happen.
+ #
+ # This is only a workaround for https://tracker.ceph.com/issues/43771 which
+ # already got rejected as not worth the effort.
+ #
+ # Are more complete workaround for the dashboard will be implemented with
+ # https://tracker.ceph.com/issues/44224
+ #
+ # @TODO: If #44224 is addressed remove this workaround
+ return []
+ result = self._rbd.config_list(ioctx)
+ return list(result)
+
+ if self._pool_name:
+ ioctx = mgr.rados.open_ioctx(self._pool_name)
+ else:
+ ioctx = self._pool_ioctx
+
+ return _list(ioctx)
+
+ def get(self, option_name):
+ # type: (str) -> str
+ option_name = self._ensure_prefix(option_name)
+ with mgr.rados.open_ioctx(self._pool_name) as pool_ioctx:
+ if self._image_name:
+ with rbd.Image(pool_ioctx, self._image_name) as image:
+ return image.metadata_get(option_name)
+ return self._rbd.pool_metadata_get(pool_ioctx, option_name)
+
+ def set(self, option_name, option_value):
+ # type: (str, str) -> None
+
+ option_value = str(option_value)
+ option_name = self._ensure_prefix(option_name)
+
+ pool_ioctx = self._pool_ioctx
+ if self._pool_name: # open ioctx
+ pool_ioctx = mgr.rados.open_ioctx(self._pool_name)
+ pool_ioctx.__enter__()
+
+ image_ioctx = self._image_ioctx
+ if self._image_name:
+ image_ioctx = rbd.Image(pool_ioctx, self._image_name)
+ image_ioctx.__enter__()
+
+ if image_ioctx:
+ image_ioctx.metadata_set(option_name, option_value)
+ else:
+ self._rbd.pool_metadata_set(pool_ioctx, option_name, option_value)
+
+ if self._image_name: # Name provided, so we opened it and now have to close it
+ image_ioctx.__exit__(None, None, None)
+ if self._pool_name:
+ pool_ioctx.__exit__(None, None, None)
+
+ def remove(self, option_name):
+ """
+ Removes an option by name. Will not raise an error, if the option hasn't been found.
+ :type option_name str
+ """
+ def _remove(ioctx):
+ try:
+ if self._image_name:
+ with rbd.Image(ioctx, self._image_name) as image:
+ image.metadata_remove(option_name)
+ else:
+ self._rbd.pool_metadata_remove(ioctx, option_name)
+ except KeyError:
+ pass
+
+ option_name = self._ensure_prefix(option_name)
+
+ if self._pool_name:
+ with mgr.rados.open_ioctx(self._pool_name) as pool_ioctx:
+ _remove(pool_ioctx)
+ else:
+ _remove(self._pool_ioctx)
+
+ def set_configuration(self, configuration):
+ if configuration:
+ for option_name, option_value in configuration.items():
+ if option_value is not None:
+ self.set(option_name, option_value)
+ else:
+ self.remove(option_name)