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.
Files changed (172) hide show
  1. scripts/describe.py +1 -0
  2. scripts/dispatcher.py +62 -0
  3. scripts/exporter.py +18 -0
  4. scripts/lock.py +1 -1
  5. scripts/node_cleanup.py +58 -0
  6. scripts/openstack.py +9 -9
  7. scripts/results.py +12 -11
  8. scripts/run.py +4 -0
  9. scripts/schedule.py +4 -0
  10. scripts/suite.py +61 -16
  11. scripts/supervisor.py +44 -0
  12. scripts/update_inventory.py +10 -4
  13. scripts/wait.py +31 -0
  14. teuthology/__init__.py +24 -21
  15. teuthology/beanstalk.py +4 -3
  16. teuthology/config.py +17 -6
  17. teuthology/contextutil.py +18 -14
  18. teuthology/describe_tests.py +25 -18
  19. teuthology/dispatcher/__init__.py +365 -0
  20. teuthology/dispatcher/supervisor.py +374 -0
  21. teuthology/exceptions.py +54 -0
  22. teuthology/exporter.py +347 -0
  23. teuthology/kill.py +76 -75
  24. teuthology/lock/cli.py +16 -7
  25. teuthology/lock/ops.py +276 -70
  26. teuthology/lock/query.py +61 -44
  27. teuthology/ls.py +9 -18
  28. teuthology/misc.py +152 -137
  29. teuthology/nuke/__init__.py +12 -351
  30. teuthology/openstack/__init__.py +4 -3
  31. teuthology/openstack/openstack-centos-7.0-user-data.txt +1 -1
  32. teuthology/openstack/openstack-centos-7.1-user-data.txt +1 -1
  33. teuthology/openstack/openstack-centos-7.2-user-data.txt +1 -1
  34. teuthology/openstack/openstack-debian-8.0-user-data.txt +1 -1
  35. teuthology/openstack/openstack-opensuse-42.1-user-data.txt +1 -1
  36. teuthology/openstack/openstack-teuthology.cron +0 -1
  37. teuthology/orchestra/cluster.py +51 -9
  38. teuthology/orchestra/connection.py +23 -16
  39. teuthology/orchestra/console.py +111 -50
  40. teuthology/orchestra/daemon/cephadmunit.py +23 -5
  41. teuthology/orchestra/daemon/state.py +10 -3
  42. teuthology/orchestra/daemon/systemd.py +10 -8
  43. teuthology/orchestra/opsys.py +32 -11
  44. teuthology/orchestra/remote.py +369 -152
  45. teuthology/orchestra/run.py +21 -12
  46. teuthology/packaging.py +54 -15
  47. teuthology/provision/__init__.py +30 -10
  48. teuthology/provision/cloud/openstack.py +12 -6
  49. teuthology/provision/cloud/util.py +1 -2
  50. teuthology/provision/downburst.py +83 -29
  51. teuthology/provision/fog.py +68 -20
  52. teuthology/provision/openstack.py +5 -4
  53. teuthology/provision/pelagos.py +13 -5
  54. teuthology/repo_utils.py +91 -44
  55. teuthology/report.py +57 -35
  56. teuthology/results.py +5 -3
  57. teuthology/run.py +21 -15
  58. teuthology/run_tasks.py +114 -40
  59. teuthology/schedule.py +4 -3
  60. teuthology/scrape.py +28 -22
  61. teuthology/suite/__init__.py +75 -46
  62. teuthology/suite/build_matrix.py +34 -24
  63. teuthology/suite/fragment-merge.lua +105 -0
  64. teuthology/suite/matrix.py +31 -2
  65. teuthology/suite/merge.py +175 -0
  66. teuthology/suite/placeholder.py +8 -8
  67. teuthology/suite/run.py +204 -102
  68. teuthology/suite/util.py +67 -211
  69. teuthology/task/__init__.py +1 -1
  70. teuthology/task/ansible.py +101 -31
  71. teuthology/task/buildpackages.py +2 -2
  72. teuthology/task/ceph_ansible.py +13 -6
  73. teuthology/task/cephmetrics.py +2 -1
  74. teuthology/task/clock.py +33 -14
  75. teuthology/task/exec.py +18 -0
  76. teuthology/task/hadoop.py +2 -2
  77. teuthology/task/install/__init__.py +51 -22
  78. teuthology/task/install/bin/adjust-ulimits +16 -0
  79. teuthology/task/install/bin/daemon-helper +114 -0
  80. teuthology/task/install/bin/stdin-killer +263 -0
  81. teuthology/task/install/deb.py +24 -4
  82. teuthology/task/install/redhat.py +36 -32
  83. teuthology/task/install/rpm.py +41 -14
  84. teuthology/task/install/util.py +48 -22
  85. teuthology/task/internal/__init__.py +69 -11
  86. teuthology/task/internal/edit_sudoers.sh +10 -0
  87. teuthology/task/internal/lock_machines.py +3 -133
  88. teuthology/task/internal/redhat.py +48 -28
  89. teuthology/task/internal/syslog.py +31 -8
  90. teuthology/task/kernel.py +155 -147
  91. teuthology/task/lockfile.py +1 -1
  92. teuthology/task/mpi.py +10 -10
  93. teuthology/task/pcp.py +1 -1
  94. teuthology/task/selinux.py +17 -8
  95. teuthology/task/ssh_keys.py +6 -6
  96. teuthology/task/tests/__init__.py +137 -77
  97. teuthology/task/tests/test_fetch_coredumps.py +116 -0
  98. teuthology/task/tests/test_run.py +4 -4
  99. teuthology/timer.py +3 -3
  100. teuthology/util/loggerfile.py +19 -0
  101. teuthology/util/scanner.py +159 -0
  102. teuthology/util/sentry.py +52 -0
  103. teuthology/util/time.py +52 -0
  104. teuthology-1.2.0.data/scripts/adjust-ulimits +16 -0
  105. teuthology-1.2.0.data/scripts/daemon-helper +114 -0
  106. teuthology-1.2.0.data/scripts/stdin-killer +263 -0
  107. teuthology-1.2.0.dist-info/METADATA +89 -0
  108. teuthology-1.2.0.dist-info/RECORD +174 -0
  109. {teuthology-1.0.0.dist-info → teuthology-1.2.0.dist-info}/WHEEL +1 -1
  110. {teuthology-1.0.0.dist-info → teuthology-1.2.0.dist-info}/entry_points.txt +5 -2
  111. scripts/nuke.py +0 -45
  112. scripts/worker.py +0 -37
  113. teuthology/nuke/actions.py +0 -456
  114. teuthology/openstack/test/__init__.py +0 -0
  115. teuthology/openstack/test/openstack-integration.py +0 -286
  116. teuthology/openstack/test/test_config.py +0 -35
  117. teuthology/openstack/test/test_openstack.py +0 -1695
  118. teuthology/orchestra/test/__init__.py +0 -0
  119. teuthology/orchestra/test/integration/__init__.py +0 -0
  120. teuthology/orchestra/test/integration/test_integration.py +0 -94
  121. teuthology/orchestra/test/test_cluster.py +0 -240
  122. teuthology/orchestra/test/test_connection.py +0 -106
  123. teuthology/orchestra/test/test_console.py +0 -217
  124. teuthology/orchestra/test/test_opsys.py +0 -404
  125. teuthology/orchestra/test/test_remote.py +0 -185
  126. teuthology/orchestra/test/test_run.py +0 -286
  127. teuthology/orchestra/test/test_systemd.py +0 -54
  128. teuthology/orchestra/test/util.py +0 -12
  129. teuthology/sentry.py +0 -18
  130. teuthology/test/__init__.py +0 -0
  131. teuthology/test/fake_archive.py +0 -107
  132. teuthology/test/fake_fs.py +0 -92
  133. teuthology/test/integration/__init__.py +0 -0
  134. teuthology/test/integration/test_suite.py +0 -86
  135. teuthology/test/task/__init__.py +0 -205
  136. teuthology/test/task/test_ansible.py +0 -624
  137. teuthology/test/task/test_ceph_ansible.py +0 -176
  138. teuthology/test/task/test_console_log.py +0 -88
  139. teuthology/test/task/test_install.py +0 -337
  140. teuthology/test/task/test_internal.py +0 -57
  141. teuthology/test/task/test_kernel.py +0 -243
  142. teuthology/test/task/test_pcp.py +0 -379
  143. teuthology/test/task/test_selinux.py +0 -35
  144. teuthology/test/test_config.py +0 -189
  145. teuthology/test/test_contextutil.py +0 -68
  146. teuthology/test/test_describe_tests.py +0 -316
  147. teuthology/test/test_email_sleep_before_teardown.py +0 -81
  148. teuthology/test/test_exit.py +0 -97
  149. teuthology/test/test_get_distro.py +0 -47
  150. teuthology/test/test_get_distro_version.py +0 -47
  151. teuthology/test/test_get_multi_machine_types.py +0 -27
  152. teuthology/test/test_job_status.py +0 -60
  153. teuthology/test/test_ls.py +0 -48
  154. teuthology/test/test_misc.py +0 -368
  155. teuthology/test/test_nuke.py +0 -232
  156. teuthology/test/test_packaging.py +0 -763
  157. teuthology/test/test_parallel.py +0 -28
  158. teuthology/test/test_repo_utils.py +0 -204
  159. teuthology/test/test_report.py +0 -77
  160. teuthology/test/test_results.py +0 -155
  161. teuthology/test/test_run.py +0 -238
  162. teuthology/test/test_safepath.py +0 -55
  163. teuthology/test/test_schedule.py +0 -45
  164. teuthology/test/test_scrape.py +0 -167
  165. teuthology/test/test_timer.py +0 -80
  166. teuthology/test/test_vps_os_vers_parameter_checking.py +0 -84
  167. teuthology/test/test_worker.py +0 -303
  168. teuthology/worker.py +0 -339
  169. teuthology-1.0.0.dist-info/METADATA +0 -76
  170. teuthology-1.0.0.dist-info/RECORD +0 -210
  171. {teuthology-1.0.0.dist-info → teuthology-1.2.0.dist-info}/LICENSE +0 -0
  172. {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)
