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 result
= self
._rbd
.config_list(ioctx
)
133 ioctx
= mgr
.rados
.open_ioctx(self
._pool
_name
)
134 ioctx
.set_namespace(self
._namespace
)
136 ioctx
= self
._pool
_ioctx
140 def get(self
, option_name
):
142 option_name
= self
._ensure
_prefix
(option_name
)
143 with mgr
.rados
.open_ioctx(self
._pool
_name
) as pool_ioctx
:
144 pool_ioctx
.set_namespace(self
._namespace
)
146 with rbd
.Image(pool_ioctx
, self
._image
_name
) as image
:
147 return image
.metadata_get(option_name
)
148 return self
._rbd
.pool_metadata_get(pool_ioctx
, option_name
)
150 def set(self
, option_name
, option_value
):
151 # type: (str, str) -> None
153 option_value
= str(option_value
)
154 option_name
= self
._ensure
_prefix
(option_name
)
156 pool_ioctx
= self
._pool
_ioctx
157 if self
._pool
_name
: # open ioctx
158 pool_ioctx
= mgr
.rados
.open_ioctx(self
._pool
_name
)
159 pool_ioctx
.__enter
__() # type: ignore
160 pool_ioctx
.set_namespace(self
._namespace
) # type: ignore
162 image_ioctx
= self
._image
_ioctx
164 image_ioctx
= rbd
.Image(pool_ioctx
, self
._image
_name
)
165 image_ioctx
.__enter
__() # type: ignore
168 image_ioctx
.metadata_set(option_name
, option_value
) # type: ignore
170 self
._rbd
.pool_metadata_set(pool_ioctx
, option_name
, option_value
)
172 if self
._image
_name
: # Name provided, so we opened it and now have to close it
173 image_ioctx
.__exit
__(None, None, None) # type: ignore
175 pool_ioctx
.__exit
__(None, None, None) # type: ignore
177 def remove(self
, option_name
):
179 Removes an option by name. Will not raise an error, if the option hasn't been found.
180 :type option_name str
185 with rbd
.Image(ioctx
, self
._image
_name
) as image
:
186 image
.metadata_remove(option_name
)
188 self
._rbd
.pool_metadata_remove(ioctx
, option_name
)
192 option_name
= self
._ensure
_prefix
(option_name
)
195 with mgr
.rados
.open_ioctx(self
._pool
_name
) as pool_ioctx
:
196 pool_ioctx
.set_namespace(self
._namespace
)
199 _remove(self
._pool
_ioctx
)
201 def set_configuration(self
, configuration
):
203 for option_name
, option_value
in configuration
.items():
204 if option_value
is not None:
205 self
.set(option_name
, option_value
)
207 self
.remove(option_name
)
210 class RbdService(object):
213 def _rbd_disk_usage(cls
, image
, snaps
, whole_object
=True):
214 class DUCallback(object):
218 def __call__(self
, offset
, length
, exists
):
220 self
.used_size
+= length
225 for _
, size
, name
in snaps
:
227 du_callb
= DUCallback()
228 image
.diff_iterate(0, size
, prev_snap
, du_callb
,
229 whole_object
=whole_object
)
230 snap_map
[name
] = du_callb
.used_size
231 total_used_size
+= du_callb
.used_size
234 return total_used_size
, snap_map
237 def _rbd_image(cls
, ioctx
, pool_name
, namespace
, image_name
):
238 with rbd
.Image(ioctx
, image_name
) as img
:
241 stat
['name'] = image_name
242 stat
['id'] = img
.id()
243 stat
['pool_name'] = pool_name
244 stat
['namespace'] = namespace
245 features
= img
.features()
246 stat
['features'] = features
247 stat
['features_name'] = format_bitmask(features
)
249 # the following keys are deprecated
250 del stat
['parent_pool']
251 del stat
['parent_name']
253 stat
['timestamp'] = "{}Z".format(img
.create_timestamp()
256 stat
['stripe_count'] = img
.stripe_count()
257 stat
['stripe_unit'] = img
.stripe_unit()
259 data_pool_name
= CephService
.get_pool_name_from_id(
261 if data_pool_name
== pool_name
:
262 data_pool_name
= None
263 stat
['data_pool'] = data_pool_name
266 stat
['parent'] = img
.get_parent_image_spec()
267 except rbd
.ImageNotFound
:
269 stat
['parent'] = None
272 stat
['snapshots'] = []
273 for snap
in img
.list_snaps():
274 snap
['timestamp'] = "{}Z".format(
275 img
.get_snap_timestamp(snap
['id']).isoformat())
276 snap
['is_protected'] = img
.is_protected_snap(snap
['name'])
277 snap
['used_bytes'] = None
278 snap
['children'] = []
279 img
.set_snap(snap
['name'])
280 for child_pool_name
, child_image_name
in img
.list_children():
281 snap
['children'].append({
282 'pool_name': child_pool_name
,
283 'image_name': child_image_name
285 stat
['snapshots'].append(snap
)
288 img_flags
= img
.flags()
289 if 'fast-diff' in stat
['features_name'] and \
290 not rbd
.RBD_FLAG_FAST_DIFF_INVALID
& img_flags
:
291 snaps
= [(s
['id'], s
['size'], s
['name'])
292 for s
in stat
['snapshots']]
293 snaps
.sort(key
=lambda s
: s
[0])
294 snaps
+= [(snaps
[-1][0] + 1 if snaps
else 0, stat
['size'], None)]
295 total_prov_bytes
, snaps_prov_bytes
= cls
._rbd
_disk
_usage
(
297 stat
['total_disk_usage'] = total_prov_bytes
298 for snap
, prov_bytes
in snaps_prov_bytes
.items():
300 stat
['disk_usage'] = prov_bytes
302 for ss
in stat
['snapshots']:
303 if ss
['name'] == snap
:
304 ss
['disk_usage'] = prov_bytes
307 stat
['total_disk_usage'] = None
308 stat
['disk_usage'] = None
310 stat
['configuration'] = RbdConfiguration(pool_ioctx
=ioctx
, image_name
=image_name
).list()
315 def _rbd_image_names(cls
, ioctx
):
317 return rbd_inst
.list(ioctx
)
320 def _rbd_image_stat(cls
, ioctx
, pool_name
, namespace
, image_name
):
321 return cls
._rbd
_image
(ioctx
, pool_name
, namespace
, image_name
)
325 def rbd_pool_list(cls
, pool_name
, namespace
=None):
327 with mgr
.rados
.open_ioctx(pool_name
) as ioctx
:
330 namespaces
= [namespace
]
332 namespaces
= rbd_inst
.namespace_list(ioctx
)
333 # images without namespace
334 namespaces
.append('')
335 for current_namespace
in namespaces
:
336 ioctx
.set_namespace(current_namespace
)
337 names
= cls
._rbd
_image
_names
(ioctx
)
340 stat
= cls
._rbd
_image
_stat
(ioctx
, pool_name
, current_namespace
, name
)
341 except rbd
.ImageNotFound
:
342 # may have been removed in the meanwhile
348 def get_image(cls
, image_spec
):
349 pool_name
, namespace
, image_name
= parse_image_spec(image_spec
)
350 ioctx
= mgr
.rados
.open_ioctx(pool_name
)
352 ioctx
.set_namespace(namespace
)
354 return cls
._rbd
_image
(ioctx
, pool_name
, namespace
, image_name
)
355 except rbd
.ImageNotFound
:
356 raise cherrypy
.HTTPError(404, 'Image not found')
359 class RbdSnapshotService(object):
362 def remove_snapshot(cls
, image_spec
, snapshot_name
, unprotect
=False):
363 def _remove_snapshot(ioctx
, img
, snapshot_name
, unprotect
):
365 img
.unprotect_snap(snapshot_name
)
366 img
.remove_snap(snapshot_name
)
368 pool_name
, namespace
, image_name
= parse_image_spec(image_spec
)
369 return rbd_image_call(pool_name
, namespace
, image_name
,
370 _remove_snapshot
, snapshot_name
, unprotect
)