]>
git.proxmox.com Git - ceph.git/blob - ceph/qa/tasks/rbd.py
10 from io
import StringIO
11 from teuthology
.orchestra
import run
12 from teuthology
import misc
as teuthology
13 from teuthology
import contextutil
14 from teuthology
.parallel
import parallel
15 from teuthology
.task
.common_fs_utils
import generic_mkfs
16 from teuthology
.task
.common_fs_utils
import generic_mount
17 from teuthology
.task
.common_fs_utils
import default_image_name
20 #V1 image unsupported but required for testing purposes
21 os
.environ
["RBD_FORCE_ALLOW_V1"] = "1"
23 log
= logging
.getLogger(__name__
)
25 ENCRYPTION_PASSPHRASE
= "password"
27 @contextlib.contextmanager
28 def create_image(ctx
, config
):
41 encryption_format: luks2
44 Image size is expressed as a number of megabytes; default value
47 Image format value must be either 1 or 2; default value is 1.
50 assert isinstance(config
, dict) or isinstance(config
, list), \
51 "task create_image only supports a list or dictionary for configuration"
53 if isinstance(config
, dict):
54 images
= config
.items()
56 images
= [(role
, None) for role
in config
]
58 testdir
= teuthology
.get_testdir(ctx
)
59 passphrase_file
= '{tdir}/passphrase'.format(tdir
=testdir
)
60 for role
, properties
in images
:
61 if properties
is None:
63 name
= properties
.get('image_name', default_image_name(role
))
64 size
= properties
.get('image_size', 10240)
65 fmt
= properties
.get('image_format', 1)
66 encryption_format
= properties
.get('encryption_format', 'none')
67 (remote
,) = ctx
.cluster
.only(role
).remotes
.keys()
68 log
.info('Creating image {name} with size {size}'.format(name
=name
,
73 '{tdir}/archive/coverage'.format(tdir
=testdir
),
80 # omit format option if using the default (format 1)
81 # since old versions of don't support it
83 args
+= ['--image-format', str(fmt
)]
86 if encryption_format
!= 'none':
90 ENCRYPTION_PASSPHRASE
,
99 '{tdir}/archive/coverage'.format(tdir
=testdir
),
113 log
.info('Deleting rbd images...')
114 remote
.run(args
=['rm', '-f', passphrase_file
])
115 for role
, properties
in images
:
116 if properties
is None:
118 name
= properties
.get('image_name', default_image_name(role
))
119 (remote
,) = ctx
.cluster
.only(role
).remotes
.keys()
124 '{tdir}/archive/coverage'.format(tdir
=testdir
),
132 @contextlib.contextmanager
133 def clone_image(ctx
, config
):
143 parent_name: testimage
144 image_name: cloneimage
146 assert isinstance(config
, dict) or isinstance(config
, list), \
147 "task clone_image only supports a list or dictionary for configuration"
149 if isinstance(config
, dict):
150 images
= config
.items()
152 images
= [(role
, None) for role
in config
]
154 testdir
= teuthology
.get_testdir(ctx
)
155 for role
, properties
in images
:
156 if properties
is None:
159 name
= properties
.get('image_name', default_image_name(role
))
160 parent_name
= properties
.get('parent_name')
161 assert parent_name
is not None, \
162 "parent_name is required"
163 parent_spec
= '{name}@{snap}'.format(name
=parent_name
, snap
=name
)
165 (remote
,) = ctx
.cluster
.only(role
).remotes
.keys()
166 log
.info('Clone image {parent} to {child}'.format(parent
=parent_name
,
168 for cmd
in [('snap', 'create', parent_spec
),
169 ('snap', 'protect', parent_spec
),
170 ('clone', parent_spec
, name
)]:
174 '{tdir}/archive/coverage'.format(tdir
=testdir
),
178 remote
.run(args
=args
)
183 log
.info('Deleting rbd clones...')
184 for role
, properties
in images
:
185 if properties
is None:
187 name
= properties
.get('image_name', default_image_name(role
))
188 parent_name
= properties
.get('parent_name')
189 parent_spec
= '{name}@{snap}'.format(name
=parent_name
, snap
=name
)
191 (remote
,) = ctx
.cluster
.only(role
).remotes
.keys()
193 for cmd
in [('rm', name
),
194 ('snap', 'unprotect', parent_spec
),
195 ('snap', 'rm', parent_spec
)]:
199 '{tdir}/archive/coverage'.format(tdir
=testdir
),
203 remote
.run(args
=args
)
205 @contextlib.contextmanager
206 def modprobe(ctx
, config
):
208 Load the rbd kernel module..
214 - rbd.create_image: [client.0]
215 - rbd.modprobe: [client.0]
217 log
.info('Loading rbd kernel module...')
219 (remote
,) = ctx
.cluster
.only(role
).remotes
.keys()
230 log
.info('Unloading rbd kernel module...')
232 (remote
,) = ctx
.cluster
.only(role
).remotes
.keys()
239 # force errors to be ignored; necessary if more
240 # than one device was created, which may mean
241 # the module isn't quite ready to go the first
248 @contextlib.contextmanager
249 def dev_create(ctx
, config
):
251 Map block devices to rbd images.
257 - rbd.create_image: [client.0]
258 - rbd.modprobe: [client.0]
261 image_name: testimage.client.0
262 encryption_format: luks2
264 assert isinstance(config
, dict) or isinstance(config
, list), \
265 "task dev_create only supports a list or dictionary for configuration"
267 if isinstance(config
, dict):
268 images
= config
.items()
270 images
= [(role
, None) for role
in config
]
272 log
.info('Creating rbd block devices...')
274 testdir
= teuthology
.get_testdir(ctx
)
275 passphrase_file
= '{tdir}/passphrase'.format(tdir
=testdir
)
278 for role
, properties
in images
:
279 if properties
is None:
281 name
= properties
.get('image_name', default_image_name(role
))
282 encryption_format
= properties
.get('encryption_format', 'none')
283 (remote
,) = ctx
.cluster
.only(role
).remotes
.keys()
285 if encryption_format
== 'none':
286 device_path
[role
] = '/dev/rbd/rbd/{image}'.format(image
=name
)
287 device_specific_args
= []
292 ENCRYPTION_PASSPHRASE
,
297 device_specific_args
= [
299 'encryption-format=%s,encryption-passphrase-file=%s' % (
300 encryption_format
, passphrase_file
)]
308 '{tdir}/archive/coverage'.format(tdir
=testdir
),
310 '--id', role
.rsplit('.')[-1],
313 name
] + device_specific_args
,
317 if encryption_format
!= 'none':
318 device_path
[role
] = map_fp
.getvalue().rstrip()
319 properties
['device_path'] = device_path
[role
]
320 remote
.run(args
=['sudo', 'chmod', '666', device_path
[role
]])
324 log
.info('Unmapping rbd devices...')
325 remote
.run(args
=['rm', '-f', passphrase_file
])
326 for role
, properties
in images
:
327 if not device_path
.get(role
):
330 if properties
is None:
332 encryption_format
= properties
.get('encryption_format', 'none')
333 (remote
,) = ctx
.cluster
.only(role
).remotes
.keys()
335 if encryption_format
== 'none':
336 device_specific_args
= []
338 device_specific_args
= ['-t', 'nbd']
342 'LD_LIBRARY_PATH={tdir}/binary/usr/local/lib'.format(tdir
=testdir
),
346 '{tdir}/archive/coverage'.format(tdir
=testdir
),
351 ] + device_specific_args
,
355 def rbd_devname_rtn(ctx
, image
):
356 return '/dev/rbd/rbd/{image}'.format(image
=image
)
358 def canonical_path(ctx
, role
, path
):
360 Determine the canonical path for a given path on the host
361 representing the given role. A canonical path contains no
362 . or .. components, and includes no symbolic links.
364 version_fp
= StringIO()
365 ctx
.cluster
.only(role
).run(
366 args
=[ 'readlink', '-f', path
],
369 canonical_path
= version_fp
.getvalue().rstrip('\n')
371 return canonical_path
373 @contextlib.contextmanager
374 def run_xfstests(ctx
, config
):
376 Run xfstests over specified devices.
378 Warning: both the test and scratch devices specified will be
379 overwritten. Normally xfstests modifies (but does not destroy)
380 the test device, but for now the run script used here re-makes
383 Note: Only one instance of xfstests can run on a single host at
384 a time, although this is not enforced.
386 This task in its current form needs some improvement. For
387 example, it assumes all roles provided in the config are
388 clients, and that the config provided is a list of key/value
389 pairs. For now please use the xfstests() interface, below.
399 scratch_dev: 'scratch_dev'
401 tests: 'generic/100 xfs/003 xfs/005 xfs/006 generic/015'
406 with
parallel() as p
:
407 for role
, properties
in config
.items():
408 p
.spawn(run_xfstests_one_client
, ctx
, role
, properties
)
413 except StopIteration:
416 exc
= sys
.exc_info()[1]
421 def run_xfstests_one_client(ctx
, role
, properties
):
423 Spawned routine to handle xfs tests for a single client
425 testdir
= teuthology
.get_testdir(ctx
)
427 count
= properties
.get('count')
428 test_dev
= properties
.get('test_dev')
429 assert test_dev
is not None, \
430 "task run_xfstests requires test_dev to be defined"
431 test_dev
= canonical_path(ctx
, role
, test_dev
)
433 scratch_dev
= properties
.get('scratch_dev')
434 assert scratch_dev
is not None, \
435 "task run_xfstests requires scratch_dev to be defined"
436 scratch_dev
= canonical_path(ctx
, role
, scratch_dev
)
438 fs_type
= properties
.get('fs_type')
439 tests
= properties
.get('tests')
440 exclude_list
= properties
.get('exclude')
441 randomize
= properties
.get('randomize')
443 (remote
,) = ctx
.cluster
.only(role
).remotes
.keys()
445 # Fetch the test script
446 test_root
= teuthology
.get_testdir(ctx
)
447 test_script
= 'run_xfstests.sh'
448 test_path
= os
.path
.join(test_root
, test_script
)
450 xfstests_url
= properties
.get('xfstests_url')
451 assert xfstests_url
is not None, \
452 "task run_xfstests requires xfstests_url to be defined"
454 xfstests_krbd_url
= xfstests_url
+ '/' + test_script
456 log
.info('Fetching {script} for {role} from {url}'.format(
459 url
=xfstests_krbd_url
))
461 args
= [ 'wget', '-O', test_path
, '--', xfstests_krbd_url
]
462 remote
.run(args
=args
)
464 log
.info('Running xfstests on {role}:'.format(role
=role
))
465 log
.info(' iteration count: {count}:'.format(count
=count
))
466 log
.info(' test device: {dev}'.format(dev
=test_dev
))
467 log
.info(' scratch device: {dev}'.format(dev
=scratch_dev
))
468 log
.info(' using fs_type: {fs_type}'.format(fs_type
=fs_type
))
469 log
.info(' tests to run: {tests}'.format(tests
=tests
))
470 log
.info(' exclude list: {}'.format(' '.join(exclude_list
)))
471 log
.info(' randomize: {randomize}'.format(randomize
=randomize
))
474 with tempfile
.NamedTemporaryFile(mode
='w', prefix
='exclude') as exclude_file
:
475 for test
in exclude_list
:
476 exclude_file
.write("{}\n".format(test
))
478 remote
.put_file(exclude_file
.name
, exclude_file
.name
)
480 # Note that the device paths are interpreted using
481 # readlink -f <path> in order to get their canonical
482 # pathname (so it matches what the kernel remembers).
485 'TESTDIR={tdir}'.format(tdir
=testdir
),
488 '{tdir}/archive/coverage'.format(tdir
=testdir
),
497 args
.extend(['-x', exclude_file
.name
])
501 args
.extend(['--', tests
])
502 remote
.run(args
=args
, logger
=log
.getChild(role
))
504 log
.info('Removing {script} on {role}'.format(script
=test_script
,
506 remote
.run(args
=['rm', '-f', test_path
])
508 @contextlib.contextmanager
509 def xfstests(ctx
, config
):
511 Run xfstests over rbd devices. This interface sets up all
512 required configuration automatically if not otherwise specified.
513 Note that only one instance of xfstests can run on a single host
514 at a time. By default, the set of tests specified is run once.
515 If a (non-zero) count value is supplied, the complete set of
516 tests will be run that number of times.
522 # Image sizes are in MB
526 test_image: 'test_image'
529 scratch_image: 'scratch_image'
533 tests: 'generic/100 xfs/003 xfs/005 xfs/006 generic/015'
537 xfstests_url: 'https://raw.github.com/ceph/ceph-ci/wip-55555/qa'
540 config
= { 'all': None }
541 assert isinstance(config
, dict) or isinstance(config
, list), \
542 "task xfstests only supports a list or dictionary for configuration"
543 if isinstance(config
, dict):
544 config
= teuthology
.replace_all_with_clients(ctx
.cluster
, config
)
545 runs
= config
.items()
547 runs
= [(role
, None) for role
in config
]
549 running_xfstests
= {}
550 for role
, properties
in runs
:
551 assert role
.startswith('client.'), \
552 "task xfstests can only run on client nodes"
553 for host
, roles_for_host
in ctx
.cluster
.remotes
.items():
554 if role
in roles_for_host
:
555 assert host
not in running_xfstests
, \
556 "task xfstests allows only one instance at a time per host"
557 running_xfstests
[host
] = True
562 image_map_config
= {}
563 scratch_map_config
= {}
565 for role
, properties
in runs
:
566 if properties
is None:
569 test_image
= properties
.get('test_image', 'test_image.{role}'.format(role
=role
))
570 test_size
= properties
.get('test_size', 10000) # 10G
571 test_fmt
= properties
.get('test_format', 1)
572 scratch_image
= properties
.get('scratch_image', 'scratch_image.{role}'.format(role
=role
))
573 scratch_size
= properties
.get('scratch_size', 10000) # 10G
574 scratch_fmt
= properties
.get('scratch_format', 1)
576 images_config
[role
] = dict(
577 image_name
=test_image
,
578 image_size
=test_size
,
579 image_format
=test_fmt
,
582 scratch_config
[role
] = dict(
583 image_name
=scratch_image
,
584 image_size
=scratch_size
,
585 image_format
=scratch_fmt
,
588 xfstests_branch
= properties
.get('xfstests_branch', 'master')
589 xfstests_url
= properties
.get('xfstests_url', 'https://raw.github.com/ceph/ceph/{branch}/qa'.format(branch
=xfstests_branch
))
591 xfstests_config
[role
] = dict(
592 count
=properties
.get('count', 1),
593 test_dev
='/dev/rbd/rbd/{image}'.format(image
=test_image
),
594 scratch_dev
='/dev/rbd/rbd/{image}'.format(image
=scratch_image
),
595 fs_type
=properties
.get('fs_type', 'xfs'),
596 randomize
=properties
.get('randomize', False),
597 tests
=properties
.get('tests'),
598 exclude
=properties
.get('exclude', []),
599 xfstests_url
=xfstests_url
,
602 log
.info('Setting up xfstests using RBD images:')
603 log
.info(' test ({size} MB): {image}'.format(size
=test_size
,
605 log
.info(' scratch ({size} MB): {image}'.format(size
=scratch_size
,
606 image
=scratch_image
))
607 modprobe_config
[role
] = None
608 image_map_config
[role
] = {'image_name': test_image
}
609 scratch_map_config
[role
] = {'image_name': scratch_image
}
611 with contextutil
.nested(
612 lambda: create_image(ctx
=ctx
, config
=images_config
),
613 lambda: create_image(ctx
=ctx
, config
=scratch_config
),
614 lambda: modprobe(ctx
=ctx
, config
=modprobe_config
),
615 lambda: dev_create(ctx
=ctx
, config
=image_map_config
),
616 lambda: dev_create(ctx
=ctx
, config
=scratch_map_config
),
617 lambda: run_xfstests(ctx
=ctx
, config
=xfstests_config
),
622 @contextlib.contextmanager
623 def task(ctx
, config
):
625 Create and mount an rbd image.
627 For example, you can specify which clients to run on::
631 - rbd: [client.0, client.1]
633 There are a few image options::
638 client.0: # uses defaults
645 To use default options on all clients::
652 To create 20GiB images and format them with xfs on all clients::
662 config
= { 'all': None }
664 if isinstance(config
, dict):
665 norm_config
= teuthology
.replace_all_with_clients(ctx
.cluster
, config
)
666 if isinstance(norm_config
, dict):
668 for role
, properties
in norm_config
.items():
669 if properties
is None:
671 role_images
[role
] = properties
.get('image_name')
673 role_images
= norm_config
675 log
.debug('rbd config is: %s', norm_config
)
677 with contextutil
.nested(
678 lambda: create_image(ctx
=ctx
, config
=norm_config
),
679 lambda: modprobe(ctx
=ctx
, config
=norm_config
),
680 lambda: dev_create(ctx
=ctx
, config
=norm_config
),
681 lambda: generic_mkfs(ctx
=ctx
, config
=norm_config
,
682 devname_rtn
=rbd_devname_rtn
),
683 lambda: generic_mount(ctx
=ctx
, config
=role_images
,
684 devname_rtn
=rbd_devname_rtn
),