10 from hashlib
import md5
11 from textwrap
import dedent
13 from tasks
.cephfs
.cephfs_test_case
import CephFSTestCase
14 from teuthology
.exceptions
import CommandFailedError
15 from teuthology
.misc
import sudo_write_file
17 log
= logging
.getLogger(__name__
)
19 class TestVolumes(CephFSTestCase
):
20 TEST_VOLUME_PREFIX
= "volume"
21 TEST_SUBVOLUME_PREFIX
="subvolume"
22 TEST_GROUP_PREFIX
="group"
23 TEST_SNAPSHOT_PREFIX
="snapshot"
24 TEST_CLONE_PREFIX
="clone"
25 TEST_FILE_NAME_PREFIX
="subvolume_file"
27 # for filling subvolume with data
32 DEFAULT_FILE_SIZE
= 1 # MB
33 DEFAULT_NUMBER_OF_FILES
= 1024
35 def _fs_cmd(self
, *args
):
36 return self
.mgr_cluster
.mon_manager
.raw_cluster_cmd("fs", *args
)
38 def _raw_cmd(self
, *args
):
39 return self
.mgr_cluster
.mon_manager
.raw_cluster_cmd(*args
)
41 def __check_clone_state(self
, state
, clone
, clone_group
=None, timo
=120):
43 args
= ["clone", "status", self
.volname
, clone
]
45 args
.append(clone_group
)
48 result
= json
.loads(self
._fs
_cmd
(*args
))
49 if result
["status"]["state"] == state
:
53 self
.assertTrue(check
< timo
)
55 def _wait_for_clone_to_complete(self
, clone
, clone_group
=None, timo
=120):
56 self
.__check
_clone
_state
("complete", clone
, clone_group
, timo
)
58 def _wait_for_clone_to_fail(self
, clone
, clone_group
=None, timo
=120):
59 self
.__check
_clone
_state
("failed", clone
, clone_group
, timo
)
61 def _check_clone_canceled(self
, clone
, clone_group
=None):
62 self
.__check
_clone
_state
("canceled", clone
, clone_group
, timo
=1)
64 def _get_subvolume_snapshot_path(self
, subvolume
, snapshot
, source_group
, subvol_path
, source_version
):
65 if source_version
== 2:
67 if subvol_path
is not None:
68 (base_path
, uuid_str
) = os
.path
.split(subvol_path
)
70 (base_path
, uuid_str
) = os
.path
.split(self
._get
_subvolume
_path
(self
.volname
, subvolume
, group_name
=source_group
))
71 return os
.path
.join(base_path
, ".snap", snapshot
, uuid_str
)
74 base_path
= self
._get
_subvolume
_path
(self
.volname
, subvolume
, group_name
=source_group
)
75 return os
.path
.join(base_path
, ".snap", snapshot
)
77 def _verify_clone_attrs(self
, source_path
, clone_path
):
81 p
= self
.mount_a
.run_shell(["find", path1
])
82 paths
= p
.stdout
.getvalue().strip().split()
84 # for each entry in source and clone (sink) verify certain inode attributes:
85 # inode type, mode, ownership, [am]time.
86 for source_path
in paths
:
87 sink_entry
= source_path
[len(path1
)+1:]
88 sink_path
= os
.path
.join(path2
, sink_entry
)
91 sval
= int(self
.mount_a
.run_shell(['stat', '-c' '%f', source_path
]).stdout
.getvalue().strip(), 16)
92 cval
= int(self
.mount_a
.run_shell(['stat', '-c' '%f', sink_path
]).stdout
.getvalue().strip(), 16)
93 self
.assertEqual(sval
, cval
)
96 sval
= int(self
.mount_a
.run_shell(['stat', '-c' '%u', source_path
]).stdout
.getvalue().strip())
97 cval
= int(self
.mount_a
.run_shell(['stat', '-c' '%u', sink_path
]).stdout
.getvalue().strip())
98 self
.assertEqual(sval
, cval
)
100 sval
= int(self
.mount_a
.run_shell(['stat', '-c' '%g', source_path
]).stdout
.getvalue().strip())
101 cval
= int(self
.mount_a
.run_shell(['stat', '-c' '%g', sink_path
]).stdout
.getvalue().strip())
102 self
.assertEqual(sval
, cval
)
105 sval
= int(self
.mount_a
.run_shell(['stat', '-c' '%X', source_path
]).stdout
.getvalue().strip())
106 cval
= int(self
.mount_a
.run_shell(['stat', '-c' '%X', sink_path
]).stdout
.getvalue().strip())
107 self
.assertEqual(sval
, cval
)
109 sval
= int(self
.mount_a
.run_shell(['stat', '-c' '%Y', source_path
]).stdout
.getvalue().strip())
110 cval
= int(self
.mount_a
.run_shell(['stat', '-c' '%Y', sink_path
]).stdout
.getvalue().strip())
111 self
.assertEqual(sval
, cval
)
113 def _verify_clone_root(self
, source_path
, clone_path
, clone
, clone_group
, clone_pool
):
114 # verifies following clone root attrs quota, data_pool and pool_namespace
115 # remaining attributes of clone root are validated in _verify_clone_attrs
117 clone_info
= json
.loads(self
._get
_subvolume
_info
(self
.volname
, clone
, clone_group
))
119 # verify quota is inherited from source snapshot
120 src_quota
= self
.mount_a
.getfattr(source_path
, "ceph.quota.max_bytes")
121 self
.assertEqual(clone_info
["bytes_quota"], "infinite" if src_quota
is None else int(src_quota
))
124 # verify pool is set as per request
125 self
.assertEqual(clone_info
["data_pool"], clone_pool
)
127 # verify pool and pool namespace are inherited from snapshot
128 self
.assertEqual(clone_info
["data_pool"],
129 self
.mount_a
.getfattr(source_path
, "ceph.dir.layout.pool"))
130 self
.assertEqual(clone_info
["pool_namespace"],
131 self
.mount_a
.getfattr(source_path
, "ceph.dir.layout.pool_namespace"))
133 def _verify_clone(self
, subvolume
, snapshot
, clone
,
134 source_group
=None, clone_group
=None, clone_pool
=None,
135 subvol_path
=None, source_version
=2, timo
=120):
136 # pass in subvol_path (subvolume path when snapshot was taken) when subvolume is removed
137 # but snapshots are retained for clone verification
138 path1
= self
._get
_subvolume
_snapshot
_path
(subvolume
, snapshot
, source_group
, subvol_path
, source_version
)
139 path2
= self
._get
_subvolume
_path
(self
.volname
, clone
, group_name
=clone_group
)
142 # TODO: currently snapshot rentries are not stable if snapshot source entries
143 # are removed, https://tracker.ceph.com/issues/46747
144 while check
< timo
and subvol_path
is None:
145 val1
= int(self
.mount_a
.getfattr(path1
, "ceph.dir.rentries"))
146 val2
= int(self
.mount_a
.getfattr(path2
, "ceph.dir.rentries"))
151 self
.assertTrue(check
< timo
)
153 self
._verify
_clone
_root
(path1
, path2
, clone
, clone_group
, clone_pool
)
154 self
._verify
_clone
_attrs
(path1
, path2
)
156 def _generate_random_volume_name(self
, count
=1):
157 n
= self
.volume_start
158 volumes
= [f
"{TestVolumes.TEST_VOLUME_PREFIX}_{i:016}" for i
in range(n
, n
+count
)]
159 self
.volume_start
+= count
160 return volumes
[0] if count
== 1 else volumes
162 def _generate_random_subvolume_name(self
, count
=1):
163 n
= self
.subvolume_start
164 subvolumes
= [f
"{TestVolumes.TEST_SUBVOLUME_PREFIX}_{i:016}" for i
in range(n
, n
+count
)]
165 self
.subvolume_start
+= count
166 return subvolumes
[0] if count
== 1 else subvolumes
168 def _generate_random_group_name(self
, count
=1):
170 groups
= [f
"{TestVolumes.TEST_GROUP_PREFIX}_{i:016}" for i
in range(n
, n
+count
)]
171 self
.group_start
+= count
172 return groups
[0] if count
== 1 else groups
174 def _generate_random_snapshot_name(self
, count
=1):
175 n
= self
.snapshot_start
176 snaps
= [f
"{TestVolumes.TEST_SNAPSHOT_PREFIX}_{i:016}" for i
in range(n
, n
+count
)]
177 self
.snapshot_start
+= count
178 return snaps
[0] if count
== 1 else snaps
180 def _generate_random_clone_name(self
, count
=1):
182 clones
= [f
"{TestVolumes.TEST_CLONE_PREFIX}_{i:016}" for i
in range(n
, n
+count
)]
183 self
.clone_start
+= count
184 return clones
[0] if count
== 1 else clones
186 def _enable_multi_fs(self
):
187 self
._fs
_cmd
("flag", "set", "enable_multiple", "true", "--yes-i-really-mean-it")
189 def _create_or_reuse_test_volume(self
):
190 result
= json
.loads(self
._fs
_cmd
("volume", "ls"))
192 self
.vol_created
= True
193 self
.volname
= self
._generate
_random
_volume
_name
()
194 self
._fs
_cmd
("volume", "create", self
.volname
)
196 self
.volname
= result
[0]['name']
198 def _get_subvolume_group_path(self
, vol_name
, group_name
):
199 args
= ("subvolumegroup", "getpath", vol_name
, group_name
)
200 path
= self
._fs
_cmd
(*args
)
201 # remove the leading '/', and trailing whitespaces
202 return path
[1:].rstrip()
204 def _get_subvolume_path(self
, vol_name
, subvol_name
, group_name
=None):
205 args
= ["subvolume", "getpath", vol_name
, subvol_name
]
207 args
.append(group_name
)
209 path
= self
._fs
_cmd
(*args
)
210 # remove the leading '/', and trailing whitespaces
211 return path
[1:].rstrip()
213 def _get_subvolume_info(self
, vol_name
, subvol_name
, group_name
=None):
214 args
= ["subvolume", "info", vol_name
, subvol_name
]
216 args
.append(group_name
)
218 subvol_md
= self
._fs
_cmd
(*args
)
221 def _get_subvolume_snapshot_info(self
, vol_name
, subvol_name
, snapname
, group_name
=None):
222 args
= ["subvolume", "snapshot", "info", vol_name
, subvol_name
, snapname
]
224 args
.append(group_name
)
226 snap_md
= self
._fs
_cmd
(*args
)
229 def _delete_test_volume(self
):
230 self
._fs
_cmd
("volume", "rm", self
.volname
, "--yes-i-really-mean-it")
232 def _do_subvolume_pool_and_namespace_update(self
, subvolume
, pool
=None, pool_namespace
=None, subvolume_group
=None):
233 subvolpath
= self
._get
_subvolume
_path
(self
.volname
, subvolume
, group_name
=subvolume_group
)
236 self
.mount_a
.setfattr(subvolpath
, 'ceph.dir.layout.pool', pool
)
238 if pool_namespace
is not None:
239 self
.mount_a
.setfattr(subvolpath
, 'ceph.dir.layout.pool_namespace', pool_namespace
)
241 def _do_subvolume_attr_update(self
, subvolume
, uid
, gid
, mode
, subvolume_group
=None):
242 subvolpath
= self
._get
_subvolume
_path
(self
.volname
, subvolume
, group_name
=subvolume_group
)
245 self
.mount_a
.run_shell(['chmod', mode
, subvolpath
])
248 self
.mount_a
.run_shell(['chown', uid
, subvolpath
])
249 self
.mount_a
.run_shell(['chgrp', gid
, subvolpath
])
251 def _do_subvolume_io(self
, subvolume
, subvolume_group
=None, create_dir
=None,
252 number_of_files
=DEFAULT_NUMBER_OF_FILES
, file_size
=DEFAULT_FILE_SIZE
):
253 # get subvolume path for IO
254 args
= ["subvolume", "getpath", self
.volname
, subvolume
]
256 args
.append(subvolume_group
)
258 subvolpath
= self
._fs
_cmd
(*args
)
259 self
.assertNotEqual(subvolpath
, None)
260 subvolpath
= subvolpath
[1:].rstrip() # remove "/" prefix and any trailing newline
264 io_path
= os
.path
.join(subvolpath
, create_dir
)
265 self
.mount_a
.run_shell(["mkdir", "-p", io_path
])
267 log
.debug("filling subvolume {0} with {1} files each {2}MB size under directory {3}".format(subvolume
, number_of_files
, file_size
, io_path
))
268 for i
in range(number_of_files
):
269 filename
= "{0}.{1}".format(TestVolumes
.TEST_FILE_NAME_PREFIX
, i
)
270 self
.mount_a
.write_n_mb(os
.path
.join(io_path
, filename
), file_size
)
272 def _do_subvolume_io_mixed(self
, subvolume
, subvolume_group
=None):
273 subvolpath
= self
._get
_subvolume
_path
(self
.volname
, subvolume
, group_name
=subvolume_group
)
275 reg_file
= "regfile.0"
276 reg_path
= os
.path
.join(subvolpath
, reg_file
)
277 dir_path
= os
.path
.join(subvolpath
, "dir.0")
278 sym_path1
= os
.path
.join(subvolpath
, "sym.0")
279 # this symlink's ownership would be changed
280 sym_path2
= os
.path
.join(dir_path
, "sym.0")
282 #self.mount_a.write_n_mb(reg_path, TestVolumes.DEFAULT_FILE_SIZE)
283 self
.mount_a
.run_shell(["sudo", "mkdir", dir_path
], omit_sudo
=False)
284 self
.mount_a
.run_shell(["sudo", "ln", "-s", "./{}".format(reg_file
), sym_path1
], omit_sudo
=False)
285 self
.mount_a
.run_shell(["sudo", "ln", "-s", "./{}".format(reg_file
), sym_path2
], omit_sudo
=False)
286 # flip ownership to nobody. assumption: nobody's id is 65534
287 self
.mount_a
.run_shell(["sudo", "chown", "-h", "65534:65534", sym_path2
], omit_sudo
=False)
289 def _wait_for_trash_empty(self
, timeout
=30):
290 # XXX: construct the trash dir path (note that there is no mgr
291 # [sub]volume interface for this).
292 trashdir
= os
.path
.join("./", "volumes", "_deleting")
293 self
.mount_a
.wait_for_dir_empty(trashdir
, timeout
=timeout
)
295 def _assert_meta_location_and_version(self
, vol_name
, subvol_name
, subvol_group
=None, version
=2, legacy
=False):
297 subvol_path
= self
._get
_subvolume
_path
(vol_name
, subvol_name
, group_name
=subvol_group
)
299 m
.update(("/"+subvol_path
).encode('utf-8'))
300 meta_filename
= "{0}.meta".format(m
.digest().hex())
301 metapath
= os
.path
.join(".", "volumes", "_legacy", meta_filename
)
303 group
= subvol_group
if subvol_group
is not None else '_nogroup'
304 metapath
= os
.path
.join(".", "volumes", group
, subvol_name
, ".meta")
306 out
= self
.mount_a
.run_shell(['cat', metapath
])
307 lines
= out
.stdout
.getvalue().strip().split('\n')
310 if line
== "version = " + str(version
):
313 self
.assertEqual(sv_version
, version
, "version expected was '{0}' but got '{1}' from meta file at '{2}'".format(
314 version
, sv_version
, metapath
))
316 def _create_v1_subvolume(self
, subvol_name
, subvol_group
=None, has_snapshot
=True, subvol_type
='subvolume', state
='complete'):
317 group
= subvol_group
if subvol_group
is not None else '_nogroup'
318 basepath
= os
.path
.join("volumes", group
, subvol_name
)
319 uuid_str
= str(uuid
.uuid4())
320 createpath
= os
.path
.join(basepath
, uuid_str
)
321 self
.mount_a
.run_shell(['mkdir', '-p', createpath
])
323 # create a v1 snapshot, to prevent auto upgrades
325 snappath
= os
.path
.join(createpath
, ".snap", "fake")
326 self
.mount_a
.run_shell(['mkdir', '-p', snappath
])
328 # add required xattrs to subvolume
329 default_pool
= self
.mount_a
.getfattr(".", "ceph.dir.layout.pool")
330 self
.mount_a
.setfattr(createpath
, 'ceph.dir.layout.pool', default_pool
)
332 # create a v1 .meta file
333 meta_contents
= "[GLOBAL]\nversion = 1\ntype = {0}\npath = {1}\nstate = {2}\n".format(subvol_type
, "/" + createpath
, state
)
334 if state
== 'pending':
335 # add a fake clone source
336 meta_contents
= meta_contents
+ '[source]\nvolume = fake\nsubvolume = fake\nsnapshot = fake\n'
337 meta_filepath1
= os
.path
.join(self
.mount_a
.mountpoint
, basepath
, ".meta")
338 sudo_write_file(self
.mount_a
.client_remote
, meta_filepath1
, meta_contents
)
341 def _update_fake_trash(self
, subvol_name
, subvol_group
=None, trash_name
='fake', create
=True):
342 group
= subvol_group
if subvol_group
is not None else '_nogroup'
343 trashpath
= os
.path
.join("volumes", group
, subvol_name
, '.trash', trash_name
)
345 self
.mount_a
.run_shell(['mkdir', '-p', trashpath
])
347 self
.mount_a
.run_shell(['rmdir', trashpath
])
350 super(TestVolumes
, self
).setUp()
352 self
.vol_created
= False
353 self
._enable
_multi
_fs
()
354 self
._create
_or
_reuse
_test
_volume
()
355 self
.config_set('mon', 'mon_allow_pool_delete', True)
356 self
.volume_start
= random
.randint(1, (1<<20))
357 self
.subvolume_start
= random
.randint(1, (1<<20))
358 self
.group_start
= random
.randint(1, (1<<20))
359 self
.snapshot_start
= random
.randint(1, (1<<20))
360 self
.clone_start
= random
.randint(1, (1<<20))
364 self
._delete
_test
_volume
()
365 super(TestVolumes
, self
).tearDown()
367 def test_connection_expiration(self
):
368 # unmount any cephfs mounts
369 self
.mount_a
.umount_wait()
370 sessions
= self
._session
_list
()
371 self
.assertLessEqual(len(sessions
), 1) # maybe mgr is already mounted
373 # Get the mgr to definitely mount cephfs
374 subvolume
= self
._generate
_random
_subvolume
_name
()
375 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
376 sessions
= self
._session
_list
()
377 self
.assertEqual(len(sessions
), 1)
379 # Now wait for the mgr to expire the connection:
380 self
.wait_until_evicted(sessions
[0]['id'], timeout
=90)
382 def test_volume_create(self
):
384 That the volume can be created and then cleans up
386 volname
= self
._generate
_random
_volume
_name
()
387 self
._fs
_cmd
("volume", "create", volname
)
388 volumels
= json
.loads(self
._fs
_cmd
("volume", "ls"))
390 if not (volname
in ([volume
['name'] for volume
in volumels
])):
391 raise RuntimeError("Error creating volume '{0}'".format(volname
))
394 self
._fs
_cmd
("volume", "rm", volname
, "--yes-i-really-mean-it")
396 def test_volume_ls(self
):
398 That the existing and the newly created volumes can be listed and
401 vls
= json
.loads(self
._fs
_cmd
("volume", "ls"))
402 volumes
= [volume
['name'] for volume
in vls
]
404 #create new volumes and add it to the existing list of volumes
405 volumenames
= self
._generate
_random
_volume
_name
(3)
406 for volumename
in volumenames
:
407 self
._fs
_cmd
("volume", "create", volumename
)
408 volumes
.extend(volumenames
)
412 volumels
= json
.loads(self
._fs
_cmd
('volume', 'ls'))
413 if len(volumels
) == 0:
414 raise RuntimeError("Expected the 'fs volume ls' command to list the created volumes.")
416 volnames
= [volume
['name'] for volume
in volumels
]
417 if collections
.Counter(volnames
) != collections
.Counter(volumes
):
418 raise RuntimeError("Error creating or listing volumes")
421 for volume
in volumenames
:
422 self
._fs
_cmd
("volume", "rm", volume
, "--yes-i-really-mean-it")
424 def test_volume_rm(self
):
426 That the volume can only be removed when --yes-i-really-mean-it is used
427 and verify that the deleted volume is not listed anymore.
429 for m
in self
.mounts
:
432 self
._fs
_cmd
("volume", "rm", self
.volname
)
433 except CommandFailedError
as ce
:
434 if ce
.exitstatus
!= errno
.EPERM
:
435 raise RuntimeError("expected the 'fs volume rm' command to fail with EPERM, "
436 "but it failed with {0}".format(ce
.exitstatus
))
438 self
._fs
_cmd
("volume", "rm", self
.volname
, "--yes-i-really-mean-it")
441 volumes
= json
.loads(self
._fs
_cmd
("volume", "ls", "--format=json-pretty"))
442 if (self
.volname
in [volume
['name'] for volume
in volumes
]):
443 raise RuntimeError("Expected the 'fs volume rm' command to succeed. "
444 "The volume {0} not removed.".format(self
.volname
))
446 raise RuntimeError("expected the 'fs volume rm' command to fail.")
448 def test_subvolume_marked(self
):
450 ensure a subvolume is marked with the ceph.dir.subvolume xattr
452 subvolume
= self
._generate
_random
_subvolume
_name
()
455 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
458 subvolpath
= self
._get
_subvolume
_path
(self
.volname
, subvolume
)
460 # subdirectory of a subvolume cannot be moved outside the subvolume once marked with
461 # the xattr ceph.dir.subvolume, hence test by attempting to rename subvol path (incarnation)
462 # outside the subvolume
463 dstpath
= os
.path
.join(self
.mount_a
.mountpoint
, 'volumes', '_nogroup', 'new_subvol_location')
464 srcpath
= os
.path
.join(self
.mount_a
.mountpoint
, subvolpath
)
465 rename_script
= dedent("""
469 os.rename("{src}", "{dst}")
471 if e.errno != errno.EXDEV:
472 raise RuntimeError("invalid error code on renaming subvolume incarnation out of subvolume directory")
474 raise RuntimeError("expected renaming subvolume incarnation out of subvolume directory to fail")
476 self
.mount_a
.run_python(rename_script
.format(src
=srcpath
, dst
=dstpath
))
479 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
481 # verify trash dir is clean
482 self
._wait
_for
_trash
_empty
()
484 def test_volume_rm_arbitrary_pool_removal(self
):
486 That the arbitrary pool added to the volume out of band is removed
487 successfully on volume removal.
489 for m
in self
.mounts
:
491 new_pool
= "new_pool"
492 # add arbitrary data pool
493 self
.fs
.add_data_pool(new_pool
)
494 vol_status
= json
.loads(self
._fs
_cmd
("status", self
.volname
, "--format=json-pretty"))
495 self
._fs
_cmd
("volume", "rm", self
.volname
, "--yes-i-really-mean-it")
498 volumes
= json
.loads(self
._fs
_cmd
("volume", "ls", "--format=json-pretty"))
499 volnames
= [volume
['name'] for volume
in volumes
]
500 self
.assertNotIn(self
.volname
, volnames
)
502 #check if osd pools are gone
503 pools
= json
.loads(self
._raw
_cmd
("osd", "pool", "ls", "--format=json-pretty"))
504 for pool
in vol_status
["pools"]:
505 self
.assertNotIn(pool
["name"], pools
)
507 def test_volume_rm_when_mon_delete_pool_false(self
):
509 That the volume can only be removed when mon_allowd_pool_delete is set
510 to true and verify that the pools are removed after volume deletion.
512 for m
in self
.mounts
:
514 self
.config_set('mon', 'mon_allow_pool_delete', False)
516 self
._fs
_cmd
("volume", "rm", self
.volname
, "--yes-i-really-mean-it")
517 except CommandFailedError
as ce
:
518 self
.assertEqual(ce
.exitstatus
, errno
.EPERM
,
519 "expected the 'fs volume rm' command to fail with EPERM, "
520 "but it failed with {0}".format(ce
.exitstatus
))
521 vol_status
= json
.loads(self
._fs
_cmd
("status", self
.volname
, "--format=json-pretty"))
522 self
.config_set('mon', 'mon_allow_pool_delete', True)
523 self
._fs
_cmd
("volume", "rm", self
.volname
, "--yes-i-really-mean-it")
526 volumes
= json
.loads(self
._fs
_cmd
("volume", "ls", "--format=json-pretty"))
527 volnames
= [volume
['name'] for volume
in volumes
]
528 self
.assertNotIn(self
.volname
, volnames
,
529 "volume {0} exists after removal".format(self
.volname
))
530 #check if pools are gone
531 pools
= json
.loads(self
._raw
_cmd
("osd", "pool", "ls", "--format=json-pretty"))
532 for pool
in vol_status
["pools"]:
533 self
.assertNotIn(pool
["name"], pools
,
534 "pool {0} exists after volume removal".format(pool
["name"]))
536 ### basic subvolume operations
538 def test_subvolume_create_and_rm(self
):
540 subvolume
= self
._generate
_random
_subvolume
_name
()
541 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
543 # make sure it exists
544 subvolpath
= self
._fs
_cmd
("subvolume", "getpath", self
.volname
, subvolume
)
545 self
.assertNotEqual(subvolpath
, None)
548 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
551 self
._fs
_cmd
("subvolume", "getpath", self
.volname
, subvolume
)
552 except CommandFailedError
as ce
:
553 if ce
.exitstatus
!= errno
.ENOENT
:
556 raise RuntimeError("expected the 'fs subvolume getpath' command to fail. Subvolume not removed.")
558 # verify trash dir is clean
559 self
._wait
_for
_trash
_empty
()
561 def test_subvolume_expand(self
):
563 That a subvolume can be expanded in size and its quota matches the expected size.
567 subvolname
= self
._generate
_random
_subvolume
_name
()
568 osize
= self
.DEFAULT_FILE_SIZE
*1024*1024
569 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolname
, "--size", str(osize
))
571 # make sure it exists
572 subvolpath
= self
._get
_subvolume
_path
(self
.volname
, subvolname
)
573 self
.assertNotEqual(subvolpath
, None)
575 # expand the subvolume
577 self
._fs
_cmd
("subvolume", "resize", self
.volname
, subvolname
, str(nsize
))
580 size
= int(self
.mount_a
.getfattr(subvolpath
, "ceph.quota.max_bytes"))
581 self
.assertEqual(size
, nsize
)
584 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolname
)
586 # verify trash dir is clean
587 self
._wait
_for
_trash
_empty
()
589 def test_subvolume_shrink(self
):
591 That a subvolume can be shrinked in size and its quota matches the expected size.
595 subvolname
= self
._generate
_random
_subvolume
_name
()
596 osize
= self
.DEFAULT_FILE_SIZE
*1024*1024
597 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolname
, "--size", str(osize
))
599 # make sure it exists
600 subvolpath
= self
._get
_subvolume
_path
(self
.volname
, subvolname
)
601 self
.assertNotEqual(subvolpath
, None)
603 # shrink the subvolume
605 self
._fs
_cmd
("subvolume", "resize", self
.volname
, subvolname
, str(nsize
))
608 size
= int(self
.mount_a
.getfattr(subvolpath
, "ceph.quota.max_bytes"))
609 self
.assertEqual(size
, nsize
)
612 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolname
)
614 # verify trash dir is clean
615 self
._wait
_for
_trash
_empty
()
617 def test_subvolume_resize_fail_invalid_size(self
):
619 That a subvolume cannot be resized to an invalid size and the quota did not change
622 osize
= self
.DEFAULT_FILE_SIZE
*1024*1024
624 subvolname
= self
._generate
_random
_subvolume
_name
()
625 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolname
, "--size", str(osize
))
627 # make sure it exists
628 subvolpath
= self
._get
_subvolume
_path
(self
.volname
, subvolname
)
629 self
.assertNotEqual(subvolpath
, None)
631 # try to resize the subvolume with an invalid size -10
634 self
._fs
_cmd
("subvolume", "resize", self
.volname
, subvolname
, str(nsize
))
635 except CommandFailedError
as ce
:
636 self
.assertEqual(ce
.exitstatus
, errno
.EINVAL
, "invalid error code on resize of subvolume with invalid size")
638 self
.fail("expected the 'fs subvolume resize' command to fail")
640 # verify the quota did not change
641 size
= int(self
.mount_a
.getfattr(subvolpath
, "ceph.quota.max_bytes"))
642 self
.assertEqual(size
, osize
)
645 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolname
)
647 # verify trash dir is clean
648 self
._wait
_for
_trash
_empty
()
650 def test_subvolume_resize_fail_zero_size(self
):
652 That a subvolume cannot be resized to a zero size and the quota did not change
655 osize
= self
.DEFAULT_FILE_SIZE
*1024*1024
657 subvolname
= self
._generate
_random
_subvolume
_name
()
658 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolname
, "--size", str(osize
))
660 # make sure it exists
661 subvolpath
= self
._get
_subvolume
_path
(self
.volname
, subvolname
)
662 self
.assertNotEqual(subvolpath
, None)
664 # try to resize the subvolume with size 0
667 self
._fs
_cmd
("subvolume", "resize", self
.volname
, subvolname
, str(nsize
))
668 except CommandFailedError
as ce
:
669 self
.assertEqual(ce
.exitstatus
, errno
.EINVAL
, "invalid error code on resize of subvolume with invalid size")
671 self
.fail("expected the 'fs subvolume resize' command to fail")
673 # verify the quota did not change
674 size
= int(self
.mount_a
.getfattr(subvolpath
, "ceph.quota.max_bytes"))
675 self
.assertEqual(size
, osize
)
678 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolname
)
680 # verify trash dir is clean
681 self
._wait
_for
_trash
_empty
()
683 def test_subvolume_resize_quota_lt_used_size(self
):
685 That a subvolume can be resized to a size smaller than the current used size
686 and the resulting quota matches the expected size.
689 osize
= self
.DEFAULT_FILE_SIZE
*1024*1024*20
691 subvolname
= self
._generate
_random
_subvolume
_name
()
692 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolname
, "--size", str(osize
))
694 # make sure it exists
695 subvolpath
= self
._get
_subvolume
_path
(self
.volname
, subvolname
)
696 self
.assertNotEqual(subvolpath
, None)
698 # create one file of 10MB
699 file_size
=self
.DEFAULT_FILE_SIZE
*10
701 log
.debug("filling subvolume {0} with {1} file of size {2}MB".format(subvolname
,
704 filename
= "{0}.{1}".format(TestVolumes
.TEST_FILE_NAME_PREFIX
, self
.DEFAULT_NUMBER_OF_FILES
+1)
705 self
.mount_a
.write_n_mb(os
.path
.join(subvolpath
, filename
), file_size
)
707 usedsize
= int(self
.mount_a
.getfattr(subvolpath
, "ceph.dir.rbytes"))
708 susedsize
= int(self
.mount_a
.run_shell(['stat', '-c' '%s', subvolpath
]).stdout
.getvalue().strip())
709 self
.assertEqual(usedsize
, susedsize
)
711 # shrink the subvolume
712 nsize
= usedsize
// 2
714 self
._fs
_cmd
("subvolume", "resize", self
.volname
, subvolname
, str(nsize
))
715 except CommandFailedError
:
716 self
.fail("expected the 'fs subvolume resize' command to succeed")
719 size
= int(self
.mount_a
.getfattr(subvolpath
, "ceph.quota.max_bytes"))
720 self
.assertEqual(size
, nsize
)
723 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolname
)
725 # verify trash dir is clean
726 self
._wait
_for
_trash
_empty
()
729 def test_subvolume_resize_fail_quota_lt_used_size_no_shrink(self
):
731 That a subvolume cannot be resized to a size smaller than the current used size
732 when --no_shrink is given and the quota did not change.
735 osize
= self
.DEFAULT_FILE_SIZE
*1024*1024*20
737 subvolname
= self
._generate
_random
_subvolume
_name
()
738 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolname
, "--size", str(osize
))
740 # make sure it exists
741 subvolpath
= self
._get
_subvolume
_path
(self
.volname
, subvolname
)
742 self
.assertNotEqual(subvolpath
, None)
744 # create one file of 10MB
745 file_size
=self
.DEFAULT_FILE_SIZE
*10
747 log
.debug("filling subvolume {0} with {1} file of size {2}MB".format(subvolname
,
750 filename
= "{0}.{1}".format(TestVolumes
.TEST_FILE_NAME_PREFIX
, self
.DEFAULT_NUMBER_OF_FILES
+2)
751 self
.mount_a
.write_n_mb(os
.path
.join(subvolpath
, filename
), file_size
)
753 usedsize
= int(self
.mount_a
.getfattr(subvolpath
, "ceph.dir.rbytes"))
754 susedsize
= int(self
.mount_a
.run_shell(['stat', '-c' '%s', subvolpath
]).stdout
.getvalue().strip())
755 self
.assertEqual(usedsize
, susedsize
)
757 # shrink the subvolume
758 nsize
= usedsize
// 2
760 self
._fs
_cmd
("subvolume", "resize", self
.volname
, subvolname
, str(nsize
), "--no_shrink")
761 except CommandFailedError
as ce
:
762 self
.assertEqual(ce
.exitstatus
, errno
.EINVAL
, "invalid error code on resize of subvolume with invalid size")
764 self
.fail("expected the 'fs subvolume resize' command to fail")
766 # verify the quota did not change
767 size
= int(self
.mount_a
.getfattr(subvolpath
, "ceph.quota.max_bytes"))
768 self
.assertEqual(size
, osize
)
771 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolname
)
773 # verify trash dir is clean
774 self
._wait
_for
_trash
_empty
()
776 def test_subvolume_resize_expand_on_full_subvolume(self
):
778 That the subvolume can be expanded from a full subvolume and future writes succeed.
781 osize
= self
.DEFAULT_FILE_SIZE
*1024*1024*10
782 # create subvolume of quota 10MB and make sure it exists
783 subvolname
= self
._generate
_random
_subvolume
_name
()
784 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolname
, "--size", str(osize
))
785 subvolpath
= self
._get
_subvolume
_path
(self
.volname
, subvolname
)
786 self
.assertNotEqual(subvolpath
, None)
788 # create one file of size 10MB and write
789 file_size
=self
.DEFAULT_FILE_SIZE
*10
791 log
.debug("filling subvolume {0} with {1} file of size {2}MB".format(subvolname
,
794 filename
= "{0}.{1}".format(TestVolumes
.TEST_FILE_NAME_PREFIX
, self
.DEFAULT_NUMBER_OF_FILES
+3)
795 self
.mount_a
.write_n_mb(os
.path
.join(subvolpath
, filename
), file_size
)
797 # create a file of size 5MB and try write more
798 file_size
=file_size
// 2
800 log
.debug("filling subvolume {0} with {1} file of size {2}MB".format(subvolname
,
803 filename
= "{0}.{1}".format(TestVolumes
.TEST_FILE_NAME_PREFIX
, self
.DEFAULT_NUMBER_OF_FILES
+4)
805 self
.mount_a
.write_n_mb(os
.path
.join(subvolpath
, filename
), file_size
)
806 except CommandFailedError
:
807 # Not able to write. So expand the subvolume more and try writing the 5MB file again
809 self
._fs
_cmd
("subvolume", "resize", self
.volname
, subvolname
, str(nsize
))
811 self
.mount_a
.write_n_mb(os
.path
.join(subvolpath
, filename
), file_size
)
812 except CommandFailedError
:
813 self
.fail("expected filling subvolume {0} with {1} file of size {2}MB"
814 "to succeed".format(subvolname
, number_of_files
, file_size
))
816 self
.fail("expected filling subvolume {0} with {1} file of size {2}MB"
817 "to fail".format(subvolname
, number_of_files
, file_size
))
820 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolname
)
822 # verify trash dir is clean
823 self
._wait
_for
_trash
_empty
()
825 def test_subvolume_create_idempotence(self
):
827 subvolume
= self
._generate
_random
_subvolume
_name
()
828 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
830 # try creating w/ same subvolume name -- should be idempotent
831 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
834 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
836 # verify trash dir is clean
837 self
._wait
_for
_trash
_empty
()
839 def test_subvolume_create_idempotence_resize(self
):
841 subvolume
= self
._generate
_random
_subvolume
_name
()
842 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
844 # try creating w/ same subvolume name with size -- should set quota
845 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
, "1000000000")
847 # get subvolume metadata
848 subvol_info
= json
.loads(self
._get
_subvolume
_info
(self
.volname
, subvolume
))
849 self
.assertEqual(subvol_info
["bytes_quota"], 1000000000)
852 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
854 # verify trash dir is clean
855 self
._wait
_for
_trash
_empty
()
857 def test_subvolume_pin_export(self
):
858 self
.fs
.set_max_mds(2)
859 status
= self
.fs
.wait_for_daemons()
861 subvolume
= self
._generate
_random
_subvolume
_name
()
862 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
863 self
._fs
_cmd
("subvolume", "pin", self
.volname
, subvolume
, "export", "1")
864 path
= self
._fs
_cmd
("subvolume", "getpath", self
.volname
, subvolume
)
865 path
= os
.path
.dirname(path
) # get subvolume path
867 self
._get
_subtrees
(status
=status
, rank
=1)
868 self
._wait
_subtrees
([(path
, 1)], status
=status
)
871 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
873 # verify trash dir is clean
874 self
._wait
_for
_trash
_empty
()
876 def test_subvolumegroup_pin_distributed(self
):
877 self
.fs
.set_max_mds(2)
878 status
= self
.fs
.wait_for_daemons()
879 self
.config_set('mds', 'mds_export_ephemeral_distributed', True)
882 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, group
)
883 self
._fs
_cmd
("subvolumegroup", "pin", self
.volname
, group
, "distributed", "True")
884 # (no effect on distribution) pin the group directory to 0 so rank 0 has all subtree bounds visible
885 self
._fs
_cmd
("subvolumegroup", "pin", self
.volname
, group
, "export", "0")
886 subvolumes
= self
._generate
_random
_subvolume
_name
(10)
887 for subvolume
in subvolumes
:
888 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
, "--group_name", group
)
889 self
._wait
_distributed
_subtrees
(10, status
=status
)
892 for subvolume
in subvolumes
:
893 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
, group
)
895 # verify trash dir is clean
896 self
._wait
_for
_trash
_empty
()
898 def test_subvolume_pin_random(self
):
899 self
.fs
.set_max_mds(2)
900 self
.fs
.wait_for_daemons()
901 self
.config_set('mds', 'mds_export_ephemeral_random', True)
903 subvolume
= self
._generate
_random
_subvolume
_name
()
904 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
905 self
._fs
_cmd
("subvolume", "pin", self
.volname
, subvolume
, "random", ".01")
909 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
911 # verify trash dir is clean
912 self
._wait
_for
_trash
_empty
()
914 def test_subvolume_create_isolated_namespace(self
):
916 Create subvolume in separate rados namespace
920 subvolume
= self
._generate
_random
_subvolume
_name
()
921 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
, "--namespace-isolated")
923 # get subvolume metadata
924 subvol_info
= json
.loads(self
._get
_subvolume
_info
(self
.volname
, subvolume
))
925 self
.assertNotEqual(len(subvol_info
), 0)
926 self
.assertEqual(subvol_info
["pool_namespace"], "fsvolumens_" + subvolume
)
929 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
931 # verify trash dir is clean
932 self
._wait
_for
_trash
_empty
()
934 def test_subvolume_create_with_invalid_data_pool_layout(self
):
935 subvolume
= self
._generate
_random
_subvolume
_name
()
936 data_pool
= "invalid_pool"
937 # create subvolume with invalid data pool layout
939 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
, "--pool_layout", data_pool
)
940 except CommandFailedError
as ce
:
941 self
.assertEqual(ce
.exitstatus
, errno
.EINVAL
, "invalid error code on create of subvolume with invalid pool layout")
943 self
.fail("expected the 'fs subvolume create' command to fail")
945 # verify trash dir is clean
946 self
._wait
_for
_trash
_empty
()
948 def test_subvolume_rm_force(self
):
949 # test removing non-existing subvolume with --force
950 subvolume
= self
._generate
_random
_subvolume
_name
()
952 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
, "--force")
953 except CommandFailedError
:
954 self
.fail("expected the 'fs subvolume rm --force' command to succeed")
956 def test_subvolume_create_with_auto_cleanup_on_fail(self
):
957 subvolume
= self
._generate
_random
_subvolume
_name
()
958 data_pool
= "invalid_pool"
959 # create subvolume with invalid data pool layout fails
960 with self
.assertRaises(CommandFailedError
):
961 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
, "--pool_layout", data_pool
)
963 # check whether subvol path is cleaned up
965 self
._fs
_cmd
("subvolume", "getpath", self
.volname
, subvolume
)
966 except CommandFailedError
as ce
:
967 self
.assertEqual(ce
.exitstatus
, errno
.ENOENT
, "invalid error code on getpath of non-existent subvolume")
969 self
.fail("expected the 'fs subvolume getpath' command to fail")
971 # verify trash dir is clean
972 self
._wait
_for
_trash
_empty
()
974 def test_subvolume_create_with_invalid_size(self
):
975 # create subvolume with an invalid size -1
976 subvolume
= self
._generate
_random
_subvolume
_name
()
978 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
, "--size", "-1")
979 except CommandFailedError
as ce
:
980 self
.assertEqual(ce
.exitstatus
, errno
.EINVAL
, "invalid error code on create of subvolume with invalid size")
982 self
.fail("expected the 'fs subvolume create' command to fail")
984 # verify trash dir is clean
985 self
._wait
_for
_trash
_empty
()
987 def test_nonexistent_subvolume_rm(self
):
988 # remove non-existing subvolume
989 subvolume
= "non_existent_subvolume"
991 # try, remove subvolume
993 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
994 except CommandFailedError
as ce
:
995 if ce
.exitstatus
!= errno
.ENOENT
:
998 raise RuntimeError("expected the 'fs subvolume rm' command to fail")
1000 def test_nonexistent_subvolume_group_create(self
):
1001 subvolume
= self
._generate
_random
_subvolume
_name
()
1002 group
= "non_existent_group"
1004 # try, creating subvolume in a nonexistent group
1006 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
, "--group_name", group
)
1007 except CommandFailedError
as ce
:
1008 if ce
.exitstatus
!= errno
.ENOENT
:
1011 raise RuntimeError("expected the 'fs subvolume create' command to fail")
1013 def test_default_uid_gid_subvolume(self
):
1014 subvolume
= self
._generate
_random
_subvolume
_name
()
1019 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
1020 subvol_path
= self
._get
_subvolume
_path
(self
.volname
, subvolume
)
1022 # check subvolume's uid and gid
1023 stat
= self
.mount_a
.stat(subvol_path
)
1024 self
.assertEqual(stat
['st_uid'], expected_uid
)
1025 self
.assertEqual(stat
['st_gid'], expected_gid
)
1028 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
1030 # verify trash dir is clean
1031 self
._wait
_for
_trash
_empty
()
1033 def test_subvolume_ls(self
):
1034 # tests the 'fs subvolume ls' command
1039 subvolumes
= self
._generate
_random
_subvolume
_name
(3)
1040 for subvolume
in subvolumes
:
1041 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
1044 subvolumels
= json
.loads(self
._fs
_cmd
('subvolume', 'ls', self
.volname
))
1045 if len(subvolumels
) == 0:
1046 self
.fail("Expected the 'fs subvolume ls' command to list the created subvolumes.")
1048 subvolnames
= [subvolume
['name'] for subvolume
in subvolumels
]
1049 if collections
.Counter(subvolnames
) != collections
.Counter(subvolumes
):
1050 self
.fail("Error creating or listing subvolumes")
1053 for subvolume
in subvolumes
:
1054 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
1056 # verify trash dir is clean
1057 self
._wait
_for
_trash
_empty
()
1059 def test_subvolume_ls_for_notexistent_default_group(self
):
1060 # tests the 'fs subvolume ls' command when the default group '_nogroup' doesn't exist
1061 # prerequisite: we expect that the volume is created and the default group _nogroup is
1062 # NOT created (i.e. a subvolume without group is not created)
1065 subvolumels
= json
.loads(self
._fs
_cmd
('subvolume', 'ls', self
.volname
))
1066 if len(subvolumels
) > 0:
1067 raise RuntimeError("Expected the 'fs subvolume ls' command to output an empty list.")
1069 def test_subvolume_resize_infinite_size(self
):
1071 That a subvolume can be resized to an infinite size by unsetting its quota.
1075 subvolname
= self
._generate
_random
_subvolume
_name
()
1076 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolname
, "--size",
1077 str(self
.DEFAULT_FILE_SIZE
*1024*1024))
1079 # make sure it exists
1080 subvolpath
= self
._get
_subvolume
_path
(self
.volname
, subvolname
)
1081 self
.assertNotEqual(subvolpath
, None)
1084 self
._fs
_cmd
("subvolume", "resize", self
.volname
, subvolname
, "inf")
1086 # verify that the quota is None
1087 size
= self
.mount_a
.getfattr(subvolpath
, "ceph.quota.max_bytes")
1088 self
.assertEqual(size
, None)
1091 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolname
)
1093 # verify trash dir is clean
1094 self
._wait
_for
_trash
_empty
()
1096 def test_subvolume_resize_infinite_size_future_writes(self
):
1098 That a subvolume can be resized to an infinite size and the future writes succeed.
1102 subvolname
= self
._generate
_random
_subvolume
_name
()
1103 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolname
, "--size",
1104 str(self
.DEFAULT_FILE_SIZE
*1024*1024*5))
1106 # make sure it exists
1107 subvolpath
= self
._get
_subvolume
_path
(self
.volname
, subvolname
)
1108 self
.assertNotEqual(subvolpath
, None)
1111 self
._fs
_cmd
("subvolume", "resize", self
.volname
, subvolname
, "inf")
1113 # verify that the quota is None
1114 size
= self
.mount_a
.getfattr(subvolpath
, "ceph.quota.max_bytes")
1115 self
.assertEqual(size
, None)
1117 # create one file of 10MB and try to write
1118 file_size
=self
.DEFAULT_FILE_SIZE
*10
1120 log
.debug("filling subvolume {0} with {1} file of size {2}MB".format(subvolname
,
1123 filename
= "{0}.{1}".format(TestVolumes
.TEST_FILE_NAME_PREFIX
, self
.DEFAULT_NUMBER_OF_FILES
+5)
1126 self
.mount_a
.write_n_mb(os
.path
.join(subvolpath
, filename
), file_size
)
1127 except CommandFailedError
:
1128 self
.fail("expected filling subvolume {0} with {1} file of size {2}MB "
1129 "to succeed".format(subvolname
, number_of_files
, file_size
))
1132 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolname
)
1134 # verify trash dir is clean
1135 self
._wait
_for
_trash
_empty
()
1137 def test_subvolume_info(self
):
1138 # tests the 'fs subvolume info' command
1140 subvol_md
= ["atime", "bytes_pcent", "bytes_quota", "bytes_used", "created_at", "ctime",
1141 "data_pool", "gid", "mode", "mon_addrs", "mtime", "path", "pool_namespace",
1142 "type", "uid", "features", "state"]
1145 subvolume
= self
._generate
_random
_subvolume
_name
()
1146 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
1148 # get subvolume metadata
1149 subvol_info
= json
.loads(self
._get
_subvolume
_info
(self
.volname
, subvolume
))
1150 for md
in subvol_md
:
1151 self
.assertIn(md
, subvol_info
, "'{0}' key not present in metadata of subvolume".format(md
))
1153 self
.assertEqual(subvol_info
["bytes_pcent"], "undefined", "bytes_pcent should be set to undefined if quota is not set")
1154 self
.assertEqual(subvol_info
["bytes_quota"], "infinite", "bytes_quota should be set to infinite if quota is not set")
1155 self
.assertEqual(subvol_info
["pool_namespace"], "", "expected pool namespace to be empty")
1156 self
.assertEqual(subvol_info
["state"], "complete", "expected state to be complete")
1158 self
.assertEqual(len(subvol_info
["features"]), 3,
1159 msg
="expected 3 features, found '{0}' ({1})".format(len(subvol_info
["features"]), subvol_info
["features"]))
1160 for feature
in ['snapshot-clone', 'snapshot-autoprotect', 'snapshot-retention']:
1161 self
.assertIn(feature
, subvol_info
["features"], msg
="expected feature '{0}' in subvolume".format(feature
))
1163 nsize
= self
.DEFAULT_FILE_SIZE
*1024*1024
1164 self
._fs
_cmd
("subvolume", "resize", self
.volname
, subvolume
, str(nsize
))
1166 # get subvolume metadata after quota set
1167 subvol_info
= json
.loads(self
._get
_subvolume
_info
(self
.volname
, subvolume
))
1168 for md
in subvol_md
:
1169 self
.assertIn(md
, subvol_info
, "'{0}' key not present in metadata of subvolume".format(md
))
1171 self
.assertNotEqual(subvol_info
["bytes_pcent"], "undefined", "bytes_pcent should not be set to undefined if quota is not set")
1172 self
.assertEqual(subvol_info
["bytes_quota"], nsize
, "bytes_quota should be set to '{0}'".format(nsize
))
1173 self
.assertEqual(subvol_info
["type"], "subvolume", "type should be set to subvolume")
1174 self
.assertEqual(subvol_info
["state"], "complete", "expected state to be complete")
1176 self
.assertEqual(len(subvol_info
["features"]), 3,
1177 msg
="expected 3 features, found '{0}' ({1})".format(len(subvol_info
["features"]), subvol_info
["features"]))
1178 for feature
in ['snapshot-clone', 'snapshot-autoprotect', 'snapshot-retention']:
1179 self
.assertIn(feature
, subvol_info
["features"], msg
="expected feature '{0}' in subvolume".format(feature
))
1182 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
1184 # verify trash dir is clean
1185 self
._wait
_for
_trash
_empty
()
1187 def test_clone_subvolume_info(self
):
1189 # tests the 'fs subvolume info' command for a clone
1190 subvol_md
= ["atime", "bytes_pcent", "bytes_quota", "bytes_used", "created_at", "ctime",
1191 "data_pool", "gid", "mode", "mon_addrs", "mtime", "path", "pool_namespace",
1194 subvolume
= self
._generate
_random
_subvolume
_name
()
1195 snapshot
= self
._generate
_random
_snapshot
_name
()
1196 clone
= self
._generate
_random
_clone
_name
()
1199 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
1202 self
._do
_subvolume
_io
(subvolume
, number_of_files
=1)
1204 # snapshot subvolume
1205 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
1208 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone
)
1210 # check clone status
1211 self
._wait
_for
_clone
_to
_complete
(clone
)
1214 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
1216 subvol_info
= json
.loads(self
._get
_subvolume
_info
(self
.volname
, clone
))
1217 if len(subvol_info
) == 0:
1218 raise RuntimeError("Expected the 'fs subvolume info' command to list metadata of subvolume")
1219 for md
in subvol_md
:
1220 if md
not in subvol_info
.keys():
1221 raise RuntimeError("%s not present in the metadata of subvolume" % md
)
1222 if subvol_info
["type"] != "clone":
1223 raise RuntimeError("type should be set to clone")
1226 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
1227 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
)
1229 # verify trash dir is clean
1230 self
._wait
_for
_trash
_empty
()
1233 ### subvolume group operations
1235 def test_subvolume_create_and_rm_in_group(self
):
1236 subvolume
= self
._generate
_random
_subvolume
_name
()
1237 group
= self
._generate
_random
_group
_name
()
1240 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, group
)
1242 # create subvolume in group
1243 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
, "--group_name", group
)
1246 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
, group
)
1248 # verify trash dir is clean
1249 self
._wait
_for
_trash
_empty
()
1252 self
._fs
_cmd
("subvolumegroup", "rm", self
.volname
, group
)
1254 def test_subvolume_group_create_with_desired_data_pool_layout(self
):
1255 group1
, group2
= self
._generate
_random
_group
_name
(2)
1258 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, group1
)
1259 group1_path
= self
._get
_subvolume
_group
_path
(self
.volname
, group1
)
1261 default_pool
= self
.mount_a
.getfattr(group1_path
, "ceph.dir.layout.pool")
1262 new_pool
= "new_pool"
1263 self
.assertNotEqual(default_pool
, new_pool
)
1266 self
.fs
.add_data_pool(new_pool
)
1268 # create group specifying the new data pool as its pool layout
1269 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, group2
,
1270 "--pool_layout", new_pool
)
1271 group2_path
= self
._get
_subvolume
_group
_path
(self
.volname
, group2
)
1273 desired_pool
= self
.mount_a
.getfattr(group2_path
, "ceph.dir.layout.pool")
1274 self
.assertEqual(desired_pool
, new_pool
)
1276 self
._fs
_cmd
("subvolumegroup", "rm", self
.volname
, group1
)
1277 self
._fs
_cmd
("subvolumegroup", "rm", self
.volname
, group2
)
1279 def test_subvolume_group_create_with_invalid_data_pool_layout(self
):
1280 group
= self
._generate
_random
_group
_name
()
1281 data_pool
= "invalid_pool"
1282 # create group with invalid data pool layout
1284 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, group
, "--pool_layout", data_pool
)
1285 except CommandFailedError
as ce
:
1286 if ce
.exitstatus
!= errno
.EINVAL
:
1289 raise RuntimeError("expected the 'fs subvolumegroup create' command to fail")
1291 def test_subvolume_group_rm_force(self
):
1292 # test removing non-existing subvolume group with --force
1293 group
= self
._generate
_random
_group
_name
()
1295 self
._fs
_cmd
("subvolumegroup", "rm", self
.volname
, group
, "--force")
1296 except CommandFailedError
:
1297 raise RuntimeError("expected the 'fs subvolumegroup rm --force' command to succeed")
1299 def test_subvolume_group_create_with_auto_cleanup_on_fail(self
):
1300 group
= self
._generate
_random
_group
_name
()
1301 data_pool
= "invalid_pool"
1302 # create group with invalid data pool layout
1303 with self
.assertRaises(CommandFailedError
):
1304 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, group
, "--pool_layout", data_pool
)
1306 # check whether group path is cleaned up
1308 self
._fs
_cmd
("subvolumegroup", "getpath", self
.volname
, group
)
1309 except CommandFailedError
as ce
:
1310 if ce
.exitstatus
!= errno
.ENOENT
:
1313 raise RuntimeError("expected the 'fs subvolumegroup getpath' command to fail")
1315 def test_subvolume_create_with_desired_data_pool_layout_in_group(self
):
1316 subvol1
, subvol2
= self
._generate
_random
_subvolume
_name
(2)
1317 group
= self
._generate
_random
_group
_name
()
1319 # create group. this also helps set default pool layout for subvolumes
1320 # created within the group.
1321 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, group
)
1323 # create subvolume in group.
1324 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvol1
, "--group_name", group
)
1325 subvol1_path
= self
._get
_subvolume
_path
(self
.volname
, subvol1
, group_name
=group
)
1327 default_pool
= self
.mount_a
.getfattr(subvol1_path
, "ceph.dir.layout.pool")
1328 new_pool
= "new_pool"
1329 self
.assertNotEqual(default_pool
, new_pool
)
1332 self
.fs
.add_data_pool(new_pool
)
1334 # create subvolume specifying the new data pool as its pool layout
1335 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvol2
, "--group_name", group
,
1336 "--pool_layout", new_pool
)
1337 subvol2_path
= self
._get
_subvolume
_path
(self
.volname
, subvol2
, group_name
=group
)
1339 desired_pool
= self
.mount_a
.getfattr(subvol2_path
, "ceph.dir.layout.pool")
1340 self
.assertEqual(desired_pool
, new_pool
)
1342 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvol2
, group
)
1343 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvol1
, group
)
1344 self
._fs
_cmd
("subvolumegroup", "rm", self
.volname
, group
)
1346 # verify trash dir is clean
1347 self
._wait
_for
_trash
_empty
()
1349 def test_subvolume_group_create_with_desired_mode(self
):
1350 group1
, group2
= self
._generate
_random
_group
_name
(2)
1352 expected_mode1
= "755"
1354 expected_mode2
= "777"
1357 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, group1
)
1358 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, group2
, "--mode", "777")
1360 group1_path
= self
._get
_subvolume
_group
_path
(self
.volname
, group1
)
1361 group2_path
= self
._get
_subvolume
_group
_path
(self
.volname
, group2
)
1363 # check group's mode
1364 actual_mode1
= self
.mount_a
.run_shell(['stat', '-c' '%a', group1_path
]).stdout
.getvalue().strip()
1365 actual_mode2
= self
.mount_a
.run_shell(['stat', '-c' '%a', group2_path
]).stdout
.getvalue().strip()
1366 self
.assertEqual(actual_mode1
, expected_mode1
)
1367 self
.assertEqual(actual_mode2
, expected_mode2
)
1369 self
._fs
_cmd
("subvolumegroup", "rm", self
.volname
, group1
)
1370 self
._fs
_cmd
("subvolumegroup", "rm", self
.volname
, group2
)
1372 def test_subvolume_group_create_with_desired_uid_gid(self
):
1374 That the subvolume group can be created with the desired uid and gid and its uid and gid matches the
1380 # create subvolume group
1381 subvolgroupname
= self
._generate
_random
_group
_name
()
1382 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, subvolgroupname
, "--uid", str(uid
), "--gid", str(gid
))
1384 # make sure it exists
1385 subvolgrouppath
= self
._get
_subvolume
_group
_path
(self
.volname
, subvolgroupname
)
1386 self
.assertNotEqual(subvolgrouppath
, None)
1388 # verify the uid and gid
1389 suid
= int(self
.mount_a
.run_shell(['stat', '-c' '%u', subvolgrouppath
]).stdout
.getvalue().strip())
1390 sgid
= int(self
.mount_a
.run_shell(['stat', '-c' '%g', subvolgrouppath
]).stdout
.getvalue().strip())
1391 self
.assertEqual(uid
, suid
)
1392 self
.assertEqual(gid
, sgid
)
1395 self
._fs
_cmd
("subvolumegroup", "rm", self
.volname
, subvolgroupname
)
1397 def test_subvolume_create_with_desired_mode_in_group(self
):
1398 subvol1
, subvol2
, subvol3
= self
._generate
_random
_subvolume
_name
(3)
1400 group
= self
._generate
_random
_group
_name
()
1402 expected_mode1
= "755"
1404 expected_mode2
= "777"
1407 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, group
)
1409 # create subvolume in group
1410 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvol1
, "--group_name", group
)
1411 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvol2
, "--group_name", group
, "--mode", "777")
1412 # check whether mode 0777 also works
1413 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvol3
, "--group_name", group
, "--mode", "0777")
1415 subvol1_path
= self
._get
_subvolume
_path
(self
.volname
, subvol1
, group_name
=group
)
1416 subvol2_path
= self
._get
_subvolume
_path
(self
.volname
, subvol2
, group_name
=group
)
1417 subvol3_path
= self
._get
_subvolume
_path
(self
.volname
, subvol3
, group_name
=group
)
1419 # check subvolume's mode
1420 actual_mode1
= self
.mount_a
.run_shell(['stat', '-c' '%a', subvol1_path
]).stdout
.getvalue().strip()
1421 actual_mode2
= self
.mount_a
.run_shell(['stat', '-c' '%a', subvol2_path
]).stdout
.getvalue().strip()
1422 actual_mode3
= self
.mount_a
.run_shell(['stat', '-c' '%a', subvol3_path
]).stdout
.getvalue().strip()
1423 self
.assertEqual(actual_mode1
, expected_mode1
)
1424 self
.assertEqual(actual_mode2
, expected_mode2
)
1425 self
.assertEqual(actual_mode3
, expected_mode2
)
1427 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvol1
, group
)
1428 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvol2
, group
)
1429 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvol3
, group
)
1430 self
._fs
_cmd
("subvolumegroup", "rm", self
.volname
, group
)
1432 # verify trash dir is clean
1433 self
._wait
_for
_trash
_empty
()
1435 def test_subvolume_create_with_desired_uid_gid(self
):
1437 That the subvolume can be created with the desired uid and gid and its uid and gid matches the
1444 subvolname
= self
._generate
_random
_subvolume
_name
()
1445 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolname
, "--uid", str(uid
), "--gid", str(gid
))
1447 # make sure it exists
1448 subvolpath
= self
._get
_subvolume
_path
(self
.volname
, subvolname
)
1449 self
.assertNotEqual(subvolpath
, None)
1451 # verify the uid and gid
1452 suid
= int(self
.mount_a
.run_shell(['stat', '-c' '%u', subvolpath
]).stdout
.getvalue().strip())
1453 sgid
= int(self
.mount_a
.run_shell(['stat', '-c' '%g', subvolpath
]).stdout
.getvalue().strip())
1454 self
.assertEqual(uid
, suid
)
1455 self
.assertEqual(gid
, sgid
)
1458 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolname
)
1460 # verify trash dir is clean
1461 self
._wait
_for
_trash
_empty
()
1463 def test_nonexistent_subvolume_group_rm(self
):
1464 group
= "non_existent_group"
1466 # try, remove subvolume group
1468 self
._fs
_cmd
("subvolumegroup", "rm", self
.volname
, group
)
1469 except CommandFailedError
as ce
:
1470 if ce
.exitstatus
!= errno
.ENOENT
:
1473 raise RuntimeError("expected the 'fs subvolumegroup rm' command to fail")
1475 def test_default_uid_gid_subvolume_group(self
):
1476 group
= self
._generate
_random
_group
_name
()
1481 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, group
)
1482 group_path
= self
._get
_subvolume
_group
_path
(self
.volname
, group
)
1484 # check group's uid and gid
1485 stat
= self
.mount_a
.stat(group_path
)
1486 self
.assertEqual(stat
['st_uid'], expected_uid
)
1487 self
.assertEqual(stat
['st_gid'], expected_gid
)
1490 self
._fs
_cmd
("subvolumegroup", "rm", self
.volname
, group
)
1492 def test_subvolume_group_ls(self
):
1493 # tests the 'fs subvolumegroup ls' command
1495 subvolumegroups
= []
1497 #create subvolumegroups
1498 subvolumegroups
= self
._generate
_random
_group
_name
(3)
1499 for groupname
in subvolumegroups
:
1500 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, groupname
)
1502 subvolumegroupls
= json
.loads(self
._fs
_cmd
('subvolumegroup', 'ls', self
.volname
))
1503 if len(subvolumegroupls
) == 0:
1504 raise RuntimeError("Expected the 'fs subvolumegroup ls' command to list the created subvolume groups")
1506 subvolgroupnames
= [subvolumegroup
['name'] for subvolumegroup
in subvolumegroupls
]
1507 if collections
.Counter(subvolgroupnames
) != collections
.Counter(subvolumegroups
):
1508 raise RuntimeError("Error creating or listing subvolume groups")
1510 def test_subvolume_group_ls_for_nonexistent_volume(self
):
1511 # tests the 'fs subvolumegroup ls' command when /volume doesn't exist
1512 # prerequisite: we expect that the test volume is created and a subvolumegroup is NOT created
1514 # list subvolume groups
1515 subvolumegroupls
= json
.loads(self
._fs
_cmd
('subvolumegroup', 'ls', self
.volname
))
1516 if len(subvolumegroupls
) > 0:
1517 raise RuntimeError("Expected the 'fs subvolumegroup ls' command to output an empty list")
1519 ### snapshot operations
1521 def test_subvolume_snapshot_create_and_rm(self
):
1522 subvolume
= self
._generate
_random
_subvolume
_name
()
1523 snapshot
= self
._generate
_random
_snapshot
_name
()
1526 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
1528 # snapshot subvolume
1529 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
1532 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
1535 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
1537 # verify trash dir is clean
1538 self
._wait
_for
_trash
_empty
()
1540 def test_subvolume_snapshot_info(self
):
1543 tests the 'fs subvolume snapshot info' command
1546 snap_md
= ["created_at", "data_pool", "has_pending_clones", "size"]
1548 subvolume
= self
._generate
_random
_subvolume
_name
()
1549 snapshot
, snap_missing
= self
._generate
_random
_snapshot
_name
(2)
1552 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
1555 self
._do
_subvolume
_io
(subvolume
, number_of_files
=1)
1557 # snapshot subvolume
1558 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
1560 snap_info
= json
.loads(self
._get
_subvolume
_snapshot
_info
(self
.volname
, subvolume
, snapshot
))
1562 self
.assertIn(md
, snap_info
, "'{0}' key not present in metadata of snapshot".format(md
))
1563 self
.assertEqual(snap_info
["has_pending_clones"], "no")
1565 # snapshot info for non-existent snapshot
1567 self
._get
_subvolume
_snapshot
_info
(self
.volname
, subvolume
, snap_missing
)
1568 except CommandFailedError
as ce
:
1569 self
.assertEqual(ce
.exitstatus
, errno
.ENOENT
, "invalid error code on snapshot info of non-existent snapshot")
1571 self
.fail("expected snapshot info of non-existent snapshot to fail")
1574 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
1577 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
1579 # verify trash dir is clean
1580 self
._wait
_for
_trash
_empty
()
1582 def test_subvolume_snapshot_create_idempotence(self
):
1583 subvolume
= self
._generate
_random
_subvolume
_name
()
1584 snapshot
= self
._generate
_random
_snapshot
_name
()
1587 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
1589 # snapshot subvolume
1590 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
1592 # try creating w/ same subvolume snapshot name -- should be idempotent
1593 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
1596 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
1599 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
1601 # verify trash dir is clean
1602 self
._wait
_for
_trash
_empty
()
1604 def test_nonexistent_subvolume_snapshot_rm(self
):
1605 subvolume
= self
._generate
_random
_subvolume
_name
()
1606 snapshot
= self
._generate
_random
_snapshot
_name
()
1609 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
1611 # snapshot subvolume
1612 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
1615 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
1617 # remove snapshot again
1619 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
1620 except CommandFailedError
as ce
:
1621 if ce
.exitstatus
!= errno
.ENOENT
:
1624 raise RuntimeError("expected the 'fs subvolume snapshot rm' command to fail")
1627 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
1629 # verify trash dir is clean
1630 self
._wait
_for
_trash
_empty
()
1632 def test_subvolume_snapshot_rm_force(self
):
1633 # test removing non existing subvolume snapshot with --force
1634 subvolume
= self
._generate
_random
_subvolume
_name
()
1635 snapshot
= self
._generate
_random
_snapshot
_name
()
1639 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
, "--force")
1640 except CommandFailedError
:
1641 raise RuntimeError("expected the 'fs subvolume snapshot rm --force' command to succeed")
1643 def test_subvolume_snapshot_in_group(self
):
1644 subvolume
= self
._generate
_random
_subvolume
_name
()
1645 group
= self
._generate
_random
_group
_name
()
1646 snapshot
= self
._generate
_random
_snapshot
_name
()
1649 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, group
)
1651 # create subvolume in group
1652 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
, "--group_name", group
)
1654 # snapshot subvolume in group
1655 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
, group
)
1658 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
, group
)
1661 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
, group
)
1663 # verify trash dir is clean
1664 self
._wait
_for
_trash
_empty
()
1667 self
._fs
_cmd
("subvolumegroup", "rm", self
.volname
, group
)
1669 def test_subvolume_snapshot_ls(self
):
1670 # tests the 'fs subvolume snapshot ls' command
1675 subvolume
= self
._generate
_random
_subvolume
_name
()
1676 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
1678 # create subvolume snapshots
1679 snapshots
= self
._generate
_random
_snapshot
_name
(3)
1680 for snapshot
in snapshots
:
1681 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
1683 subvolsnapshotls
= json
.loads(self
._fs
_cmd
('subvolume', 'snapshot', 'ls', self
.volname
, subvolume
))
1684 if len(subvolsnapshotls
) == 0:
1685 self
.fail("Expected the 'fs subvolume snapshot ls' command to list the created subvolume snapshots")
1687 snapshotnames
= [snapshot
['name'] for snapshot
in subvolsnapshotls
]
1688 if collections
.Counter(snapshotnames
) != collections
.Counter(snapshots
):
1689 self
.fail("Error creating or listing subvolume snapshots")
1692 for snapshot
in snapshots
:
1693 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
1696 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
1698 # verify trash dir is clean
1699 self
._wait
_for
_trash
_empty
()
1701 def test_subvolume_group_snapshot_unsupported_status(self
):
1702 group
= self
._generate
_random
_group
_name
()
1703 snapshot
= self
._generate
_random
_snapshot
_name
()
1706 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, group
)
1710 self
._fs
_cmd
("subvolumegroup", "snapshot", "create", self
.volname
, group
, snapshot
)
1711 except CommandFailedError
as ce
:
1712 self
.assertEqual(ce
.exitstatus
, errno
.ENOSYS
, "invalid error code on subvolumegroup snapshot create")
1714 self
.fail("expected subvolumegroup snapshot create command to fail")
1717 self
._fs
_cmd
("subvolumegroup", "rm", self
.volname
, group
)
1719 @unittest.skip("skipping subvolumegroup snapshot tests")
1720 def test_subvolume_group_snapshot_create_and_rm(self
):
1721 subvolume
= self
._generate
_random
_subvolume
_name
()
1722 group
= self
._generate
_random
_group
_name
()
1723 snapshot
= self
._generate
_random
_snapshot
_name
()
1726 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, group
)
1728 # create subvolume in group
1729 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
, "--group_name", group
)
1732 self
._fs
_cmd
("subvolumegroup", "snapshot", "create", self
.volname
, group
, snapshot
)
1735 self
._fs
_cmd
("subvolumegroup", "snapshot", "rm", self
.volname
, group
, snapshot
)
1738 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
, group
)
1740 # verify trash dir is clean
1741 self
._wait
_for
_trash
_empty
()
1744 self
._fs
_cmd
("subvolumegroup", "rm", self
.volname
, group
)
1746 @unittest.skip("skipping subvolumegroup snapshot tests")
1747 def test_subvolume_group_snapshot_idempotence(self
):
1748 subvolume
= self
._generate
_random
_subvolume
_name
()
1749 group
= self
._generate
_random
_group
_name
()
1750 snapshot
= self
._generate
_random
_snapshot
_name
()
1753 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, group
)
1755 # create subvolume in group
1756 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
, "--group_name", group
)
1759 self
._fs
_cmd
("subvolumegroup", "snapshot", "create", self
.volname
, group
, snapshot
)
1761 # try creating snapshot w/ same snapshot name -- shoule be idempotent
1762 self
._fs
_cmd
("subvolumegroup", "snapshot", "create", self
.volname
, group
, snapshot
)
1765 self
._fs
_cmd
("subvolumegroup", "snapshot", "rm", self
.volname
, group
, snapshot
)
1768 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
, group
)
1770 # verify trash dir is clean
1771 self
._wait
_for
_trash
_empty
()
1774 self
._fs
_cmd
("subvolumegroup", "rm", self
.volname
, group
)
1776 @unittest.skip("skipping subvolumegroup snapshot tests")
1777 def test_nonexistent_subvolume_group_snapshot_rm(self
):
1778 subvolume
= self
._generate
_random
_subvolume
_name
()
1779 group
= self
._generate
_random
_group
_name
()
1780 snapshot
= self
._generate
_random
_snapshot
_name
()
1783 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, group
)
1785 # create subvolume in group
1786 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
, "--group_name", group
)
1789 self
._fs
_cmd
("subvolumegroup", "snapshot", "create", self
.volname
, group
, snapshot
)
1792 self
._fs
_cmd
("subvolumegroup", "snapshot", "rm", self
.volname
, group
, snapshot
)
1796 self
._fs
_cmd
("subvolumegroup", "snapshot", "rm", self
.volname
, group
, snapshot
)
1797 except CommandFailedError
as ce
:
1798 if ce
.exitstatus
!= errno
.ENOENT
:
1801 raise RuntimeError("expected the 'fs subvolumegroup snapshot rm' command to fail")
1804 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
, group
)
1806 # verify trash dir is clean
1807 self
._wait
_for
_trash
_empty
()
1810 self
._fs
_cmd
("subvolumegroup", "rm", self
.volname
, group
)
1812 @unittest.skip("skipping subvolumegroup snapshot tests")
1813 def test_subvolume_group_snapshot_rm_force(self
):
1814 # test removing non-existing subvolume group snapshot with --force
1815 group
= self
._generate
_random
_group
_name
()
1816 snapshot
= self
._generate
_random
_snapshot
_name
()
1819 self
._fs
_cmd
("subvolumegroup", "snapshot", "rm", self
.volname
, group
, snapshot
, "--force")
1820 except CommandFailedError
:
1821 raise RuntimeError("expected the 'fs subvolumegroup snapshot rm --force' command to succeed")
1823 @unittest.skip("skipping subvolumegroup snapshot tests")
1824 def test_subvolume_group_snapshot_ls(self
):
1825 # tests the 'fs subvolumegroup snapshot ls' command
1830 group
= self
._generate
_random
_group
_name
()
1831 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, group
)
1833 # create subvolumegroup snapshots
1834 snapshots
= self
._generate
_random
_snapshot
_name
(3)
1835 for snapshot
in snapshots
:
1836 self
._fs
_cmd
("subvolumegroup", "snapshot", "create", self
.volname
, group
, snapshot
)
1838 subvolgrpsnapshotls
= json
.loads(self
._fs
_cmd
('subvolumegroup', 'snapshot', 'ls', self
.volname
, group
))
1839 if len(subvolgrpsnapshotls
) == 0:
1840 raise RuntimeError("Expected the 'fs subvolumegroup snapshot ls' command to list the created subvolume group snapshots")
1842 snapshotnames
= [snapshot
['name'] for snapshot
in subvolgrpsnapshotls
]
1843 if collections
.Counter(snapshotnames
) != collections
.Counter(snapshots
):
1844 raise RuntimeError("Error creating or listing subvolume group snapshots")
1846 def test_async_subvolume_rm(self
):
1847 subvolumes
= self
._generate
_random
_subvolume
_name
(100)
1850 for subvolume
in subvolumes
:
1851 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
1852 self
._do
_subvolume
_io
(subvolume
, number_of_files
=10)
1854 self
.mount_a
.umount_wait()
1857 for subvolume
in subvolumes
:
1858 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
1860 self
.mount_a
.mount_wait()
1862 # verify trash dir is clean
1863 self
._wait
_for
_trash
_empty
(timeout
=300)
1865 def test_mgr_eviction(self
):
1866 # unmount any cephfs mounts
1867 self
.mount_a
.umount_wait()
1868 sessions
= self
._session
_list
()
1869 self
.assertLessEqual(len(sessions
), 1) # maybe mgr is already mounted
1871 # Get the mgr to definitely mount cephfs
1872 subvolume
= self
._generate
_random
_subvolume
_name
()
1873 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
1874 sessions
= self
._session
_list
()
1875 self
.assertEqual(len(sessions
), 1)
1877 # Now fail the mgr, check the session was evicted
1878 mgr
= self
.mgr_cluster
.get_active_id()
1879 self
.mgr_cluster
.mgr_fail(mgr
)
1880 self
.wait_until_evicted(sessions
[0]['id'])
1882 def test_subvolume_upgrade_legacy_to_v1(self
):
1884 poor man's upgrade test -- rather than going through a full upgrade cycle,
1885 emulate subvolumes by going through the wormhole and verify if they are
1887 further ensure that a legacy volume is not updated to v2.
1889 subvolume1
, subvolume2
= self
._generate
_random
_subvolume
_name
(2)
1890 group
= self
._generate
_random
_group
_name
()
1892 # emulate a old-fashioned subvolume -- one in the default group and
1893 # the other in a custom group
1894 createpath1
= os
.path
.join(".", "volumes", "_nogroup", subvolume1
)
1895 self
.mount_a
.run_shell(['mkdir', '-p', createpath1
])
1898 createpath2
= os
.path
.join(".", "volumes", group
, subvolume2
)
1899 self
.mount_a
.run_shell(['mkdir', '-p', createpath2
])
1901 # this would auto-upgrade on access without anyone noticing
1902 subvolpath1
= self
._fs
_cmd
("subvolume", "getpath", self
.volname
, subvolume1
)
1903 self
.assertNotEqual(subvolpath1
, None)
1904 subvolpath1
= subvolpath1
.rstrip() # remove "/" prefix and any trailing newline
1906 subvolpath2
= self
._fs
_cmd
("subvolume", "getpath", self
.volname
, subvolume2
, group
)
1907 self
.assertNotEqual(subvolpath2
, None)
1908 subvolpath2
= subvolpath2
.rstrip() # remove "/" prefix and any trailing newline
1910 # and... the subvolume path returned should be what we created behind the scene
1911 self
.assertEqual(createpath1
[1:], subvolpath1
)
1912 self
.assertEqual(createpath2
[1:], subvolpath2
)
1914 # ensure metadata file is in legacy location, with required version v1
1915 self
._assert
_meta
_location
_and
_version
(self
.volname
, subvolume1
, version
=1, legacy
=True)
1916 self
._assert
_meta
_location
_and
_version
(self
.volname
, subvolume2
, subvol_group
=group
, version
=1, legacy
=True)
1919 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume1
)
1920 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume2
, group
)
1922 # verify trash dir is clean
1923 self
._wait
_for
_trash
_empty
()
1926 self
._fs
_cmd
("subvolumegroup", "rm", self
.volname
, group
)
1928 def test_subvolume_no_upgrade_v1_sanity(self
):
1930 poor man's upgrade test -- theme continues...
1932 This test is to ensure v1 subvolumes are retained as is, due to a snapshot being present, and runs through
1933 a series of operations on the v1 subvolume to ensure they work as expected.
1935 subvol_md
= ["atime", "bytes_pcent", "bytes_quota", "bytes_used", "created_at", "ctime",
1936 "data_pool", "gid", "mode", "mon_addrs", "mtime", "path", "pool_namespace",
1937 "type", "uid", "features", "state"]
1938 snap_md
= ["created_at", "data_pool", "has_pending_clones", "size"]
1940 subvolume
= self
._generate
_random
_subvolume
_name
()
1941 snapshot
= self
._generate
_random
_snapshot
_name
()
1942 clone1
, clone2
= self
._generate
_random
_clone
_name
(2)
1947 # emulate a v1 subvolume -- in the default group
1948 subvolume_path
= self
._create
_v
1_subvolume
(subvolume
)
1951 subvolpath
= self
._get
_subvolume
_path
(self
.volname
, subvolume
)
1952 self
.assertEqual(subvolpath
, subvolume_path
)
1955 subvolumes
= json
.loads(self
._fs
_cmd
('subvolume', 'ls', self
.volname
))
1956 self
.assertEqual(len(subvolumes
), 1, "subvolume ls count mismatch, expected '1', found {0}".format(len(subvolumes
)))
1957 self
.assertEqual(subvolumes
[0]['name'], subvolume
,
1958 "subvolume name mismatch in ls output, expected '{0}', found '{1}'".format(subvolume
, subvolumes
[0]['name']))
1961 subvol_info
= json
.loads(self
._get
_subvolume
_info
(self
.volname
, subvolume
))
1962 for md
in subvol_md
:
1963 self
.assertIn(md
, subvol_info
, "'{0}' key not present in metadata of subvolume".format(md
))
1965 self
.assertEqual(subvol_info
["state"], "complete",
1966 msg
="expected state to be 'complete', found '{0}".format(subvol_info
["state"]))
1967 self
.assertEqual(len(subvol_info
["features"]), 2,
1968 msg
="expected 1 feature, found '{0}' ({1})".format(len(subvol_info
["features"]), subvol_info
["features"]))
1969 for feature
in ['snapshot-clone', 'snapshot-autoprotect']:
1970 self
.assertIn(feature
, subvol_info
["features"], msg
="expected feature '{0}' in subvolume".format(feature
))
1973 nsize
= self
.DEFAULT_FILE_SIZE
*1024*1024*10
1974 self
._fs
_cmd
("subvolume", "resize", self
.volname
, subvolume
, str(nsize
))
1975 subvol_info
= json
.loads(self
._get
_subvolume
_info
(self
.volname
, subvolume
))
1976 for md
in subvol_md
:
1977 self
.assertIn(md
, subvol_info
, "'{0}' key not present in metadata of subvolume".format(md
))
1978 self
.assertEqual(subvol_info
["bytes_quota"], nsize
, "bytes_quota should be set to '{0}'".format(nsize
))
1980 # create (idempotent) (change some attrs, to ensure attrs are preserved from the snapshot on clone)
1981 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
, "--mode", mode
, "--uid", uid
, "--gid", gid
)
1984 self
._do
_subvolume
_io
(subvolume
, number_of_files
=8)
1987 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
1990 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone1
)
1992 # check clone status
1993 self
._wait
_for
_clone
_to
_complete
(clone1
)
1995 # ensure clone is v2
1996 self
._assert
_meta
_location
_and
_version
(self
.volname
, clone1
, version
=2)
1999 self
._verify
_clone
(subvolume
, snapshot
, clone1
, source_version
=1)
2001 # clone (older snapshot)
2002 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, 'fake', clone2
)
2004 # check clone status
2005 self
._wait
_for
_clone
_to
_complete
(clone2
)
2007 # ensure clone is v2
2008 self
._assert
_meta
_location
_and
_version
(self
.volname
, clone2
, version
=2)
2011 # TODO: rentries will mismatch till this is fixed https://tracker.ceph.com/issues/46747
2012 #self._verify_clone(subvolume, 'fake', clone2, source_version=1)
2015 snap_info
= json
.loads(self
._get
_subvolume
_snapshot
_info
(self
.volname
, subvolume
, snapshot
))
2017 self
.assertIn(md
, snap_info
, "'{0}' key not present in metadata of snapshot".format(md
))
2018 self
.assertEqual(snap_info
["has_pending_clones"], "no")
2021 subvol_snapshots
= json
.loads(self
._fs
_cmd
('subvolume', 'snapshot', 'ls', self
.volname
, subvolume
))
2022 self
.assertEqual(len(subvol_snapshots
), 2, "subvolume ls count mismatch, expected 2', found {0}".format(len(subvol_snapshots
)))
2023 snapshotnames
= [snapshot
['name'] for snapshot
in subvol_snapshots
]
2024 for name
in [snapshot
, 'fake']:
2025 self
.assertIn(name
, snapshotnames
, msg
="expected snapshot '{0}' in subvolume snapshot ls".format(name
))
2028 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
2029 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, "fake")
2031 # ensure volume is still at version 1
2032 self
._assert
_meta
_location
_and
_version
(self
.volname
, subvolume
, version
=1)
2035 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
2036 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone1
)
2037 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone2
)
2039 # verify trash dir is clean
2040 self
._wait
_for
_trash
_empty
()
2042 def test_subvolume_no_upgrade_v1_to_v2(self
):
2044 poor man's upgrade test -- theme continues...
2045 ensure v1 to v2 upgrades are not done automatically due to various states of v1
2047 subvolume1
, subvolume2
, subvolume3
= self
._generate
_random
_subvolume
_name
(3)
2048 group
= self
._generate
_random
_group
_name
()
2050 # emulate a v1 subvolume -- in the default group
2051 subvol1_path
= self
._create
_v
1_subvolume
(subvolume1
)
2053 # emulate a v1 subvolume -- in a custom group
2054 subvol2_path
= self
._create
_v
1_subvolume
(subvolume2
, subvol_group
=group
)
2056 # emulate a v1 subvolume -- in a clone pending state
2057 self
._create
_v
1_subvolume
(subvolume3
, subvol_type
='clone', has_snapshot
=False, state
='pending')
2059 # this would attempt auto-upgrade on access, but fail to do so as snapshots exist
2060 subvolpath1
= self
._get
_subvolume
_path
(self
.volname
, subvolume1
)
2061 self
.assertEqual(subvolpath1
, subvol1_path
)
2063 subvolpath2
= self
._get
_subvolume
_path
(self
.volname
, subvolume2
, group_name
=group
)
2064 self
.assertEqual(subvolpath2
, subvol2_path
)
2066 # this would attempt auto-upgrade on access, but fail to do so as volume is not complete
2067 # use clone status, as only certain operations are allowed in pending state
2068 status
= json
.loads(self
._fs
_cmd
("clone", "status", self
.volname
, subvolume3
))
2069 self
.assertEqual(status
["status"]["state"], "pending")
2072 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume1
, "fake")
2073 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume2
, "fake", group
)
2075 # ensure metadata file is in v1 location, with version retained as v1
2076 self
._assert
_meta
_location
_and
_version
(self
.volname
, subvolume1
, version
=1)
2077 self
._assert
_meta
_location
_and
_version
(self
.volname
, subvolume2
, subvol_group
=group
, version
=1)
2080 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume1
)
2081 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume2
, group
)
2083 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume3
)
2084 except CommandFailedError
as ce
:
2085 self
.assertEqual(ce
.exitstatus
, errno
.EAGAIN
, "invalid error code on rm of subvolume undergoing clone")
2087 self
.fail("expected rm of subvolume undergoing clone to fail")
2089 # ensure metadata file is in v1 location, with version retained as v1
2090 self
._assert
_meta
_location
_and
_version
(self
.volname
, subvolume3
, version
=1)
2091 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume3
, "--force")
2093 # verify list subvolumes returns an empty list
2094 subvolumels
= json
.loads(self
._fs
_cmd
('subvolume', 'ls', self
.volname
))
2095 self
.assertEqual(len(subvolumels
), 0)
2097 # verify trash dir is clean
2098 self
._wait
_for
_trash
_empty
()
2100 def test_subvolume_upgrade_v1_to_v2(self
):
2102 poor man's upgrade test -- theme continues...
2103 ensure v1 to v2 upgrades work
2105 subvolume1
, subvolume2
= self
._generate
_random
_subvolume
_name
(2)
2106 group
= self
._generate
_random
_group
_name
()
2108 # emulate a v1 subvolume -- in the default group
2109 subvol1_path
= self
._create
_v
1_subvolume
(subvolume1
, has_snapshot
=False)
2111 # emulate a v1 subvolume -- in a custom group
2112 subvol2_path
= self
._create
_v
1_subvolume
(subvolume2
, subvol_group
=group
, has_snapshot
=False)
2114 # this would attempt auto-upgrade on access
2115 subvolpath1
= self
._get
_subvolume
_path
(self
.volname
, subvolume1
)
2116 self
.assertEqual(subvolpath1
, subvol1_path
)
2118 subvolpath2
= self
._get
_subvolume
_path
(self
.volname
, subvolume2
, group_name
=group
)
2119 self
.assertEqual(subvolpath2
, subvol2_path
)
2121 # ensure metadata file is in v2 location, with version retained as v2
2122 self
._assert
_meta
_location
_and
_version
(self
.volname
, subvolume1
, version
=2)
2123 self
._assert
_meta
_location
_and
_version
(self
.volname
, subvolume2
, subvol_group
=group
, version
=2)
2126 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume1
)
2127 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume2
, group
)
2129 # verify trash dir is clean
2130 self
._wait
_for
_trash
_empty
()
2132 def test_subvolume_rm_with_snapshots(self
):
2133 subvolume
= self
._generate
_random
_subvolume
_name
()
2134 snapshot
= self
._generate
_random
_snapshot
_name
()
2137 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
2139 # snapshot subvolume
2140 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
2142 # remove subvolume -- should fail with ENOTEMPTY since it has snapshots
2144 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
2145 except CommandFailedError
as ce
:
2146 if ce
.exitstatus
!= errno
.ENOTEMPTY
:
2147 raise RuntimeError("invalid error code returned when deleting subvolume with snapshots")
2149 raise RuntimeError("expected subvolume deletion to fail")
2152 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
2155 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
2157 # verify trash dir is clean
2158 self
._wait
_for
_trash
_empty
()
2160 def test_subvolume_retain_snapshot_without_snapshots(self
):
2162 ensure retain snapshots based delete of a subvolume with no snapshots, deletes the subbvolume
2164 subvolume
= self
._generate
_random
_subvolume
_name
()
2167 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
2169 # remove with snapshot retention (should remove volume, no snapshots to retain)
2170 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
, "--retain-snapshots")
2172 # verify list subvolumes returns an empty list
2173 subvolumels
= json
.loads(self
._fs
_cmd
('subvolume', 'ls', self
.volname
))
2174 self
.assertEqual(len(subvolumels
), 0)
2176 # verify trash dir is clean
2177 self
._wait
_for
_trash
_empty
()
2179 def test_subvolume_retain_snapshot_with_snapshots(self
):
2181 ensure retain snapshots based delete of a subvolume with snapshots retains the subvolume
2182 also test allowed and dis-allowed operations on a retained subvolume
2184 snap_md
= ["created_at", "data_pool", "has_pending_clones", "size"]
2186 subvolume
= self
._generate
_random
_subvolume
_name
()
2187 snapshot
= self
._generate
_random
_snapshot
_name
()
2190 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
2192 # snapshot subvolume
2193 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
2195 # remove subvolume -- should fail with ENOTEMPTY since it has snapshots
2197 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
2198 except CommandFailedError
as ce
:
2199 self
.assertEqual(ce
.exitstatus
, errno
.ENOTEMPTY
, "invalid error code on rm of retained subvolume with snapshots")
2201 self
.fail("expected rm of subvolume with retained snapshots to fail")
2203 # remove with snapshot retention
2204 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
, "--retain-snapshots")
2207 subvol_info
= json
.loads(self
._fs
_cmd
("subvolume", "info", self
.volname
, subvolume
))
2208 self
.assertEqual(subvol_info
["state"], "snapshot-retained",
2209 msg
="expected state to be 'snapshot-retained', found '{0}".format(subvol_info
["state"]))
2211 ## test allowed ops in retained state
2213 subvolumes
= json
.loads(self
._fs
_cmd
('subvolume', 'ls', self
.volname
))
2214 self
.assertEqual(len(subvolumes
), 1, "subvolume ls count mismatch, expected '1', found {0}".format(len(subvolumes
)))
2215 self
.assertEqual(subvolumes
[0]['name'], subvolume
,
2216 "subvolume name mismatch in ls output, expected '{0}', found '{1}'".format(subvolume
, subvolumes
[0]['name']))
2219 snap_info
= json
.loads(self
._get
_subvolume
_snapshot
_info
(self
.volname
, subvolume
, snapshot
))
2221 self
.assertIn(md
, snap_info
, "'{0}' key not present in metadata of snapshot".format(md
))
2222 self
.assertEqual(snap_info
["has_pending_clones"], "no")
2224 # rm --force (allowed but should fail)
2226 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
, "--force")
2227 except CommandFailedError
as ce
:
2228 self
.assertEqual(ce
.exitstatus
, errno
.ENOTEMPTY
, "invalid error code on rm of subvolume with retained snapshots")
2230 self
.fail("expected rm of subvolume with retained snapshots to fail")
2232 # rm (allowed but should fail)
2234 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
2235 except CommandFailedError
as ce
:
2236 self
.assertEqual(ce
.exitstatus
, errno
.ENOTEMPTY
, "invalid error code on rm of subvolume with retained snapshots")
2238 self
.fail("expected rm of subvolume with retained snapshots to fail")
2240 ## test disallowed ops
2243 self
._fs
_cmd
("subvolume", "getpath", self
.volname
, subvolume
)
2244 except CommandFailedError
as ce
:
2245 self
.assertEqual(ce
.exitstatus
, errno
.ENOENT
, "invalid error code on getpath of subvolume with retained snapshots")
2247 self
.fail("expected getpath of subvolume with retained snapshots to fail")
2250 nsize
= self
.DEFAULT_FILE_SIZE
*1024*1024
2252 self
._fs
_cmd
("subvolume", "resize", self
.volname
, subvolume
, str(nsize
))
2253 except CommandFailedError
as ce
:
2254 self
.assertEqual(ce
.exitstatus
, errno
.ENOENT
, "invalid error code on resize of subvolume with retained snapshots")
2256 self
.fail("expected resize of subvolume with retained snapshots to fail")
2260 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, "fail")
2261 except CommandFailedError
as ce
:
2262 self
.assertEqual(ce
.exitstatus
, errno
.ENOENT
, "invalid error code on snapshot create of subvolume with retained snapshots")
2264 self
.fail("expected snapshot create of subvolume with retained snapshots to fail")
2266 # remove snapshot (should remove volume)
2267 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
2269 # verify list subvolumes returns an empty list
2270 subvolumels
= json
.loads(self
._fs
_cmd
('subvolume', 'ls', self
.volname
))
2271 self
.assertEqual(len(subvolumels
), 0)
2273 # verify trash dir is clean
2274 self
._wait
_for
_trash
_empty
()
2276 def test_subvolume_retain_snapshot_invalid_recreate(self
):
2278 ensure retained subvolume recreate does not leave any incarnations in the subvolume and trash
2280 subvolume
= self
._generate
_random
_subvolume
_name
()
2281 snapshot
= self
._generate
_random
_snapshot
_name
()
2284 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
2286 # snapshot subvolume
2287 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
2289 # remove with snapshot retention
2290 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
, "--retain-snapshots")
2292 # recreate subvolume with an invalid pool
2293 data_pool
= "invalid_pool"
2295 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
, "--pool_layout", data_pool
)
2296 except CommandFailedError
as ce
:
2297 self
.assertEqual(ce
.exitstatus
, errno
.EINVAL
, "invalid error code on recreate of subvolume with invalid poolname")
2299 self
.fail("expected recreate of subvolume with invalid poolname to fail")
2302 subvol_info
= json
.loads(self
._fs
_cmd
("subvolume", "info", self
.volname
, subvolume
))
2303 self
.assertEqual(subvol_info
["state"], "snapshot-retained",
2304 msg
="expected state to be 'snapshot-retained', found '{0}".format(subvol_info
["state"]))
2308 self
._fs
_cmd
("subvolume", "getpath", self
.volname
, subvolume
)
2309 except CommandFailedError
as ce
:
2310 self
.assertEqual(ce
.exitstatus
, errno
.ENOENT
, "invalid error code on getpath of subvolume with retained snapshots")
2312 self
.fail("expected getpath of subvolume with retained snapshots to fail")
2314 # remove snapshot (should remove volume)
2315 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
2317 # verify trash dir is clean
2318 self
._wait
_for
_trash
_empty
()
2320 def test_subvolume_retain_snapshot_trash_busy_recreate(self
):
2322 ensure retained subvolume recreate fails if its trash is not yet purged
2324 subvolume
= self
._generate
_random
_subvolume
_name
()
2325 snapshot
= self
._generate
_random
_snapshot
_name
()
2328 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
2330 # snapshot subvolume
2331 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
2333 # remove with snapshot retention
2334 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
, "--retain-snapshots")
2336 # fake a trash entry
2337 self
._update
_fake
_trash
(subvolume
)
2339 # recreate subvolume
2341 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
2342 except CommandFailedError
as ce
:
2343 self
.assertEqual(ce
.exitstatus
, errno
.EAGAIN
, "invalid error code on recreate of subvolume with purge pending")
2345 self
.fail("expected recreate of subvolume with purge pending to fail")
2347 # clear fake trash entry
2348 self
._update
_fake
_trash
(subvolume
, create
=False)
2350 # recreate subvolume
2351 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
2354 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
2357 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
2359 # verify trash dir is clean
2360 self
._wait
_for
_trash
_empty
()
2362 def test_subvolume_retain_snapshot_trash_busy_recreate_clone(self
):
2364 ensure retained clone recreate fails if its trash is not yet purged
2366 subvolume
= self
._generate
_random
_subvolume
_name
()
2367 snapshot
= self
._generate
_random
_snapshot
_name
()
2368 clone
= self
._generate
_random
_clone
_name
()
2371 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
2373 # snapshot subvolume
2374 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
2376 # clone subvolume snapshot
2377 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone
)
2379 # check clone status
2380 self
._wait
_for
_clone
_to
_complete
(clone
)
2383 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, clone
, snapshot
)
2385 # remove clone with snapshot retention
2386 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
, "--retain-snapshots")
2388 # fake a trash entry
2389 self
._update
_fake
_trash
(clone
)
2391 # clone subvolume snapshot (recreate)
2393 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone
)
2394 except CommandFailedError
as ce
:
2395 self
.assertEqual(ce
.exitstatus
, errno
.EAGAIN
, "invalid error code on recreate of clone with purge pending")
2397 self
.fail("expected recreate of clone with purge pending to fail")
2399 # clear fake trash entry
2400 self
._update
_fake
_trash
(clone
, create
=False)
2402 # recreate subvolume
2403 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone
)
2405 # check clone status
2406 self
._wait
_for
_clone
_to
_complete
(clone
)
2409 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
2410 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, clone
, snapshot
)
2413 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
2414 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
)
2416 # verify trash dir is clean
2417 self
._wait
_for
_trash
_empty
()
2419 def test_subvolume_retain_snapshot_recreate_subvolume(self
):
2421 ensure a retained subvolume can be recreated and further snapshotted
2423 snap_md
= ["created_at", "data_pool", "has_pending_clones", "size"]
2425 subvolume
= self
._generate
_random
_subvolume
_name
()
2426 snapshot1
, snapshot2
= self
._generate
_random
_snapshot
_name
(2)
2429 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
2431 # snapshot subvolume
2432 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot1
)
2434 # remove with snapshot retention
2435 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
, "--retain-snapshots")
2438 subvol_info
= json
.loads(self
._fs
_cmd
("subvolume", "info", self
.volname
, subvolume
))
2439 self
.assertEqual(subvol_info
["state"], "snapshot-retained",
2440 msg
="expected state to be 'snapshot-retained', found '{0}".format(subvol_info
["state"]))
2442 # recreate retained subvolume
2443 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
2446 subvol_info
= json
.loads(self
._fs
_cmd
("subvolume", "info", self
.volname
, subvolume
))
2447 self
.assertEqual(subvol_info
["state"], "complete",
2448 msg
="expected state to be 'snapshot-retained', found '{0}".format(subvol_info
["state"]))
2450 # snapshot info (older snapshot)
2451 snap_info
= json
.loads(self
._get
_subvolume
_snapshot
_info
(self
.volname
, subvolume
, snapshot1
))
2453 self
.assertIn(md
, snap_info
, "'{0}' key not present in metadata of snapshot".format(md
))
2454 self
.assertEqual(snap_info
["has_pending_clones"], "no")
2456 # snap-create (new snapshot)
2457 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot2
)
2459 # remove with retain snapshots
2460 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
, "--retain-snapshots")
2463 subvolsnapshotls
= json
.loads(self
._fs
_cmd
('subvolume', 'snapshot', 'ls', self
.volname
, subvolume
))
2464 self
.assertEqual(len(subvolsnapshotls
), 2, "Expected the 'fs subvolume snapshot ls' command to list the"
2465 " created subvolume snapshots")
2466 snapshotnames
= [snapshot
['name'] for snapshot
in subvolsnapshotls
]
2467 for snap
in [snapshot1
, snapshot2
]:
2468 self
.assertIn(snap
, snapshotnames
, "Missing snapshot '{0}' in snapshot list".format(snap
))
2470 # remove snapshots (should remove volume)
2471 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot1
)
2472 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot2
)
2474 # verify list subvolumes returns an empty list
2475 subvolumels
= json
.loads(self
._fs
_cmd
('subvolume', 'ls', self
.volname
))
2476 self
.assertEqual(len(subvolumels
), 0)
2478 # verify trash dir is clean
2479 self
._wait
_for
_trash
_empty
()
2481 def test_subvolume_retain_snapshot_clone(self
):
2483 clone a snapshot from a snapshot retained subvolume
2485 subvolume
= self
._generate
_random
_subvolume
_name
()
2486 snapshot
= self
._generate
_random
_snapshot
_name
()
2487 clone
= self
._generate
_random
_clone
_name
()
2490 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
2492 # store path for clone verification
2493 subvol_path
= self
._get
_subvolume
_path
(self
.volname
, subvolume
)
2496 self
._do
_subvolume
_io
(subvolume
, number_of_files
=16)
2498 # snapshot subvolume
2499 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
2501 # remove with snapshot retention
2502 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
, "--retain-snapshots")
2504 # clone retained subvolume snapshot
2505 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone
)
2507 # check clone status
2508 self
._wait
_for
_clone
_to
_complete
(clone
)
2511 self
._verify
_clone
(subvolume
, snapshot
, clone
, subvol_path
=subvol_path
)
2513 # remove snapshots (removes retained volume)
2514 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
2517 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
)
2519 # verify list subvolumes returns an empty list
2520 subvolumels
= json
.loads(self
._fs
_cmd
('subvolume', 'ls', self
.volname
))
2521 self
.assertEqual(len(subvolumels
), 0)
2523 # verify trash dir is clean
2524 self
._wait
_for
_trash
_empty
()
2526 def test_subvolume_retain_snapshot_recreate(self
):
2528 recreate a subvolume from one of its retained snapshots
2530 subvolume
= self
._generate
_random
_subvolume
_name
()
2531 snapshot
= self
._generate
_random
_snapshot
_name
()
2534 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
2536 # store path for clone verification
2537 subvol_path
= self
._get
_subvolume
_path
(self
.volname
, subvolume
)
2540 self
._do
_subvolume
_io
(subvolume
, number_of_files
=16)
2542 # snapshot subvolume
2543 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
2545 # remove with snapshot retention
2546 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
, "--retain-snapshots")
2548 # recreate retained subvolume using its own snapshot to clone
2549 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, subvolume
)
2551 # check clone status
2552 self
._wait
_for
_clone
_to
_complete
(subvolume
)
2555 self
._verify
_clone
(subvolume
, snapshot
, subvolume
, subvol_path
=subvol_path
)
2558 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
2561 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
2563 # verify list subvolumes returns an empty list
2564 subvolumels
= json
.loads(self
._fs
_cmd
('subvolume', 'ls', self
.volname
))
2565 self
.assertEqual(len(subvolumels
), 0)
2567 # verify trash dir is clean
2568 self
._wait
_for
_trash
_empty
()
2570 def test_subvolume_clone_retain_snapshot_with_snapshots(self
):
2572 retain snapshots of a cloned subvolume and check disallowed operations
2574 subvolume
= self
._generate
_random
_subvolume
_name
()
2575 snapshot1
, snapshot2
= self
._generate
_random
_snapshot
_name
(2)
2576 clone
= self
._generate
_random
_clone
_name
()
2579 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
2581 # store path for clone verification
2582 subvol1_path
= self
._get
_subvolume
_path
(self
.volname
, subvolume
)
2585 self
._do
_subvolume
_io
(subvolume
, number_of_files
=16)
2587 # snapshot subvolume
2588 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot1
)
2590 # remove with snapshot retention
2591 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
, "--retain-snapshots")
2593 # clone retained subvolume snapshot
2594 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot1
, clone
)
2596 # check clone status
2597 self
._wait
_for
_clone
_to
_complete
(clone
)
2600 self
._verify
_clone
(subvolume
, snapshot1
, clone
, subvol_path
=subvol1_path
)
2602 # create a snapshot on the clone
2603 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, clone
, snapshot2
)
2606 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
, "--retain-snapshots")
2609 clonesnapshotls
= json
.loads(self
._fs
_cmd
('subvolume', 'snapshot', 'ls', self
.volname
, clone
))
2610 self
.assertEqual(len(clonesnapshotls
), 1, "Expected the 'fs subvolume snapshot ls' command to list the"
2611 " created subvolume snapshots")
2612 snapshotnames
= [snapshot
['name'] for snapshot
in clonesnapshotls
]
2613 for snap
in [snapshot2
]:
2614 self
.assertIn(snap
, snapshotnames
, "Missing snapshot '{0}' in snapshot list".format(snap
))
2616 ## check disallowed operations on retained clone
2619 self
._fs
_cmd
("clone", "status", self
.volname
, clone
)
2620 except CommandFailedError
as ce
:
2621 self
.assertEqual(ce
.exitstatus
, errno
.ENOENT
, "invalid error code on clone status of clone with retained snapshots")
2623 self
.fail("expected clone status of clone with retained snapshots to fail")
2627 self
._fs
_cmd
("clone", "cancel", self
.volname
, clone
)
2628 except CommandFailedError
as ce
:
2629 self
.assertEqual(ce
.exitstatus
, errno
.ENOENT
, "invalid error code on clone cancel of clone with retained snapshots")
2631 self
.fail("expected clone cancel of clone with retained snapshots to fail")
2633 # remove snapshots (removes subvolumes as all are in retained state)
2634 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot1
)
2635 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, clone
, snapshot2
)
2637 # verify list subvolumes returns an empty list
2638 subvolumels
= json
.loads(self
._fs
_cmd
('subvolume', 'ls', self
.volname
))
2639 self
.assertEqual(len(subvolumels
), 0)
2641 # verify trash dir is clean
2642 self
._wait
_for
_trash
_empty
()
2644 def test_subvolume_retain_snapshot_clone_from_newer_snapshot(self
):
2646 clone a subvolume from recreated subvolume's latest snapshot
2648 subvolume
= self
._generate
_random
_subvolume
_name
()
2649 snapshot1
, snapshot2
= self
._generate
_random
_snapshot
_name
(2)
2650 clone
= self
._generate
_random
_clone
_name
(1)
2653 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
2656 self
._do
_subvolume
_io
(subvolume
, number_of_files
=16)
2658 # snapshot subvolume
2659 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot1
)
2661 # remove with snapshot retention
2662 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
, "--retain-snapshots")
2664 # recreate subvolume
2665 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
2667 # get and store path for clone verification
2668 subvol2_path
= self
._get
_subvolume
_path
(self
.volname
, subvolume
)
2671 self
._do
_subvolume
_io
(subvolume
, number_of_files
=16)
2673 # snapshot newer subvolume
2674 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot2
)
2676 # remove with snapshot retention
2677 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
, "--retain-snapshots")
2679 # clone retained subvolume's newer snapshot
2680 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot2
, clone
)
2682 # check clone status
2683 self
._wait
_for
_clone
_to
_complete
(clone
)
2686 self
._verify
_clone
(subvolume
, snapshot2
, clone
, subvol_path
=subvol2_path
)
2689 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot1
)
2690 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot2
)
2693 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
)
2695 # verify list subvolumes returns an empty list
2696 subvolumels
= json
.loads(self
._fs
_cmd
('subvolume', 'ls', self
.volname
))
2697 self
.assertEqual(len(subvolumels
), 0)
2699 # verify trash dir is clean
2700 self
._wait
_for
_trash
_empty
()
2702 def test_subvolume_snapshot_protect_unprotect_sanity(self
):
2704 Snapshot protect/unprotect commands are deprecated. This test exists to ensure that
2705 invoking the command does not cause errors, till they are removed from a subsequent release.
2707 subvolume
= self
._generate
_random
_subvolume
_name
()
2708 snapshot
= self
._generate
_random
_snapshot
_name
()
2709 clone
= self
._generate
_random
_clone
_name
()
2712 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
2715 self
._do
_subvolume
_io
(subvolume
, number_of_files
=64)
2717 # snapshot subvolume
2718 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
2720 # now, protect snapshot
2721 self
._fs
_cmd
("subvolume", "snapshot", "protect", self
.volname
, subvolume
, snapshot
)
2724 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone
)
2726 # check clone status
2727 self
._wait
_for
_clone
_to
_complete
(clone
)
2729 # now, unprotect snapshot
2730 self
._fs
_cmd
("subvolume", "snapshot", "unprotect", self
.volname
, subvolume
, snapshot
)
2733 self
._verify
_clone
(subvolume
, snapshot
, clone
)
2736 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
2739 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
2740 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
)
2742 # verify trash dir is clean
2743 self
._wait
_for
_trash
_empty
()
2745 def test_subvolume_snapshot_clone(self
):
2746 subvolume
= self
._generate
_random
_subvolume
_name
()
2747 snapshot
= self
._generate
_random
_snapshot
_name
()
2748 clone
= self
._generate
_random
_clone
_name
()
2751 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
2754 self
._do
_subvolume
_io
(subvolume
, number_of_files
=64)
2756 # snapshot subvolume
2757 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
2760 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone
)
2762 # check clone status
2763 self
._wait
_for
_clone
_to
_complete
(clone
)
2766 self
._verify
_clone
(subvolume
, snapshot
, clone
)
2769 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
2772 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
2773 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
)
2775 # verify trash dir is clean
2776 self
._wait
_for
_trash
_empty
()
2778 def test_subvolume_snapshot_reconf_max_concurrent_clones(self
):
2780 Validate 'max_concurrent_clones' config option
2783 # get the default number of cloner threads
2784 default_max_concurrent_clones
= int(self
.config_get('mgr', 'mgr/volumes/max_concurrent_clones'))
2785 self
.assertEqual(default_max_concurrent_clones
, 4)
2787 # Increase number of cloner threads
2788 self
.config_set('mgr', 'mgr/volumes/max_concurrent_clones', 6)
2789 max_concurrent_clones
= int(self
.config_get('mgr', 'mgr/volumes/max_concurrent_clones'))
2790 self
.assertEqual(max_concurrent_clones
, 6)
2792 # Decrease number of cloner threads
2793 self
.config_set('mgr', 'mgr/volumes/max_concurrent_clones', 2)
2794 max_concurrent_clones
= int(self
.config_get('mgr', 'mgr/volumes/max_concurrent_clones'))
2795 self
.assertEqual(max_concurrent_clones
, 2)
2797 def test_subvolume_snapshot_clone_pool_layout(self
):
2798 subvolume
= self
._generate
_random
_subvolume
_name
()
2799 snapshot
= self
._generate
_random
_snapshot
_name
()
2800 clone
= self
._generate
_random
_clone
_name
()
2803 new_pool
= "new_pool"
2804 self
.fs
.add_data_pool(new_pool
)
2807 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
2810 self
._do
_subvolume
_io
(subvolume
, number_of_files
=32)
2812 # snapshot subvolume
2813 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
2816 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone
, "--pool_layout", new_pool
)
2818 # check clone status
2819 self
._wait
_for
_clone
_to
_complete
(clone
)
2822 self
._verify
_clone
(subvolume
, snapshot
, clone
, clone_pool
=new_pool
)
2825 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
2827 subvol_path
= self
._get
_subvolume
_path
(self
.volname
, clone
)
2828 desired_pool
= self
.mount_a
.getfattr(subvol_path
, "ceph.dir.layout.pool")
2829 self
.assertEqual(desired_pool
, new_pool
)
2832 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
2833 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
)
2835 # verify trash dir is clean
2836 self
._wait
_for
_trash
_empty
()
2838 def test_subvolume_snapshot_clone_with_attrs(self
):
2839 subvolume
= self
._generate
_random
_subvolume
_name
()
2840 snapshot
= self
._generate
_random
_snapshot
_name
()
2841 clone
= self
._generate
_random
_clone
_name
()
2851 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
, "--mode", mode
, "--uid", uid
, "--gid", gid
)
2854 self
._do
_subvolume
_io
(subvolume
, number_of_files
=32)
2856 # snapshot subvolume
2857 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
2859 # change subvolume attrs (to ensure clone picks up snapshot attrs)
2860 self
._do
_subvolume
_attr
_update
(subvolume
, new_uid
, new_gid
, new_mode
)
2863 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone
)
2865 # check clone status
2866 self
._wait
_for
_clone
_to
_complete
(clone
)
2869 self
._verify
_clone
(subvolume
, snapshot
, clone
)
2872 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
2875 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
2876 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
)
2878 # verify trash dir is clean
2879 self
._wait
_for
_trash
_empty
()
2881 def test_subvolume_clone_inherit_snapshot_namespace_and_size(self
):
2882 subvolume
= self
._generate
_random
_subvolume
_name
()
2883 snapshot
= self
._generate
_random
_snapshot
_name
()
2884 clone
= self
._generate
_random
_clone
_name
()
2885 osize
= self
.DEFAULT_FILE_SIZE
*1024*1024*12
2887 # create subvolume, in an isolated namespace with a specified size
2888 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
, "--namespace-isolated", "--size", str(osize
))
2891 self
._do
_subvolume
_io
(subvolume
, number_of_files
=8)
2893 # snapshot subvolume
2894 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
2896 # create a pool different from current subvolume pool
2897 subvol_path
= self
._get
_subvolume
_path
(self
.volname
, subvolume
)
2898 default_pool
= self
.mount_a
.getfattr(subvol_path
, "ceph.dir.layout.pool")
2899 new_pool
= "new_pool"
2900 self
.assertNotEqual(default_pool
, new_pool
)
2901 self
.fs
.add_data_pool(new_pool
)
2903 # update source subvolume pool
2904 self
._do
_subvolume
_pool
_and
_namespace
_update
(subvolume
, pool
=new_pool
, pool_namespace
="")
2906 # schedule a clone, with NO --pool specification
2907 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone
)
2909 # check clone status
2910 self
._wait
_for
_clone
_to
_complete
(clone
)
2913 self
._verify
_clone
(subvolume
, snapshot
, clone
)
2916 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
2919 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
2920 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
)
2922 # verify trash dir is clean
2923 self
._wait
_for
_trash
_empty
()
2925 def test_subvolume_snapshot_clone_and_reclone(self
):
2926 subvolume
= self
._generate
_random
_subvolume
_name
()
2927 snapshot
= self
._generate
_random
_snapshot
_name
()
2928 clone1
, clone2
= self
._generate
_random
_clone
_name
(2)
2931 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
2934 self
._do
_subvolume
_io
(subvolume
, number_of_files
=32)
2936 # snapshot subvolume
2937 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
2940 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone1
)
2942 # check clone status
2943 self
._wait
_for
_clone
_to
_complete
(clone1
)
2946 self
._verify
_clone
(subvolume
, snapshot
, clone1
)
2949 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
2951 # now the clone is just like a normal subvolume -- snapshot the clone and fork
2952 # another clone. before that do some IO so it's can be differentiated.
2953 self
._do
_subvolume
_io
(clone1
, create_dir
="data", number_of_files
=32)
2955 # snapshot clone -- use same snap name
2956 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, clone1
, snapshot
)
2959 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, clone1
, snapshot
, clone2
)
2961 # check clone status
2962 self
._wait
_for
_clone
_to
_complete
(clone2
)
2965 self
._verify
_clone
(clone1
, snapshot
, clone2
)
2968 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, clone1
, snapshot
)
2971 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
2972 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone1
)
2973 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone2
)
2975 # verify trash dir is clean
2976 self
._wait
_for
_trash
_empty
()
2978 def test_subvolume_snapshot_clone_under_group(self
):
2979 subvolume
= self
._generate
_random
_subvolume
_name
()
2980 snapshot
= self
._generate
_random
_snapshot
_name
()
2981 clone
= self
._generate
_random
_clone
_name
()
2982 group
= self
._generate
_random
_group
_name
()
2985 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
2988 self
._do
_subvolume
_io
(subvolume
, number_of_files
=32)
2990 # snapshot subvolume
2991 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
2994 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, group
)
2997 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone
, '--target_group_name', group
)
2999 # check clone status
3000 self
._wait
_for
_clone
_to
_complete
(clone
, clone_group
=group
)
3003 self
._verify
_clone
(subvolume
, snapshot
, clone
, clone_group
=group
)
3006 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
3009 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
3010 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
, group
)
3013 self
._fs
_cmd
("subvolumegroup", "rm", self
.volname
, group
)
3015 # verify trash dir is clean
3016 self
._wait
_for
_trash
_empty
()
3018 def test_subvolume_under_group_snapshot_clone(self
):
3019 subvolume
= self
._generate
_random
_subvolume
_name
()
3020 group
= self
._generate
_random
_group
_name
()
3021 snapshot
= self
._generate
_random
_snapshot
_name
()
3022 clone
= self
._generate
_random
_clone
_name
()
3025 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, group
)
3028 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
, group
)
3031 self
._do
_subvolume
_io
(subvolume
, subvolume_group
=group
, number_of_files
=32)
3033 # snapshot subvolume
3034 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
, group
)
3037 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone
, '--group_name', group
)
3039 # check clone status
3040 self
._wait
_for
_clone
_to
_complete
(clone
)
3043 self
._verify
_clone
(subvolume
, snapshot
, clone
, source_group
=group
)
3046 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
, group
)
3049 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
, group
)
3050 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
)
3053 self
._fs
_cmd
("subvolumegroup", "rm", self
.volname
, group
)
3055 # verify trash dir is clean
3056 self
._wait
_for
_trash
_empty
()
3058 def test_subvolume_snapshot_clone_different_groups(self
):
3059 subvolume
= self
._generate
_random
_subvolume
_name
()
3060 snapshot
= self
._generate
_random
_snapshot
_name
()
3061 clone
= self
._generate
_random
_clone
_name
()
3062 s_group
, c_group
= self
._generate
_random
_group
_name
(2)
3065 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, s_group
)
3066 self
._fs
_cmd
("subvolumegroup", "create", self
.volname
, c_group
)
3069 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
, s_group
)
3072 self
._do
_subvolume
_io
(subvolume
, subvolume_group
=s_group
, number_of_files
=32)
3074 # snapshot subvolume
3075 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
, s_group
)
3078 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone
,
3079 '--group_name', s_group
, '--target_group_name', c_group
)
3081 # check clone status
3082 self
._wait
_for
_clone
_to
_complete
(clone
, clone_group
=c_group
)
3085 self
._verify
_clone
(subvolume
, snapshot
, clone
, source_group
=s_group
, clone_group
=c_group
)
3088 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
, s_group
)
3091 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
, s_group
)
3092 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
, c_group
)
3095 self
._fs
_cmd
("subvolumegroup", "rm", self
.volname
, s_group
)
3096 self
._fs
_cmd
("subvolumegroup", "rm", self
.volname
, c_group
)
3098 # verify trash dir is clean
3099 self
._wait
_for
_trash
_empty
()
3101 def test_subvolume_snapshot_clone_with_upgrade(self
):
3103 yet another poor man's upgrade test -- rather than going through a full
3104 upgrade cycle, emulate old types subvolumes by going through the wormhole
3105 and verify clone operation.
3106 further ensure that a legacy volume is not updated to v2, but clone is.
3108 subvolume
= self
._generate
_random
_subvolume
_name
()
3109 snapshot
= self
._generate
_random
_snapshot
_name
()
3110 clone
= self
._generate
_random
_clone
_name
()
3112 # emulate a old-fashioned subvolume
3113 createpath
= os
.path
.join(".", "volumes", "_nogroup", subvolume
)
3114 self
.mount_a
.run_shell(['mkdir', '-p', createpath
])
3116 # add required xattrs to subvolume
3117 default_pool
= self
.mount_a
.getfattr(".", "ceph.dir.layout.pool")
3118 self
.mount_a
.setfattr(createpath
, 'ceph.dir.layout.pool', default_pool
)
3121 self
._do
_subvolume
_io
(subvolume
, number_of_files
=64)
3123 # snapshot subvolume
3124 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
3126 # ensure metadata file is in legacy location, with required version v1
3127 self
._assert
_meta
_location
_and
_version
(self
.volname
, subvolume
, version
=1, legacy
=True)
3130 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone
)
3132 # snapshot should not be deletable now
3134 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
3135 except CommandFailedError
as ce
:
3136 self
.assertEqual(ce
.exitstatus
, errno
.EAGAIN
, msg
="invalid error code when removing source snapshot of a clone")
3138 self
.fail("expected removing source snapshot of a clone to fail")
3140 # check clone status
3141 self
._wait
_for
_clone
_to
_complete
(clone
)
3144 self
._verify
_clone
(subvolume
, snapshot
, clone
, source_version
=1)
3147 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
3149 # ensure metadata file is in v2 location, with required version v2
3150 self
._assert
_meta
_location
_and
_version
(self
.volname
, clone
)
3153 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
3154 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
)
3156 # verify trash dir is clean
3157 self
._wait
_for
_trash
_empty
()
3159 def test_subvolume_clone_in_progress_getpath(self
):
3160 subvolume
= self
._generate
_random
_subvolume
_name
()
3161 snapshot
= self
._generate
_random
_snapshot
_name
()
3162 clone
= self
._generate
_random
_clone
_name
()
3165 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
3168 self
._do
_subvolume
_io
(subvolume
, number_of_files
=64)
3170 # snapshot subvolume
3171 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
3174 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone
)
3176 # clone should not be accessible right now
3178 self
._get
_subvolume
_path
(self
.volname
, clone
)
3179 except CommandFailedError
as ce
:
3180 if ce
.exitstatus
!= errno
.EAGAIN
:
3181 raise RuntimeError("invalid error code when fetching path of an pending clone")
3183 raise RuntimeError("expected fetching path of an pending clone to fail")
3185 # check clone status
3186 self
._wait
_for
_clone
_to
_complete
(clone
)
3188 # clone should be accessible now
3189 subvolpath
= self
._get
_subvolume
_path
(self
.volname
, clone
)
3190 self
.assertNotEqual(subvolpath
, None)
3193 self
._verify
_clone
(subvolume
, snapshot
, clone
)
3196 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
3199 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
3200 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
)
3202 # verify trash dir is clean
3203 self
._wait
_for
_trash
_empty
()
3205 def test_subvolume_clone_in_progress_snapshot_rm(self
):
3206 subvolume
= self
._generate
_random
_subvolume
_name
()
3207 snapshot
= self
._generate
_random
_snapshot
_name
()
3208 clone
= self
._generate
_random
_clone
_name
()
3211 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
3214 self
._do
_subvolume
_io
(subvolume
, number_of_files
=64)
3216 # snapshot subvolume
3217 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
3220 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone
)
3222 # snapshot should not be deletable now
3224 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
3225 except CommandFailedError
as ce
:
3226 self
.assertEqual(ce
.exitstatus
, errno
.EAGAIN
, msg
="invalid error code when removing source snapshot of a clone")
3228 self
.fail("expected removing source snapshot of a clone to fail")
3230 # check clone status
3231 self
._wait
_for
_clone
_to
_complete
(clone
)
3233 # clone should be accessible now
3234 subvolpath
= self
._get
_subvolume
_path
(self
.volname
, clone
)
3235 self
.assertNotEqual(subvolpath
, None)
3238 self
._verify
_clone
(subvolume
, snapshot
, clone
)
3241 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
3244 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
3245 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
)
3247 # verify trash dir is clean
3248 self
._wait
_for
_trash
_empty
()
3250 def test_subvolume_clone_in_progress_source(self
):
3251 subvolume
= self
._generate
_random
_subvolume
_name
()
3252 snapshot
= self
._generate
_random
_snapshot
_name
()
3253 clone
= self
._generate
_random
_clone
_name
()
3256 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
3259 self
._do
_subvolume
_io
(subvolume
, number_of_files
=64)
3261 # snapshot subvolume
3262 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
3265 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone
)
3267 # verify clone source
3268 result
= json
.loads(self
._fs
_cmd
("clone", "status", self
.volname
, clone
))
3269 source
= result
['status']['source']
3270 self
.assertEqual(source
['volume'], self
.volname
)
3271 self
.assertEqual(source
['subvolume'], subvolume
)
3272 self
.assertEqual(source
.get('group', None), None)
3273 self
.assertEqual(source
['snapshot'], snapshot
)
3275 # check clone status
3276 self
._wait
_for
_clone
_to
_complete
(clone
)
3278 # clone should be accessible now
3279 subvolpath
= self
._get
_subvolume
_path
(self
.volname
, clone
)
3280 self
.assertNotEqual(subvolpath
, None)
3283 self
._verify
_clone
(subvolume
, snapshot
, clone
)
3286 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
3289 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
3290 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
)
3292 # verify trash dir is clean
3293 self
._wait
_for
_trash
_empty
()
3295 def test_non_clone_status(self
):
3296 subvolume
= self
._generate
_random
_subvolume
_name
()
3299 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
3302 self
._fs
_cmd
("clone", "status", self
.volname
, subvolume
)
3303 except CommandFailedError
as ce
:
3304 if ce
.exitstatus
!= errno
.ENOTSUP
:
3305 raise RuntimeError("invalid error code when fetching status of a non cloned subvolume")
3307 raise RuntimeError("expected fetching of clone status of a subvolume to fail")
3310 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
3312 # verify trash dir is clean
3313 self
._wait
_for
_trash
_empty
()
3315 def test_subvolume_snapshot_clone_on_existing_subvolumes(self
):
3316 subvolume1
, subvolume2
= self
._generate
_random
_subvolume
_name
(2)
3317 snapshot
= self
._generate
_random
_snapshot
_name
()
3318 clone
= self
._generate
_random
_clone
_name
()
3321 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume1
)
3322 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume2
)
3325 self
._do
_subvolume
_io
(subvolume1
, number_of_files
=32)
3327 # snapshot subvolume
3328 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume1
, snapshot
)
3330 # schedule a clone with target as subvolume2
3332 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume1
, snapshot
, subvolume2
)
3333 except CommandFailedError
as ce
:
3334 if ce
.exitstatus
!= errno
.EEXIST
:
3335 raise RuntimeError("invalid error code when cloning to existing subvolume")
3337 raise RuntimeError("expected cloning to fail if the target is an existing subvolume")
3339 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume1
, snapshot
, clone
)
3341 # schedule a clone with target as clone
3343 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume1
, snapshot
, clone
)
3344 except CommandFailedError
as ce
:
3345 if ce
.exitstatus
!= errno
.EEXIST
:
3346 raise RuntimeError("invalid error code when cloning to existing clone")
3348 raise RuntimeError("expected cloning to fail if the target is an existing clone")
3350 # check clone status
3351 self
._wait
_for
_clone
_to
_complete
(clone
)
3354 self
._verify
_clone
(subvolume1
, snapshot
, clone
)
3357 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume1
, snapshot
)
3360 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume1
)
3361 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume2
)
3362 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
)
3364 # verify trash dir is clean
3365 self
._wait
_for
_trash
_empty
()
3367 def test_subvolume_snapshot_clone_fail_with_remove(self
):
3368 subvolume
= self
._generate
_random
_subvolume
_name
()
3369 snapshot
= self
._generate
_random
_snapshot
_name
()
3370 clone1
, clone2
= self
._generate
_random
_clone
_name
(2)
3372 pool_capacity
= 32 * 1024 * 1024
3373 # number of files required to fill up 99% of the pool
3374 nr_files
= int((pool_capacity
* 0.99) / (TestVolumes
.DEFAULT_FILE_SIZE
* 1024 * 1024))
3377 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
3380 self
._do
_subvolume
_io
(subvolume
, number_of_files
=nr_files
)
3382 # snapshot subvolume
3383 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
3386 new_pool
= "new_pool"
3387 self
.fs
.add_data_pool(new_pool
)
3389 self
.fs
.mon_manager
.raw_cluster_cmd("osd", "pool", "set-quota", new_pool
,
3390 "max_bytes", "{0}".format(pool_capacity
// 4))
3393 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone1
, "--pool_layout", new_pool
)
3395 # check clone status -- this should dramatically overshoot the pool quota
3396 self
._wait
_for
_clone
_to
_complete
(clone1
)
3399 self
._verify
_clone
(subvolume
, snapshot
, clone1
, clone_pool
=new_pool
)
3401 # wait a bit so that subsequent I/O will give pool full error
3405 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone2
, "--pool_layout", new_pool
)
3407 # check clone status
3408 self
._wait
_for
_clone
_to
_fail
(clone2
)
3411 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
3414 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
3415 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone1
)
3417 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone2
)
3418 except CommandFailedError
as ce
:
3419 if ce
.exitstatus
!= errno
.EAGAIN
:
3420 raise RuntimeError("invalid error code when trying to remove failed clone")
3422 raise RuntimeError("expected error when removing a failed clone")
3424 # ... and with force, failed clone can be removed
3425 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone2
, "--force")
3427 # verify trash dir is clean
3428 self
._wait
_for
_trash
_empty
()
3430 def test_subvolume_snapshot_attr_clone(self
):
3431 subvolume
= self
._generate
_random
_subvolume
_name
()
3432 snapshot
= self
._generate
_random
_snapshot
_name
()
3433 clone
= self
._generate
_random
_clone
_name
()
3436 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
3439 self
._do
_subvolume
_io
_mixed
(subvolume
)
3441 # snapshot subvolume
3442 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
3445 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone
)
3447 # check clone status
3448 self
._wait
_for
_clone
_to
_complete
(clone
)
3451 self
._verify
_clone
(subvolume
, snapshot
, clone
)
3454 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
3457 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
3458 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
)
3460 # verify trash dir is clean
3461 self
._wait
_for
_trash
_empty
()
3463 def test_subvolume_snapshot_clone_cancel_in_progress(self
):
3464 subvolume
= self
._generate
_random
_subvolume
_name
()
3465 snapshot
= self
._generate
_random
_snapshot
_name
()
3466 clone
= self
._generate
_random
_clone
_name
()
3469 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
3472 self
._do
_subvolume
_io
(subvolume
, number_of_files
=128)
3474 # snapshot subvolume
3475 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
3478 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone
)
3480 # cancel on-going clone
3481 self
._fs
_cmd
("clone", "cancel", self
.volname
, clone
)
3483 # verify canceled state
3484 self
._check
_clone
_canceled
(clone
)
3487 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
3490 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
3491 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
, "--force")
3493 # verify trash dir is clean
3494 self
._wait
_for
_trash
_empty
()
3496 def test_subvolume_snapshot_clone_cancel_pending(self
):
3498 this test is a bit more involved compared to canceling an in-progress clone.
3499 we'd need to ensure that a to-be canceled clone has still not been picked up
3500 by cloner threads. exploit the fact that clones are picked up in an FCFS
3501 fashion and there are four (4) cloner threads by default. When the number of
3502 cloner threads increase, this test _may_ start tripping -- so, the number of
3503 clone operations would need to be jacked up.
3505 # default number of clone threads
3507 # good enough for 4 threads
3509 # yeh, 1gig -- we need the clone to run for sometime
3512 subvolume
= self
._generate
_random
_subvolume
_name
()
3513 snapshot
= self
._generate
_random
_snapshot
_name
()
3514 clones
= self
._generate
_random
_clone
_name
(NR_CLONES
)
3517 self
._fs
_cmd
("subvolume", "create", self
.volname
, subvolume
)
3520 self
._do
_subvolume
_io
(subvolume
, number_of_files
=4, file_size
=FILE_SIZE_MB
)
3522 # snapshot subvolume
3523 self
._fs
_cmd
("subvolume", "snapshot", "create", self
.volname
, subvolume
, snapshot
)
3526 for clone
in clones
:
3527 self
._fs
_cmd
("subvolume", "snapshot", "clone", self
.volname
, subvolume
, snapshot
, clone
)
3529 to_wait
= clones
[0:NR_THREADS
]
3530 to_cancel
= clones
[NR_THREADS
:]
3532 # cancel pending clones and verify
3533 for clone
in to_cancel
:
3534 status
= json
.loads(self
._fs
_cmd
("clone", "status", self
.volname
, clone
))
3535 self
.assertEqual(status
["status"]["state"], "pending")
3536 self
._fs
_cmd
("clone", "cancel", self
.volname
, clone
)
3537 self
._check
_clone
_canceled
(clone
)
3539 # let's cancel on-going clones. handle the case where some of the clones
3541 for clone
in list(to_wait
):
3543 self
._fs
_cmd
("clone", "cancel", self
.volname
, clone
)
3544 to_cancel
.append(clone
)
3545 to_wait
.remove(clone
)
3546 except CommandFailedError
as ce
:
3547 if ce
.exitstatus
!= errno
.EINVAL
:
3548 raise RuntimeError("invalid error code when cancelling on-going clone")
3551 self
._fs
_cmd
("subvolume", "snapshot", "rm", self
.volname
, subvolume
, snapshot
)
3554 self
._fs
_cmd
("subvolume", "rm", self
.volname
, subvolume
)
3555 for clone
in to_wait
:
3556 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
)
3557 for clone
in to_cancel
:
3558 self
._fs
_cmd
("subvolume", "rm", self
.volname
, clone
, "--force")
3560 # verify trash dir is clean
3561 self
._wait
_for
_trash
_empty
()