1 # -*- coding: utf-8 -*-
2 # pylint: disable=unused-argument
3 from __future__
import absolute_import
12 from ..tools
import ViewCache
13 from .ceph_service
import CephService
16 from typing
import List
18 pass # For typing only
21 RBD_FEATURES_NAME_MAPPING
= {
22 rbd
.RBD_FEATURE_LAYERING
: "layering",
23 rbd
.RBD_FEATURE_STRIPINGV2
: "striping",
24 rbd
.RBD_FEATURE_EXCLUSIVE_LOCK
: "exclusive-lock",
25 rbd
.RBD_FEATURE_OBJECT_MAP
: "object-map",
26 rbd
.RBD_FEATURE_FAST_DIFF
: "fast-diff",
27 rbd
.RBD_FEATURE_DEEP_FLATTEN
: "deep-flatten",
28 rbd
.RBD_FEATURE_JOURNALING
: "journaling",
29 rbd
.RBD_FEATURE_DATA_POOL
: "data-pool",
30 rbd
.RBD_FEATURE_OPERATIONS
: "operations",
34 def format_bitmask(features
):
38 @DISABLEDOCTEST: >>> format_bitmask(45)
39 ['deep-flatten', 'exclusive-lock', 'layering', 'object-map']
41 names
= [val
for key
, val
in RBD_FEATURES_NAME_MAPPING
.items()
42 if key
& features
== key
]
46 def format_features(features
):
48 Converts the features list to bitmask:
50 @DISABLEDOCTEST: >>> format_features(['deep-flatten', 'exclusive-lock',
51 'layering', 'object-map'])
54 @DISABLEDOCTEST: >>> format_features(None) is None
57 @DISABLEDOCTEST: >>> format_features('deep-flatten, exclusive-lock')
60 if isinstance(features
, six
.string_types
):
61 features
= features
.split(',')
63 if not isinstance(features
, list):
67 for key
, value
in RBD_FEATURES_NAME_MAPPING
.items():
73 def get_image_spec(pool_name
, namespace
, rbd_name
):
74 namespace
= '{}/'.format(namespace
) if namespace
else ''
75 return '{}/{}{}'.format(pool_name
, namespace
, rbd_name
)
78 def parse_image_spec(image_spec
):
79 namespace_spec
, image_name
= image_spec
.rsplit('/', 1)
80 if '/' in namespace_spec
:
81 pool_name
, namespace
= namespace_spec
.rsplit('/', 1)
83 pool_name
, namespace
= namespace_spec
, None
84 return pool_name
, namespace
, image_name
87 def rbd_call(pool_name
, namespace
, func
, *args
, **kwargs
):
88 with mgr
.rados
.open_ioctx(pool_name
) as ioctx
:
89 ioctx
.set_namespace(namespace
if namespace
is not None else '')
90 func(ioctx
, *args
, **kwargs
)
93 def rbd_image_call(pool_name
, namespace
, image_name
, func
, *args
, **kwargs
):
94 def _ioctx_func(ioctx
, image_name
, func
, *args
, **kwargs
):
95 with rbd
.Image(ioctx
, image_name
) as img
:
96 func(ioctx
, img
, *args
, **kwargs
)
98 return rbd_call(pool_name
, namespace
, _ioctx_func
, image_name
, func
, *args
, **kwargs
)
101 class RbdConfiguration(object):
104 def __init__(self
, pool_name
='', namespace
='', image_name
='', pool_ioctx
=None,
106 # type: (str, str, str, object, object) -> None
107 assert bool(pool_name
) != bool(pool_ioctx
) # xor
108 self
._pool
_name
= pool_name
109 self
._namespace
= namespace
if namespace
is not None else ''
110 self
._image
_name
= image_name
111 self
._pool
_ioctx
= pool_ioctx
112 self
._image
_ioctx
= image_ioctx
115 def _ensure_prefix(option
):
117 return option
if option
.startswith('conf_') else 'conf_' + option
120 # type: () -> List[dict]
122 if self
._image
_name
: # image config
124 with rbd
.Image(ioctx
, self
._image
_name
) as image
:
125 result
= image
.config_list()
126 except rbd
.ImageNotFound
:
129 pg_status
= list(CephService
.get_pool_pg_status(self
._pool
_name
).keys())
130 if len(pg_status
) == 1 and 'incomplete' in pg_status
[0]:
131 # If config_list would be called with ioctx if it's a bad pool,
132 # the dashboard would stop working, waiting for the response
133 # that would not happen.
135 # This is only a workaround for https://tracker.ceph.com/issues/43771 which
136 # already got rejected as not worth the effort.
138 # Are more complete workaround for the dashboard will be implemented with
139 # https://tracker.ceph.com/issues/44224
141 # @TODO: If #44224 is addressed remove this workaround
143 result
= self
._rbd
.config_list(ioctx
)
147 ioctx
= mgr
.rados
.open_ioctx(self
._pool
_name
)
148 ioctx
.set_namespace(self
._namespace
)
150 ioctx
= self
._pool
_ioctx
154 def get(self
, option_name
):
156 option_name
= self
._ensure
_prefix
(option_name
)
157 with mgr
.rados
.open_ioctx(self
._pool
_name
) as pool_ioctx
:
158 pool_ioctx
.set_namespace(self
._namespace
)
160 with rbd
.Image(pool_ioctx
, self
._image
_name
) as image
:
161 return image
.metadata_get(option_name
)
162 return self
._rbd
.pool_metadata_get(pool_ioctx
, option_name
)
164 def set(self
, option_name
, option_value
):
165 # type: (str, str) -> None
167 option_value
= str(option_value
)
168 option_name
= self
._ensure
_prefix
(option_name
)
170 pool_ioctx
= self
._pool
_ioctx
171 if self
._pool
_name
: # open ioctx
172 pool_ioctx
= mgr
.rados
.open_ioctx(self
._pool
_name
)
173 pool_ioctx
.__enter
__() # type: ignore
174 pool_ioctx
.set_namespace(self
._namespace
) # type: ignore
176 image_ioctx
= self
._image
_ioctx
178 image_ioctx
= rbd
.Image(pool_ioctx
, self
._image
_name
)
179 image_ioctx
.__enter
__() # type: ignore
182 image_ioctx
.metadata_set(option_name
, option_value
) # type: ignore
184 self
._rbd
.pool_metadata_set(pool_ioctx
, option_name
, option_value
)
186 if self
._image
_name
: # Name provided, so we opened it and now have to close it
187 image_ioctx
.__exit
__(None, None, None) # type: ignore
189 pool_ioctx
.__exit
__(None, None, None) # type: ignore
191 def remove(self
, option_name
):
193 Removes an option by name. Will not raise an error, if the option hasn't been found.
194 :type option_name str
199 with rbd
.Image(ioctx
, self
._image
_name
) as image
:
200 image
.metadata_remove(option_name
)
202 self
._rbd
.pool_metadata_remove(ioctx
, option_name
)
206 option_name
= self
._ensure
_prefix
(option_name
)
209 with mgr
.rados
.open_ioctx(self
._pool
_name
) as pool_ioctx
:
210 pool_ioctx
.set_namespace(self
._namespace
)
213 _remove(self
._pool
_ioctx
)
215 def set_configuration(self
, configuration
):
217 for option_name
, option_value
in configuration
.items():
218 if option_value
is not None:
219 self
.set(option_name
, option_value
)
221 self
.remove(option_name
)
224 class RbdService(object):
227 def _rbd_disk_usage(cls
, image
, snaps
, whole_object
=True):
228 class DUCallback(object):
232 def __call__(self
, offset
, length
, exists
):
234 self
.used_size
+= length
239 for _
, size
, name
in snaps
:
241 du_callb
= DUCallback()
242 image
.diff_iterate(0, size
, prev_snap
, du_callb
,
243 whole_object
=whole_object
)
244 snap_map
[name
] = du_callb
.used_size
245 total_used_size
+= du_callb
.used_size
248 return total_used_size
, snap_map
251 def _rbd_image(cls
, ioctx
, pool_name
, namespace
, image_name
):
252 with rbd
.Image(ioctx
, image_name
) as img
:
255 stat
['name'] = image_name
256 stat
['id'] = img
.id()
257 stat
['pool_name'] = pool_name
258 stat
['namespace'] = namespace
259 features
= img
.features()
260 stat
['features'] = features
261 stat
['features_name'] = format_bitmask(features
)
263 # the following keys are deprecated
264 del stat
['parent_pool']
265 del stat
['parent_name']
267 stat
['timestamp'] = "{}Z".format(img
.create_timestamp()
270 stat
['stripe_count'] = img
.stripe_count()
271 stat
['stripe_unit'] = img
.stripe_unit()
273 data_pool_name
= CephService
.get_pool_name_from_id(
275 if data_pool_name
== pool_name
:
276 data_pool_name
= None
277 stat
['data_pool'] = data_pool_name
280 stat
['parent'] = img
.get_parent_image_spec()
281 except rbd
.ImageNotFound
:
283 stat
['parent'] = None
286 stat
['snapshots'] = []
287 for snap
in img
.list_snaps():
288 snap
['timestamp'] = "{}Z".format(
289 img
.get_snap_timestamp(snap
['id']).isoformat())
290 snap
['is_protected'] = img
.is_protected_snap(snap
['name'])
291 snap
['used_bytes'] = None
292 snap
['children'] = []
293 img
.set_snap(snap
['name'])
294 for child_pool_name
, child_image_name
in img
.list_children():
295 snap
['children'].append({
296 'pool_name': child_pool_name
,
297 'image_name': child_image_name
299 stat
['snapshots'].append(snap
)
302 img_flags
= img
.flags()
303 if 'fast-diff' in stat
['features_name'] and \
304 not rbd
.RBD_FLAG_FAST_DIFF_INVALID
& img_flags
:
305 snaps
= [(s
['id'], s
['size'], s
['name'])
306 for s
in stat
['snapshots']]
307 snaps
.sort(key
=lambda s
: s
[0])
308 snaps
+= [(snaps
[-1][0] + 1 if snaps
else 0, stat
['size'], None)]
309 total_prov_bytes
, snaps_prov_bytes
= cls
._rbd
_disk
_usage
(
311 stat
['total_disk_usage'] = total_prov_bytes
312 for snap
, prov_bytes
in snaps_prov_bytes
.items():
314 stat
['disk_usage'] = prov_bytes
316 for ss
in stat
['snapshots']:
317 if ss
['name'] == snap
:
318 ss
['disk_usage'] = prov_bytes
321 stat
['total_disk_usage'] = None
322 stat
['disk_usage'] = None
324 stat
['configuration'] = RbdConfiguration(pool_ioctx
=ioctx
, image_name
=image_name
).list()
329 def _rbd_image_names(cls
, ioctx
):
331 return rbd_inst
.list(ioctx
)
334 def _rbd_image_stat(cls
, ioctx
, pool_name
, namespace
, image_name
):
335 return cls
._rbd
_image
(ioctx
, pool_name
, namespace
, image_name
)
339 def rbd_pool_list(cls
, pool_name
, namespace
=None):
341 with mgr
.rados
.open_ioctx(pool_name
) as ioctx
:
344 namespaces
= [namespace
]
346 namespaces
= rbd_inst
.namespace_list(ioctx
)
347 # images without namespace
348 namespaces
.append('')
349 for current_namespace
in namespaces
:
350 ioctx
.set_namespace(current_namespace
)
351 names
= cls
._rbd
_image
_names
(ioctx
)
354 stat
= cls
._rbd
_image
_stat
(ioctx
, pool_name
, current_namespace
, name
)
355 except rbd
.ImageNotFound
:
356 # may have been removed in the meanwhile
362 def get_image(cls
, image_spec
):
363 pool_name
, namespace
, image_name
= parse_image_spec(image_spec
)
364 ioctx
= mgr
.rados
.open_ioctx(pool_name
)
366 ioctx
.set_namespace(namespace
)
368 return cls
._rbd
_image
(ioctx
, pool_name
, namespace
, image_name
)
369 except rbd
.ImageNotFound
:
370 raise cherrypy
.HTTPError(404, 'Image not found')
373 class RbdSnapshotService(object):
376 def remove_snapshot(cls
, image_spec
, snapshot_name
, unprotect
=False):
377 def _remove_snapshot(ioctx
, img
, snapshot_name
, unprotect
):
379 img
.unprotect_snap(snapshot_name
)
380 img
.remove_snap(snapshot_name
)
382 pool_name
, namespace
, image_name
= parse_image_spec(image_spec
)
383 return rbd_image_call(pool_name
, namespace
, image_name
,
384 _remove_snapshot
, snapshot_name
, unprotect
)