1 # -*- coding: utf-8 -*-
2 # pylint: disable=unused-argument
3 from __future__
import absolute_import
9 from ..tools
import ViewCache
10 from .ceph_service
import CephService
13 from typing
import List
15 pass # For typing only
18 RBD_FEATURES_NAME_MAPPING
= {
19 rbd
.RBD_FEATURE_LAYERING
: "layering",
20 rbd
.RBD_FEATURE_STRIPINGV2
: "striping",
21 rbd
.RBD_FEATURE_EXCLUSIVE_LOCK
: "exclusive-lock",
22 rbd
.RBD_FEATURE_OBJECT_MAP
: "object-map",
23 rbd
.RBD_FEATURE_FAST_DIFF
: "fast-diff",
24 rbd
.RBD_FEATURE_DEEP_FLATTEN
: "deep-flatten",
25 rbd
.RBD_FEATURE_JOURNALING
: "journaling",
26 rbd
.RBD_FEATURE_DATA_POOL
: "data-pool",
27 rbd
.RBD_FEATURE_OPERATIONS
: "operations",
31 def format_bitmask(features
):
35 @DISABLEDOCTEST: >>> format_bitmask(45)
36 ['deep-flatten', 'exclusive-lock', 'layering', 'object-map']
38 names
= [val
for key
, val
in RBD_FEATURES_NAME_MAPPING
.items()
39 if key
& features
== key
]
43 def format_features(features
):
45 Converts the features list to bitmask:
47 @DISABLEDOCTEST: >>> format_features(['deep-flatten', 'exclusive-lock',
48 'layering', 'object-map'])
51 @DISABLEDOCTEST: >>> format_features(None) is None
54 @DISABLEDOCTEST: >>> format_features('deep-flatten, exclusive-lock')
57 if isinstance(features
, str):
58 features
= features
.split(',')
60 if not isinstance(features
, list):
64 for key
, value
in RBD_FEATURES_NAME_MAPPING
.items():
70 def get_image_spec(pool_name
, namespace
, rbd_name
):
71 namespace
= '{}/'.format(namespace
) if namespace
else ''
72 return '{}/{}{}'.format(pool_name
, namespace
, rbd_name
)
75 def parse_image_spec(image_spec
):
76 namespace_spec
, image_name
= image_spec
.rsplit('/', 1)
77 if '/' in namespace_spec
:
78 pool_name
, namespace
= namespace_spec
.rsplit('/', 1)
80 pool_name
, namespace
= namespace_spec
, None
81 return pool_name
, namespace
, image_name
84 def rbd_call(pool_name
, namespace
, func
, *args
, **kwargs
):
85 with mgr
.rados
.open_ioctx(pool_name
) as ioctx
:
86 ioctx
.set_namespace(namespace
if namespace
is not None else '')
87 func(ioctx
, *args
, **kwargs
)
90 def rbd_image_call(pool_name
, namespace
, image_name
, func
, *args
, **kwargs
):
91 def _ioctx_func(ioctx
, image_name
, func
, *args
, **kwargs
):
92 with rbd
.Image(ioctx
, image_name
) as img
:
93 func(ioctx
, img
, *args
, **kwargs
)
95 return rbd_call(pool_name
, namespace
, _ioctx_func
, image_name
, func
, *args
, **kwargs
)
98 class RbdConfiguration(object):
101 def __init__(self
, pool_name
='', namespace
='', image_name
='', pool_ioctx
=None,
103 # type: (str, str, str, object, object) -> None
104 assert bool(pool_name
) != bool(pool_ioctx
) # xor
105 self
._pool
_name
= pool_name
106 self
._namespace
= namespace
if namespace
is not None else ''
107 self
._image
_name
= image_name
108 self
._pool
_ioctx
= pool_ioctx
109 self
._image
_ioctx
= image_ioctx
112 def _ensure_prefix(option
):
114 return option
if option
.startswith('conf_') else 'conf_' + option
117 # type: () -> List[dict]
119 if self
._image
_name
: # image config
121 with rbd
.Image(ioctx
, self
._image
_name
) as image
:
122 result
= image
.config_list()
123 except rbd
.ImageNotFound
:
126 pg_status
= list(CephService
.get_pool_pg_status(self
._pool
_name
).keys())
127 if len(pg_status
) == 1 and 'incomplete' in pg_status
[0]:
128 # If config_list would be called with ioctx if it's a bad pool,
129 # the dashboard would stop working, waiting for the response
130 # that would not happen.
132 # This is only a workaround for https://tracker.ceph.com/issues/43771 which
133 # already got rejected as not worth the effort.
135 # Are more complete workaround for the dashboard will be implemented with
136 # https://tracker.ceph.com/issues/44224
138 # @TODO: If #44224 is addressed remove this workaround
140 result
= self
._rbd
.config_list(ioctx
)
144 ioctx
= mgr
.rados
.open_ioctx(self
._pool
_name
)
145 ioctx
.set_namespace(self
._namespace
)
147 ioctx
= self
._pool
_ioctx
151 def get(self
, option_name
):
153 option_name
= self
._ensure
_prefix
(option_name
)
154 with mgr
.rados
.open_ioctx(self
._pool
_name
) as pool_ioctx
:
155 pool_ioctx
.set_namespace(self
._namespace
)
157 with rbd
.Image(pool_ioctx
, self
._image
_name
) as image
:
158 return image
.metadata_get(option_name
)
159 return self
._rbd
.pool_metadata_get(pool_ioctx
, option_name
)
161 def set(self
, option_name
, option_value
):
162 # type: (str, str) -> None
164 option_value
= str(option_value
)
165 option_name
= self
._ensure
_prefix
(option_name
)
167 pool_ioctx
= self
._pool
_ioctx
168 if self
._pool
_name
: # open ioctx
169 pool_ioctx
= mgr
.rados
.open_ioctx(self
._pool
_name
)
170 pool_ioctx
.__enter
__() # type: ignore
171 pool_ioctx
.set_namespace(self
._namespace
) # type: ignore
173 image_ioctx
= self
._image
_ioctx
175 image_ioctx
= rbd
.Image(pool_ioctx
, self
._image
_name
)
176 image_ioctx
.__enter
__() # type: ignore
179 image_ioctx
.metadata_set(option_name
, option_value
) # type: ignore
181 self
._rbd
.pool_metadata_set(pool_ioctx
, option_name
, option_value
)
183 if self
._image
_name
: # Name provided, so we opened it and now have to close it
184 image_ioctx
.__exit
__(None, None, None) # type: ignore
186 pool_ioctx
.__exit
__(None, None, None) # type: ignore
188 def remove(self
, option_name
):
190 Removes an option by name. Will not raise an error, if the option hasn't been found.
191 :type option_name str
196 with rbd
.Image(ioctx
, self
._image
_name
) as image
:
197 image
.metadata_remove(option_name
)
199 self
._rbd
.pool_metadata_remove(ioctx
, option_name
)
203 option_name
= self
._ensure
_prefix
(option_name
)
206 with mgr
.rados
.open_ioctx(self
._pool
_name
) as pool_ioctx
:
207 pool_ioctx
.set_namespace(self
._namespace
)
210 _remove(self
._pool
_ioctx
)
212 def set_configuration(self
, configuration
):
214 for option_name
, option_value
in configuration
.items():
215 if option_value
is not None:
216 self
.set(option_name
, option_value
)
218 self
.remove(option_name
)
221 class RbdService(object):
224 def _rbd_disk_usage(cls
, image
, snaps
, whole_object
=True):
225 class DUCallback(object):
229 def __call__(self
, offset
, length
, exists
):
231 self
.used_size
+= length
236 for _
, size
, name
in snaps
:
238 du_callb
= DUCallback()
239 image
.diff_iterate(0, size
, prev_snap
, du_callb
,
240 whole_object
=whole_object
)
241 snap_map
[name
] = du_callb
.used_size
242 total_used_size
+= du_callb
.used_size
245 return total_used_size
, snap_map
248 def _rbd_image(cls
, ioctx
, pool_name
, namespace
, image_name
):
249 with rbd
.Image(ioctx
, image_name
) as img
:
252 stat
['name'] = image_name
254 stat
['unique_id'] = get_image_spec(pool_name
, namespace
, stat
['block_name_prefix'])
255 stat
['id'] = stat
['unique_id']
256 stat
['image_format'] = 1
258 stat
['unique_id'] = get_image_spec(pool_name
, namespace
, img
.id())
259 stat
['id'] = img
.id()
260 stat
['image_format'] = 2
262 stat
['pool_name'] = pool_name
263 stat
['namespace'] = namespace
264 features
= img
.features()
265 stat
['features'] = features
266 stat
['features_name'] = format_bitmask(features
)
268 # the following keys are deprecated
269 del stat
['parent_pool']
270 del stat
['parent_name']
272 stat
['timestamp'] = "{}Z".format(img
.create_timestamp()
275 stat
['stripe_count'] = img
.stripe_count()
276 stat
['stripe_unit'] = img
.stripe_unit()
278 data_pool_name
= CephService
.get_pool_name_from_id(
280 if data_pool_name
== pool_name
:
281 data_pool_name
= None
282 stat
['data_pool'] = data_pool_name
285 stat
['parent'] = img
.get_parent_image_spec()
286 except rbd
.ImageNotFound
:
288 stat
['parent'] = None
291 stat
['snapshots'] = []
292 for snap
in img
.list_snaps():
293 snap
['timestamp'] = "{}Z".format(
294 img
.get_snap_timestamp(snap
['id']).isoformat())
295 snap
['is_protected'] = img
.is_protected_snap(snap
['name'])
296 snap
['used_bytes'] = None
297 snap
['children'] = []
298 img
.set_snap(snap
['name'])
299 for child_pool_name
, child_image_name
in img
.list_children():
300 snap
['children'].append({
301 'pool_name': child_pool_name
,
302 'image_name': child_image_name
304 stat
['snapshots'].append(snap
)
307 img_flags
= img
.flags()
308 if 'fast-diff' in stat
['features_name'] and \
309 not rbd
.RBD_FLAG_FAST_DIFF_INVALID
& img_flags
:
310 snaps
= [(s
['id'], s
['size'], s
['name'])
311 for s
in stat
['snapshots']]
312 snaps
.sort(key
=lambda s
: s
[0])
313 snaps
+= [(snaps
[-1][0] + 1 if snaps
else 0, stat
['size'], None)]
314 total_prov_bytes
, snaps_prov_bytes
= cls
._rbd
_disk
_usage
(
316 stat
['total_disk_usage'] = total_prov_bytes
317 for snap
, prov_bytes
in snaps_prov_bytes
.items():
319 stat
['disk_usage'] = prov_bytes
321 for ss
in stat
['snapshots']:
322 if ss
['name'] == snap
:
323 ss
['disk_usage'] = prov_bytes
326 stat
['total_disk_usage'] = None
327 stat
['disk_usage'] = None
329 stat
['configuration'] = RbdConfiguration(pool_ioctx
=ioctx
, image_name
=image_name
).list()
334 def _rbd_image_names(cls
, ioctx
):
336 return rbd_inst
.list(ioctx
)
339 def _rbd_image_stat(cls
, ioctx
, pool_name
, namespace
, image_name
):
340 return cls
._rbd
_image
(ioctx
, pool_name
, namespace
, image_name
)
344 def rbd_pool_list(cls
, pool_name
, namespace
=None):
346 with mgr
.rados
.open_ioctx(pool_name
) as ioctx
:
349 namespaces
= [namespace
]
351 namespaces
= rbd_inst
.namespace_list(ioctx
)
352 # images without namespace
353 namespaces
.append('')
354 for current_namespace
in namespaces
:
355 ioctx
.set_namespace(current_namespace
)
356 names
= cls
._rbd
_image
_names
(ioctx
)
359 stat
= cls
._rbd
_image
_stat
(ioctx
, pool_name
, current_namespace
, name
)
360 except rbd
.ImageNotFound
:
361 # may have been removed in the meanwhile
367 def get_image(cls
, image_spec
):
368 pool_name
, namespace
, image_name
= parse_image_spec(image_spec
)
369 ioctx
= mgr
.rados
.open_ioctx(pool_name
)
371 ioctx
.set_namespace(namespace
)
373 return cls
._rbd
_image
(ioctx
, pool_name
, namespace
, image_name
)
374 except rbd
.ImageNotFound
:
375 raise cherrypy
.HTTPError(404, 'Image not found')
378 class RbdSnapshotService(object):
381 def remove_snapshot(cls
, image_spec
, snapshot_name
, unprotect
=False):
382 def _remove_snapshot(ioctx
, img
, snapshot_name
, unprotect
):
384 img
.unprotect_snap(snapshot_name
)
385 img
.remove_snap(snapshot_name
)
387 pool_name
, namespace
, image_name
= parse_image_spec(image_spec
)
388 return rbd_image_call(pool_name
, namespace
, image_name
,
389 _remove_snapshot
, snapshot_name
, unprotect
)