teuthology 1.0.0__py3-none-any.whl → 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scripts/describe.py +1 -0
- scripts/dispatcher.py +62 -0
- scripts/exporter.py +18 -0
- scripts/lock.py +1 -1
- scripts/node_cleanup.py +58 -0
- scripts/openstack.py +9 -9
- scripts/results.py +12 -11
- scripts/run.py +4 -0
- scripts/schedule.py +4 -0
- scripts/suite.py +61 -16
- scripts/supervisor.py +44 -0
- scripts/update_inventory.py +10 -4
- scripts/wait.py +31 -0
- teuthology/__init__.py +24 -21
- teuthology/beanstalk.py +4 -3
- teuthology/config.py +17 -6
- teuthology/contextutil.py +18 -14
- teuthology/describe_tests.py +25 -18
- teuthology/dispatcher/__init__.py +365 -0
- teuthology/dispatcher/supervisor.py +374 -0
- teuthology/exceptions.py +54 -0
- teuthology/exporter.py +347 -0
- teuthology/kill.py +76 -75
- teuthology/lock/cli.py +16 -7
- teuthology/lock/ops.py +276 -70
- teuthology/lock/query.py +61 -44
- teuthology/ls.py +9 -18
- teuthology/misc.py +152 -137
- teuthology/nuke/__init__.py +12 -351
- teuthology/openstack/__init__.py +4 -3
- teuthology/openstack/openstack-centos-7.0-user-data.txt +1 -1
- teuthology/openstack/openstack-centos-7.1-user-data.txt +1 -1
- teuthology/openstack/openstack-centos-7.2-user-data.txt +1 -1
- teuthology/openstack/openstack-debian-8.0-user-data.txt +1 -1
- teuthology/openstack/openstack-opensuse-42.1-user-data.txt +1 -1
- teuthology/openstack/openstack-teuthology.cron +0 -1
- teuthology/orchestra/cluster.py +51 -9
- teuthology/orchestra/connection.py +23 -16
- teuthology/orchestra/console.py +111 -50
- teuthology/orchestra/daemon/cephadmunit.py +23 -5
- teuthology/orchestra/daemon/state.py +10 -3
- teuthology/orchestra/daemon/systemd.py +10 -8
- teuthology/orchestra/opsys.py +32 -11
- teuthology/orchestra/remote.py +369 -152
- teuthology/orchestra/run.py +21 -12
- teuthology/packaging.py +54 -15
- teuthology/provision/__init__.py +30 -10
- teuthology/provision/cloud/openstack.py +12 -6
- teuthology/provision/cloud/util.py +1 -2
- teuthology/provision/downburst.py +83 -29
- teuthology/provision/fog.py +68 -20
- teuthology/provision/openstack.py +5 -4
- teuthology/provision/pelagos.py +13 -5
- teuthology/repo_utils.py +91 -44
- teuthology/report.py +57 -35
- teuthology/results.py +5 -3
- teuthology/run.py +21 -15
- teuthology/run_tasks.py +114 -40
- teuthology/schedule.py +4 -3
- teuthology/scrape.py +28 -22
- teuthology/suite/__init__.py +75 -46
- teuthology/suite/build_matrix.py +34 -24
- teuthology/suite/fragment-merge.lua +105 -0
- teuthology/suite/matrix.py +31 -2
- teuthology/suite/merge.py +175 -0
- teuthology/suite/placeholder.py +8 -8
- teuthology/suite/run.py +204 -102
- teuthology/suite/util.py +67 -211
- teuthology/task/__init__.py +1 -1
- teuthology/task/ansible.py +101 -31
- teuthology/task/buildpackages.py +2 -2
- teuthology/task/ceph_ansible.py +13 -6
- teuthology/task/cephmetrics.py +2 -1
- teuthology/task/clock.py +33 -14
- teuthology/task/exec.py +18 -0
- teuthology/task/hadoop.py +2 -2
- teuthology/task/install/__init__.py +51 -22
- teuthology/task/install/bin/adjust-ulimits +16 -0
- teuthology/task/install/bin/daemon-helper +114 -0
- teuthology/task/install/bin/stdin-killer +263 -0
- teuthology/task/install/deb.py +24 -4
- teuthology/task/install/redhat.py +36 -32
- teuthology/task/install/rpm.py +41 -14
- teuthology/task/install/util.py +48 -22
- teuthology/task/internal/__init__.py +69 -11
- teuthology/task/internal/edit_sudoers.sh +10 -0
- teuthology/task/internal/lock_machines.py +3 -133
- teuthology/task/internal/redhat.py +48 -28
- teuthology/task/internal/syslog.py +31 -8
- teuthology/task/kernel.py +155 -147
- teuthology/task/lockfile.py +1 -1
- teuthology/task/mpi.py +10 -10
- teuthology/task/pcp.py +1 -1
- teuthology/task/selinux.py +17 -8
- teuthology/task/ssh_keys.py +6 -6
- teuthology/task/tests/__init__.py +137 -77
- teuthology/task/tests/test_fetch_coredumps.py +116 -0
- teuthology/task/tests/test_run.py +4 -4
- teuthology/timer.py +3 -3
- teuthology/util/loggerfile.py +19 -0
- teuthology/util/scanner.py +159 -0
- teuthology/util/sentry.py +52 -0
- teuthology/util/time.py +52 -0
- teuthology-1.2.0.data/scripts/adjust-ulimits +16 -0
- teuthology-1.2.0.data/scripts/daemon-helper +114 -0
- teuthology-1.2.0.data/scripts/stdin-killer +263 -0
- teuthology-1.2.0.dist-info/METADATA +89 -0
- teuthology-1.2.0.dist-info/RECORD +174 -0
- {teuthology-1.0.0.dist-info → teuthology-1.2.0.dist-info}/WHEEL +1 -1
- {teuthology-1.0.0.dist-info → teuthology-1.2.0.dist-info}/entry_points.txt +5 -2
- scripts/nuke.py +0 -45
- scripts/worker.py +0 -37
- teuthology/nuke/actions.py +0 -456
- teuthology/openstack/test/__init__.py +0 -0
- teuthology/openstack/test/openstack-integration.py +0 -286
- teuthology/openstack/test/test_config.py +0 -35
- teuthology/openstack/test/test_openstack.py +0 -1695
- teuthology/orchestra/test/__init__.py +0 -0
- teuthology/orchestra/test/integration/__init__.py +0 -0
- teuthology/orchestra/test/integration/test_integration.py +0 -94
- teuthology/orchestra/test/test_cluster.py +0 -240
- teuthology/orchestra/test/test_connection.py +0 -106
- teuthology/orchestra/test/test_console.py +0 -217
- teuthology/orchestra/test/test_opsys.py +0 -404
- teuthology/orchestra/test/test_remote.py +0 -185
- teuthology/orchestra/test/test_run.py +0 -286
- teuthology/orchestra/test/test_systemd.py +0 -54
- teuthology/orchestra/test/util.py +0 -12
- teuthology/sentry.py +0 -18
- teuthology/test/__init__.py +0 -0
- teuthology/test/fake_archive.py +0 -107
- teuthology/test/fake_fs.py +0 -92
- teuthology/test/integration/__init__.py +0 -0
- teuthology/test/integration/test_suite.py +0 -86
- teuthology/test/task/__init__.py +0 -205
- teuthology/test/task/test_ansible.py +0 -624
- teuthology/test/task/test_ceph_ansible.py +0 -176
- teuthology/test/task/test_console_log.py +0 -88
- teuthology/test/task/test_install.py +0 -337
- teuthology/test/task/test_internal.py +0 -57
- teuthology/test/task/test_kernel.py +0 -243
- teuthology/test/task/test_pcp.py +0 -379
- teuthology/test/task/test_selinux.py +0 -35
- teuthology/test/test_config.py +0 -189
- teuthology/test/test_contextutil.py +0 -68
- teuthology/test/test_describe_tests.py +0 -316
- teuthology/test/test_email_sleep_before_teardown.py +0 -81
- teuthology/test/test_exit.py +0 -97
- teuthology/test/test_get_distro.py +0 -47
- teuthology/test/test_get_distro_version.py +0 -47
- teuthology/test/test_get_multi_machine_types.py +0 -27
- teuthology/test/test_job_status.py +0 -60
- teuthology/test/test_ls.py +0 -48
- teuthology/test/test_misc.py +0 -368
- teuthology/test/test_nuke.py +0 -232
- teuthology/test/test_packaging.py +0 -763
- teuthology/test/test_parallel.py +0 -28
- teuthology/test/test_repo_utils.py +0 -204
- teuthology/test/test_report.py +0 -77
- teuthology/test/test_results.py +0 -155
- teuthology/test/test_run.py +0 -238
- teuthology/test/test_safepath.py +0 -55
- teuthology/test/test_schedule.py +0 -45
- teuthology/test/test_scrape.py +0 -167
- teuthology/test/test_timer.py +0 -80
- teuthology/test/test_vps_os_vers_parameter_checking.py +0 -84
- teuthology/test/test_worker.py +0 -303
- teuthology/worker.py +0 -339
- teuthology-1.0.0.dist-info/METADATA +0 -76
- teuthology-1.0.0.dist-info/RECORD +0 -210
- {teuthology-1.0.0.dist-info → teuthology-1.2.0.dist-info}/LICENSE +0 -0
- {teuthology-1.0.0.dist-info → teuthology-1.2.0.dist-info}/top_level.txt +0 -0
teuthology/orchestra/run.py
CHANGED
@@ -9,7 +9,7 @@ from paramiko import ChannelFile
|
|
9
9
|
import gevent
|
10
10
|
import gevent.event
|
11
11
|
import socket
|
12
|
-
import
|
12
|
+
import shlex
|
13
13
|
import logging
|
14
14
|
import shutil
|
15
15
|
|
@@ -90,7 +90,7 @@ class RemoteProcess(object):
|
|
90
90
|
Execute remote command
|
91
91
|
"""
|
92
92
|
for line in self.command.split('\n'):
|
93
|
-
log.getChild(self.hostname).
|
93
|
+
log.getChild(self.hostname).debug('%s> %s' % (self.label or '', line))
|
94
94
|
|
95
95
|
if hasattr(self, 'timeout'):
|
96
96
|
(self._stdin_buf, self._stdout_buf, self._stderr_buf) = \
|
@@ -114,7 +114,7 @@ class RemoteProcess(object):
|
|
114
114
|
# FIXME: Is this actually true?
|
115
115
|
raise RuntimeError(self.deadlock_warning % 'stdin')
|
116
116
|
|
117
|
-
def setup_output_stream(self, stream_obj, stream_name):
|
117
|
+
def setup_output_stream(self, stream_obj, stream_name, quiet=False):
|
118
118
|
if stream_obj is not PIPE:
|
119
119
|
# Log the stream
|
120
120
|
host_log = self.logger.getChild(self.hostname)
|
@@ -125,6 +125,7 @@ class RemoteProcess(object):
|
|
125
125
|
getattr(self, stream_name),
|
126
126
|
stream_log,
|
127
127
|
stream_obj,
|
128
|
+
quiet,
|
128
129
|
)
|
129
130
|
)
|
130
131
|
setattr(self, stream_name, stream_obj)
|
@@ -251,14 +252,14 @@ def quote(args):
|
|
251
252
|
if isinstance(a, Raw):
|
252
253
|
yield a.value
|
253
254
|
else:
|
254
|
-
yield
|
255
|
+
yield shlex.quote(a)
|
255
256
|
if isinstance(args, list):
|
256
257
|
return ' '.join(_quote(args))
|
257
258
|
else:
|
258
259
|
return args
|
259
260
|
|
260
261
|
|
261
|
-
def copy_to_log(f, logger, loglevel=logging.INFO, capture=None):
|
262
|
+
def copy_to_log(f, logger, loglevel=logging.INFO, capture=None, quiet=False):
|
262
263
|
"""
|
263
264
|
Copy line by line from file in f to the log from logger
|
264
265
|
|
@@ -266,6 +267,8 @@ def copy_to_log(f, logger, loglevel=logging.INFO, capture=None):
|
|
266
267
|
:param logger: the destination logger object
|
267
268
|
:param loglevel: the level of logging data
|
268
269
|
:param capture: an optional stream object for data copy
|
270
|
+
:param quiet: suppress `logger` usage if True, this is useful only
|
271
|
+
in combination with `capture`, defaults False
|
269
272
|
"""
|
270
273
|
# Work-around for http://tracker.ceph.com/issues/8313
|
271
274
|
if isinstance(f, ChannelFile):
|
@@ -284,6 +287,8 @@ def copy_to_log(f, logger, loglevel=logging.INFO, capture=None):
|
|
284
287
|
capture.write(line)
|
285
288
|
line = line.rstrip()
|
286
289
|
# Second part of work-around for http://tracker.ceph.com/issues/8313
|
290
|
+
if quiet:
|
291
|
+
continue
|
287
292
|
try:
|
288
293
|
if isinstance(line, bytes):
|
289
294
|
line = line.decode('utf-8', 'replace')
|
@@ -305,15 +310,17 @@ def copy_and_close(src, fdst):
|
|
305
310
|
fdst.close()
|
306
311
|
|
307
312
|
|
308
|
-
def copy_file_to(src, logger, stream=None):
|
313
|
+
def copy_file_to(src, logger, stream=None, quiet=False):
|
309
314
|
"""
|
310
315
|
Copy file
|
311
316
|
:param src: file to be copied.
|
312
317
|
:param logger: the logger object
|
313
|
-
:param stream: an optional file-like object which will receive
|
314
|
-
src.
|
318
|
+
:param stream: an optional file-like object which will receive
|
319
|
+
a copy of src.
|
320
|
+
:param quiet: disable logger usage if True, useful in combination
|
321
|
+
with `stream` parameter, defaults False.
|
315
322
|
"""
|
316
|
-
copy_to_log(src, logger, capture=stream)
|
323
|
+
copy_to_log(src, logger, capture=stream, quiet=quiet)
|
317
324
|
|
318
325
|
def spawn_asyncresult(fn, *args, **kwargs):
|
319
326
|
"""
|
@@ -384,6 +391,7 @@ def run(
|
|
384
391
|
wait=True,
|
385
392
|
name=None,
|
386
393
|
label=None,
|
394
|
+
quiet=False,
|
387
395
|
timeout=None,
|
388
396
|
cwd=None,
|
389
397
|
# omit_sudo is used by vstart_runner.py
|
@@ -392,7 +400,7 @@ def run(
|
|
392
400
|
"""
|
393
401
|
Run a command remotely. If any of 'args' contains shell metacharacters
|
394
402
|
that you want to pass unquoted, pass it as an instance of Raw(); otherwise
|
395
|
-
it will be quoted with
|
403
|
+
it will be quoted with shlex.quote() (single quote, and single quotes
|
396
404
|
enclosed in double quotes).
|
397
405
|
|
398
406
|
:param client: SSHConnection to run the command with
|
@@ -417,6 +425,7 @@ def run(
|
|
417
425
|
:param name: Human readable name (probably hostname) of the destination
|
418
426
|
host
|
419
427
|
:param label: Can be used to label or describe what the command is doing.
|
428
|
+
:param quiet: Do not log command's stdout and stderr, defaults False.
|
420
429
|
:param timeout: timeout value for args to complete on remote channel of
|
421
430
|
paramiko
|
422
431
|
:param cwd: Directory in which the command should be executed.
|
@@ -440,8 +449,8 @@ def run(
|
|
440
449
|
cwd=cwd)
|
441
450
|
r.execute()
|
442
451
|
r.setup_stdin(stdin)
|
443
|
-
r.setup_output_stream(stderr, 'stderr')
|
444
|
-
r.setup_output_stream(stdout, 'stdout')
|
452
|
+
r.setup_output_stream(stderr, 'stderr', quiet)
|
453
|
+
r.setup_output_stream(stdout, 'stdout', quiet)
|
445
454
|
if wait:
|
446
455
|
r.wait()
|
447
456
|
return r
|
teuthology/packaging.py
CHANGED
@@ -395,12 +395,12 @@ def _get_config_value_for_remote(ctx, remote, config, key):
|
|
395
395
|
|
396
396
|
config = {
|
397
397
|
'all':
|
398
|
-
{'branch': '
|
398
|
+
{'branch': 'main'},
|
399
399
|
'branch': 'next'
|
400
400
|
}
|
401
401
|
_get_config_value_for_remote(ctx, remote, config, 'branch')
|
402
402
|
|
403
|
-
would return '
|
403
|
+
would return 'main'.
|
404
404
|
|
405
405
|
:param ctx: the argparse.Namespace object
|
406
406
|
:param remote: the teuthology.orchestra.remote.Remote object
|
@@ -479,7 +479,7 @@ class GitbuilderProject(object):
|
|
479
479
|
)
|
480
480
|
# when we're initializing with a remote we most likely have
|
481
481
|
# a task config, not the entire teuthology job config
|
482
|
-
self.flavor = self.job_config.get("flavor", "
|
482
|
+
self.flavor = self.job_config.get("flavor", "default")
|
483
483
|
self.tag = self.job_config.get("tag")
|
484
484
|
|
485
485
|
def _init_from_config(self):
|
@@ -549,10 +549,6 @@ class GitbuilderProject(object):
|
|
549
549
|
"""
|
550
550
|
The base url that points at this project on gitbuilder.
|
551
551
|
|
552
|
-
For example::
|
553
|
-
|
554
|
-
http://gitbuilder.ceph.com/ceph-deb-raring-x86_64-basic/ref/master
|
555
|
-
|
556
552
|
:returns: A string of the base url for this project
|
557
553
|
"""
|
558
554
|
return self._get_base_url()
|
@@ -656,9 +652,9 @@ class GitbuilderProject(object):
|
|
656
652
|
remote, the sha1 from the config will be used.
|
657
653
|
|
658
654
|
If a tag, branch or sha1 can't be found it will default to use the
|
659
|
-
build from the
|
655
|
+
build from the main branch.
|
660
656
|
|
661
|
-
:returns: A string URI. Ex: ref/
|
657
|
+
:returns: A string URI. Ex: ref/main
|
662
658
|
"""
|
663
659
|
ref_name, ref_val = next(iter(self._choose_reference().items()))
|
664
660
|
if ref_name == 'sha1':
|
@@ -673,7 +669,7 @@ class GitbuilderProject(object):
|
|
673
669
|
Decide which to use.
|
674
670
|
|
675
671
|
:returns: a single-key dict containing the name and value of the
|
676
|
-
reference to use, e.g. {'branch': '
|
672
|
+
reference to use, e.g. {'branch': 'main'}
|
677
673
|
"""
|
678
674
|
tag = branch = sha1 = None
|
679
675
|
if self.remote:
|
@@ -716,8 +712,8 @@ class GitbuilderProject(object):
|
|
716
712
|
warn('sha1')
|
717
713
|
return dict(sha1=sha1)
|
718
714
|
else:
|
719
|
-
log.warning("defaulting to
|
720
|
-
return dict(branch='
|
715
|
+
log.warning("defaulting to main branch")
|
716
|
+
return dict(branch='main')
|
721
717
|
|
722
718
|
def _get_base_url(self):
|
723
719
|
"""
|
@@ -853,6 +849,9 @@ class ShamanProject(GitbuilderProject):
|
|
853
849
|
super(ShamanProject, self).__init__(project, job_config, ctx, remote)
|
854
850
|
self.query_url = 'https://%s/api/' % config.shaman_host
|
855
851
|
|
852
|
+
# Force to use the "noarch" instead to build the uri.
|
853
|
+
self.force_noarch = self.job_config.get("shaman", {}).get("force_noarch", False)
|
854
|
+
|
856
855
|
def _get_base_url(self):
|
857
856
|
self.assert_result()
|
858
857
|
return self._result.json()[0]['url']
|
@@ -876,13 +875,12 @@ class ShamanProject(GitbuilderProject):
|
|
876
875
|
@property
|
877
876
|
def _search_uri(self):
|
878
877
|
flavor = self.flavor
|
879
|
-
if flavor == 'basic':
|
880
|
-
flavor = 'default'
|
881
878
|
req_obj = OrderedDict()
|
882
879
|
req_obj['status'] = 'ready'
|
883
880
|
req_obj['project'] = self.project
|
884
881
|
req_obj['flavor'] = flavor
|
885
|
-
|
882
|
+
arch = "noarch" if self.force_noarch else self.arch
|
883
|
+
req_obj['distros'] = '%s/%s' % (self.distro, arch)
|
886
884
|
ref_name, ref_val = list(self._choose_reference().items())[0]
|
887
885
|
if ref_name == 'tag':
|
888
886
|
req_obj['sha1'] = self._sha1 = self._tag_to_sha1()
|
@@ -970,6 +968,47 @@ class ShamanProject(GitbuilderProject):
|
|
970
968
|
'repo',
|
971
969
|
)
|
972
970
|
|
971
|
+
@property
|
972
|
+
def build_complete(self):
|
973
|
+
# use the repo search results to get a ref and a sha1; the
|
974
|
+
# input to teuthology-suite doesn't contain both
|
975
|
+
try:
|
976
|
+
self.assert_result()
|
977
|
+
except VersionNotFoundError:
|
978
|
+
return False
|
979
|
+
|
980
|
+
# self._result has status, project, flavor, distros, arch, and sha1
|
981
|
+
# restrictions, so the only reason for multiples should be "multiple
|
982
|
+
# builds of the same sha1 etc."; the first entry is the newest
|
983
|
+
search_result = self._result.json()[0]
|
984
|
+
|
985
|
+
# now look for the build complete status
|
986
|
+
path = '/'.join(
|
987
|
+
('builds/ceph', search_result['ref'], search_result['sha1'])
|
988
|
+
)
|
989
|
+
build_url = urljoin(self.query_url, path)
|
990
|
+
|
991
|
+
try:
|
992
|
+
resp = requests.get(build_url)
|
993
|
+
resp.raise_for_status()
|
994
|
+
except requests.HttpError:
|
995
|
+
return False
|
996
|
+
log.debug(f'looking for {self.distro} {self.arch} {self.flavor}')
|
997
|
+
for build in resp.json():
|
998
|
+
log.debug(f'build: {build["distro"]}/{build["distro_version"]} {build["distro_arch"]} {build["flavor"]}')
|
999
|
+
if (
|
1000
|
+
# we must compare build arch to self.arch, since shaman's
|
1001
|
+
# results can have multiple arches but we're searching
|
1002
|
+
# for precisely one here
|
1003
|
+
build['distro'] == search_result['distro'] and
|
1004
|
+
build['distro_version'] == search_result['distro_version'] and
|
1005
|
+
build['flavor'] == search_result['flavor'] and
|
1006
|
+
build['distro_arch'] == self.arch and
|
1007
|
+
build['status'] == 'completed'
|
1008
|
+
):
|
1009
|
+
return True
|
1010
|
+
return False
|
1011
|
+
|
973
1012
|
def _get_repo(self):
|
974
1013
|
resp = requests.get(self.repo_url)
|
975
1014
|
resp.raise_for_status()
|
teuthology/provision/__init__.py
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
import logging
|
2
|
+
import os
|
2
3
|
|
4
|
+
import teuthology.exporter
|
3
5
|
import teuthology.lock.query
|
4
6
|
from teuthology.misc import decanonicalize_hostname, get_distro, get_distro_version
|
5
7
|
|
@@ -8,19 +10,19 @@ from teuthology.provision import downburst
|
|
8
10
|
from teuthology.provision import fog
|
9
11
|
from teuthology.provision import openstack
|
10
12
|
from teuthology.provision import pelagos
|
11
|
-
import os
|
12
13
|
|
13
14
|
log = logging.getLogger(__name__)
|
14
15
|
|
15
16
|
|
16
|
-
def _logfile(
|
17
|
-
if
|
18
|
-
return
|
19
|
-
|
17
|
+
def _logfile(shortname: str, archive_path: str = ""):
|
18
|
+
if os.path.isfile(archive_path):
|
19
|
+
return f"{archive_path}/{shortname}.downburst.log"
|
20
|
+
|
20
21
|
|
21
22
|
def get_reimage_types():
|
22
23
|
return pelagos.get_types() + fog.get_types()
|
23
24
|
|
25
|
+
|
24
26
|
def reimage(ctx, machine_name, machine_type):
|
25
27
|
os_type = get_distro(ctx)
|
26
28
|
os_version = get_distro_version(ctx)
|
@@ -36,7 +38,21 @@ def reimage(ctx, machine_name, machine_type):
|
|
36
38
|
else:
|
37
39
|
raise Exception("The machine_type '%s' is not known to any "
|
38
40
|
"of configured provisioners" % machine_type)
|
39
|
-
|
41
|
+
status = "fail"
|
42
|
+
try:
|
43
|
+
result = obj.create()
|
44
|
+
status = "success"
|
45
|
+
except Exception:
|
46
|
+
# We only need this clause so that we avoid triggering the finally
|
47
|
+
# clause below in cases where the exception raised is KeyboardInterrupt
|
48
|
+
# or SystemExit
|
49
|
+
raise
|
50
|
+
finally:
|
51
|
+
teuthology.exporter.NodeReimagingResults().record(
|
52
|
+
machine_type=machine_type,
|
53
|
+
status=status,
|
54
|
+
)
|
55
|
+
return result
|
40
56
|
|
41
57
|
|
42
58
|
def create_if_vm(ctx, machine_name, _downburst=None):
|
@@ -78,8 +94,12 @@ def create_if_vm(ctx, machine_name, _downburst=None):
|
|
78
94
|
return dbrst.create()
|
79
95
|
|
80
96
|
|
81
|
-
def destroy_if_vm(
|
82
|
-
|
97
|
+
def destroy_if_vm(
|
98
|
+
machine_name: str,
|
99
|
+
user: str = "",
|
100
|
+
description: str = "",
|
101
|
+
_downburst=None
|
102
|
+
):
|
83
103
|
"""
|
84
104
|
Use downburst to destroy a virtual machine
|
85
105
|
|
@@ -99,7 +119,7 @@ def destroy_if_vm(ctx, machine_name, user=None, description=None,
|
|
99
119
|
log.error(msg.format(node=machine_name, as_user=user,
|
100
120
|
locked_by=status_info['locked_by']))
|
101
121
|
return False
|
102
|
-
if (description
|
122
|
+
if (description and description !=
|
103
123
|
status_info['description']):
|
104
124
|
msg = "Tried to destroy {node} with description {desc_arg} " + \
|
105
125
|
"but it is locked with description {desc_lock}"
|
@@ -117,5 +137,5 @@ def destroy_if_vm(ctx, machine_name, user=None, description=None,
|
|
117
137
|
dbrst = _downburst or \
|
118
138
|
downburst.Downburst(name=machine_name, os_type=None,
|
119
139
|
os_version=None, status=status_info,
|
120
|
-
logfile=_logfile(
|
140
|
+
logfile=_logfile(description, shortname))
|
121
141
|
return dbrst.destroy()
|
@@ -84,7 +84,13 @@ class OpenStackProvider(Provider):
|
|
84
84
|
@property
|
85
85
|
def images(self):
|
86
86
|
if not hasattr(self, '_images'):
|
87
|
-
|
87
|
+
exclude_image = self.conf.get('exclude_image', [])
|
88
|
+
if exclude_image and not isinstance(exclude_image, list):
|
89
|
+
exclude_image = [exclude_image]
|
90
|
+
exclude_re = [re.compile(x) for x in exclude_image]
|
91
|
+
images = retry(self.driver.list_images)
|
92
|
+
self._images = [_ for _ in images
|
93
|
+
if not any(x.match(_.name) for x in exclude_re)]
|
88
94
|
return self._images
|
89
95
|
|
90
96
|
@property
|
@@ -126,7 +132,7 @@ class OpenStackProvider(Provider):
|
|
126
132
|
else:
|
127
133
|
self._networks = list()
|
128
134
|
except AttributeError:
|
129
|
-
log.
|
135
|
+
log.warning("Unable to list networks for %s", self.driver)
|
130
136
|
self._networks = list()
|
131
137
|
return self._networks
|
132
138
|
|
@@ -144,7 +150,7 @@ class OpenStackProvider(Provider):
|
|
144
150
|
self.driver.ex_list_security_groups
|
145
151
|
)
|
146
152
|
except AttributeError:
|
147
|
-
log.
|
153
|
+
log.warning("Unable to list security groups for %s", self.driver)
|
148
154
|
self._security_groups = list()
|
149
155
|
return self._security_groups
|
150
156
|
|
@@ -420,7 +426,7 @@ class OpenStackProvisioner(base.Provisioner):
|
|
420
426
|
msg = "Unknown error locating %s"
|
421
427
|
if not matches:
|
422
428
|
msg = "No nodes found with name '%s'" % self.name
|
423
|
-
log.
|
429
|
+
log.warning(msg)
|
424
430
|
return
|
425
431
|
elif len(matches) > 1:
|
426
432
|
msg = "More than one node found with name '%s'"
|
@@ -438,9 +444,9 @@ class OpenStackProvisioner(base.Provisioner):
|
|
438
444
|
self._destroy_volumes()
|
439
445
|
nodes = self._find_nodes()
|
440
446
|
if not nodes:
|
441
|
-
log.
|
447
|
+
log.warning("Didn't find any nodes named '%s' to destroy!", self.name)
|
442
448
|
return True
|
443
449
|
if len(nodes) > 1:
|
444
|
-
log.
|
450
|
+
log.warning("Found multiple nodes named '%s' to destroy!", self.name)
|
445
451
|
log.info("Destroying nodes: %s", nodes)
|
446
452
|
return all([node.destroy() for node in nodes])
|
@@ -1,5 +1,4 @@
|
|
1
1
|
import datetime
|
2
|
-
import dateutil.tz
|
3
2
|
import dateutil.parser
|
4
3
|
import json
|
5
4
|
import os
|
@@ -103,7 +102,7 @@ class AuthToken(object):
|
|
103
102
|
def expired(self):
|
104
103
|
if self.expires is None:
|
105
104
|
return True
|
106
|
-
utcnow = datetime.datetime.now(
|
105
|
+
utcnow = datetime.datetime.now(datetime.timezone.utc)
|
107
106
|
offset = datetime.timedelta(minutes=30)
|
108
107
|
return self.expires < (utcnow + offset)
|
109
108
|
|
@@ -8,6 +8,7 @@ import yaml
|
|
8
8
|
from teuthology.config import config
|
9
9
|
from teuthology.contextutil import safe_while
|
10
10
|
from teuthology.misc import decanonicalize_hostname
|
11
|
+
from teuthology.misc import deep_merge
|
11
12
|
from teuthology.lock import query
|
12
13
|
|
13
14
|
log = logging.getLogger(__name__)
|
@@ -20,7 +21,11 @@ def downburst_executable():
|
|
20
21
|
Return '' if no executable downburst is found.
|
21
22
|
"""
|
22
23
|
if config.downburst:
|
23
|
-
|
24
|
+
if isinstance(config.downburst, dict):
|
25
|
+
if 'path' in config.downburst:
|
26
|
+
return config.downburst['path']
|
27
|
+
else:
|
28
|
+
return config.downburst
|
24
29
|
path = os.environ.get('PATH', None)
|
25
30
|
if path:
|
26
31
|
for p in os.environ.get('PATH', '').split(os.pathsep):
|
@@ -37,6 +42,18 @@ def downburst_executable():
|
|
37
42
|
return ''
|
38
43
|
|
39
44
|
|
45
|
+
def downburst_environment():
|
46
|
+
env = dict()
|
47
|
+
env['PATH'] = os.environ.get('PATH')
|
48
|
+
discover_url = os.environ.get('DOWNBURST_DISCOVER_URL')
|
49
|
+
if config.downburst and not discover_url:
|
50
|
+
if isinstance(config.downburst, dict):
|
51
|
+
discover_url = config.downburst.get('discover_url')
|
52
|
+
if discover_url:
|
53
|
+
env['DOWNBURST_DISCOVER_URL'] = discover_url
|
54
|
+
return env
|
55
|
+
|
56
|
+
|
40
57
|
class Downburst(object):
|
41
58
|
"""
|
42
59
|
A class that provides methods for creating and destroying virtual machine
|
@@ -55,6 +72,7 @@ class Downburst(object):
|
|
55
72
|
self.logfile = logfile
|
56
73
|
self.host = decanonicalize_hostname(self.status['vm_host']['name'])
|
57
74
|
self.executable = downburst_executable()
|
75
|
+
self.environment = downburst_environment()
|
58
76
|
|
59
77
|
def create(self):
|
60
78
|
"""
|
@@ -118,7 +136,10 @@ class Downburst(object):
|
|
118
136
|
distro=self.os_type,
|
119
137
|
distroversion=self.os_version
|
120
138
|
))
|
121
|
-
|
139
|
+
log.debug(args)
|
140
|
+
proc = subprocess.Popen(args, universal_newlines=True,
|
141
|
+
env=self.environment,
|
142
|
+
stdout=subprocess.PIPE,
|
122
143
|
stderr=subprocess.PIPE)
|
123
144
|
out, err = proc.communicate()
|
124
145
|
return (proc.returncode, out, err)
|
@@ -135,7 +156,9 @@ class Downburst(object):
|
|
135
156
|
if self.logfile:
|
136
157
|
args.extend(['-l', self.logfile])
|
137
158
|
args.extend(['destroy', self.shortname])
|
138
|
-
|
159
|
+
log.debug(args)
|
160
|
+
proc = subprocess.Popen(args, universal_newlines=True,
|
161
|
+
stdout=subprocess.PIPE,
|
139
162
|
stderr=subprocess.PIPE,)
|
140
163
|
out, err = proc.communicate()
|
141
164
|
log.info(out)
|
@@ -143,7 +166,7 @@ class Downburst(object):
|
|
143
166
|
if proc.returncode != 0:
|
144
167
|
not_found_msg = "no domain with matching name '%s'" % self.shortname
|
145
168
|
if not_found_msg in err:
|
146
|
-
log.
|
169
|
+
log.warning("Ignoring error during destroy: %s", err)
|
147
170
|
return True
|
148
171
|
log.error("Error destroying %s: %s", self.name, err)
|
149
172
|
return False
|
@@ -156,24 +179,44 @@ class Downburst(object):
|
|
156
179
|
"""
|
157
180
|
Assemble a configuration to pass to downburst, and write it to a file.
|
158
181
|
"""
|
159
|
-
config_fd = tempfile.NamedTemporaryFile(delete=False)
|
182
|
+
config_fd = tempfile.NamedTemporaryFile(delete=False, mode='wt')
|
160
183
|
|
161
184
|
os_type = self.os_type.lower()
|
162
|
-
|
185
|
+
os_version = self.os_version.lower()
|
163
186
|
|
187
|
+
mac_address = self.status['mac_address']
|
188
|
+
defaults = dict(
|
189
|
+
downburst=dict(
|
190
|
+
machine=dict(
|
191
|
+
disk=os.environ.get('DOWNBURST_DISK_SIZE', '100G'),
|
192
|
+
ram=os.environ.get('DOWNBURST_RAM_SIZE', '3.8G'),
|
193
|
+
cpus=int(os.environ.get('DOWNBURST_CPUS', 1)),
|
194
|
+
volumes=dict(
|
195
|
+
count=int(os.environ.get('DOWNBURST_EXTRA_DISK_NUMBER', 4)),
|
196
|
+
size=os.environ.get('DOWNBURST_EXTRA_DISK_SIZE', '100G'),
|
197
|
+
),
|
198
|
+
),
|
199
|
+
)
|
200
|
+
)
|
201
|
+
downburst_config = defaults['downburst']
|
202
|
+
if config.downburst and isinstance(config.downburst, dict):
|
203
|
+
deep_merge(downburst_config, config.downburst)
|
204
|
+
log.debug('downburst_config: %s', downburst_config)
|
205
|
+
machine = downburst_config['machine']
|
206
|
+
log.debug('Using machine config: %s', machine)
|
164
207
|
file_info = {
|
165
|
-
'disk-size': '
|
166
|
-
'ram': '
|
167
|
-
'cpus':
|
208
|
+
'disk-size': machine['disk'],
|
209
|
+
'ram': machine['ram'],
|
210
|
+
'cpus': machine['cpus'],
|
168
211
|
'networks': [
|
169
212
|
{'source': 'front', 'mac': mac_address}],
|
170
213
|
'distro': os_type,
|
171
214
|
'distroversion': self.os_version,
|
172
|
-
'additional-disks':
|
173
|
-
'additional-disks-size': '
|
215
|
+
'additional-disks': machine['volumes']['count'],
|
216
|
+
'additional-disks-size': machine['volumes']['size'],
|
174
217
|
'arch': 'x86_64',
|
175
218
|
}
|
176
|
-
fqdn = self.name.split('@')[1]
|
219
|
+
fqdn = self.name.split('@')[-1]
|
177
220
|
file_out = {
|
178
221
|
'downburst': file_info,
|
179
222
|
'local-hostname': fqdn,
|
@@ -188,6 +231,12 @@ class Downburst(object):
|
|
188
231
|
['passwd', '-d', self.user],
|
189
232
|
]
|
190
233
|
}
|
234
|
+
# for opensuse-15.2 we need to replace systemd-logger with rsyslog for teuthology
|
235
|
+
if os_type == 'opensuse' and os_version == '15.2':
|
236
|
+
user_info['runcmd'].extend([
|
237
|
+
['zypper', 'rm', '-y', 'systemd-logger'],
|
238
|
+
['zypper', 'in', '-y', 'rsyslog'],
|
239
|
+
])
|
191
240
|
# Install git on downbursted VMs to clone upstream linux-firmware.
|
192
241
|
# Issue #17154
|
193
242
|
if 'packages' not in user_info:
|
@@ -208,7 +257,7 @@ class Downburst(object):
|
|
208
257
|
# to install 'python' to get python2.7, which ansible needs
|
209
258
|
if os_type in ('ubuntu', 'fedora'):
|
210
259
|
user_info['packages'].append('python')
|
211
|
-
user_fd = tempfile.NamedTemporaryFile(delete=False)
|
260
|
+
user_fd = tempfile.NamedTemporaryFile(delete=False, mode='wt')
|
212
261
|
user_str = "#cloud-config\n" + yaml.safe_dump(user_info)
|
213
262
|
user_fd.write(user_str)
|
214
263
|
self.user_path = user_fd.name
|
@@ -240,27 +289,32 @@ def get_distro_from_downburst():
|
|
240
289
|
or if downburst is unable to produce a json list, then use a default
|
241
290
|
table.
|
242
291
|
"""
|
243
|
-
default_table = {
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
292
|
+
default_table = {'rhel_minimal': ['6.4', '6.5'],
|
293
|
+
'fedora': ['17', '18', '19', '20', '22'],
|
294
|
+
'centos': ['6.3', '6.4', '6.5', '7.0',
|
295
|
+
'7.2', '7.4', '8.2'],
|
296
|
+
'centos_minimal': ['6.4', '6.5'],
|
297
|
+
'ubuntu': ['8.04(hardy)', '9.10(karmic)',
|
298
|
+
'10.04(lucid)', '10.10(maverick)',
|
299
|
+
'11.04(natty)', '11.10(oneiric)',
|
300
|
+
'12.04(precise)', '12.10(quantal)',
|
301
|
+
'13.04(raring)', '13.10(saucy)',
|
302
|
+
'14.04(trusty)', 'utopic(utopic)',
|
303
|
+
'16.04(xenial)', '18.04(bionic)',
|
304
|
+
'20.04(focal)'],
|
305
|
+
'sles': ['12-sp3', '15-sp1', '15-sp2'],
|
306
|
+
'opensuse': ['12.3', '15.1', '15.2'],
|
307
|
+
'debian': ['6.0', '7.0', '8.0']}
|
257
308
|
executable_cmd = downburst_executable()
|
309
|
+
environment_dict = downburst_environment()
|
258
310
|
if not executable_cmd:
|
259
|
-
log.
|
311
|
+
log.warning("Downburst not found!")
|
260
312
|
log.info('Using default values for supported os_type/os_version')
|
261
313
|
return default_table
|
262
314
|
try:
|
263
|
-
|
315
|
+
log.debug(executable_cmd)
|
316
|
+
output = subprocess.check_output([executable_cmd, 'list-json'],
|
317
|
+
env=environment_dict)
|
264
318
|
downburst_data = json.loads(output)
|
265
319
|
return downburst_data
|
266
320
|
except (subprocess.CalledProcessError, OSError):
|