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
@@ -0,0 +1,263 @@
|
|
1
|
+
#!/bin/python3
|
2
|
+
|
3
|
+
# Forward stdin to a subcommand. If EOF is read from stdin or
|
4
|
+
# stdin/stdout/stderr are closed or hungup, then give the command "timeout"
|
5
|
+
# seconds to complete before it is killed.
|
6
|
+
#
|
7
|
+
# The command is run in a separate process group. This is mostly to simplify
|
8
|
+
# killing the set of processes (if well-behaving). You can configure that with
|
9
|
+
# --setpgrp switch.
|
10
|
+
|
11
|
+
# usage: stdin-killer [-h] [--timeout TIMEOUT] [--debug DEBUG] [--signal SIGNAL] [--verbose] [--setpgrp {no,self,child}] command [arguments ...]
|
12
|
+
#
|
13
|
+
# wait for stdin EOF then kill forked subcommand
|
14
|
+
#
|
15
|
+
# positional arguments:
|
16
|
+
# command command to execute
|
17
|
+
# arguments arguments to command
|
18
|
+
#
|
19
|
+
# options:
|
20
|
+
# -h, --help show this help message and exit
|
21
|
+
# --timeout TIMEOUT time to wait for forked subcommand to willing terminate
|
22
|
+
# --debug DEBUG debug file
|
23
|
+
# --signal SIGNAL signal to send
|
24
|
+
# --verbose increase debugging
|
25
|
+
# --setpgrp {no,self,child}
|
26
|
+
# create process group
|
27
|
+
|
28
|
+
|
29
|
+
import argparse
|
30
|
+
import fcntl
|
31
|
+
import logging
|
32
|
+
import os
|
33
|
+
import select
|
34
|
+
import signal
|
35
|
+
import struct
|
36
|
+
import subprocess
|
37
|
+
import sys
|
38
|
+
import time
|
39
|
+
|
40
|
+
NAME = "stdin-killer"
|
41
|
+
|
42
|
+
log = logging.getLogger(NAME)
|
43
|
+
PAGE_SIZE = 4096
|
44
|
+
|
45
|
+
POLL_HANGUP = select.POLLHUP | (select.POLLRDHUP if hasattr(select, 'POLLRDHUP') else 0) | select.POLLERR
|
46
|
+
|
47
|
+
|
48
|
+
def handle_event(poll, buffer, fd, event, p):
|
49
|
+
if sigfdr == fd:
|
50
|
+
b = os.read(sigfdr, 1)
|
51
|
+
(signum,) = struct.unpack("B", b)
|
52
|
+
log.debug("got signal %d", signum)
|
53
|
+
try:
|
54
|
+
p.wait(timeout=0)
|
55
|
+
return True
|
56
|
+
except subprocess.TimeoutExpired:
|
57
|
+
pass
|
58
|
+
elif 0 == fd:
|
59
|
+
if event & POLL_HANGUP:
|
60
|
+
log.debug("peer closed connection, waiting for process exit")
|
61
|
+
poll.unregister(0)
|
62
|
+
sys.stdin.close()
|
63
|
+
if len(buffer) == 0 and p.stdin is not None:
|
64
|
+
p.stdin.close()
|
65
|
+
p.stdin = None
|
66
|
+
return True
|
67
|
+
elif event & select.POLLIN:
|
68
|
+
b = os.read(0, PAGE_SIZE)
|
69
|
+
if b == b"":
|
70
|
+
log.debug("read EOF")
|
71
|
+
poll.unregister(0)
|
72
|
+
sys.stdin.close()
|
73
|
+
if len(buffer) == 0:
|
74
|
+
p.stdin.close()
|
75
|
+
return True
|
76
|
+
if p.stdin is not None:
|
77
|
+
buffer += b
|
78
|
+
# ignore further POLLIN until buffer is written to p.stdin
|
79
|
+
poll.register(0, POLL_HANGUP)
|
80
|
+
poll.register(p.stdin.fileno(), select.POLLOUT)
|
81
|
+
elif p.stdin is not None and p.stdin.fileno() == fd:
|
82
|
+
assert event & select.POLLOUT
|
83
|
+
b = buffer[:PAGE_SIZE]
|
84
|
+
log.debug("sending %d bytes to process", len(b))
|
85
|
+
try:
|
86
|
+
n = p.stdin.write(b)
|
87
|
+
p.stdin.flush()
|
88
|
+
log.debug("wrote %d bytes", n)
|
89
|
+
buffer = buffer[n:]
|
90
|
+
poll.register(0, select.POLLIN | POLL_HANGUP)
|
91
|
+
poll.unregister(p.stdin.fileno())
|
92
|
+
except BrokenPipeError:
|
93
|
+
log.debug("got SIGPIPE")
|
94
|
+
poll.unregister(p.stdin.fileno())
|
95
|
+
p.stdin.close()
|
96
|
+
p.stdin = None
|
97
|
+
return True
|
98
|
+
except BlockingIOError:
|
99
|
+
poll.register(p.stdin.fileno(), select.POLLOUT | POLL_HANGUP)
|
100
|
+
elif 1 == fd:
|
101
|
+
assert event & POLL_HANGUP
|
102
|
+
log.debug("stdout pipe has closed")
|
103
|
+
poll.unregister(1)
|
104
|
+
return True
|
105
|
+
elif 2 == fd:
|
106
|
+
assert event & POLL_HANGUP
|
107
|
+
log.debug("stderr pipe has closed")
|
108
|
+
poll.unregister(2)
|
109
|
+
return True
|
110
|
+
else:
|
111
|
+
assert False
|
112
|
+
return False
|
113
|
+
|
114
|
+
|
115
|
+
def listen_for_events(sigfdr, p, timeout):
|
116
|
+
poll = select.poll()
|
117
|
+
# listen for data on stdin
|
118
|
+
poll.register(0, select.POLLIN | POLL_HANGUP)
|
119
|
+
# listen for stdout/stderr to be closed, if they are closed then my parent
|
120
|
+
# is gone and I should expire the command and myself.
|
121
|
+
poll.register(1, POLL_HANGUP)
|
122
|
+
poll.register(2, POLL_HANGUP)
|
123
|
+
# for SIGCHLD
|
124
|
+
poll.register(sigfdr, select.POLLIN)
|
125
|
+
buffer = bytearray()
|
126
|
+
expired = 0.0
|
127
|
+
while True:
|
128
|
+
if expired > 0.0:
|
129
|
+
since = time.monotonic() - expired
|
130
|
+
wait = int((timeout - since) * 1000.0)
|
131
|
+
if wait <= 0:
|
132
|
+
return
|
133
|
+
else:
|
134
|
+
wait = 5000
|
135
|
+
log.debug("polling for %d milliseconds", wait)
|
136
|
+
events = poll.poll(wait)
|
137
|
+
for fd, event in events:
|
138
|
+
log.debug("event: (%d, %d)", fd, event)
|
139
|
+
if handle_event(poll, buffer, fd, event, p):
|
140
|
+
if p.returncode is not None:
|
141
|
+
return
|
142
|
+
if expired == 0.0:
|
143
|
+
expired = time.monotonic()
|
144
|
+
log.info(
|
145
|
+
"expiration expected; waiting %d seconds for command to complete",
|
146
|
+
NS.timeout,
|
147
|
+
)
|
148
|
+
|
149
|
+
|
150
|
+
if __name__ == "__main__":
|
151
|
+
signal.signal(signal.SIGPIPE, signal.SIG_IGN)
|
152
|
+
try:
|
153
|
+
(sigfdr, sigfdw) = os.pipe2(os.O_NONBLOCK | os.O_CLOEXEC)
|
154
|
+
except AttributeError:
|
155
|
+
# pipe2 is only available on "some flavors of Unix"
|
156
|
+
# https://docs.python.org/3.10/library/os.html?highlight=pipe2#os.pipe2
|
157
|
+
pipe_ends = os.pipe()
|
158
|
+
for fd in pipe_ends:
|
159
|
+
flags = fcntl.fcntl(fd, fcntl.F_GETFL)
|
160
|
+
fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK | os.O_CLOEXEC)
|
161
|
+
(sigfdr, sigfdw) = pipe_ends
|
162
|
+
|
163
|
+
signal.set_wakeup_fd(sigfdw)
|
164
|
+
|
165
|
+
def do_nothing(signum, frame):
|
166
|
+
pass
|
167
|
+
|
168
|
+
signal.signal(signal.SIGCHLD, do_nothing)
|
169
|
+
|
170
|
+
P = argparse.ArgumentParser(
|
171
|
+
description="wait for stdin EOF then kill forked subcommand"
|
172
|
+
)
|
173
|
+
P.add_argument(
|
174
|
+
"--timeout",
|
175
|
+
action="store",
|
176
|
+
default=5,
|
177
|
+
help="time to wait for forked subcommand to willing terminate",
|
178
|
+
type=int,
|
179
|
+
)
|
180
|
+
P.add_argument("--debug", action="store", help="debug file", type=str)
|
181
|
+
P.add_argument(
|
182
|
+
"--signal",
|
183
|
+
action="store",
|
184
|
+
help="signal to send",
|
185
|
+
type=int,
|
186
|
+
default=signal.SIGKILL,
|
187
|
+
)
|
188
|
+
P.add_argument("--verbose", action="store_true", help="increase debugging")
|
189
|
+
P.add_argument(
|
190
|
+
"--setpgrp",
|
191
|
+
action="store",
|
192
|
+
choices=["no", "self", "child"],
|
193
|
+
default="self",
|
194
|
+
help="create process group",
|
195
|
+
)
|
196
|
+
P.add_argument(
|
197
|
+
"cmd", metavar="command", type=str, nargs=1, help="command to execute"
|
198
|
+
)
|
199
|
+
P.add_argument(
|
200
|
+
"args", metavar="arguments", type=str, nargs="*", help="arguments to command"
|
201
|
+
)
|
202
|
+
NS = P.parse_args()
|
203
|
+
|
204
|
+
logargs = {}
|
205
|
+
if NS.debug is not None:
|
206
|
+
logargs["filename"] = NS.debug
|
207
|
+
else:
|
208
|
+
logargs["stream"] = sys.stderr
|
209
|
+
if NS.verbose:
|
210
|
+
logargs["level"] = logging.DEBUG
|
211
|
+
else:
|
212
|
+
logargs["level"] = logging.INFO
|
213
|
+
logargs["format"] = f"%(asctime)s {NAME} %(levelname)s: %(message)s"
|
214
|
+
logargs["datefmt"] = "%Y-%m-%dT%H:%M:%S"
|
215
|
+
logging.basicConfig(**logargs)
|
216
|
+
|
217
|
+
cargs = NS.cmd + NS.args
|
218
|
+
popen_kwargs = {
|
219
|
+
"stdin": subprocess.PIPE,
|
220
|
+
}
|
221
|
+
|
222
|
+
if NS.setpgrp == "self":
|
223
|
+
pgrp = os.getpgrp()
|
224
|
+
if pgrp != os.getpid():
|
225
|
+
os.setpgrp()
|
226
|
+
pgrp = os.getpgrp()
|
227
|
+
elif NS.setpgrp == "child":
|
228
|
+
popen_kwargs["preexec_fn"] = os.setpgrp
|
229
|
+
pgrp = None
|
230
|
+
elif NS.setpgrp == "no":
|
231
|
+
pgrp = 0
|
232
|
+
else:
|
233
|
+
assert False
|
234
|
+
|
235
|
+
log.debug("executing %s", cargs)
|
236
|
+
p = subprocess.Popen(cargs, **popen_kwargs)
|
237
|
+
if pgrp is None:
|
238
|
+
pgrp = p.pid
|
239
|
+
flags = fcntl.fcntl(p.stdin.fileno(), fcntl.F_GETFL)
|
240
|
+
fcntl.fcntl(p.stdin.fileno(), fcntl.F_SETFL, flags | os.O_NONBLOCK)
|
241
|
+
|
242
|
+
listen_for_events(sigfdr, p, NS.timeout)
|
243
|
+
|
244
|
+
if p.returncode is None:
|
245
|
+
log.error("timeout expired: sending signal %d to command and myself", NS.signal)
|
246
|
+
if pgrp == 0:
|
247
|
+
os.kill(p.pid, NS.signal)
|
248
|
+
else:
|
249
|
+
os.killpg(pgrp, NS.signal) # should kill me too
|
250
|
+
os.kill(os.getpid(), NS.signal) # to exit abnormally with same signal
|
251
|
+
log.error("signal did not cause termination, sending myself SIGKILL")
|
252
|
+
os.kill(os.getpid(), signal.SIGKILL) # failsafe
|
253
|
+
rc = p.returncode
|
254
|
+
log.debug("rc = %d", rc)
|
255
|
+
assert rc is not None
|
256
|
+
if rc < 0:
|
257
|
+
log.error("command terminated with signal %d: sending same signal to myself!", -rc)
|
258
|
+
os.kill(os.getpid(), -rc) # kill myself with the same signal
|
259
|
+
log.error("signal did not cause termination, sending myself SIGKILL")
|
260
|
+
os.kill(os.getpid(), signal.SIGKILL) # failsafe
|
261
|
+
else:
|
262
|
+
log.info("command exited with status %d: exiting normally with same code!", rc)
|
263
|
+
sys.exit(rc)
|
teuthology/task/install/deb.py
CHANGED
@@ -4,12 +4,32 @@ import os
|
|
4
4
|
from io import StringIO
|
5
5
|
|
6
6
|
from teuthology.orchestra import run
|
7
|
+
from teuthology.contextutil import safe_while
|
7
8
|
|
8
9
|
from teuthology.task.install.util import _get_builder_project, _get_local_dir
|
9
10
|
|
10
11
|
|
11
12
|
log = logging.getLogger(__name__)
|
12
13
|
|
14
|
+
def _retry_if_eagain_in_output(remote, args):
|
15
|
+
# wait at most 5 minutes
|
16
|
+
with safe_while(sleep=10, tries=30) as proceed:
|
17
|
+
while proceed():
|
18
|
+
stderr = StringIO()
|
19
|
+
try:
|
20
|
+
return remote.run(args=args, stderr=stderr)
|
21
|
+
except run.CommandFailedError:
|
22
|
+
if "could not get lock" in stderr.getvalue().lower():
|
23
|
+
stdout = StringIO()
|
24
|
+
args = ['sudo', 'fuser', '-v', '/var/lib/dpkg/lock-frontend']
|
25
|
+
remote.run(args=args, stdout=stdout)
|
26
|
+
log.info("The processes holding 'lock-frontend':\n{}".format(stdout.getvalue()))
|
27
|
+
continue
|
28
|
+
else:
|
29
|
+
raise
|
30
|
+
|
31
|
+
def install_dep_packages(remote, args):
|
32
|
+
_retry_if_eagain_in_output(remote, args)
|
13
33
|
|
14
34
|
def _update_package_list_and_install(ctx, remote, debs, config):
|
15
35
|
"""
|
@@ -71,11 +91,11 @@ def _update_package_list_and_install(ctx, remote, debs, config):
|
|
71
91
|
'Dpkg::Options::="--force-confold"'),
|
72
92
|
'install',
|
73
93
|
]
|
74
|
-
remote
|
94
|
+
install_dep_packages(remote,
|
75
95
|
args=install_cmd + ['%s=%s' % (d, version) for d in debs],
|
76
96
|
)
|
77
97
|
if system_pkglist:
|
78
|
-
remote
|
98
|
+
install_dep_packages(remote,
|
79
99
|
args=install_cmd + system_pkglist,
|
80
100
|
)
|
81
101
|
ldir = _get_local_dir(config, remote)
|
@@ -122,7 +142,7 @@ def _remove(ctx, config, remote, debs):
|
|
122
142
|
run.Raw('|'),
|
123
143
|
# Any package that is unpacked or half-installed and also requires
|
124
144
|
# reinstallation
|
125
|
-
'grep', '^.\(U\|H\)R',
|
145
|
+
'grep', r'^.\(U\|H\)R',
|
126
146
|
run.Raw('|'),
|
127
147
|
'awk', '{print $2}',
|
128
148
|
run.Raw('|'),
|
@@ -195,7 +215,7 @@ def _upgrade_packages(ctx, config, remote, debs):
|
|
195
215
|
builder.install_repo()
|
196
216
|
|
197
217
|
remote.run(args=['sudo', 'apt-get', 'update'], check_status=False)
|
198
|
-
remote
|
218
|
+
install_dep_packages(remote,
|
199
219
|
args=[
|
200
220
|
'sudo',
|
201
221
|
'DEBIAN_FRONTEND=noninteractive', 'apt-get', '-y', '--force-yes',
|
@@ -6,6 +6,7 @@ import os
|
|
6
6
|
from teuthology import packaging
|
7
7
|
from teuthology.orchestra import run
|
8
8
|
from teuthology.parallel import parallel
|
9
|
+
from teuthology.config import config as teuth_config
|
9
10
|
|
10
11
|
log = logging.getLogger(__name__)
|
11
12
|
|
@@ -40,28 +41,21 @@ def install(ctx, config):
|
|
40
41
|
- ceph-osd
|
41
42
|
- ceph-mds
|
42
43
|
"""
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
yaml_path = ds_yaml
|
53
|
-
# default to user home dir if one exists
|
54
|
-
default_yaml = os.path.expanduser('~/downstream.yaml')
|
55
|
-
if os.path.exists(default_yaml):
|
56
|
-
yaml_path = default_yaml
|
57
|
-
log.info("using yaml path %s", yaml_path)
|
58
|
-
downstream_config = yaml.safe_load(open(yaml_path))
|
44
|
+
# Look for rh specific packages
|
45
|
+
ds_yaml = os.path.join(
|
46
|
+
teuth_config.get('ds_yaml_dir'),
|
47
|
+
config.get('rhbuild') + ".yaml",
|
48
|
+
)
|
49
|
+
if not os.path.exists(ds_yaml):
|
50
|
+
raise FileNotFoundError(f'Downstream rh version yaml file missing: {ds_yaml}')
|
51
|
+
log.info("using yaml path %s", ds_yaml)
|
52
|
+
downstream_config = yaml.safe_load(open(ds_yaml))
|
59
53
|
rh_versions = downstream_config.get('versions', dict()).get('supported', [])
|
60
|
-
external_config = dict(extra_system_packages=config.get('extra_system_packages'
|
61
|
-
extra_packages=config.get('extra_packages'
|
54
|
+
external_config = dict(extra_system_packages=config.get('extra_system_packages'),
|
55
|
+
extra_packages=config.get('extra_packages'),
|
62
56
|
)
|
63
57
|
downstream_config.update(external_config)
|
64
|
-
version = config
|
58
|
+
version = config.get('rhbuild')
|
65
59
|
if version in rh_versions:
|
66
60
|
log.info("%s is a supported version", version)
|
67
61
|
else:
|
@@ -95,10 +89,16 @@ def install_pkgs(ctx, remote, version, downstream_config):
|
|
95
89
|
:param downstream_config the dict object that has downstream pkg info
|
96
90
|
"""
|
97
91
|
rh_version_check = downstream_config.get('versions').get('rpm').get('mapped')
|
98
|
-
rh_rpm_pkgs = downstream_config.get('pkgs').get('rpm')
|
99
|
-
|
100
|
-
|
92
|
+
rh_rpm_pkgs = downstream_config.get('pkgs').get('rpm')
|
93
|
+
extras = [downstream_config.get('extra_system_packages'),
|
94
|
+
downstream_config.get('extra_packages')]
|
95
|
+
for extra in extras:
|
96
|
+
if isinstance(extra, dict):
|
97
|
+
rh_rpm_pkgs += extra.get('rpm', [])
|
98
|
+
elif isinstance(extra, list):
|
99
|
+
rh_rpm_pkgs += extra
|
101
100
|
pkgs = str.join(' ', rh_rpm_pkgs)
|
101
|
+
|
102
102
|
log.info("Remove any epel packages installed on node %s", remote.shortname)
|
103
103
|
# below packages can come from epel and still work, ensure we use cdn pkgs
|
104
104
|
remote.run(
|
@@ -172,9 +172,14 @@ def install_deb_pkgs(
|
|
172
172
|
: param downstream_config the dict object that has downstream pkg info
|
173
173
|
"""
|
174
174
|
rh_version_check = downstream_config.get('versions').get('deb').get('mapped')
|
175
|
-
rh_deb_pkgs = downstream_config.get('pkgs').get('deb')
|
176
|
-
|
177
|
-
|
175
|
+
rh_deb_pkgs = downstream_config.get('pkgs').get('deb')
|
176
|
+
extras = [downstream_config.get('extra_system_packages'),
|
177
|
+
downstream_config.get('extra_packages')]
|
178
|
+
for extra in extras:
|
179
|
+
if isinstance(extra, dict):
|
180
|
+
rh_deb_pkgs += extra.get('deb', [])
|
181
|
+
elif isinstance(extra, list):
|
182
|
+
rh_deb_pkgs += extra
|
178
183
|
pkgs = str.join(' ', rh_deb_pkgs)
|
179
184
|
log.info("Installing redhat ceph packages")
|
180
185
|
remote.run(args=['sudo', 'apt-get', '-y', 'install',
|
@@ -200,14 +205,13 @@ def uninstall_pkgs(ctx, remote, downstream_config):
|
|
200
205
|
:param remote: the teuthology.orchestra.remote.Remote object
|
201
206
|
:param downstream_config the dict object that has downstream pkg info
|
202
207
|
"""
|
203
|
-
rh_rpm_pkgs = downstream_config.get('pkgs').get('rpm')
|
204
|
-
rpm_pkgs = str.join(' ', rh_rpm_pkgs)
|
205
|
-
|
206
|
-
rh_deb_pkgs = downstream_config.get('pkgs').get('deb')
|
207
|
-
deb_pkgs = str.join(' ', rh_deb_pkgs)
|
208
208
|
|
209
209
|
if remote.os.name == 'rhel':
|
210
|
-
|
210
|
+
pkgs = downstream_config.get('pkgs').get('rpm')
|
211
|
+
if pkgs:
|
212
|
+
remote.sh(['sudo', 'yum', 'remove'] + pkgs + ['-y'])
|
211
213
|
else:
|
212
|
-
|
214
|
+
pkgs = downstream_config.get('pkgs').get('deb')
|
215
|
+
if pkgs:
|
216
|
+
remote.sh(['sudo', 'apt-get', 'remove'] + pkgs + ['-y'])
|
213
217
|
remote.run(args=['sudo', 'rm', '-rf', '/var/lib/ceph'])
|
teuthology/task/install/rpm.py
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
import logging
|
2
2
|
import os.path
|
3
|
+
from io import StringIO
|
3
4
|
|
4
5
|
from distutils.version import LooseVersion
|
5
6
|
|
6
7
|
from teuthology.config import config as teuth_config
|
8
|
+
from teuthology.contextutil import safe_while
|
7
9
|
from teuthology.orchestra import run
|
8
10
|
from teuthology import packaging
|
9
11
|
|
@@ -143,6 +145,21 @@ def _downgrade_packages(ctx, remote, pkgs, pkg_version, config):
|
|
143
145
|
remote.run(args='sudo yum -y downgrade {}'.format(' '.join(pkgs_opt)))
|
144
146
|
return [pkg for pkg in pkgs if pkg not in downgrade_pkgs]
|
145
147
|
|
148
|
+
def _retry_if_failures_are_recoverable(remote, args):
|
149
|
+
# wait at most 5 minutes
|
150
|
+
with safe_while(sleep=10, tries=30) as proceed:
|
151
|
+
while proceed():
|
152
|
+
stdout = StringIO()
|
153
|
+
stderr = StringIO()
|
154
|
+
try:
|
155
|
+
return remote.run(args=args, stderr=stderr, stdout=stdout)
|
156
|
+
except run.CommandFailedError:
|
157
|
+
if "status code: 503" in stdout.getvalue().lower():
|
158
|
+
continue
|
159
|
+
if "failed to download metadata for repo" in stderr.getvalue().lower():
|
160
|
+
continue
|
161
|
+
else:
|
162
|
+
raise
|
146
163
|
|
147
164
|
def _update_package_list_and_install(ctx, remote, rpm, config):
|
148
165
|
"""
|
@@ -156,6 +173,13 @@ def _update_package_list_and_install(ctx, remote, rpm, config):
|
|
156
173
|
:param rpm: list of packages names to install
|
157
174
|
:param config: the config dict
|
158
175
|
"""
|
176
|
+
|
177
|
+
enable_coprs = config.get('enable_coprs', [])
|
178
|
+
if len(enable_coprs):
|
179
|
+
remote.run(args=['sudo', 'dnf', '-y', 'install', 'dnf-command(copr)'])
|
180
|
+
for copr in enable_coprs:
|
181
|
+
remote.run(args=['sudo', 'dnf', '-y', 'copr', 'enable', copr])
|
182
|
+
|
159
183
|
# rpm does not force installation of a particular version of the project
|
160
184
|
# packages, so we can put extra_system_packages together with the rest
|
161
185
|
system_pkglist = config.get('extra_system_packages')
|
@@ -236,26 +260,27 @@ def _update_package_list_and_install(ctx, remote, rpm, config):
|
|
236
260
|
rpm = _downgrade_packages(ctx, remote, rpm, pkg_version, config)
|
237
261
|
|
238
262
|
if system_pkglist:
|
239
|
-
remote
|
263
|
+
_retry_if_failures_are_recoverable(remote,
|
240
264
|
args='{install_cmd} {rpms}'
|
241
265
|
.format(install_cmd=install_cmd, rpms=' '.join(rpm))
|
242
266
|
)
|
243
267
|
else:
|
244
268
|
for cpack in rpm:
|
245
269
|
if ldir:
|
246
|
-
remote
|
270
|
+
_retry_if_failures_are_recoverable(remote,
|
271
|
+
args='''
|
247
272
|
if test -e {pkg} ; then
|
248
273
|
{remove_cmd} {pkg} ;
|
249
274
|
{install_cmd} {pkg} ;
|
250
275
|
else
|
251
276
|
{install_cmd} {cpack} ;
|
252
277
|
fi
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
278
|
+
'''.format(remove_cmd=remove_cmd,
|
279
|
+
install_cmd=install_cmd,
|
280
|
+
pkg=os.path.join(ldir, cpack),
|
281
|
+
cpack=cpack))
|
257
282
|
else:
|
258
|
-
remote
|
283
|
+
_retry_if_failures_are_recoverable(remote,
|
259
284
|
args='{install_cmd} {cpack}'
|
260
285
|
.format(install_cmd=install_cmd, cpack=cpack)
|
261
286
|
)
|
@@ -350,6 +375,8 @@ def _remove_sources_list(ctx, config, remote):
|
|
350
375
|
if remote.os.name not in ['opensuse', 'sle']:
|
351
376
|
_yum_unset_check_obsoletes(remote)
|
352
377
|
|
378
|
+
for copr in config.get('enable_coprs', []):
|
379
|
+
remote.run(args=['sudo', 'dnf', '-y', 'copr', 'disable', copr])
|
353
380
|
|
354
381
|
def _upgrade_packages(ctx, config, remote, pkgs):
|
355
382
|
"""
|
@@ -401,14 +428,14 @@ def _upgrade_packages(ctx, config, remote, pkgs):
|
|
401
428
|
# Actually upgrade the project packages
|
402
429
|
if builder.dist_release in ['opensuse', 'sle']:
|
403
430
|
pkg_mng_opts = '-n'
|
404
|
-
|
405
|
-
|
431
|
+
pkg_mng_subcommand = 'install'
|
432
|
+
pkg_mng_subcommand_opts = ['--capability', '--no-recommends']
|
406
433
|
else:
|
407
434
|
pkg_mng_opts = '-y'
|
408
|
-
|
409
|
-
|
410
|
-
args = ['sudo', pkg_mng_cmd, pkg_mng_opts,
|
411
|
-
|
412
|
-
|
435
|
+
pkg_mng_subcommand = 'upgrade'
|
436
|
+
pkg_mng_subcommand_opts = []
|
437
|
+
args = ['sudo', pkg_mng_cmd, pkg_mng_opts, pkg_mng_subcommand]
|
438
|
+
if pkg_mng_subcommand_opts:
|
439
|
+
args += pkg_mng_subcommand_opts
|
413
440
|
args += pkgs
|
414
441
|
remote.run(args=args)
|
teuthology/task/install/util.py
CHANGED
@@ -38,7 +38,7 @@ def get_flavor(config):
|
|
38
38
|
Determine the flavor to use.
|
39
39
|
"""
|
40
40
|
config = config or dict()
|
41
|
-
flavor = config.get('flavor', '
|
41
|
+
flavor = config.get('flavor', 'default')
|
42
42
|
|
43
43
|
if config.get('path'):
|
44
44
|
# local dir precludes any other flavors
|
@@ -51,18 +51,14 @@ def get_flavor(config):
|
|
51
51
|
flavor = 'gcov'
|
52
52
|
return flavor
|
53
53
|
|
54
|
-
|
55
|
-
@contextlib.contextmanager
|
56
|
-
def ship_utilities(ctx, config):
|
54
|
+
def _ship_utilities(ctx):
|
57
55
|
"""
|
58
56
|
Write a copy of valgrind.supp to each of the remote sites. Set executables
|
59
57
|
used by Ceph in /usr/local/bin. When finished (upon exit of the teuthology
|
60
58
|
run), remove these files.
|
61
59
|
|
62
60
|
:param ctx: Context
|
63
|
-
:param config: Configuration
|
64
61
|
"""
|
65
|
-
assert config is None
|
66
62
|
testdir = teuthology.get_testdir(ctx)
|
67
63
|
filenames = []
|
68
64
|
|
@@ -85,11 +81,11 @@ def ship_utilities(ctx, config):
|
|
85
81
|
except IOError as e:
|
86
82
|
log.info('Cannot ship supression file for valgrind: %s...', e.strerror)
|
87
83
|
|
88
|
-
FILES = ['daemon-helper', 'adjust-ulimits']
|
84
|
+
FILES = ['daemon-helper', 'adjust-ulimits', 'stdin-killer']
|
89
85
|
destdir = '/usr/bin'
|
90
86
|
for filename in FILES:
|
91
87
|
log.info('Shipping %r...', filename)
|
92
|
-
src = os.path.join(os.path.dirname(__file__), filename)
|
88
|
+
src = os.path.join(os.path.dirname(__file__), 'bin', filename)
|
93
89
|
dst = os.path.join(destdir, filename)
|
94
90
|
filenames.append(dst)
|
95
91
|
with open(src, 'rb') as f:
|
@@ -109,19 +105,49 @@ def ship_utilities(ctx, config):
|
|
109
105
|
dst,
|
110
106
|
],
|
111
107
|
)
|
108
|
+
return filenames
|
112
109
|
|
113
|
-
|
110
|
+
def _remove_utilities(ctx, filenames):
|
111
|
+
"""
|
112
|
+
Remove the shipped utilities.
|
113
|
+
|
114
|
+
:param ctx: Context
|
115
|
+
:param filenames: The utilities install paths
|
116
|
+
"""
|
117
|
+
log.info('Removing shipped files: %s...', ' '.join(filenames))
|
118
|
+
if filenames == []:
|
119
|
+
return
|
120
|
+
run.wait(
|
121
|
+
ctx.cluster.run(
|
122
|
+
args=[
|
123
|
+
'sudo',
|
124
|
+
'rm',
|
125
|
+
'-f',
|
126
|
+
'--',
|
127
|
+
] + list(filenames),
|
128
|
+
wait=False,
|
129
|
+
),
|
130
|
+
)
|
131
|
+
|
132
|
+
@contextlib.contextmanager
|
133
|
+
def ship_utilities(ctx, config):
|
134
|
+
"""
|
135
|
+
Ship utilities during the first call, and skip it in the following ones.
|
136
|
+
See also `_ship_utilities`.
|
137
|
+
|
138
|
+
:param ctx: Context
|
139
|
+
:param config: Configuration
|
140
|
+
"""
|
141
|
+
assert config is None
|
142
|
+
|
143
|
+
do_ship_utilities = ctx.get('do_ship_utilities', True)
|
144
|
+
if do_ship_utilities:
|
145
|
+
ctx['do_ship_utilities'] = False
|
146
|
+
filenames = _ship_utilities(ctx)
|
147
|
+
try:
|
148
|
+
yield
|
149
|
+
finally:
|
150
|
+
_remove_utilities(ctx, filenames)
|
151
|
+
else:
|
152
|
+
log.info('Utilities already shipped, skip it...')
|
114
153
|
yield
|
115
|
-
finally:
|
116
|
-
log.info('Removing shipped files: %s...', ' '.join(filenames))
|
117
|
-
run.wait(
|
118
|
-
ctx.cluster.run(
|
119
|
-
args=[
|
120
|
-
'sudo',
|
121
|
-
'rm',
|
122
|
-
'-f',
|
123
|
-
'--',
|
124
|
-
] + list(filenames),
|
125
|
-
wait=False,
|
126
|
-
),
|
127
|
-
)
|