12 from tasks
.util
.workunit
import get_refspec_after_overrides
13 from teuthology
import contextutil
14 from teuthology
import misc
as teuthology
15 from teuthology
.config
import config
as teuth_config
16 from teuthology
.orchestra
import run
17 from teuthology
.packaging
import install_package
, remove_package
19 log
= logging
.getLogger(__name__
)
22 DEFAULT_IMAGE_URL
= 'http://download.ceph.com/qa/ubuntu-12.04.qcow2'
23 DEFAULT_IMAGE_SIZE
= 10240 # in megabytes
24 ENCRYPTION_HEADER_SIZE
= 16 # in megabytes
26 DEFAULT_MEM
= 4096 # in megabytes
28 def normalize_disks(config
):
29 # normalize the 'disks' parameter into a list of dictionaries
30 for client
, client_config
in config
.items():
31 clone
= client_config
.get('clone', False)
32 image_url
= client_config
.get('image_url', DEFAULT_IMAGE_URL
)
33 device_type
= client_config
.get('type', 'filesystem')
34 encryption_format
= client_config
.get('encryption_format', 'none')
35 parent_encryption_format
= client_config
.get(
36 'parent_encryption_format', 'none')
38 disks
= client_config
.get('disks', DEFAULT_NUM_DISKS
)
39 if not isinstance(disks
, list):
40 disks
= [{'image_name': '{client}.{num}'.format(client
=client
,
42 for i
in range(int(disks
))]
43 client_config
['disks'] = disks
45 for i
, disk
in enumerate(disks
):
46 if 'action' not in disk
:
47 disk
['action'] = 'create'
48 assert disk
['action'] in ['none', 'create', 'clone'], 'invalid disk action'
49 assert disk
['action'] != 'clone' or 'parent_name' in disk
, 'parent_name required for clone'
51 if 'image_size' not in disk
:
52 disk
['image_size'] = DEFAULT_IMAGE_SIZE
53 disk
['image_size'] = int(disk
['image_size'])
55 if 'image_url' not in disk
and i
== 0:
56 disk
['image_url'] = image_url
58 if 'device_type' not in disk
:
59 disk
['device_type'] = device_type
61 disk
['device_letter'] = chr(ord('a') + i
)
63 if 'encryption_format' not in disk
:
65 disk
['encryption_format'] = parent_encryption_format
67 disk
['encryption_format'] = encryption_format
68 assert disk
['encryption_format'] in ['none', 'luks1', 'luks2'], 'invalid encryption format'
70 assert disks
, 'at least one rbd device must be used'
74 if disk
['action'] != 'create':
77 clone
['action'] = 'clone'
78 clone
['parent_name'] = clone
['image_name']
79 clone
['image_name'] += '-clone'
80 del disk
['device_letter']
82 clone
['encryption_format'] = encryption_format
83 assert clone
['encryption_format'] in ['none', 'luks1', 'luks2'], 'invalid encryption format'
85 clone
['parent_encryption_format'] = parent_encryption_format
86 assert clone
['parent_encryption_format'] in ['none', 'luks1', 'luks2'], 'invalid encryption format'
90 def create_images(ctx
, config
, managers
):
91 for client
, client_config
in config
.items():
92 disks
= client_config
['disks']
94 if disk
.get('action') != 'create' or (
95 'image_url' in disk
and
96 disk
['encryption_format'] == 'none'):
98 image_size
= disk
['image_size']
99 if disk
['encryption_format'] != 'none':
100 image_size
+= ENCRYPTION_HEADER_SIZE
103 'image_name': disk
['image_name'],
105 'image_size': image_size
,
106 'encryption_format': disk
['encryption_format'],
110 lambda create_config
=create_config
:
111 rbd
.create_image(ctx
=ctx
, config
=create_config
)
114 def create_clones(ctx
, config
, managers
):
115 for client
, client_config
in config
.items():
116 disks
= client_config
['disks']
118 if disk
['action'] != 'clone':
123 'image_name': disk
['image_name'],
124 'parent_name': disk
['parent_name'],
125 'encryption_format': disk
['encryption_format'],
129 lambda create_config
=create_config
:
130 rbd
.clone_image(ctx
=ctx
, config
=create_config
)
133 def create_encrypted_devices(ctx
, config
, managers
):
134 for client
, client_config
in config
.items():
135 disks
= client_config
['disks']
137 if (disk
['encryption_format'] == 'none' and
138 disk
.get('parent_encryption_format', 'none') == 'none') or \
139 'device_letter' not in disk
:
142 dev_config
= {client
: disk
}
144 lambda dev_config
=dev_config
:
145 rbd
.dev_create(ctx
=ctx
, config
=dev_config
)
148 @contextlib.contextmanager
149 def create_dirs(ctx
, config
):
151 Handle directory creation and cleanup
153 testdir
= teuthology
.get_testdir(ctx
)
154 for client
, client_config
in config
.items():
155 assert 'test' in client_config
, 'You must specify a test to run'
156 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
159 'install', '-d', '-m0755', '--',
160 '{tdir}/qemu'.format(tdir
=testdir
),
161 '{tdir}/archive/qemu'.format(tdir
=testdir
),
167 for client
, client_config
in config
.items():
168 assert 'test' in client_config
, 'You must specify a test to run'
169 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
172 'rmdir', '{tdir}/qemu'.format(tdir
=testdir
), run
.Raw('||'), 'true',
176 @contextlib.contextmanager
177 def install_block_rbd_driver(ctx
, config
):
179 Make sure qemu rbd block driver (block-rbd.so) is installed
182 for client
, _
in config
.items():
183 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
184 if remote
.os
.package_type
== 'rpm':
185 packages
[client
] = ['qemu-kvm-block-rbd']
187 packages
[client
] = ['qemu-block-extra', 'qemu-utils']
188 for pkg
in packages
[client
]:
189 install_package(pkg
, remote
)
193 for client
, _
in config
.items():
194 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
195 for pkg
in packages
[client
]:
196 remove_package(pkg
, remote
)
198 @contextlib.contextmanager
199 def generate_iso(ctx
, config
):
200 """Execute system commands to generate iso"""
201 log
.info('generating iso...')
202 testdir
= teuthology
.get_testdir(ctx
)
204 # use ctx.config instead of config, because config has been
205 # through teuthology.replace_all_with_clients()
206 refspec
= get_refspec_after_overrides(ctx
.config
, {})
208 git_url
= teuth_config
.get_ceph_qa_suite_git_url()
209 log
.info('Pulling tests from %s ref %s', git_url
, refspec
)
211 for client
, client_config
in config
.items():
212 assert 'test' in client_config
, 'You must specify a test to run'
213 test
= client_config
['test']
215 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
217 clone_dir
= '{tdir}/qemu_clone.{role}'.format(tdir
=testdir
, role
=client
)
218 remote
.run(args
=refspec
.clone(git_url
, clone_dir
))
220 src_dir
= os
.path
.dirname(__file__
)
221 userdata_path
= os
.path
.join(testdir
, 'qemu', 'userdata.' + client
)
222 metadata_path
= os
.path
.join(testdir
, 'qemu', 'metadata.' + client
)
224 with
open(os
.path
.join(src_dir
, 'userdata_setup.yaml')) as f
:
225 test_setup
= ''.join(f
.readlines())
226 # configuring the commands to setup the nfs mount
227 mnt_dir
= "/export/{client}".format(client
=client
)
228 test_setup
= test_setup
.format(
232 with
open(os
.path
.join(src_dir
, 'userdata_teardown.yaml')) as f
:
233 test_teardown
= ''.join(f
.readlines())
235 user_data
= test_setup
237 disks
= client_config
['disks']
239 if disk
['device_type'] != 'filesystem' or \
240 'device_letter' not in disk
or \
243 if disk
['encryption_format'] == 'none' and \
244 disk
.get('parent_encryption_format', 'none') == 'none':
245 dev_name
= 'vd' + disk
['device_letter']
247 # encrypted disks use if=ide interface, instead of if=virtio
248 dev_name
= 'sd' + disk
['device_letter']
252 mkdir /mnt/test_{dev_name}
253 mkfs -t xfs /dev/{dev_name}
254 mount -t xfs /dev/{dev_name} /mnt/test_{dev_name}
255 """.format(dev_name
=dev_name
)
260 test -d /etc/ceph || mkdir /etc/ceph
261 cp /mnt/cdrom/ceph.* /etc/ceph/
264 cloud_config_archive
= client_config
.get('cloud_config_archive', [])
265 if cloud_config_archive
:
266 user_data
+= yaml
.safe_dump(cloud_config_archive
, default_style
='|',
267 default_flow_style
=False)
269 # this may change later to pass the directories as args to the
270 # script or something. xfstests needs that.
274 test -d /mnt/test_b && cd /mnt/test_b
275 /mnt/cdrom/test.sh > /mnt/log/test.log 2>&1 && touch /mnt/log/success
278 user_data
= user_data
.format(
279 ceph_branch
=ctx
.config
.get('branch'),
280 ceph_sha1
=ctx
.config
.get('sha1'))
281 remote
.write_file(userdata_path
, user_data
)
283 with
open(os
.path
.join(src_dir
, 'metadata.yaml'), 'rb') as f
:
284 remote
.write_file(metadata_path
, f
)
286 test_file
= '{tdir}/qemu/{client}.test.sh'.format(tdir
=testdir
, client
=client
)
288 log
.info('fetching test %s for %s', test
, client
)
291 'cp', '--', os
.path
.join(clone_dir
, test
), test_file
,
293 'chmod', '755', test_file
,
298 'genisoimage', '-quiet', '-input-charset', 'utf-8',
299 '-volid', 'cidata', '-joliet', '-rock',
300 '-o', '{tdir}/qemu/{client}.iso'.format(tdir
=testdir
, client
=client
),
302 'user-data={userdata}'.format(userdata
=userdata_path
),
303 'meta-data={metadata}'.format(metadata
=metadata_path
),
304 'ceph.conf=/etc/ceph/ceph.conf',
305 'ceph.keyring=/etc/ceph/ceph.keyring',
306 'test.sh={file}'.format(file=test_file
),
312 for client
in config
.keys():
313 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
317 '{tdir}/qemu/{client}.iso'.format(tdir
=testdir
, client
=client
),
318 os
.path
.join(testdir
, 'qemu', 'userdata.' + client
),
319 os
.path
.join(testdir
, 'qemu', 'metadata.' + client
),
320 '{tdir}/qemu/{client}.test.sh'.format(tdir
=testdir
, client
=client
),
321 '{tdir}/qemu_clone.{client}'.format(tdir
=testdir
, client
=client
),
325 @contextlib.contextmanager
326 def download_image(ctx
, config
):
327 """Downland base image, remove image file when done"""
328 log
.info('downloading base image')
329 testdir
= teuthology
.get_testdir(ctx
)
331 client_base_files
= {}
332 for client
, client_config
in config
.items():
333 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
335 client_base_files
[client
] = []
336 disks
= client_config
['disks']
338 if disk
['action'] != 'create' or 'image_url' not in disk
:
341 base_file
= '{tdir}/qemu/base.{name}.qcow2'.format(tdir
=testdir
,
342 name
=disk
['image_name'])
343 client_base_files
[client
].append(base_file
)
347 'wget', '-nv', '-O', base_file
, disk
['image_url'],
351 if disk
['encryption_format'] == 'none':
354 'qemu-img', 'convert', '-f', 'qcow2', '-O', 'raw',
355 base_file
, 'rbd:rbd/{image_name}'.format(image_name
=disk
['image_name'])
359 dev_config
= {client
: {'image_name': disk
['image_name'],
360 'encryption_format': disk
['encryption_format']}}
361 raw_file
= '{tdir}/qemu/base.{name}.raw'.format(
362 tdir
=testdir
, name
=disk
['image_name'])
363 client_base_files
[client
].append(raw_file
)
366 'qemu-img', 'convert', '-f', 'qcow2', '-O', 'raw',
370 with rbd
.dev_create(ctx
, dev_config
):
373 'dd', 'if={name}'.format(name
=raw_file
),
374 'of={name}'.format(name
=dev_config
[client
]['device_path']),
375 'bs=4M', 'conv=fdatasync'
380 if disk
['action'] == 'clone' or \
381 disk
['encryption_format'] != 'none' or \
382 (disk
['action'] == 'create' and 'image_url' not in disk
):
388 '--size={image_size}M'.format(image_size
=disk
['image_size']),
389 disk
['image_name'], run
.Raw('||'), 'true'
396 log
.debug('cleaning up base image files')
397 for client
, base_files
in client_base_files
.items():
398 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
399 for base_file
in base_files
:
402 'rm', '-f', base_file
,
407 def _setup_nfs_mount(remote
, client
, service_name
, mount_dir
):
409 Sets up an nfs mount on the remote that the guest can use to
410 store logs. This nfs mount is also used to touch a file
411 at the end of the test to indicate if the test was successful
414 export_dir
= "/export/{client}".format(client
=client
)
415 log
.info("Creating the nfs export directory...")
417 'sudo', 'mkdir', '-p', export_dir
,
419 log
.info("Mounting the test directory...")
421 'sudo', 'mount', '--bind', mount_dir
, export_dir
,
423 log
.info("Adding mount to /etc/exports...")
424 export
= "{dir} *(rw,no_root_squash,no_subtree_check,insecure)".format(
427 log
.info("Deleting export from /etc/exports...")
429 'sudo', 'sed', '-i', "\|{export_dir}|d".format(export_dir
=export_dir
),
433 'echo', export
, run
.Raw("|"),
434 'sudo', 'tee', '-a', "/etc/exports",
436 log
.info("Restarting NFS...")
437 if remote
.os
.package_type
== "deb":
438 remote
.run(args
=['sudo', 'service', 'nfs-kernel-server', 'restart'])
440 remote
.run(args
=['sudo', 'systemctl', 'restart', service_name
])
443 def _teardown_nfs_mount(remote
, client
, service_name
):
445 Tears down the nfs mount on the remote used for logging and reporting the
446 status of the tests being ran in the guest.
448 log
.info("Tearing down the nfs mount for {remote}".format(remote
=remote
))
449 export_dir
= "/export/{client}".format(client
=client
)
450 log
.info("Stopping NFS...")
451 if remote
.os
.package_type
== "deb":
453 'sudo', 'service', 'nfs-kernel-server', 'stop'
457 'sudo', 'systemctl', 'stop', service_name
459 log
.info("Unmounting exported directory...")
461 'sudo', 'umount', export_dir
463 log
.info("Deleting export from /etc/exports...")
465 'sudo', 'sed', '-i', "\|{export_dir}|d".format(export_dir
=export_dir
),
468 log
.info("Starting NFS...")
469 if remote
.os
.package_type
== "deb":
471 'sudo', 'service', 'nfs-kernel-server', 'start'
475 'sudo', 'systemctl', 'start', service_name
479 @contextlib.contextmanager
480 def run_qemu(ctx
, config
):
481 """Setup kvm environment and start qemu"""
483 testdir
= teuthology
.get_testdir(ctx
)
484 for client
, client_config
in config
.items():
485 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
486 log_dir
= '{tdir}/archive/qemu/{client}'.format(tdir
=testdir
, client
=client
)
489 'mkdir', log_dir
, run
.Raw('&&'),
490 'sudo', 'modprobe', 'kvm',
494 nfs_service_name
= 'nfs'
495 if remote
.os
.name
in ['rhel', 'centos'] and float(remote
.os
.version
) >= 8:
496 nfs_service_name
= 'nfs-server'
498 # make an nfs mount to use for logging and to
499 # allow to test to tell teuthology the tests outcome
500 _setup_nfs_mount(remote
, client
, nfs_service_name
, log_dir
)
502 # Hack to make sure /dev/kvm permissions are set correctly
503 # See http://tracker.ceph.com/issues/17977 and
504 # https://bugzilla.redhat.com/show_bug.cgi?id=1333159
505 remote
.run(args
='sudo udevadm control --reload')
506 remote
.run(args
='sudo udevadm trigger /dev/kvm')
507 remote
.run(args
='ls -l /dev/kvm')
509 qemu_cmd
= 'qemu-system-x86_64'
510 if remote
.os
.package_type
== "rpm":
511 qemu_cmd
= "/usr/libexec/qemu-kvm"
515 '{tdir}/archive/coverage'.format(tdir
=testdir
),
518 qemu_cmd
, '-enable-kvm', '-nographic', '-cpu', 'host',
519 '-smp', str(client_config
.get('cpus', DEFAULT_CPUS
)),
520 '-m', str(client_config
.get('memory', DEFAULT_MEM
)),
521 # cd holding metadata for cloud-init
522 '-cdrom', '{tdir}/qemu/{client}.iso'.format(tdir
=testdir
, client
=client
),
526 ceph_config
= ctx
.ceph
['ceph'].conf
.get('global', {})
527 ceph_config
.update(ctx
.ceph
['ceph'].conf
.get('client', {}))
528 ceph_config
.update(ctx
.ceph
['ceph'].conf
.get(client
, {}))
529 if ceph_config
.get('rbd cache', True):
530 if ceph_config
.get('rbd cache max dirty', 1) > 0:
531 cachemode
= 'writeback'
533 cachemode
= 'writethrough'
535 disks
= client_config
['disks']
537 if 'device_letter' not in disk
:
540 if disk
['encryption_format'] == 'none' and \
541 disk
.get('parent_encryption_format', 'none') == 'none':
543 disk_spec
= 'rbd:rbd/{img}:id={id}'.format(
544 img
=disk
['image_name'],
545 id=client
[len('client.'):]
548 # encrypted disks use ide as a temporary workaround for
549 # a bug in qemu when using virtio over nbd
550 # TODO: use librbd encryption directly via qemu (not via nbd)
552 disk_spec
= disk
['device_path']
556 'file={disk_spec},format=raw,if={interface},cache={cachemode}'.format(
562 time_wait
= client_config
.get('time_wait', 0)
564 log
.info('starting qemu...')
568 logger
=log
.getChild(client
),
577 log
.info('waiting for qemu tests to finish...')
581 log
.debug('waiting {time_wait} sec for workloads detect finish...'.format(
582 time_wait
=time_wait
));
583 time
.sleep(time_wait
)
585 log
.debug('checking that qemu tests succeeded...')
586 for client
in config
.keys():
587 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
589 # ensure we have permissions to all the logs
590 log_dir
= '{tdir}/archive/qemu/{client}'.format(tdir
=testdir
,
594 'sudo', 'chmod', 'a+rw', '-R', log_dir
599 _teardown_nfs_mount(remote
, client
, nfs_service_name
)
600 # check for test status
604 '{tdir}/archive/qemu/{client}/success'.format(
610 log
.info("Deleting exported directory...")
611 for client
in config
.keys():
612 (remote
,) = ctx
.cluster
.only(client
).remotes
.keys()
614 'sudo', 'rm', '-r', '/export'
618 @contextlib.contextmanager
619 def task(ctx
, config
):
621 Run a test inside of QEMU on top of rbd. Only one test
622 is supported per client.
624 For example, you can specify which clients to run on::
630 test: http://download.ceph.com/qa/test.sh
632 test: http://download.ceph.com/qa/test2.sh
634 Or use the same settings on all clients:
640 test: http://download.ceph.com/qa/test.sh
642 For tests that want to explicitly describe the RBD images to connect:
648 test: http://download.ceph.com/qa/test.sh
649 clone: True/False (optionally clone all created disks),
650 image_url: <URL> (optional default image URL)
651 type: filesystem / block (optional default device type)
654 action: create / clone / none (optional, defaults to create)
655 image_name: <image name> (optional)
656 parent_name: <parent_name> (if action == clone),
657 type: filesystem / block (optional, defaults to fileystem)
658 image_url: <URL> (optional),
659 image_size: <MiB> (optional)
660 encryption_format: luks1 / luks2 / none (optional, defaults to none)
664 You can set the amount of CPUs and memory the VM has (default is 1 CPU and
671 test: http://download.ceph.com/qa/test.sh
673 memory: 512 # megabytes
675 If you need to configure additional cloud-config options, set cloud_config
676 to the required data set::
682 test: http://ceph.com/qa/test.sh
683 cloud_config_archive:
692 assert isinstance(config
, dict), \
693 "task qemu only supports a dictionary for configuration"
695 config
= teuthology
.replace_all_with_clients(ctx
.cluster
, config
)
696 normalize_disks(config
)
699 create_images(ctx
=ctx
, config
=config
, managers
=managers
)
701 lambda: create_dirs(ctx
=ctx
, config
=config
),
702 lambda: install_block_rbd_driver(ctx
=ctx
, config
=config
),
703 lambda: generate_iso(ctx
=ctx
, config
=config
),
704 lambda: download_image(ctx
=ctx
, config
=config
),
706 create_clones(ctx
=ctx
, config
=config
, managers
=managers
)
707 create_encrypted_devices(ctx
=ctx
, config
=config
, managers
=managers
)
709 lambda: run_qemu(ctx
=ctx
, config
=config
),
712 with contextutil
.nested(*managers
):