1 # vim: expandtab smarttab shiftwidth=4 softtabstop=4
9 from datetime
import datetime
, timedelta
10 from nose
import with_setup
, SkipTest
11 from nose
.tools
import eq_
as eq
, assert_raises
, assert_not_equal
12 from rados
import (Rados
,
13 LIBRADOS_OP_FLAG_FADVISE_DONTNEED
,
14 LIBRADOS_OP_FLAG_FADVISE_NOCACHE
,
15 LIBRADOS_OP_FLAG_FADVISE_RANDOM
)
16 from rbd
import (RBD
, Group
, Image
, ImageNotFound
, InvalidArgument
, ImageExists
,
17 ImageBusy
, ImageHasSnapshots
, ReadOnlyImage
,
18 FunctionNotSupported
, ArgumentOutOfRange
,
19 ECANCELED
, OperationCanceled
,
20 DiskQuotaExceeded
, ConnectionShutdown
, PermissionError
,
21 RBD_FEATURE_LAYERING
, RBD_FEATURE_STRIPINGV2
,
22 RBD_FEATURE_EXCLUSIVE_LOCK
, RBD_FEATURE_JOURNALING
,
23 RBD_MIRROR_MODE_DISABLED
, RBD_MIRROR_MODE_IMAGE
,
24 RBD_MIRROR_MODE_POOL
, RBD_MIRROR_IMAGE_ENABLED
,
25 RBD_MIRROR_IMAGE_DISABLED
, MIRROR_IMAGE_STATUS_STATE_UNKNOWN
,
26 RBD_LOCK_MODE_EXCLUSIVE
, RBD_OPERATION_FEATURE_GROUP
,
27 RBD_SNAP_NAMESPACE_TYPE_TRASH
,
28 RBD_IMAGE_MIGRATION_STATE_PREPARED
, RBD_CONFIG_SOURCE_CONFIG
,
29 RBD_CONFIG_SOURCE_POOL
, RBD_CONFIG_SOURCE_IMAGE
,
30 RBD_MIRROR_PEER_ATTRIBUTE_NAME_MON_HOST
,
31 RBD_MIRROR_PEER_ATTRIBUTE_NAME_KEY
)
44 IMG_SIZE
= 8 << 20 # 8 MiB
45 IMG_ORDER
= 22 # 4 MiB objects
47 os
.environ
["RBD_FORCE_ALLOW_V1"] = "1"
51 rados
= Rados(conffile
='')
54 pool_name
= get_temp_pool_name()
55 rados
.create_pool(pool_name
)
57 ioctx
= rados
.open_ioctx(pool_name
)
58 RBD().pool_init(ioctx
, True)
60 features
= os
.getenv("RBD_FEATURES")
61 features
= int(features
) if features
is not None else 61
63 def teardown_module():
67 rados
.delete_pool(pool_name
)
70 def get_temp_pool_name():
73 return "test-rbd-api-" + socket
.gethostname() + '-' + str(os
.getpid()) + \
76 def get_temp_image_name():
79 return "image" + str(image_idx
)
81 def get_temp_group_name():
84 return "group" + str(group_idx
)
86 def get_temp_snap_name():
89 return "snap" + str(snap_idx
)
93 image_name
= get_temp_image_name()
94 if features
is not None:
95 RBD().create(ioctx
, image_name
, IMG_SIZE
, IMG_ORDER
, old_format
=False,
96 features
=int(features
))
98 RBD().create(ioctx
, image_name
, IMG_SIZE
, IMG_ORDER
, old_format
=True)
102 if image_name
is not None:
103 RBD().remove(ioctx
, image_name
)
107 group_name
= get_temp_group_name()
108 RBD().group_create(ioctx
, group_name
)
111 if group_name
is not None:
112 RBD().group_remove(ioctx
, group_name
)
115 new_group_name
= "new" + group_name
116 RBD().group_rename(ioctx
, group_name
, new_group_name
)
118 def require_new_format():
120 def _require_new_format(*args
, **kwargs
):
124 return fn(*args
, **kwargs
)
125 return functools
.wraps(fn
)(_require_new_format
)
128 def require_features(required_features
):
130 def _require_features(*args
, **kwargs
):
134 for feature
in required_features
:
135 if feature
& features
!= feature
:
137 return fn(*args
, **kwargs
)
138 return functools
.wraps(fn
)(_require_features
)
141 def blacklist_features(blacklisted_features
):
143 def _blacklist_features(*args
, **kwargs
):
145 for feature
in blacklisted_features
:
146 if features
is not None and feature
& features
== feature
:
148 return fn(*args
, **kwargs
)
149 return functools
.wraps(fn
)(_blacklist_features
)
159 def check_default_params(format
, order
=None, features
=None, stripe_count
=None,
160 stripe_unit
=None, exception
=None):
164 for k
in ['rbd_default_format', 'rbd_default_order', 'rbd_default_features',
165 'rbd_default_stripe_count', 'rbd_default_stripe_unit']:
166 orig_vals
[k
] = rados
.conf_get(k
)
168 rados
.conf_set('rbd_default_format', str(format
))
169 if order
is not None:
170 rados
.conf_set('rbd_default_order', str(order
or 0))
171 if features
is not None:
172 rados
.conf_set('rbd_default_features', str(features
or 0))
173 if stripe_count
is not None:
174 rados
.conf_set('rbd_default_stripe_count', str(stripe_count
or 0))
175 if stripe_unit
is not None:
176 rados
.conf_set('rbd_default_stripe_unit', str(stripe_unit
or 0))
177 feature_data_pool
= 0
178 datapool
= rados
.conf_get('rbd_default_data_pool')
179 if not len(datapool
) == 0:
180 feature_data_pool
= 128
181 image_name
= get_temp_image_name()
182 if exception
is None:
183 RBD().create(ioctx
, image_name
, IMG_SIZE
)
185 with
Image(ioctx
, image_name
) as image
:
186 eq(format
== 1, image
.old_format())
188 expected_order
= int(rados
.conf_get('rbd_default_order'))
189 actual_order
= image
.stat()['order']
190 eq(expected_order
, actual_order
)
192 expected_features
= features
194 expected_features
= 0
195 elif expected_features
is None:
196 expected_features
= 61 | feature_data_pool
198 expected_features |
= feature_data_pool
199 eq(expected_features
, image
.features())
201 expected_stripe_count
= stripe_count
202 if not expected_stripe_count
or format
== 1 or \
203 features
& RBD_FEATURE_STRIPINGV2
== 0:
204 expected_stripe_count
= 1
205 eq(expected_stripe_count
, image
.stripe_count())
207 expected_stripe_unit
= stripe_unit
208 if not expected_stripe_unit
or format
== 1 or \
209 features
& RBD_FEATURE_STRIPINGV2
== 0:
210 expected_stripe_unit
= 1 << actual_order
211 eq(expected_stripe_unit
, image
.stripe_unit())
213 RBD().remove(ioctx
, image_name
)
215 assert_raises(exception
, RBD().create
, ioctx
, image_name
, IMG_SIZE
)
217 for k
, v
in orig_vals
.items():
220 def test_create_defaults():
221 # basic format 1 and 2
222 check_default_params(1)
223 check_default_params(2)
225 check_default_params(1, 0, exception
=ArgumentOutOfRange
)
226 check_default_params(2, 0, exception
=ArgumentOutOfRange
)
227 check_default_params(1, 11, exception
=ArgumentOutOfRange
)
228 check_default_params(2, 11, exception
=ArgumentOutOfRange
)
229 check_default_params(1, 65, exception
=ArgumentOutOfRange
)
230 check_default_params(2, 65, exception
=ArgumentOutOfRange
)
231 # striping and features are ignored for format 1
232 check_default_params(1, 20, 0, 1, 1)
233 check_default_params(1, 20, 3, 1, 1)
234 check_default_params(1, 20, 0, 0, 0)
235 # striping is ignored if stripingv2 is not set
236 check_default_params(2, 20, 0, 1, 1 << 20)
237 check_default_params(2, 20, RBD_FEATURE_LAYERING
, 1, 1 << 20)
238 check_default_params(2, 20, 0, 0, 0)
239 # striping with stripingv2 is fine
240 check_default_params(2, 20, RBD_FEATURE_STRIPINGV2
, 1, 1 << 16)
241 check_default_params(2, 20, RBD_FEATURE_STRIPINGV2
, 10, 1 << 20)
242 check_default_params(2, 20, RBD_FEATURE_STRIPINGV2
, 10, 1 << 16)
243 check_default_params(2, 20, 0, 0, 0)
244 # make sure invalid combinations of stripe unit and order are still invalid
245 check_default_params(2, 22, RBD_FEATURE_STRIPINGV2
, 10, 1 << 50, exception
=InvalidArgument
)
246 check_default_params(2, 22, RBD_FEATURE_STRIPINGV2
, 10, 100, exception
=InvalidArgument
)
247 check_default_params(2, 22, RBD_FEATURE_STRIPINGV2
, 0, 1, exception
=InvalidArgument
)
248 check_default_params(2, 22, RBD_FEATURE_STRIPINGV2
, 1, 0, exception
=InvalidArgument
)
249 # 0 stripe unit and count are still ignored
250 check_default_params(2, 22, 0, 0, 0)
252 def test_context_manager():
253 with
Rados(conffile
='') as cluster
:
254 with cluster
.open_ioctx(pool_name
) as ioctx
:
255 image_name
= get_temp_image_name()
256 RBD().create(ioctx
, image_name
, IMG_SIZE
)
257 with
Image(ioctx
, image_name
) as image
:
258 data
= rand_data(256)
260 read
= image
.read(0, 256)
261 RBD().remove(ioctx
, image_name
)
264 def test_open_read_only():
265 with
Rados(conffile
='') as cluster
:
266 with cluster
.open_ioctx(pool_name
) as ioctx
:
267 image_name
= get_temp_image_name()
268 RBD().create(ioctx
, image_name
, IMG_SIZE
)
269 data
= rand_data(256)
270 with
Image(ioctx
, image_name
) as image
:
272 image
.create_snap('snap')
273 with
Image(ioctx
, image_name
, read_only
=True) as image
:
274 read
= image
.read(0, 256)
276 assert_raises(ReadOnlyImage
, image
.write
, data
, 0)
277 assert_raises(ReadOnlyImage
, image
.create_snap
, 'test')
278 assert_raises(ReadOnlyImage
, image
.remove_snap
, 'snap')
279 assert_raises(ReadOnlyImage
, image
.rollback_to_snap
, 'snap')
280 assert_raises(ReadOnlyImage
, image
.protect_snap
, 'snap')
281 assert_raises(ReadOnlyImage
, image
.unprotect_snap
, 'snap')
282 assert_raises(ReadOnlyImage
, image
.unprotect_snap
, 'snap')
283 assert_raises(ReadOnlyImage
, image
.flatten
)
284 with
Image(ioctx
, image_name
) as image
:
285 image
.remove_snap('snap')
286 RBD().remove(ioctx
, image_name
)
291 image_name
= get_temp_image_name()
292 assert_raises(ImageNotFound
, Image
, ioctx
, image_name
+ 'dne')
293 assert_raises(ImageNotFound
, Image
, ioctx
, image_name
, 'snap')
295 def test_open_readonly_dne():
297 image_name
= get_temp_image_name()
298 assert_raises(ImageNotFound
, Image
, ioctx
, image_name
+ 'dne',
300 assert_raises(ImageNotFound
, Image
, ioctx
, image_name
, 'snap',
303 @require_new_format()
304 def test_open_by_id():
305 with
Rados(conffile
='') as cluster
:
306 with cluster
.open_ioctx(pool_name
) as ioctx
:
307 image_name
= get_temp_image_name()
308 RBD().create(ioctx
, image_name
, IMG_SIZE
)
309 with
Image(ioctx
, image_name
) as image
:
310 image_id
= image
.id()
311 with
Image(ioctx
, image_id
=image_id
) as image
:
312 eq(image
.get_name(), image_name
)
313 RBD().remove(ioctx
, image_name
)
315 def test_remove_dne():
316 assert_raises(ImageNotFound
, remove_image
)
318 def test_list_empty():
319 eq([], RBD().list(ioctx
))
321 @with_setup(create_image
, remove_image
)
323 eq([image_name
], RBD().list(ioctx
))
325 with
Image(ioctx
, image_name
) as image
:
326 image_id
= image
.id()
327 eq([{'id': image_id
, 'name': image_name
}], list(RBD().list2(ioctx
)))
329 @with_setup(create_image
)
330 def test_remove_with_progress():
331 d
= {'received_callback': False}
332 def progress_cb(current
, total
):
333 d
['received_callback'] = True
336 RBD().remove(ioctx
, image_name
, on_progress
=progress_cb
)
337 eq(True, d
['received_callback'])
339 @with_setup(create_image
)
340 def test_remove_canceled():
341 def progress_cb(current
, total
):
344 assert_raises(OperationCanceled
, RBD().remove
, ioctx
, image_name
,
345 on_progress
=progress_cb
)
347 @with_setup(create_image
, remove_image
)
350 image_name2
= get_temp_image_name()
351 rbd
.rename(ioctx
, image_name
, image_name2
)
352 eq([image_name2
], rbd
.list(ioctx
))
353 rbd
.rename(ioctx
, image_name2
, image_name
)
354 eq([image_name
], rbd
.list(ioctx
))
356 def test_pool_metadata():
358 metadata
= list(rbd
.pool_metadata_list(ioctx
))
360 assert_raises(KeyError, rbd
.pool_metadata_get
, ioctx
, "key1")
361 rbd
.pool_metadata_set(ioctx
, "key1", "value1")
362 rbd
.pool_metadata_set(ioctx
, "key2", "value2")
363 value
= rbd
.pool_metadata_get(ioctx
, "key1")
365 value
= rbd
.pool_metadata_get(ioctx
, "key2")
367 metadata
= list(rbd
.pool_metadata_list(ioctx
))
369 rbd
.pool_metadata_remove(ioctx
, "key1")
370 metadata
= list(rbd
.pool_metadata_list(ioctx
))
372 eq(metadata
[0], ("key2", "value2"))
373 rbd
.pool_metadata_remove(ioctx
, "key2")
374 assert_raises(KeyError, rbd
.pool_metadata_remove
, ioctx
, "key2")
375 metadata
= list(rbd
.pool_metadata_list(ioctx
))
380 rbd
.pool_metadata_set(ioctx
, "key" + str(i
), "X" * 1025)
381 metadata
= list(rbd
.pool_metadata_list(ioctx
))
384 rbd
.pool_metadata_remove(ioctx
, "key" + str(i
))
385 metadata
= list(rbd
.pool_metadata_list(ioctx
))
386 eq(len(metadata
), N
- i
- 1)
388 def test_config_list():
391 for option
in rbd
.config_list(ioctx
):
392 eq(option
['source'], RBD_CONFIG_SOURCE_CONFIG
)
394 rbd
.pool_metadata_set(ioctx
, "conf_rbd_cache", "true")
396 for option
in rbd
.config_list(ioctx
):
397 if option
['name'] == "rbd_cache":
398 eq(option
['source'], RBD_CONFIG_SOURCE_POOL
)
400 eq(option
['source'], RBD_CONFIG_SOURCE_CONFIG
)
402 rbd
.pool_metadata_remove(ioctx
, "conf_rbd_cache")
404 for option
in rbd
.config_list(ioctx
):
405 eq(option
['source'], RBD_CONFIG_SOURCE_CONFIG
)
407 def test_namespaces():
410 eq(False, rbd
.namespace_exists(ioctx
, 'ns1'))
411 eq([], rbd
.namespace_list(ioctx
))
412 assert_raises(ImageNotFound
, rbd
.namespace_remove
, ioctx
, 'ns1')
414 rbd
.namespace_create(ioctx
, 'ns1')
415 eq(True, rbd
.namespace_exists(ioctx
, 'ns1'))
417 assert_raises(ImageExists
, rbd
.namespace_create
, ioctx
, 'ns1')
418 eq(['ns1'], rbd
.namespace_list(ioctx
))
419 rbd
.namespace_remove(ioctx
, 'ns1')
420 eq([], rbd
.namespace_list(ioctx
))
422 @require_new_format()
423 def test_pool_stats():
427 image1
= create_image()
428 image2
= create_image()
429 image3
= create_image()
430 image4
= create_image()
431 with
Image(ioctx
, image4
) as image
:
432 image
.create_snap('snap')
435 stats
= rbd
.pool_stats_get(ioctx
)
436 eq(stats
['image_count'], 4)
437 eq(stats
['image_provisioned_bytes'], 3 * IMG_SIZE
)
438 eq(stats
['image_max_provisioned_bytes'], 4 * IMG_SIZE
)
439 eq(stats
['image_snap_count'], 1)
440 eq(stats
['trash_count'], 0)
441 eq(stats
['trash_provisioned_bytes'], 0)
442 eq(stats
['trash_max_provisioned_bytes'], 0)
443 eq(stats
['trash_snap_count'], 0)
445 rbd
.remove(ioctx
, image1
)
446 rbd
.remove(ioctx
, image2
)
447 rbd
.remove(ioctx
, image3
)
448 with
Image(ioctx
, image4
) as image
:
449 image
.remove_snap('snap')
450 rbd
.remove(ioctx
, image4
)
453 return os
.urandom(size
)
455 def check_stat(info
, size
, order
):
456 assert 'block_name_prefix' in info
457 eq(info
['size'], size
)
458 eq(info
['order'], order
)
459 eq(info
['num_objs'], size
// (1 << order
))
460 eq(info
['obj_size'], 1 << order
)
462 class TestImage(object):
467 self
.image
= Image(ioctx
, image_name
)
474 @require_new_format()
475 @blacklist_features([RBD_FEATURE_EXCLUSIVE_LOCK
])
476 def test_update_features(self
):
477 features
= self
.image
.features()
478 self
.image
.update_features(RBD_FEATURE_EXCLUSIVE_LOCK
, True)
479 eq(features | RBD_FEATURE_EXCLUSIVE_LOCK
, self
.image
.features())
481 @require_features([RBD_FEATURE_STRIPINGV2
])
482 def test_create_with_params(self
):
484 image_name
= get_temp_image_name()
486 stripe_unit
= 1 << 20
488 self
.rbd
.create(ioctx
, image_name
, IMG_SIZE
, order
,
489 False, features
, stripe_unit
, stripe_count
)
490 image
= Image(ioctx
, image_name
)
492 check_stat(info
, IMG_SIZE
, order
)
493 eq(image
.features(), features
)
494 eq(image
.stripe_unit(), stripe_unit
)
495 eq(image
.stripe_count(), stripe_count
)
497 RBD().remove(ioctx
, image_name
)
499 @require_new_format()
501 assert_not_equal(b
'', self
.image
.id())
503 def test_block_name_prefix(self
):
504 assert_not_equal(b
'', self
.image
.block_name_prefix())
506 def test_create_timestamp(self
):
507 timestamp
= self
.image
.create_timestamp()
508 assert_not_equal(0, timestamp
.year
)
509 assert_not_equal(1970, timestamp
.year
)
511 def test_access_timestamp(self
):
512 timestamp
= self
.image
.access_timestamp()
513 assert_not_equal(0, timestamp
.year
)
514 assert_not_equal(1970, timestamp
.year
)
516 def test_modify_timestamp(self
):
517 timestamp
= self
.image
.modify_timestamp()
518 assert_not_equal(0, timestamp
.year
)
519 assert_not_equal(1970, timestamp
.year
)
521 def test_invalidate_cache(self
):
522 self
.image
.write(b
'abc', 0)
523 eq(b
'abc', self
.image
.read(0, 3))
524 self
.image
.invalidate_cache()
525 eq(b
'abc', self
.image
.read(0, 3))
528 info
= self
.image
.stat()
529 check_stat(info
, IMG_SIZE
, IMG_ORDER
)
531 def test_flags(self
):
532 flags
= self
.image
.flags()
535 def test_image_auto_close(self
):
536 image
= Image(ioctx
, image_name
)
538 def test_write(self
):
539 data
= rand_data(256)
540 self
.image
.write(data
, 0)
542 def test_write_with_fadvise_flags(self
):
543 data
= rand_data(256)
544 self
.image
.write(data
, 0, LIBRADOS_OP_FLAG_FADVISE_DONTNEED
)
545 self
.image
.write(data
, 0, LIBRADOS_OP_FLAG_FADVISE_NOCACHE
)
548 data
= self
.image
.read(0, 20)
551 def test_read_with_fadvise_flags(self
):
552 data
= self
.image
.read(0, 20, LIBRADOS_OP_FLAG_FADVISE_DONTNEED
)
554 data
= self
.image
.read(0, 20, LIBRADOS_OP_FLAG_FADVISE_RANDOM
)
557 def test_large_write(self
):
558 data
= rand_data(IMG_SIZE
)
559 self
.image
.write(data
, 0)
561 def test_large_read(self
):
562 data
= self
.image
.read(0, IMG_SIZE
)
563 eq(data
, b
'\0' * IMG_SIZE
)
565 def test_write_read(self
):
566 data
= rand_data(256)
568 self
.image
.write(data
, offset
)
569 read
= self
.image
.read(offset
, 256)
572 def test_read_bad_offset(self
):
573 assert_raises(InvalidArgument
, self
.image
.read
, IMG_SIZE
+ 1, IMG_SIZE
)
575 def test_resize(self
):
576 new_size
= IMG_SIZE
* 2
577 self
.image
.resize(new_size
)
578 info
= self
.image
.stat()
579 check_stat(info
, new_size
, IMG_ORDER
)
581 def test_resize_allow_shrink_False(self
):
582 new_size
= IMG_SIZE
* 2
583 self
.image
.resize(new_size
)
584 info
= self
.image
.stat()
585 check_stat(info
, new_size
, IMG_ORDER
)
586 assert_raises(InvalidArgument
, self
.image
.resize
, IMG_SIZE
, False)
589 eq(IMG_SIZE
, self
.image
.size())
590 self
.image
.create_snap('snap1')
591 new_size
= IMG_SIZE
* 2
592 self
.image
.resize(new_size
)
593 eq(new_size
, self
.image
.size())
594 self
.image
.create_snap('snap2')
595 self
.image
.set_snap('snap2')
596 eq(new_size
, self
.image
.size())
597 self
.image
.set_snap('snap1')
598 eq(IMG_SIZE
, self
.image
.size())
599 self
.image
.set_snap(None)
600 eq(new_size
, self
.image
.size())
601 self
.image
.remove_snap('snap1')
602 self
.image
.remove_snap('snap2')
604 def test_resize_down(self
):
605 new_size
= IMG_SIZE
// 2
606 data
= rand_data(256)
607 self
.image
.write(data
, IMG_SIZE
// 2);
608 self
.image
.resize(new_size
)
609 self
.image
.resize(IMG_SIZE
)
610 read
= self
.image
.read(IMG_SIZE
// 2, 256)
611 eq(b
'\0' * 256, read
)
613 def test_resize_bytes(self
):
614 new_size
= IMG_SIZE
// 2 - 5
615 data
= rand_data(256)
616 self
.image
.write(data
, IMG_SIZE
// 2 - 10);
617 self
.image
.resize(new_size
)
618 self
.image
.resize(IMG_SIZE
)
619 read
= self
.image
.read(IMG_SIZE
// 2 - 10, 5)
621 read
= self
.image
.read(IMG_SIZE
// 2 - 5, 251)
622 eq(b
'\0' * 251, read
)
624 def _test_copy(self
, features
=None, order
=None, stripe_unit
=None,
627 data
= rand_data(256)
628 self
.image
.write(data
, 256)
629 image_name
= get_temp_image_name()
631 self
.image
.copy(ioctx
, image_name
)
633 self
.image
.copy(ioctx
, image_name
, features
)
634 elif stripe_unit
is None:
635 self
.image
.copy(ioctx
, image_name
, features
, order
)
636 elif stripe_count
is None:
637 self
.image
.copy(ioctx
, image_name
, features
, order
, stripe_unit
)
639 self
.image
.copy(ioctx
, image_name
, features
, order
, stripe_unit
,
641 assert_raises(ImageExists
, self
.image
.copy
, ioctx
, image_name
)
642 copy
= Image(ioctx
, image_name
)
643 copy_data
= copy
.read(256, 256)
645 self
.rbd
.remove(ioctx
, image_name
)
651 def test_copy2(self
):
652 self
._test
_copy
(self
.image
.features(), self
.image
.stat()['order'])
654 @require_features([RBD_FEATURE_STRIPINGV2
])
655 def test_copy3(self
):
657 self
._test
_copy
(features
, self
.image
.stat()['order'],
658 self
.image
.stripe_unit(), self
.image
.stripe_count())
660 def test_deep_copy(self
):
663 self
.image
.write(b
'a' * 256, 0)
664 self
.image
.create_snap('snap1')
665 self
.image
.write(b
'b' * 256, 0)
666 dst_name
= get_temp_image_name()
667 self
.image
.deep_copy(ioctx
, dst_name
, features
=features
,
668 order
=self
.image
.stat()['order'],
669 stripe_unit
=self
.image
.stripe_unit(),
670 stripe_count
=self
.image
.stripe_count(),
672 self
.image
.remove_snap('snap1')
673 with
Image(ioctx
, dst_name
, 'snap1') as copy
:
674 copy_data
= copy
.read(0, 256)
675 eq(b
'a' * 256, copy_data
)
676 with
Image(ioctx
, dst_name
) as copy
:
677 copy_data
= copy
.read(0, 256)
678 eq(b
'b' * 256, copy_data
)
679 copy
.remove_snap('snap1')
680 self
.rbd
.remove(ioctx
, dst_name
)
682 @require_features([RBD_FEATURE_LAYERING
])
683 def test_deep_copy_clone(self
):
686 self
.image
.write(b
'a' * 256, 0)
687 self
.image
.create_snap('snap1')
688 self
.image
.write(b
'b' * 256, 0)
689 self
.image
.protect_snap('snap1')
690 clone_name
= get_temp_image_name()
691 dst_name
= get_temp_image_name()
692 self
.rbd
.clone(ioctx
, image_name
, 'snap1', ioctx
, clone_name
)
693 with
Image(ioctx
, clone_name
) as child
:
694 child
.create_snap('snap1')
695 child
.deep_copy(ioctx
, dst_name
, features
=features
,
696 order
=self
.image
.stat()['order'],
697 stripe_unit
=self
.image
.stripe_unit(),
698 stripe_count
=self
.image
.stripe_count(),
700 child
.remove_snap('snap1')
702 with
Image(ioctx
, dst_name
) as copy
:
703 copy_data
= copy
.read(0, 256)
704 eq(b
'a' * 256, copy_data
)
705 copy
.remove_snap('snap1')
706 self
.rbd
.remove(ioctx
, dst_name
)
707 self
.rbd
.remove(ioctx
, clone_name
)
708 self
.image
.unprotect_snap('snap1')
709 self
.image
.remove_snap('snap1')
711 def test_create_snap(self
):
713 self
.image
.create_snap('snap1')
714 read
= self
.image
.read(0, 256)
715 eq(read
, b
'\0' * 256)
716 data
= rand_data(256)
717 self
.image
.write(data
, 0)
718 read
= self
.image
.read(0, 256)
720 at_snapshot
= Image(ioctx
, image_name
, 'snap1')
721 snap_data
= at_snapshot
.read(0, 256)
723 eq(snap_data
, b
'\0' * 256)
724 self
.image
.remove_snap('snap1')
726 def test_list_snaps(self
):
727 eq([], list(self
.image
.list_snaps()))
728 self
.image
.create_snap('snap1')
729 eq(['snap1'], [snap
['name'] for snap
in self
.image
.list_snaps()])
730 self
.image
.create_snap('snap2')
731 eq(['snap1', 'snap2'], [snap
['name'] for snap
in self
.image
.list_snaps()])
732 self
.image
.remove_snap('snap1')
733 self
.image
.remove_snap('snap2')
735 def test_list_snaps_iterator_auto_close(self
):
736 self
.image
.create_snap('snap1')
737 self
.image
.list_snaps()
738 self
.image
.remove_snap('snap1')
740 def test_remove_snap(self
):
741 eq([], list(self
.image
.list_snaps()))
742 self
.image
.create_snap('snap1')
743 eq(['snap1'], [snap
['name'] for snap
in self
.image
.list_snaps()])
744 self
.image
.remove_snap('snap1')
745 eq([], list(self
.image
.list_snaps()))
747 def test_rename_snap(self
):
748 eq([], list(self
.image
.list_snaps()))
749 self
.image
.create_snap('snap1')
750 eq(['snap1'], [snap
['name'] for snap
in self
.image
.list_snaps()])
751 self
.image
.rename_snap("snap1", "snap1-rename")
752 eq(['snap1-rename'], [snap
['name'] for snap
in self
.image
.list_snaps()])
753 self
.image
.remove_snap('snap1-rename')
754 eq([], list(self
.image
.list_snaps()))
756 @require_features([RBD_FEATURE_LAYERING
])
757 def test_protect_snap(self
):
758 self
.image
.create_snap('snap1')
759 assert(not self
.image
.is_protected_snap('snap1'))
760 self
.image
.protect_snap('snap1')
761 assert(self
.image
.is_protected_snap('snap1'))
762 assert_raises(ImageBusy
, self
.image
.remove_snap
, 'snap1')
763 self
.image
.unprotect_snap('snap1')
764 assert(not self
.image
.is_protected_snap('snap1'))
765 self
.image
.remove_snap('snap1')
766 assert_raises(ImageNotFound
, self
.image
.unprotect_snap
, 'snap1')
767 assert_raises(ImageNotFound
, self
.image
.is_protected_snap
, 'snap1')
769 def test_snap_timestamp(self
):
770 self
.image
.create_snap('snap1')
771 eq(['snap1'], [snap
['name'] for snap
in self
.image
.list_snaps()])
772 for snap
in self
.image
.list_snaps():
774 time
= self
.image
.get_snap_timestamp(snap_id
)
775 assert_not_equal(b
'', time
.year
)
776 assert_not_equal(0, time
.year
)
777 assert_not_equal(time
.year
, '1970')
778 self
.image
.remove_snap('snap1')
780 def test_limit_snaps(self
):
781 self
.image
.set_snap_limit(2)
782 eq(2, self
.image
.get_snap_limit())
783 self
.image
.create_snap('snap1')
784 self
.image
.create_snap('snap2')
785 assert_raises(DiskQuotaExceeded
, self
.image
.create_snap
, 'snap3')
786 self
.image
.remove_snap_limit()
787 self
.image
.create_snap('snap3')
789 self
.image
.remove_snap('snap1')
790 self
.image
.remove_snap('snap2')
791 self
.image
.remove_snap('snap3')
793 @require_features([RBD_FEATURE_EXCLUSIVE_LOCK
])
794 def test_remove_with_exclusive_lock(self
):
795 assert_raises(ImageBusy
, remove_image
)
797 @blacklist_features([RBD_FEATURE_EXCLUSIVE_LOCK
])
798 def test_remove_with_snap(self
):
799 self
.image
.create_snap('snap1')
800 assert_raises(ImageHasSnapshots
, remove_image
)
801 self
.image
.remove_snap('snap1')
803 @blacklist_features([RBD_FEATURE_EXCLUSIVE_LOCK
])
804 def test_remove_with_watcher(self
):
805 data
= rand_data(256)
806 self
.image
.write(data
, 0)
807 assert_raises(ImageBusy
, remove_image
)
808 read
= self
.image
.read(0, 256)
811 def test_rollback_to_snap(self
):
812 self
.image
.write(b
'\0' * 256, 0)
813 self
.image
.create_snap('snap1')
814 read
= self
.image
.read(0, 256)
815 eq(read
, b
'\0' * 256)
816 data
= rand_data(256)
817 self
.image
.write(data
, 0)
818 read
= self
.image
.read(0, 256)
820 self
.image
.rollback_to_snap('snap1')
821 read
= self
.image
.read(0, 256)
822 eq(read
, b
'\0' * 256)
823 self
.image
.remove_snap('snap1')
825 def test_rollback_to_snap_sparse(self
):
826 self
.image
.create_snap('snap1')
827 read
= self
.image
.read(0, 256)
828 eq(read
, b
'\0' * 256)
829 data
= rand_data(256)
830 self
.image
.write(data
, 0)
831 read
= self
.image
.read(0, 256)
833 self
.image
.rollback_to_snap('snap1')
834 read
= self
.image
.read(0, 256)
835 eq(read
, b
'\0' * 256)
836 self
.image
.remove_snap('snap1')
838 def test_rollback_with_resize(self
):
839 read
= self
.image
.read(0, 256)
840 eq(read
, b
'\0' * 256)
841 data
= rand_data(256)
842 self
.image
.write(data
, 0)
843 self
.image
.create_snap('snap1')
844 read
= self
.image
.read(0, 256)
846 new_size
= IMG_SIZE
* 2
847 self
.image
.resize(new_size
)
848 check_stat(self
.image
.stat(), new_size
, IMG_ORDER
)
849 self
.image
.write(data
, new_size
- 256)
850 self
.image
.create_snap('snap2')
851 read
= self
.image
.read(new_size
- 256, 256)
853 self
.image
.rollback_to_snap('snap1')
854 check_stat(self
.image
.stat(), IMG_SIZE
, IMG_ORDER
)
855 assert_raises(InvalidArgument
, self
.image
.read
, new_size
- 256, 256)
856 self
.image
.rollback_to_snap('snap2')
857 check_stat(self
.image
.stat(), new_size
, IMG_ORDER
)
858 read
= self
.image
.read(new_size
- 256, 256)
860 self
.image
.remove_snap('snap1')
861 self
.image
.remove_snap('snap2')
863 def test_set_snap(self
):
864 self
.image
.write(b
'\0' * 256, 0)
865 self
.image
.create_snap('snap1')
866 read
= self
.image
.read(0, 256)
867 eq(read
, b
'\0' * 256)
868 data
= rand_data(256)
869 self
.image
.write(data
, 0)
870 read
= self
.image
.read(0, 256)
872 self
.image
.set_snap('snap1')
873 read
= self
.image
.read(0, 256)
874 eq(read
, b
'\0' * 256)
875 assert_raises(ReadOnlyImage
, self
.image
.write
, data
, 0)
876 self
.image
.remove_snap('snap1')
878 def test_set_no_snap(self
):
879 self
.image
.write(b
'\0' * 256, 0)
880 self
.image
.create_snap('snap1')
881 read
= self
.image
.read(0, 256)
882 eq(read
, b
'\0' * 256)
883 data
= rand_data(256)
884 self
.image
.write(data
, 0)
885 read
= self
.image
.read(0, 256)
887 self
.image
.set_snap('snap1')
888 read
= self
.image
.read(0, 256)
889 eq(read
, b
'\0' * 256)
890 assert_raises(ReadOnlyImage
, self
.image
.write
, data
, 0)
891 self
.image
.set_snap(None)
892 read
= self
.image
.read(0, 256)
894 self
.image
.remove_snap('snap1')
896 def test_set_snap_by_id(self
):
897 self
.image
.write(b
'\0' * 256, 0)
898 self
.image
.create_snap('snap1')
899 read
= self
.image
.read(0, 256)
900 eq(read
, b
'\0' * 256)
901 data
= rand_data(256)
902 self
.image
.write(data
, 0)
903 read
= self
.image
.read(0, 256)
905 snaps
= list(self
.image
.list_snaps())
906 self
.image
.set_snap_by_id(snaps
[0]['id'])
907 read
= self
.image
.read(0, 256)
908 eq(read
, b
'\0' * 256)
909 assert_raises(ReadOnlyImage
, self
.image
.write
, data
, 0)
910 self
.image
.set_snap_by_id(None)
911 read
= self
.image
.read(0, 256)
913 self
.image
.remove_snap('snap1')
915 def test_set_snap_sparse(self
):
916 self
.image
.create_snap('snap1')
917 read
= self
.image
.read(0, 256)
918 eq(read
, b
'\0' * 256)
919 data
= rand_data(256)
920 self
.image
.write(data
, 0)
921 read
= self
.image
.read(0, 256)
923 self
.image
.set_snap('snap1')
924 read
= self
.image
.read(0, 256)
925 eq(read
, b
'\0' * 256)
926 assert_raises(ReadOnlyImage
, self
.image
.write
, data
, 0)
927 self
.image
.remove_snap('snap1')
929 def test_many_snaps(self
):
931 for i
in range(num_snaps
):
932 self
.image
.create_snap(str(i
))
933 snaps
= sorted(self
.image
.list_snaps(),
934 key
=lambda snap
: int(snap
['name']))
935 eq(len(snaps
), num_snaps
)
936 for i
, snap
in enumerate(snaps
):
937 eq(snap
['size'], IMG_SIZE
)
938 eq(snap
['name'], str(i
))
939 for i
in range(num_snaps
):
940 self
.image
.remove_snap(str(i
))
942 def test_set_snap_deleted(self
):
943 self
.image
.write(b
'\0' * 256, 0)
944 self
.image
.create_snap('snap1')
945 read
= self
.image
.read(0, 256)
946 eq(read
, b
'\0' * 256)
947 data
= rand_data(256)
948 self
.image
.write(data
, 0)
949 read
= self
.image
.read(0, 256)
951 self
.image
.set_snap('snap1')
952 self
.image
.remove_snap('snap1')
953 assert_raises(ImageNotFound
, self
.image
.read
, 0, 256)
954 self
.image
.set_snap(None)
955 read
= self
.image
.read(0, 256)
958 def test_set_snap_recreated(self
):
959 self
.image
.write(b
'\0' * 256, 0)
960 self
.image
.create_snap('snap1')
961 read
= self
.image
.read(0, 256)
962 eq(read
, b
'\0' * 256)
963 data
= rand_data(256)
964 self
.image
.write(data
, 0)
965 read
= self
.image
.read(0, 256)
967 self
.image
.set_snap('snap1')
968 self
.image
.remove_snap('snap1')
969 self
.image
.create_snap('snap1')
970 assert_raises(ImageNotFound
, self
.image
.read
, 0, 256)
971 self
.image
.set_snap(None)
972 read
= self
.image
.read(0, 256)
974 self
.image
.remove_snap('snap1')
976 def test_lock_unlock(self
):
977 assert_raises(ImageNotFound
, self
.image
.unlock
, '')
978 self
.image
.lock_exclusive('')
979 assert_raises(ImageExists
, self
.image
.lock_exclusive
, '')
980 assert_raises(ImageBusy
, self
.image
.lock_exclusive
, 'test')
981 assert_raises(ImageExists
, self
.image
.lock_shared
, '', '')
982 assert_raises(ImageBusy
, self
.image
.lock_shared
, 'foo', '')
983 self
.image
.unlock('')
985 def test_list_lockers(self
):
986 eq([], self
.image
.list_lockers())
987 self
.image
.lock_exclusive('test')
988 lockers
= self
.image
.list_lockers()
989 eq(1, len(lockers
['lockers']))
990 _
, cookie
, _
= lockers
['lockers'][0]
992 eq('', lockers
['tag'])
993 assert lockers
['exclusive']
994 self
.image
.unlock('test')
995 eq([], self
.image
.list_lockers())
998 for i
in range(num_shared
):
999 self
.image
.lock_shared(str(i
), 'tag')
1000 lockers
= self
.image
.list_lockers()
1001 eq('tag', lockers
['tag'])
1002 assert not lockers
['exclusive']
1003 eq(num_shared
, len(lockers
['lockers']))
1004 cookies
= sorted(map(lambda x
: x
[1], lockers
['lockers']))
1005 for i
in range(num_shared
):
1006 eq(str(i
), cookies
[i
])
1007 self
.image
.unlock(str(i
))
1008 eq([], self
.image
.list_lockers())
1010 def test_diff_iterate(self
):
1011 check_diff(self
.image
, 0, IMG_SIZE
, None, [])
1012 self
.image
.write(b
'a' * 256, 0)
1013 check_diff(self
.image
, 0, IMG_SIZE
, None, [(0, 256, True)])
1014 self
.image
.write(b
'b' * 256, 256)
1015 check_diff(self
.image
, 0, IMG_SIZE
, None, [(0, 512, True)])
1016 self
.image
.discard(128, 256)
1017 check_diff(self
.image
, 0, IMG_SIZE
, None, [(0, 512, True)])
1019 self
.image
.create_snap('snap1')
1020 self
.image
.discard(0, 1 << IMG_ORDER
)
1021 self
.image
.create_snap('snap2')
1022 self
.image
.set_snap('snap2')
1023 check_diff(self
.image
, 0, IMG_SIZE
, 'snap1', [(0, 512, False)])
1024 self
.image
.remove_snap('snap1')
1025 self
.image
.remove_snap('snap2')
1027 def test_aio_read(self
):
1028 # this is a list so that the local cb() can modify it
1033 # test1: success case
1034 comp
= self
.image
.aio_read(0, 20, cb
)
1035 comp
.wait_for_complete_and_cb()
1036 eq(retval
[0], b
'\0' * 20)
1037 eq(comp
.get_return_value(), 20)
1038 eq(sys
.getrefcount(comp
), 2)
1042 comp
= self
.image
.aio_read(IMG_SIZE
, 20, cb
)
1043 comp
.wait_for_complete_and_cb()
1045 assert(comp
.get_return_value() < 0)
1046 eq(sys
.getrefcount(comp
), 2)
1048 def test_aio_write(self
):
1051 retval
[0] = comp
.get_return_value()
1053 data
= rand_data(256)
1054 comp
= self
.image
.aio_write(data
, 256, cb
)
1055 comp
.wait_for_complete_and_cb()
1057 eq(comp
.get_return_value(), 0)
1058 eq(sys
.getrefcount(comp
), 2)
1059 eq(self
.image
.read(256, 256), data
)
1061 def test_aio_discard(self
):
1064 retval
[0] = comp
.get_return_value()
1066 data
= rand_data(256)
1067 self
.image
.write(data
, 0)
1068 comp
= self
.image
.aio_discard(0, 256, cb
)
1069 comp
.wait_for_complete_and_cb()
1071 eq(comp
.get_return_value(), 0)
1072 eq(sys
.getrefcount(comp
), 2)
1073 eq(self
.image
.read(256, 256), b
'\0' * 256)
1075 def test_aio_flush(self
):
1078 retval
[0] = comp
.get_return_value()
1080 comp
= self
.image
.aio_flush(cb
)
1081 comp
.wait_for_complete_and_cb()
1083 eq(sys
.getrefcount(comp
), 2)
1085 def test_metadata(self
):
1086 metadata
= list(self
.image
.metadata_list())
1087 eq(len(metadata
), 0)
1088 assert_raises(KeyError, self
.image
.metadata_get
, "key1")
1089 self
.image
.metadata_set("key1", "value1")
1090 self
.image
.metadata_set("key2", "value2")
1091 value
= self
.image
.metadata_get("key1")
1093 value
= self
.image
.metadata_get("key2")
1095 metadata
= list(self
.image
.metadata_list())
1096 eq(len(metadata
), 2)
1097 self
.image
.metadata_remove("key1")
1098 metadata
= list(self
.image
.metadata_list())
1099 eq(len(metadata
), 1)
1100 eq(metadata
[0], ("key2", "value2"))
1101 self
.image
.metadata_remove("key2")
1102 assert_raises(KeyError, self
.image
.metadata_remove
, "key2")
1103 metadata
= list(self
.image
.metadata_list())
1104 eq(len(metadata
), 0)
1108 self
.image
.metadata_set("key" + str(i
), "X" * 1025)
1109 metadata
= list(self
.image
.metadata_list())
1110 eq(len(metadata
), N
)
1112 self
.image
.metadata_remove("key" + str(i
))
1113 metadata
= list(self
.image
.metadata_list())
1114 eq(len(metadata
), N
- i
- 1)
1116 def test_watchers_list(self
):
1117 watchers
= list(self
.image
.watchers_list())
1118 # The image is open (in r/w mode) from setup, so expect there to be one
1120 eq(len(watchers
), 1)
1122 def test_config_list(self
):
1123 with
Image(ioctx
, image_name
) as image
:
1124 for option
in image
.config_list():
1125 eq(option
['source'], RBD_CONFIG_SOURCE_CONFIG
)
1127 image
.metadata_set("conf_rbd_cache", "true")
1129 for option
in image
.config_list():
1130 if option
['name'] == "rbd_cache":
1131 eq(option
['source'], RBD_CONFIG_SOURCE_IMAGE
)
1133 eq(option
['source'], RBD_CONFIG_SOURCE_CONFIG
)
1135 image
.metadata_remove("conf_rbd_cache")
1137 for option
in image
.config_list():
1138 eq(option
['source'], RBD_CONFIG_SOURCE_CONFIG
)
1140 def test_sparsify(self
):
1141 assert_raises(InvalidArgument
, self
.image
.sparsify
, 16)
1142 self
.image
.sparsify(4096)
1144 class TestImageId(object):
1149 self
.image
= Image(ioctx
, image_name
)
1150 self
.image2
= Image(ioctx
, None, None, False, self
.image
.id())
1159 def test_read(self
):
1160 data
= self
.image2
.read(0, 20)
1161 eq(data
, b
'\0' * 20)
1163 def test_write(self
):
1164 data
= rand_data(256)
1165 self
.image2
.write(data
, 0)
1167 def test_resize(self
):
1168 new_size
= IMG_SIZE
* 2
1169 self
.image2
.resize(new_size
)
1170 info
= self
.image2
.stat()
1171 check_stat(info
, new_size
, IMG_ORDER
)
1173 def check_diff(image
, offset
, length
, from_snapshot
, expected
):
1175 def cb(offset
, length
, exists
):
1176 extents
.append((offset
, length
, exists
))
1177 image
.diff_iterate(0, IMG_SIZE
, None, cb
)
1178 eq(extents
, expected
)
1180 class TestClone(object):
1182 @require_features([RBD_FEATURE_LAYERING
])
1188 self
.image
= Image(ioctx
, image_name
)
1189 data
= rand_data(256)
1190 self
.image
.write(data
, IMG_SIZE
// 2)
1191 self
.image
.create_snap('snap1')
1193 self
.image
.protect_snap('snap1')
1194 self
.clone_name
= get_temp_image_name()
1195 self
.rbd
.clone(ioctx
, image_name
, 'snap1', ioctx
, self
.clone_name
,
1197 self
.clone
= Image(ioctx
, self
.clone_name
)
1202 self
.rbd
.remove(ioctx
, self
.clone_name
)
1203 self
.image
.unprotect_snap('snap1')
1204 self
.image
.remove_snap('snap1')
1208 def _test_with_params(self
, features
=None, order
=None, stripe_unit
=None,
1210 self
.image
.create_snap('snap2')
1211 self
.image
.protect_snap('snap2')
1212 clone_name2
= get_temp_image_name()
1213 if features
is None:
1214 self
.rbd
.clone(ioctx
, image_name
, 'snap2', ioctx
, clone_name2
)
1216 self
.rbd
.clone(ioctx
, image_name
, 'snap2', ioctx
, clone_name2
,
1218 elif stripe_unit
is None:
1219 self
.rbd
.clone(ioctx
, image_name
, 'snap2', ioctx
, clone_name2
,
1221 elif stripe_count
is None:
1222 self
.rbd
.clone(ioctx
, image_name
, 'snap2', ioctx
, clone_name2
,
1223 features
, order
, stripe_unit
)
1225 self
.rbd
.clone(ioctx
, image_name
, 'snap2', ioctx
, clone_name2
,
1226 features
, order
, stripe_unit
, stripe_count
)
1227 self
.rbd
.remove(ioctx
, clone_name2
)
1228 self
.image
.unprotect_snap('snap2')
1229 self
.image
.remove_snap('snap2')
1231 def test_with_params(self
):
1232 self
._test
_with
_params
()
1234 def test_with_params2(self
):
1236 self
._test
_with
_params
(features
, self
.image
.stat()['order'])
1238 @require_features([RBD_FEATURE_STRIPINGV2
])
1239 def test_with_params3(self
):
1241 self
._test
_with
_params
(features
, self
.image
.stat()['order'],
1242 self
.image
.stripe_unit(),
1243 self
.image
.stripe_count())
1245 def test_unprotected(self
):
1246 self
.image
.create_snap('snap2')
1248 clone_name2
= get_temp_image_name()
1249 rados
.conf_set("rbd_default_clone_format", "1")
1250 assert_raises(InvalidArgument
, self
.rbd
.clone
, ioctx
, image_name
,
1251 'snap2', ioctx
, clone_name2
, features
)
1252 rados
.conf_set("rbd_default_clone_format", "auto")
1253 self
.image
.remove_snap('snap2')
1255 def test_unprotect_with_children(self
):
1257 # can't remove a snapshot that has dependent clones
1258 assert_raises(ImageBusy
, self
.image
.remove_snap
, 'snap1')
1260 # validate parent info of clone created by TestClone.setUp
1261 (pool
, image
, snap
) = self
.clone
.parent_info()
1263 eq(image
, image_name
)
1265 eq(self
.image
.id(), self
.clone
.parent_id())
1267 # create a new pool...
1268 pool_name2
= get_temp_pool_name()
1269 rados
.create_pool(pool_name2
)
1270 other_ioctx
= rados
.open_ioctx(pool_name2
)
1271 other_ioctx
.application_enable('rbd')
1273 # ...with a clone of the same parent
1274 other_clone_name
= get_temp_image_name()
1275 rados
.conf_set("rbd_default_clone_format", "1")
1276 self
.rbd
.clone(ioctx
, image_name
, 'snap1', other_ioctx
,
1277 other_clone_name
, features
)
1278 rados
.conf_set("rbd_default_clone_format", "auto")
1279 self
.other_clone
= Image(other_ioctx
, other_clone_name
)
1280 # validate its parent info
1281 (pool
, image
, snap
) = self
.other_clone
.parent_info()
1283 eq(image
, image_name
)
1285 eq(self
.image
.id(), self
.other_clone
.parent_id())
1287 # can't unprotect snap with children
1288 assert_raises(ImageBusy
, self
.image
.unprotect_snap
, 'snap1')
1290 # 2 children, check that cannot remove the parent snap
1291 assert_raises(ImageBusy
, self
.image
.remove_snap
, 'snap1')
1293 # close and remove other pool's clone
1294 self
.other_clone
.close()
1295 self
.rbd
.remove(other_ioctx
, other_clone_name
)
1297 # check that we cannot yet remove the parent snap
1298 assert_raises(ImageBusy
, self
.image
.remove_snap
, 'snap1')
1301 rados
.delete_pool(pool_name2
)
1303 # unprotect, remove parent snap happen in cleanup, and should succeed
1305 def test_stat(self
):
1306 image_info
= self
.image
.stat()
1307 clone_info
= self
.clone
.stat()
1308 eq(clone_info
['size'], image_info
['size'])
1309 eq(clone_info
['size'], self
.clone
.overlap())
1311 def test_resize_stat(self
):
1312 self
.clone
.resize(IMG_SIZE
// 2)
1313 image_info
= self
.image
.stat()
1314 clone_info
= self
.clone
.stat()
1315 eq(clone_info
['size'], IMG_SIZE
// 2)
1316 eq(image_info
['size'], IMG_SIZE
)
1317 eq(self
.clone
.overlap(), IMG_SIZE
// 2)
1319 self
.clone
.resize(IMG_SIZE
* 2)
1320 image_info
= self
.image
.stat()
1321 clone_info
= self
.clone
.stat()
1322 eq(clone_info
['size'], IMG_SIZE
* 2)
1323 eq(image_info
['size'], IMG_SIZE
)
1324 eq(self
.clone
.overlap(), IMG_SIZE
// 2)
1326 def test_resize_io(self
):
1327 parent_data
= self
.image
.read(IMG_SIZE
// 2, 256)
1328 self
.image
.resize(0)
1329 self
.clone
.resize(IMG_SIZE
// 2 + 128)
1330 child_data
= self
.clone
.read(IMG_SIZE
// 2, 128)
1331 eq(child_data
, parent_data
[:128])
1332 self
.clone
.resize(IMG_SIZE
)
1333 child_data
= self
.clone
.read(IMG_SIZE
// 2, 256)
1334 eq(child_data
, parent_data
[:128] + (b
'\0' * 128))
1335 self
.clone
.resize(IMG_SIZE
// 2 + 1)
1336 child_data
= self
.clone
.read(IMG_SIZE
// 2, 1)
1337 eq(child_data
, parent_data
[0:1])
1338 self
.clone
.resize(0)
1339 self
.clone
.resize(IMG_SIZE
)
1340 child_data
= self
.clone
.read(IMG_SIZE
// 2, 256)
1341 eq(child_data
, b
'\0' * 256)
1343 def test_read(self
):
1344 parent_data
= self
.image
.read(IMG_SIZE
// 2, 256)
1345 child_data
= self
.clone
.read(IMG_SIZE
// 2, 256)
1346 eq(child_data
, parent_data
)
1348 def test_write(self
):
1349 parent_data
= self
.image
.read(IMG_SIZE
// 2, 256)
1350 new_data
= rand_data(256)
1351 self
.clone
.write(new_data
, IMG_SIZE
// 2 + 256)
1352 child_data
= self
.clone
.read(IMG_SIZE
// 2 + 256, 256)
1353 eq(child_data
, new_data
)
1354 child_data
= self
.clone
.read(IMG_SIZE
// 2, 256)
1355 eq(child_data
, parent_data
)
1356 parent_data
= self
.image
.read(IMG_SIZE
// 2 + 256, 256)
1357 eq(parent_data
, b
'\0' * 256)
1359 def check_children(self
, expected
):
1360 actual
= self
.image
.list_children()
1361 # dedup for cache pools until
1362 # http://tracker.ceph.com/issues/8187 is fixed
1363 deduped
= set([(pool_name
, image
[1]) for image
in actual
])
1364 eq(deduped
, set(expected
))
1366 def check_children2(self
, expected
):
1367 actual
= [{k
:v
for k
,v
in x
.items() if k
in expected
[0]} \
1368 for x
in self
.image
.list_children2()]
1369 eq(actual
, expected
)
1371 def check_descendants(self
, expected
):
1372 eq(list(self
.image
.list_descendants()), expected
)
1374 def get_image_id(self
, ioctx
, name
):
1375 with
Image(ioctx
, name
) as image
:
1378 def test_list_children(self
):
1381 self
.image
.set_snap('snap1')
1382 self
.check_children([(pool_name
, self
.clone_name
)])
1383 self
.check_children2(
1384 [{'pool': pool_name
, 'pool_namespace': '',
1385 'image': self
.clone_name
, 'trash': False,
1386 'id': self
.get_image_id(ioctx
, self
.clone_name
)}])
1387 self
.check_descendants(
1388 [{'pool': pool_name
, 'pool_namespace': '',
1389 'image': self
.clone_name
, 'trash': False,
1390 'id': self
.get_image_id(ioctx
, self
.clone_name
)}])
1392 self
.rbd
.remove(ioctx
, self
.clone_name
)
1393 eq(self
.image
.list_children(), [])
1394 eq(list(self
.image
.list_children2()), [])
1395 eq(list(self
.image
.list_descendants()), [])
1397 clone_name
= get_temp_image_name() + '_'
1398 expected_children
= []
1399 expected_children2
= []
1401 self
.rbd
.clone(ioctx
, image_name
, 'snap1', ioctx
,
1402 clone_name
+ str(i
), features
)
1403 expected_children
.append((pool_name
, clone_name
+ str(i
)))
1404 expected_children2
.append(
1405 {'pool': pool_name
, 'pool_namespace': '',
1406 'image': clone_name
+ str(i
), 'trash': False,
1407 'id': self
.get_image_id(ioctx
, clone_name
+ str(i
))})
1408 self
.check_children(expected_children
)
1409 self
.check_children2(expected_children2
)
1410 self
.check_descendants(expected_children2
)
1412 image6_id
= self
.get_image_id(ioctx
, clone_name
+ str(5))
1413 RBD().trash_move(ioctx
, clone_name
+ str(5), 0)
1414 expected_children
.remove((pool_name
, clone_name
+ str(5)))
1415 for item
in expected_children2
:
1416 for k
, v
in item
.items():
1418 item
["trash"] = True
1419 self
.check_children(expected_children
)
1420 self
.check_children2(expected_children2
)
1421 self
.check_descendants(expected_children2
)
1423 RBD().trash_restore(ioctx
, image6_id
, clone_name
+ str(5))
1424 expected_children
.append((pool_name
, clone_name
+ str(5)))
1425 for item
in expected_children2
:
1426 for k
, v
in item
.items():
1428 item
["trash"] = False
1429 self
.check_children(expected_children
)
1430 self
.check_children2(expected_children2
)
1431 self
.check_descendants(expected_children2
)
1434 self
.rbd
.remove(ioctx
, clone_name
+ str(i
))
1435 expected_children
.remove((pool_name
, clone_name
+ str(i
)))
1436 expected_children2
.pop(0)
1437 self
.check_children(expected_children
)
1438 self
.check_children2(expected_children2
)
1439 self
.check_descendants(expected_children2
)
1441 eq(self
.image
.list_children(), [])
1442 eq(list(self
.image
.list_children2()), [])
1443 self
.rbd
.clone(ioctx
, image_name
, 'snap1', ioctx
, self
.clone_name
,
1445 self
.check_children([(pool_name
, self
.clone_name
)])
1446 self
.check_children2(
1447 [{'pool': pool_name
, 'pool_namespace': '',
1448 'image': self
.clone_name
, 'trash': False,
1449 'id': self
.get_image_id(ioctx
, self
.clone_name
)}])
1450 self
.check_descendants(
1451 [{'pool': pool_name
, 'pool_namespace': '',
1452 'image': self
.clone_name
, 'trash': False,
1453 'id': self
.get_image_id(ioctx
, self
.clone_name
)}])
1454 self
.clone
= Image(ioctx
, self
.clone_name
)
1456 def test_flatten_errors(self
):
1457 # test that we can't flatten a non-clone
1458 assert_raises(InvalidArgument
, self
.image
.flatten
)
1460 # test that we can't flatten a snapshot
1461 self
.clone
.create_snap('snap2')
1462 self
.clone
.set_snap('snap2')
1463 assert_raises(ReadOnlyImage
, self
.clone
.flatten
)
1464 self
.clone
.remove_snap('snap2')
1466 def check_flatten_with_order(self
, new_order
):
1469 clone_name2
= get_temp_image_name()
1470 self
.rbd
.clone(ioctx
, image_name
, 'snap1', ioctx
, clone_name2
,
1471 features
, new_order
)
1472 #with Image(ioctx, 'clone2') as clone:
1473 clone2
= Image(ioctx
, clone_name2
)
1475 eq(clone2
.overlap(), 0)
1477 self
.rbd
.remove(ioctx
, clone_name2
)
1479 # flatten after resizing to non-block size
1480 self
.rbd
.clone(ioctx
, image_name
, 'snap1', ioctx
, clone_name2
,
1481 features
, new_order
)
1482 with
Image(ioctx
, clone_name2
) as clone
:
1483 clone
.resize(IMG_SIZE
// 2 - 1)
1485 eq(0, clone
.overlap())
1486 self
.rbd
.remove(ioctx
, clone_name2
)
1488 # flatten after resizing to non-block size
1489 self
.rbd
.clone(ioctx
, image_name
, 'snap1', ioctx
, clone_name2
,
1490 features
, new_order
)
1491 with
Image(ioctx
, clone_name2
) as clone
:
1492 clone
.resize(IMG_SIZE
// 2 + 1)
1494 eq(clone
.overlap(), 0)
1495 self
.rbd
.remove(ioctx
, clone_name2
)
1497 def test_flatten_basic(self
):
1498 self
.check_flatten_with_order(IMG_ORDER
)
1500 def test_flatten_smaller_order(self
):
1501 self
.check_flatten_with_order(IMG_ORDER
- 2)
1503 def test_flatten_larger_order(self
):
1504 self
.check_flatten_with_order(IMG_ORDER
+ 2)
1506 def test_flatten_drops_cache(self
):
1509 clone_name2
= get_temp_image_name()
1510 self
.rbd
.clone(ioctx
, image_name
, 'snap1', ioctx
, clone_name2
,
1511 features
, IMG_ORDER
)
1512 with
Image(ioctx
, clone_name2
) as clone
:
1513 with
Image(ioctx
, clone_name2
) as clone2
:
1514 # cache object non-existence
1515 data
= clone
.read(IMG_SIZE
// 2, 256)
1516 clone2_data
= clone2
.read(IMG_SIZE
// 2, 256)
1517 eq(data
, clone2_data
)
1519 assert_raises(ImageNotFound
, clone
.parent_info
)
1520 assert_raises(ImageNotFound
, clone2
.parent_info
)
1521 assert_raises(ImageNotFound
, clone
.parent_id
)
1522 assert_raises(ImageNotFound
, clone2
.parent_id
)
1523 after_flatten
= clone
.read(IMG_SIZE
// 2, 256)
1524 eq(data
, after_flatten
)
1525 after_flatten
= clone2
.read(IMG_SIZE
// 2, 256)
1526 eq(data
, after_flatten
)
1527 self
.rbd
.remove(ioctx
, clone_name2
)
1529 def test_flatten_multi_level(self
):
1530 self
.clone
.create_snap('snap2')
1531 self
.clone
.protect_snap('snap2')
1532 clone_name3
= get_temp_image_name()
1533 self
.rbd
.clone(ioctx
, self
.clone_name
, 'snap2', ioctx
, clone_name3
,
1535 self
.clone
.flatten()
1536 with
Image(ioctx
, clone_name3
) as clone3
:
1538 self
.clone
.unprotect_snap('snap2')
1539 self
.clone
.remove_snap('snap2')
1540 self
.rbd
.remove(ioctx
, clone_name3
)
1542 def test_flatten_with_progress(self
):
1543 d
= {'received_callback': False}
1544 def progress_cb(current
, total
):
1545 d
['received_callback'] = True
1550 clone_name
= get_temp_image_name()
1551 self
.rbd
.clone(ioctx
, image_name
, 'snap1', ioctx
, clone_name
,
1553 with
Image(ioctx
, clone_name
) as clone
:
1554 clone
.flatten(on_progress
=progress_cb
)
1555 self
.rbd
.remove(ioctx
, clone_name
)
1556 eq(True, d
['received_callback'])
1558 def test_resize_flatten_multi_level(self
):
1559 self
.clone
.create_snap('snap2')
1560 self
.clone
.protect_snap('snap2')
1561 clone_name3
= get_temp_image_name()
1562 self
.rbd
.clone(ioctx
, self
.clone_name
, 'snap2', ioctx
, clone_name3
,
1564 self
.clone
.resize(1)
1565 orig_data
= self
.image
.read(0, 256)
1566 with
Image(ioctx
, clone_name3
) as clone3
:
1567 clone3_data
= clone3
.read(0, 256)
1568 eq(orig_data
, clone3_data
)
1569 self
.clone
.flatten()
1570 with
Image(ioctx
, clone_name3
) as clone3
:
1571 clone3_data
= clone3
.read(0, 256)
1572 eq(orig_data
, clone3_data
)
1573 self
.rbd
.remove(ioctx
, clone_name3
)
1574 self
.clone
.unprotect_snap('snap2')
1575 self
.clone
.remove_snap('snap2')
1577 def test_trash_snapshot(self
):
1578 self
.image
.create_snap('snap2')
1580 clone_name
= get_temp_image_name()
1581 rados
.conf_set("rbd_default_clone_format", "2")
1582 self
.rbd
.clone(ioctx
, image_name
, 'snap2', ioctx
, clone_name
, features
)
1583 rados
.conf_set("rbd_default_clone_format", "auto")
1585 self
.image
.remove_snap('snap2')
1587 snaps
= [s
for s
in self
.image
.list_snaps() if s
['name'] != 'snap1']
1588 eq([RBD_SNAP_NAMESPACE_TYPE_TRASH
], [s
['namespace'] for s
in snaps
])
1589 eq([{'original_name' : 'snap2'}], [s
['trash'] for s
in snaps
])
1591 self
.rbd
.remove(ioctx
, clone_name
)
1592 eq([], [s
for s
in self
.image
.list_snaps() if s
['name'] != 'snap1'])
1594 class TestExclusiveLock(object):
1596 @require_features([RBD_FEATURE_EXCLUSIVE_LOCK
])
1599 rados2
= Rados(conffile
='')
1602 ioctx2
= rados2
.open_ioctx(pool_name
)
1612 def test_ownership(self
):
1613 with
Image(ioctx
, image_name
) as image1
, Image(ioctx2
, image_name
) as image2
:
1614 image1
.write(b
'0'*256, 0)
1615 eq(image1
.is_exclusive_lock_owner(), True)
1616 eq(image2
.is_exclusive_lock_owner(), False)
1618 def test_snapshot_leadership(self
):
1619 with
Image(ioctx
, image_name
) as image
:
1620 image
.create_snap('snap')
1621 eq(image
.is_exclusive_lock_owner(), True)
1623 with
Image(ioctx
, image_name
) as image
:
1624 image
.write(b
'0'*256, 0)
1625 eq(image
.is_exclusive_lock_owner(), True)
1626 image
.set_snap('snap')
1627 eq(image
.is_exclusive_lock_owner(), False)
1628 with
Image(ioctx
, image_name
, snapshot
='snap') as image
:
1629 eq(image
.is_exclusive_lock_owner(), False)
1631 with
Image(ioctx
, image_name
) as image
:
1632 image
.remove_snap('snap')
1634 def test_read_only_leadership(self
):
1635 with
Image(ioctx
, image_name
, read_only
=True) as image
:
1636 eq(image
.is_exclusive_lock_owner(), False)
1638 def test_follower_flatten(self
):
1639 with
Image(ioctx
, image_name
) as image
:
1640 image
.create_snap('snap')
1641 image
.protect_snap('snap')
1643 RBD().clone(ioctx
, image_name
, 'snap', ioctx
, 'clone', features
)
1644 with
Image(ioctx
, 'clone') as image1
, Image(ioctx2
, 'clone') as image2
:
1645 data
= rand_data(256)
1646 image1
.write(data
, 0)
1648 assert_raises(ImageNotFound
, image1
.parent_info
)
1649 assert_raises(ImageNotFound
, image1
.parent_id
)
1653 image2
.parent_info()
1654 except ImageNotFound
:
1659 RBD().remove(ioctx
, 'clone')
1660 with
Image(ioctx
, image_name
) as image
:
1661 image
.unprotect_snap('snap')
1662 image
.remove_snap('snap')
1664 def test_follower_resize(self
):
1665 with
Image(ioctx
, image_name
) as image1
, Image(ioctx2
, image_name
) as image2
:
1666 image1
.write(b
'0'*256, 0)
1667 for new_size
in [IMG_SIZE
* 2, IMG_SIZE
// 2]:
1668 image2
.resize(new_size
);
1669 eq(new_size
, image1
.size())
1671 if new_size
== image2
.size():
1674 eq(new_size
, image2
.size())
1676 def test_follower_snap_create(self
):
1677 with
Image(ioctx
, image_name
) as image1
, Image(ioctx2
, image_name
) as image2
:
1678 image2
.create_snap('snap1')
1679 image1
.remove_snap('snap1')
1681 def test_follower_snap_rollback(self
):
1682 with
Image(ioctx
, image_name
) as image1
, Image(ioctx2
, image_name
) as image2
:
1683 image1
.create_snap('snap')
1685 assert_raises(ReadOnlyImage
, image2
.rollback_to_snap
, 'snap')
1686 image1
.rollback_to_snap('snap')
1688 image1
.remove_snap('snap')
1690 def test_follower_discard(self
):
1692 with
Image(ioctx
, image_name
) as image1
, Image(ioctx2
, image_name
) as image2
:
1693 data
= rand_data(256)
1694 image1
.write(data
, 0)
1695 image2
.discard(0, 256)
1696 eq(image1
.is_exclusive_lock_owner(), False)
1697 eq(image2
.is_exclusive_lock_owner(), True)
1698 read
= image2
.read(0, 256)
1699 if rados
.conf_get('rbd_skip_partial_discard') == 'false':
1700 eq(256 * b
'\0', read
)
1704 def test_follower_write(self
):
1705 with
Image(ioctx
, image_name
) as image1
, Image(ioctx2
, image_name
) as image2
:
1706 data
= rand_data(256)
1707 image1
.write(data
, 0)
1708 image2
.write(data
, IMG_SIZE
// 2)
1709 eq(image1
.is_exclusive_lock_owner(), False)
1710 eq(image2
.is_exclusive_lock_owner(), True)
1711 for offset
in [0, IMG_SIZE
// 2]:
1712 read
= image2
.read(offset
, 256)
1714 def test_acquire_release_lock(self
):
1715 with
Image(ioctx
, image_name
) as image
:
1716 image
.lock_acquire(RBD_LOCK_MODE_EXCLUSIVE
)
1717 image
.lock_release()
1719 def test_break_lock(self
):
1720 blacklist_rados
= Rados(conffile
='')
1721 blacklist_rados
.connect()
1723 blacklist_ioctx
= blacklist_rados
.open_ioctx(pool_name
)
1725 rados2
.conf_set('rbd_blacklist_on_break_lock', 'true')
1726 with
Image(ioctx2
, image_name
) as image
, \
1727 Image(blacklist_ioctx
, image_name
) as blacklist_image
:
1728 blacklist_image
.lock_acquire(RBD_LOCK_MODE_EXCLUSIVE
)
1729 assert_raises(ReadOnlyImage
, image
.lock_acquire
,
1730 RBD_LOCK_MODE_EXCLUSIVE
)
1732 lock_owners
= list(image
.lock_get_owners())
1733 eq(1, len(lock_owners
))
1734 eq(RBD_LOCK_MODE_EXCLUSIVE
, lock_owners
[0]['mode'])
1735 image
.lock_break(RBD_LOCK_MODE_EXCLUSIVE
,
1736 lock_owners
[0]['owner'])
1738 assert_raises(ConnectionShutdown
,
1739 blacklist_image
.is_exclusive_lock_owner
)
1741 blacklist_rados
.wait_for_latest_osdmap()
1742 data
= rand_data(256)
1743 assert_raises(ConnectionShutdown
,
1744 blacklist_image
.write
, data
, 0)
1746 image
.lock_acquire(RBD_LOCK_MODE_EXCLUSIVE
)
1749 blacklist_image
.close()
1750 except ConnectionShutdown
:
1753 blacklist_ioctx
.close()
1755 blacklist_rados
.shutdown()
1757 class TestMirroring(object):
1760 def check_info(info
, global_id
, state
, primary
=None):
1761 eq(global_id
, info
['global_id'])
1762 eq(state
, info
['state'])
1763 if primary
is not None:
1764 eq(primary
, info
['primary'])
1768 self
.initial_mirror_mode
= self
.rbd
.mirror_mode_get(ioctx
)
1769 self
.rbd
.mirror_mode_set(ioctx
, RBD_MIRROR_MODE_POOL
)
1771 self
.image
= Image(ioctx
, image_name
)
1776 self
.rbd
.mirror_mode_set(ioctx
, self
.initial_mirror_mode
)
1779 def test_mirror_peer(self
):
1780 eq([], list(self
.rbd
.mirror_peer_list(ioctx
)))
1781 cluster_name
= "test_cluster"
1782 client_name
= "test_client"
1783 uuid
= self
.rbd
.mirror_peer_add(ioctx
, cluster_name
, client_name
)
1787 'cluster_name' : cluster_name
,
1788 'client_name' : client_name
,
1790 eq([peer
], list(self
.rbd
.mirror_peer_list(ioctx
)))
1791 cluster_name
= "test_cluster1"
1792 self
.rbd
.mirror_peer_set_cluster(ioctx
, uuid
, cluster_name
)
1793 client_name
= "test_client1"
1794 self
.rbd
.mirror_peer_set_client(ioctx
, uuid
, client_name
)
1797 'cluster_name' : cluster_name
,
1798 'client_name' : client_name
,
1800 eq([peer
], list(self
.rbd
.mirror_peer_list(ioctx
)))
1803 RBD_MIRROR_PEER_ATTRIBUTE_NAME_MON_HOST
: 'host1',
1804 RBD_MIRROR_PEER_ATTRIBUTE_NAME_KEY
: 'abc'
1806 self
.rbd
.mirror_peer_set_attributes(ioctx
, uuid
, attribs
)
1807 eq(attribs
, self
.rbd
.mirror_peer_get_attributes(ioctx
, uuid
))
1809 self
.rbd
.mirror_peer_remove(ioctx
, uuid
)
1810 eq([], list(self
.rbd
.mirror_peer_list(ioctx
)))
1812 @require_features([RBD_FEATURE_EXCLUSIVE_LOCK
,
1813 RBD_FEATURE_JOURNALING
])
1814 def test_mirror_image(self
):
1816 self
.rbd
.mirror_mode_set(ioctx
, RBD_MIRROR_MODE_IMAGE
)
1817 self
.image
.mirror_image_disable(True)
1818 info
= self
.image
.mirror_image_get_info()
1819 self
.check_info(info
, '', RBD_MIRROR_IMAGE_DISABLED
, False)
1821 self
.image
.mirror_image_enable()
1822 info
= self
.image
.mirror_image_get_info()
1823 global_id
= info
['global_id']
1824 self
.check_info(info
, global_id
, RBD_MIRROR_IMAGE_ENABLED
, True)
1826 self
.rbd
.mirror_mode_set(ioctx
, RBD_MIRROR_MODE_POOL
)
1829 self
.image
.mirror_image_disable(True)
1830 except InvalidArgument
:
1832 eq(True, fail
) # Fails because of mirror mode pool
1834 self
.image
.mirror_image_demote()
1835 info
= self
.image
.mirror_image_get_info()
1836 self
.check_info(info
, global_id
, RBD_MIRROR_IMAGE_ENABLED
, False)
1838 self
.image
.mirror_image_resync()
1840 self
.image
.mirror_image_promote(True)
1841 info
= self
.image
.mirror_image_get_info()
1842 self
.check_info(info
, global_id
, RBD_MIRROR_IMAGE_ENABLED
, True)
1846 self
.image
.mirror_image_resync()
1847 except InvalidArgument
:
1849 eq(True, fail
) # Fails because it is primary
1851 status
= self
.image
.mirror_image_get_status()
1852 eq(image_name
, status
['name'])
1853 eq(False, status
['up'])
1854 eq(MIRROR_IMAGE_STATUS_STATE_UNKNOWN
, status
['state'])
1855 info
= status
['info']
1856 self
.check_info(info
, global_id
, RBD_MIRROR_IMAGE_ENABLED
, True)
1858 @require_features([RBD_FEATURE_EXCLUSIVE_LOCK
,
1859 RBD_FEATURE_JOURNALING
])
1860 def test_mirror_image_status(self
):
1861 info
= self
.image
.mirror_image_get_info()
1862 global_id
= info
['global_id']
1863 state
= info
['state']
1864 primary
= info
['primary']
1866 status
= self
.image
.mirror_image_get_status()
1867 eq(image_name
, status
['name'])
1868 eq(False, status
['up'])
1869 eq(MIRROR_IMAGE_STATUS_STATE_UNKNOWN
, status
['state'])
1870 info
= status
['info']
1871 self
.check_info(info
, global_id
, state
, primary
)
1873 images
= list(self
.rbd
.mirror_image_status_list(ioctx
))
1876 eq(image_name
, status
['name'])
1877 eq(False, status
['up'])
1878 eq(MIRROR_IMAGE_STATUS_STATE_UNKNOWN
, status
['state'])
1879 info
= status
['info']
1880 self
.check_info(info
, global_id
, state
)
1882 states
= self
.rbd
.mirror_image_status_summary(ioctx
)
1883 eq([(MIRROR_IMAGE_STATUS_STATE_UNKNOWN
, 1)], states
)
1885 assert_raises(ImageNotFound
, self
.image
.mirror_image_get_instance_id
)
1886 instance_ids
= list(self
.rbd
.mirror_image_instance_id_list(ioctx
))
1887 eq(0, len(instance_ids
))
1891 self
.rbd
.create(ioctx
, image_name
+ str(i
), IMG_SIZE
, IMG_ORDER
,
1892 old_format
=False, features
=int(features
))
1893 images
= list(self
.rbd
.mirror_image_status_list(ioctx
))
1894 eq(N
+ 1, len(images
))
1896 self
.rbd
.remove(ioctx
, image_name
+ str(i
))
1899 class TestTrash(object):
1903 rados2
= Rados(conffile
='')
1906 ioctx2
= rados2
.open_ioctx(pool_name
)
1914 def test_move(self
):
1916 with
Image(ioctx
, image_name
) as image
:
1917 image_id
= image
.id()
1919 RBD().trash_move(ioctx
, image_name
, 1000)
1920 RBD().trash_remove(ioctx
, image_id
, True)
1922 def test_purge(self
):
1924 with
Image(ioctx
, image_name
) as image
:
1925 image_name1
= image_name
1926 image_id1
= image
.id()
1929 with
Image(ioctx
, image_name
) as image
:
1930 image_name2
= image_name
1931 image_id2
= image
.id()
1933 RBD().trash_move(ioctx
, image_name1
, 0)
1934 RBD().trash_move(ioctx
, image_name2
, 1000)
1935 RBD().trash_purge(ioctx
, datetime
.now())
1937 entries
= list(RBD().trash_list(ioctx
))
1938 eq([image_id2
], [x
['id'] for x
in entries
])
1939 RBD().trash_remove(ioctx
, image_id2
, True)
1941 def test_remove_denied(self
):
1943 with
Image(ioctx
, image_name
) as image
:
1944 image_id
= image
.id()
1946 RBD().trash_move(ioctx
, image_name
, 1000)
1947 assert_raises(PermissionError
, RBD().trash_remove
, ioctx
, image_id
)
1948 RBD().trash_remove(ioctx
, image_id
, True)
1950 def test_remove(self
):
1952 with
Image(ioctx
, image_name
) as image
:
1953 image_id
= image
.id()
1955 RBD().trash_move(ioctx
, image_name
, 0)
1956 RBD().trash_remove(ioctx
, image_id
)
1958 def test_remove_with_progress(self
):
1959 d
= {'received_callback': False}
1960 def progress_cb(current
, total
):
1961 d
['received_callback'] = True
1965 with
Image(ioctx
, image_name
) as image
:
1966 image_id
= image
.id()
1968 RBD().trash_move(ioctx
, image_name
, 0)
1969 RBD().trash_remove(ioctx
, image_id
, on_progress
=progress_cb
)
1970 eq(True, d
['received_callback'])
1974 with
Image(ioctx
, image_name
) as image
:
1975 image_id
= image
.id()
1977 RBD().trash_move(ioctx
, image_name
, 1000)
1979 info
= RBD().trash_get(ioctx
, image_id
)
1980 eq(image_id
, info
['id'])
1981 eq(image_name
, info
['name'])
1982 eq('USER', info
['source'])
1983 assert(info
['deferment_end_time'] > info
['deletion_time'])
1985 RBD().trash_remove(ioctx
, image_id
, True)
1987 def test_list(self
):
1989 with
Image(ioctx
, image_name
) as image
:
1990 image_id1
= image
.id()
1991 image_name1
= image_name
1992 RBD().trash_move(ioctx
, image_name
, 1000)
1995 with
Image(ioctx
, image_name
) as image
:
1996 image_id2
= image
.id()
1997 image_name2
= image_name
1998 RBD().trash_move(ioctx
, image_name
, 1000)
2000 entries
= list(RBD().trash_list(ioctx
))
2002 if e
['id'] == image_id1
:
2003 eq(e
['name'], image_name1
)
2004 elif e
['id'] == image_id2
:
2005 eq(e
['name'], image_name2
)
2008 eq(e
['source'], 'USER')
2009 assert e
['deferment_end_time'] > e
['deletion_time']
2011 RBD().trash_remove(ioctx
, image_id1
, True)
2012 RBD().trash_remove(ioctx
, image_id2
, True)
2014 def test_restore(self
):
2016 with
Image(ioctx
, image_name
) as image
:
2017 image_id
= image
.id()
2018 RBD().trash_move(ioctx
, image_name
, 1000)
2019 RBD().trash_restore(ioctx
, image_id
, image_name
)
2022 def test_create_group():
2026 def test_rename_group():
2028 if group_name
is not None:
2030 eq(["new" + group_name
], RBD().group_list(ioctx
))
2031 RBD().group_remove(ioctx
, "new" + group_name
)
2035 def test_list_groups_empty():
2036 eq([], RBD().group_list(ioctx
))
2038 @with_setup(create_group
, remove_group
)
2039 def test_list_groups():
2040 eq([group_name
], RBD().group_list(ioctx
))
2042 @with_setup(create_group
)
2043 def test_list_groups_after_removed():
2045 eq([], RBD().group_list(ioctx
))
2047 class TestGroups(object):
2053 self
.image_names
= [image_name
]
2054 self
.image
= Image(ioctx
, image_name
)
2057 snap_name
= get_temp_snap_name()
2058 self
.group
= Group(ioctx
, group_name
)
2063 for name
in self
.image_names
:
2064 RBD().remove(ioctx
, name
)
2066 def test_group_image_add(self
):
2067 self
.group
.add_image(ioctx
, image_name
)
2069 def test_group_image_list_empty(self
):
2070 eq([], list(self
.group
.list_images()))
2072 def test_group_image_list(self
):
2073 eq([], list(self
.group
.list_images()))
2074 self
.group
.add_image(ioctx
, image_name
)
2075 eq([image_name
], [img
['name'] for img
in self
.group
.list_images()])
2077 def test_group_image_list_move_to_trash(self
):
2078 eq([], list(self
.group
.list_images()))
2079 with
Image(ioctx
, image_name
) as image
:
2080 image_id
= image
.id()
2081 self
.group
.add_image(ioctx
, image_name
)
2082 eq([image_name
], [img
['name'] for img
in self
.group
.list_images()])
2083 RBD().trash_move(ioctx
, image_name
, 0)
2084 eq([], list(self
.group
.list_images()))
2085 RBD().trash_restore(ioctx
, image_id
, image_name
)
2087 def test_group_image_many_images(self
):
2088 eq([], list(self
.group
.list_images()))
2089 self
.group
.add_image(ioctx
, image_name
)
2091 for x
in range(0, 20):
2093 self
.image_names
.append(image_name
)
2094 self
.group
.add_image(ioctx
, image_name
)
2096 self
.image_names
.sort()
2097 answer
= [img
['name'] for img
in self
.group
.list_images()]
2099 eq(self
.image_names
, answer
)
2101 def test_group_image_remove(self
):
2102 eq([], list(self
.group
.list_images()))
2103 self
.group
.add_image(ioctx
, image_name
)
2104 with
Image(ioctx
, image_name
) as image
:
2105 eq(RBD_OPERATION_FEATURE_GROUP
,
2106 image
.op_features() & RBD_OPERATION_FEATURE_GROUP
)
2107 group
= image
.group()
2108 eq(group_name
, group
['name'])
2110 eq([image_name
], [img
['name'] for img
in self
.group
.list_images()])
2111 self
.group
.remove_image(ioctx
, image_name
)
2112 eq([], list(self
.group
.list_images()))
2113 with
Image(ioctx
, image_name
) as image
:
2114 eq(0, image
.op_features() & RBD_OPERATION_FEATURE_GROUP
)
2116 def test_group_snap(self
):
2118 eq([], list(self
.group
.list_snaps()))
2119 self
.group
.create_snap(snap_name
)
2120 eq([snap_name
], [snap
['name'] for snap
in self
.group
.list_snaps()])
2122 for snap
in self
.image
.list_snaps():
2123 eq(rbd
.RBD_SNAP_NAMESPACE_TYPE_GROUP
, snap
['namespace'])
2124 info
= snap
['group']
2125 eq(group_name
, info
['group_name'])
2126 eq(snap_name
, info
['group_snap_name'])
2128 self
.group
.remove_snap(snap_name
)
2129 eq([], list(self
.group
.list_snaps()))
2131 def test_group_snap_list_many(self
):
2133 eq([], list(self
.group
.list_snaps()))
2135 for x
in range(0, 20):
2136 snap_names
.append(snap_name
)
2137 self
.group
.create_snap(snap_name
)
2138 snap_name
= get_temp_snap_name()
2141 answer
= [snap
['name'] for snap
in self
.group
.list_snaps()]
2143 eq(snap_names
, answer
)
2145 def test_group_snap_namespace(self
):
2147 eq([], list(self
.group
.list_snaps()))
2148 self
.group
.add_image(ioctx
, image_name
)
2149 self
.group
.create_snap(snap_name
)
2150 eq(1, len([snap
['name'] for snap
in self
.image
.list_snaps()]))
2151 self
.group
.remove_image(ioctx
, image_name
)
2152 self
.group
.remove_snap(snap_name
)
2153 eq([], list(self
.group
.list_snaps()))
2155 def test_group_snap_rename(self
):
2157 new_snap_name
= "new" + snap_name
2159 eq([], list(self
.group
.list_snaps()))
2160 self
.group
.create_snap(snap_name
)
2161 eq([snap_name
], [snap
['name'] for snap
in self
.group
.list_snaps()])
2162 self
.group
.rename_snap(snap_name
, new_snap_name
)
2163 eq([new_snap_name
], [snap
['name'] for snap
in self
.group
.list_snaps()])
2164 self
.group
.remove_snap(new_snap_name
)
2165 eq([], list(self
.group
.list_snaps()))
2167 def test_group_snap_rollback(self
):
2168 eq([], list(self
.group
.list_images()))
2169 self
.group
.add_image(ioctx
, image_name
)
2170 with
Image(ioctx
, image_name
) as image
:
2171 image
.write(b
'\0' * 256, 0)
2172 read
= image
.read(0, 256)
2173 eq(read
, b
'\0' * 256)
2176 eq([], list(self
.group
.list_snaps()))
2177 self
.group
.create_snap(snap_name
)
2178 eq([snap_name
], [snap
['name'] for snap
in self
.group
.list_snaps()])
2180 with
Image(ioctx
, image_name
) as image
:
2181 data
= rand_data(256)
2182 image
.write(data
, 0)
2183 read
= image
.read(0, 256)
2186 self
.group
.rollback_to_snap(snap_name
)
2187 with
Image(ioctx
, image_name
) as image
:
2188 read
= image
.read(0, 256)
2189 eq(read
, b
'\0' * 256)
2191 self
.group
.remove_image(ioctx
, image_name
)
2192 eq([], list(self
.group
.list_images()))
2193 self
.group
.remove_snap(snap_name
)
2194 eq([], list(self
.group
.list_snaps()))
2196 @with_setup(create_image
, remove_image
)
2199 image_name2
= get_temp_image_name()
2201 class TestMigration(object):
2203 def test_migration(self
):
2205 RBD().migration_prepare(ioctx
, image_name
, ioctx
, image_name
, features
=63,
2206 order
=23, stripe_unit
=1<<23, stripe_count
=1,
2209 status
= RBD().migration_status(ioctx
, image_name
)
2210 eq(image_name
, status
['source_image_name'])
2211 eq(image_name
, status
['dest_image_name'])
2212 eq(RBD_IMAGE_MIGRATION_STATE_PREPARED
, status
['state'])
2214 RBD().migration_execute(ioctx
, image_name
)
2215 RBD().migration_commit(ioctx
, image_name
)
2218 def test_migration_with_progress(self
):
2219 d
= {'received_callback': False}
2220 def progress_cb(current
, total
):
2221 d
['received_callback'] = True
2225 RBD().migration_prepare(ioctx
, image_name
, ioctx
, image_name
, features
=63,
2226 order
=23, stripe_unit
=1<<23, stripe_count
=1,
2228 RBD().migration_execute(ioctx
, image_name
, on_progress
=progress_cb
)
2229 eq(True, d
['received_callback'])
2230 d
['received_callback'] = False
2232 RBD().migration_commit(ioctx
, image_name
, on_progress
=progress_cb
)
2233 eq(True, d
['received_callback'])
2236 def test_migrate_abort(self
):
2238 RBD().migration_prepare(ioctx
, image_name
, ioctx
, image_name
, features
=63,
2239 order
=23, stripe_unit
=1<<23, stripe_count
=1,
2241 RBD().migration_abort(ioctx
, image_name
)
2244 def test_migrate_abort_with_progress(self
):
2245 d
= {'received_callback': False}
2246 def progress_cb(current
, total
):
2247 d
['received_callback'] = True
2251 RBD().migration_prepare(ioctx
, image_name
, ioctx
, image_name
, features
=63,
2252 order
=23, stripe_unit
=1<<23, stripe_count
=1,
2254 RBD().migration_abort(ioctx
, image_name
, on_progress
=progress_cb
)
2255 eq(True, d
['received_callback'])