@@ -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.run(
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.run(
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.run(
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
- yaml_path = None
44
- # Look for rh specific packages in <suite_path>/rh/downstream.yaml
45
- if 'suite_path' in ctx.config:
46
- ds_yaml = os.path.join(
47
- ctx.config['suite_path'],
48
- 'rh',
49
- 'downstream.yaml',
50
- )
51
- if os.path.exists(ds_yaml):
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['rhbuild']
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
- downstream_config.get('extra_system_packages').get('rpm', []) + \
100
- downstream_config.get('extra_packages').get('rpm', [])
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
- downstream_config.get('extra_system_packages').get('deb', []) + \
177
- downstream_config.get('extra_packages').get('deb', [])
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
- remote.run(args=['sudo', 'yum', 'remove', run.Raw(rpm_pkgs), '-y'])
210
+ pkgs = downstream_config.get('pkgs').get('rpm')
211
+ if pkgs:
212
+ remote.sh(['sudo', 'yum', 'remove'] + pkgs + ['-y'])
211
213
  else:
212
- remote.run(args=['sudo', 'apt-get', 'remove', run.Raw(deb_pkgs), '-y'])
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'])
@@ -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.run(
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.run(args='''
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
- '''.format(remove_cmd=remove_cmd,
254
- install_cmd=install_cmd,
255
- pkg=os.path.join(ldir, cpack),
256
- cpack=cpack))
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.run(
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
- pkg_mng_subcommand_opts = '--capability'
405
- pkg_mng_install_opts = '--no-recommends'
431
+ pkg_mng_subcommand = 'install'
432
+ pkg_mng_subcommand_opts = ['--capability', '--no-recommends']
406
433
  else:
407
434
  pkg_mng_opts = '-y'
408
- pkg_mng_subcommand_opts = ''
409
- pkg_mng_install_opts = ''
410
- args = ['sudo', pkg_mng_cmd, pkg_mng_opts,
411
- 'install', pkg_mng_subcommand_opts,
412
- pkg_mng_install_opts]
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)
@@ -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', 'basic')
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
- try:
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
- )