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
@@ -45,12 +45,13 @@ def substitute_placeholders(input_dict, values_dict):
45
45
  # Template for the config that becomes the base for each generated job config
46
46
  dict_templ = {
47
47
  'branch': Placeholder('ceph_branch'),
48
+ 'expire': Placeholder('expire'),
48
49
  'sha1': Placeholder('ceph_hash'),
49
50
  'teuthology_branch': Placeholder('teuthology_branch'),
51
+ 'teuthology_sha1': Placeholder('teuthology_sha1'),
50
52
  'archive_upload': Placeholder('archive_upload'),
51
53
  'archive_upload_key': Placeholder('archive_upload_key'),
52
54
  'machine_type': Placeholder('machine_type'),
53
- 'nuke-on-error': True,
54
55
  'os_type': Placeholder('distro'),
55
56
  'os_version': Placeholder('distro_version'),
56
57
  'overrides': {
@@ -67,14 +68,13 @@ dict_templ = {
67
68
  'debug mgr': 20,
68
69
  'debug ms': 1},
69
70
  'osd': {
70
- 'debug filestore': 20,
71
- 'debug journal': 20,
72
- 'debug ms': 20,
73
- 'debug osd': 25
71
+ 'debug ms': 1,
72
+ 'debug osd': 20
74
73
  }
75
74
  },
76
- 'log-whitelist': ['\(MDS_ALL_DOWN\)',
77
- '\(MDS_UP_LESS_THAN_MAX\)'],
75
+ 'flavor': Placeholder('flavor'),
76
+ 'log-ignorelist': [r'\(MDS_ALL_DOWN\)',
77
+ r'\(MDS_UP_LESS_THAN_MAX\)'],
78
78
  'sha1': Placeholder('ceph_hash'),
79
79
  },
80
80
  'ceph-deploy': {
@@ -83,13 +83,13 @@ dict_templ = {
83
83
  'log file': '/var/log/ceph/ceph-$name.$pid.log'
84
84
  },
85
85
  'mon': {
86
- 'osd default pool size': 2
87
86
  }
88
87
  }
89
88
  },
90
89
  'install': {
91
90
  'ceph': {
92
91
  'sha1': Placeholder('ceph_hash'),
92
+ 'flavor': Placeholder('flavor'),
93
93
  }
94
94
  },
95
95
  'workunit': {
teuthology/suite/run.py CHANGED
@@ -1,27 +1,30 @@
1
1
  import copy
2
+ import datetime
2
3
  import logging
3
4
  import os
4
5
  import pwd
6
+ import yaml
5
7
  import re
6
8
  import time
7
- import yaml
8
9
 
9
10
  from humanfriendly import format_timespan
10
11
 
11
- from datetime import datetime
12
12
  from tempfile import NamedTemporaryFile
13
+ from teuthology import repo_utils
13
14
 
14
15
  from teuthology.config import config, JobConfig
15
16
  from teuthology.exceptions import (
16
- BranchNotFoundError, CommitNotFoundError, VersionNotFoundError
17
+ BranchMismatchError, BranchNotFoundError, CommitNotFoundError,
17
18
  )
18
19
  from teuthology.misc import deep_merge, get_results_url
19
20
  from teuthology.orchestra.opsys import OS
20
21
  from teuthology.repo_utils import build_git_url
21
22
 
22
23
  from teuthology.suite import util
24
+ from teuthology.suite.merge import config_merge
23
25
  from teuthology.suite.build_matrix import build_matrix
24
26
  from teuthology.suite.placeholder import substitute_placeholders, dict_templ
27
+ from teuthology.util.time import parse_offset, parse_timestamp, TIMESTAMP_FMT
25
28
 
26
29
  log = logging.getLogger(__name__)
27
30
 
@@ -31,8 +34,7 @@ class Run(object):
31
34
  WAIT_PAUSE = 5 * 60
32
35
  __slots__ = (
33
36
  'args', 'name', 'base_config', 'suite_repo_path', 'base_yaml_paths',
34
- 'base_args', 'package_versions', 'kernel_dict', 'config_input',
35
- 'timestamp', 'user',
37
+ 'base_args', 'kernel_dict', 'config_input', 'timestamp', 'user', 'os',
36
38
  )
37
39
 
38
40
  def __init__(self, args):
@@ -42,7 +44,7 @@ class Run(object):
42
44
  self.args = args
43
45
  # We assume timestamp is a datetime.datetime object
44
46
  self.timestamp = self.args.timestamp or \
45
- datetime.now().strftime('%Y-%m-%d_%H:%M:%S')
47
+ datetime.datetime.now().strftime(TIMESTAMP_FMT)
46
48
  self.user = self.args.user or pwd.getpwuid(os.getuid()).pw_name
47
49
 
48
50
  self.name = self.make_run_name()
@@ -53,8 +55,6 @@ class Run(object):
53
55
  config.ceph_qa_suite_git_url = self.args.suite_repo
54
56
 
55
57
  self.base_config = self.create_initial_config()
56
- # caches package versions to minimize requests to gbs
57
- self.package_versions = dict()
58
58
 
59
59
  # Interpret any relative paths as being relative to ceph-qa-suite
60
60
  # (absolute paths are unchanged by this)
@@ -74,7 +74,7 @@ class Run(object):
74
74
  self.args.suite,
75
75
  self.args.ceph_branch,
76
76
  self.args.kernel_branch or '-',
77
- self.args.kernel_flavor, worker
77
+ self.args.flavor, worker
78
78
  ]
79
79
  ).replace('/', ':')
80
80
 
@@ -87,6 +87,16 @@ class Run(object):
87
87
 
88
88
  :returns: A JobConfig object
89
89
  """
90
+ now = datetime.datetime.now(datetime.timezone.utc)
91
+ expires = self.get_expiration()
92
+ if expires:
93
+ if now > expires:
94
+ util.schedule_fail(
95
+ f"Refusing to schedule because the expiration date is in the past: {self.args.expire}",
96
+ dry_run=self.args.dry_run,
97
+ )
98
+
99
+ self.os = self.choose_os()
90
100
  self.kernel_dict = self.choose_kernel()
91
101
  ceph_hash = self.choose_ceph_hash()
92
102
  # We don't store ceph_version because we don't use it yet outside of
@@ -98,8 +108,8 @@ class Run(object):
98
108
  self.suite_repo_path = self.args.suite_dir
99
109
  else:
100
110
  self.suite_repo_path = util.fetch_repos(
101
- suite_branch, test_name=self.name)
102
- teuthology_branch = self.choose_teuthology_branch()
111
+ suite_branch, test_name=self.name, dry_run=self.args.dry_run)
112
+ teuthology_branch, teuthology_sha1 = self.choose_teuthology_branch()
103
113
 
104
114
 
105
115
  if self.args.distro_version:
@@ -113,16 +123,48 @@ class Run(object):
113
123
  ceph_hash=ceph_hash,
114
124
  ceph_repo=config.get_ceph_git_url(),
115
125
  teuthology_branch=teuthology_branch,
126
+ teuthology_sha1=teuthology_sha1,
116
127
  machine_type=self.args.machine_type,
117
- distro=self.args.distro,
118
- distro_version=self.args.distro_version,
128
+ distro=self.os.name,
129
+ distro_version=self.os.version,
119
130
  archive_upload=config.archive_upload,
120
131
  archive_upload_key=config.archive_upload_key,
121
132
  suite_repo=config.get_ceph_qa_suite_git_url(),
122
133
  suite_relpath=self.args.suite_relpath,
134
+ flavor=self.args.flavor,
135
+ expire=expires.strftime(TIMESTAMP_FMT) if expires else None,
123
136
  )
124
137
  return self.build_base_config()
125
138
 
139
+ def get_expiration(self, _base_time: datetime.datetime | None = None) -> datetime.datetime | None:
140
+ """
141
+ _base_time: For testing, calculate relative offsets from this base time
142
+
143
+ :returns: True if the job should run; False if it has expired
144
+ """
145
+ log.info(f"Checking for expiration ({self.args.expire})")
146
+ expires_str = self.args.expire
147
+ if expires_str is None:
148
+ return None
149
+ now = datetime.datetime.now(datetime.timezone.utc)
150
+ if _base_time is None:
151
+ _base_time = now
152
+ try:
153
+ expires = parse_timestamp(expires_str)
154
+ except ValueError:
155
+ expires = _base_time + parse_offset(expires_str)
156
+ return expires
157
+
158
+ def choose_os(self):
159
+ os_type = self.args.distro
160
+ os_version = self.args.distro_version
161
+ if not (os_type and os_version):
162
+ os_ = util.get_distro_defaults(
163
+ self.args.distro, self.args.machine_type)[2]
164
+ else:
165
+ os_ = OS(os_type, os_version)
166
+ return os_
167
+
126
168
  def choose_kernel(self):
127
169
  # Put together a stanza specifying the kernel hash
128
170
  if self.args.kernel_branch == 'distro':
@@ -133,20 +175,25 @@ class Run(object):
133
175
  kernel_hash = None
134
176
  else:
135
177
  kernel_hash = util.get_gitbuilder_hash(
136
- 'kernel', self.args.kernel_branch, self.args.kernel_flavor,
178
+ 'kernel', self.args.kernel_branch, 'default',
137
179
  self.args.machine_type, self.args.distro,
138
180
  self.args.distro_version,
139
181
  )
140
182
  if not kernel_hash:
141
183
  util.schedule_fail(
142
184
  "Kernel branch '{branch}' not found".format(
143
- branch=self.args.kernel_branch)
185
+ branch=self.args.kernel_branch),
186
+ dry_run=self.args.dry_run,
144
187
  )
188
+ kdb = True
189
+ if self.args.kdb is not None:
190
+ kdb = self.args.kdb
191
+
145
192
  if kernel_hash:
146
193
  log.info("kernel sha1: {hash}".format(hash=kernel_hash))
147
- kernel_dict = dict(kernel=dict(kdb=True, sha1=kernel_hash))
194
+ kernel_dict = dict(kernel=dict(kdb=kdb, sha1=kernel_hash))
148
195
  if kernel_hash != 'distro':
149
- kernel_dict['kernel']['flavor'] = self.args.kernel_flavor
196
+ kernel_dict['kernel']['flavor'] = 'default'
150
197
  else:
151
198
  kernel_dict = dict()
152
199
  return kernel_dict
@@ -159,6 +206,7 @@ class Run(object):
159
206
  """
160
207
  repo_name = self.ceph_repo_name
161
208
 
209
+ ceph_hash = None
162
210
  if self.args.ceph_sha1:
163
211
  ceph_hash = self.args.ceph_sha1
164
212
  if self.args.validate_sha1:
@@ -168,17 +216,18 @@ class Run(object):
168
216
  self.args.ceph_sha1,
169
217
  '%s.git' % repo_name
170
218
  )
171
- util.schedule_fail(message=str(exc), name=self.name)
219
+ util.schedule_fail(message=str(exc), name=self.name, dry_run=self.args.dry_run)
172
220
  log.info("ceph sha1 explicitly supplied")
173
221
 
174
222
  elif self.args.ceph_branch:
175
- ceph_hash = util.git_ls_remote(repo_name, self.args.ceph_branch)
223
+ ceph_hash = util.git_ls_remote(
224
+ self.args.ceph_repo, self.args.ceph_branch)
176
225
  if not ceph_hash:
177
226
  exc = BranchNotFoundError(
178
227
  self.args.ceph_branch,
179
228
  '%s.git' % repo_name
180
229
  )
181
- util.schedule_fail(message=str(exc), name=self.name)
230
+ util.schedule_fail(message=str(exc), name=self.name, dry_run=self.args.dry_run)
182
231
 
183
232
  log.info("ceph sha1: {hash}".format(hash=ceph_hash))
184
233
  return ceph_hash
@@ -187,21 +236,23 @@ class Run(object):
187
236
  if config.suite_verify_ceph_hash and not self.args.newest:
188
237
  # don't bother if newest; we'll search for an older one
189
238
  # Get the ceph package version
190
- try:
191
- ceph_version = util.package_version_for_hash(
192
- ceph_hash, self.args.kernel_flavor, self.args.distro,
193
- self.args.distro_version, self.args.machine_type,
194
- )
195
- except Exception as exc:
196
- util.schedule_fail(str(exc), self.name)
239
+ ceph_version = util.package_version_for_hash(
240
+ ceph_hash, self.args.flavor, self.os.name,
241
+ self.os.version, self.args.machine_type,
242
+ )
243
+ if not ceph_version:
244
+ msg = f"Packages for os_type '{self.os.name}', flavor " \
245
+ f"{self.args.flavor} and ceph hash '{ceph_hash}' not found"
246
+ util.schedule_fail(msg, self.name, dry_run=self.args.dry_run)
197
247
  log.info("ceph version: {ver}".format(ver=ceph_version))
198
248
  return ceph_version
199
249
  else:
200
250
  log.info('skipping ceph package verification')
201
251
 
202
252
  def choose_teuthology_branch(self):
203
- """Select teuthology branch, check if it is present in repo and
204
- return the branch name value.
253
+ """Select teuthology branch, check if it is present in repo and return
254
+ tuple (branch, hash) where hash is commit sha1 corresponding
255
+ to the HEAD of the branch.
205
256
 
206
257
  The branch name value is determined in the following order:
207
258
 
@@ -217,9 +268,10 @@ class Run(object):
217
268
  of the teuthology config files ``$HOME/teuthology.yaml``
218
269
  or ``/etc/teuthology.yaml`` correspondingly.
219
270
 
220
- Use ``master``.
271
+ Use ``main``.
221
272
 
222
273
  Generate exception if the branch is not present in the repo.
274
+
223
275
  """
224
276
  teuthology_branch = self.args.teuthology_branch
225
277
  if not teuthology_branch:
@@ -240,17 +292,34 @@ class Run(object):
240
292
  log.warning(
241
293
  'The teuthology branch config is empty, skipping')
242
294
  if not teuthology_branch:
243
- teuthology_branch = config.get('teuthology_branch', 'master')
244
-
245
- teuthology_hash = util.git_ls_remote(
246
- 'teuthology',
247
- teuthology_branch
248
- )
249
- if not teuthology_hash:
295
+ teuthology_branch = config.get('teuthology_branch')
296
+
297
+ if config.teuthology_path:
298
+ actual_branch = repo_utils.current_branch(config.teuthology_path)
299
+ if teuthology_branch and actual_branch != teuthology_branch:
300
+ raise BranchMismatchError(
301
+ teuthology_branch,
302
+ config.teuthology_path,
303
+ "config.teuthology_path is set",
304
+ )
305
+ if not teuthology_branch:
306
+ teuthology_branch = actual_branch
307
+ teuthology_sha1 = util.git_ls_remote(
308
+ f"file://{config.teuthology_path}",
309
+ teuthology_branch
310
+ )
311
+ else:
312
+ if not teuthology_branch:
313
+ teuthology_branch = 'main'
314
+ teuthology_sha1 = util.git_ls_remote(
315
+ 'teuthology',
316
+ teuthology_branch
317
+ )
318
+ if not teuthology_sha1:
250
319
  exc = BranchNotFoundError(teuthology_branch, build_git_url('teuthology'))
251
- util.schedule_fail(message=str(exc), name=self.name)
252
- log.info("teuthology branch: %s %s", teuthology_branch, teuthology_hash)
253
- return teuthology_branch
320
+ util.schedule_fail(message=str(exc), name=self.name, dry_run=self.args.dry_run)
321
+ log.info("teuthology branch: %s %s", teuthology_branch, teuthology_sha1)
322
+ return teuthology_branch, teuthology_sha1
254
323
 
255
324
  @property
256
325
  def ceph_repo_name(self):
@@ -268,32 +337,32 @@ class Run(object):
268
337
 
269
338
  @staticmethod
270
339
  def _repo_name(url):
271
- return re.sub('\.git$', '', url.split('/')[-1])
340
+ return re.sub(r'\.git$', '', url.split('/')[-1])
272
341
 
273
342
  def choose_suite_branch(self):
274
343
  suite_repo_name = self.suite_repo_name
275
344
  suite_repo_project_or_url = self.args.suite_repo or 'ceph-qa-suite'
276
345
  suite_branch = self.args.suite_branch
277
346
  ceph_branch = self.args.ceph_branch
278
- if suite_branch and suite_branch != 'master':
347
+ if suite_branch and suite_branch != 'main':
279
348
  if not util.git_branch_exists(
280
349
  suite_repo_project_or_url,
281
350
  suite_branch
282
351
  ):
283
352
  exc = BranchNotFoundError(suite_branch, suite_repo_name)
284
- util.schedule_fail(message=str(exc), name=self.name)
353
+ util.schedule_fail(message=str(exc), name=self.name, dry_run=self.args.dry_run)
285
354
  elif not suite_branch:
286
355
  # Decide what branch of the suite repo to use
287
356
  if util.git_branch_exists(suite_repo_project_or_url, ceph_branch):
288
357
  suite_branch = ceph_branch
289
358
  else:
290
359
  log.info(
291
- "branch {0} not in {1}; will use master for"
360
+ "branch {0} not in {1}; will use main for"
292
361
  " ceph-qa-suite".format(
293
362
  ceph_branch,
294
363
  suite_repo_name
295
364
  ))
296
- suite_branch = 'master'
365
+ suite_branch = 'main'
297
366
  return suite_branch
298
367
 
299
368
  def choose_suite_hash(self, suite_branch):
@@ -305,7 +374,7 @@ class Run(object):
305
374
  )
306
375
  if not suite_hash:
307
376
  exc = BranchNotFoundError(suite_branch, suite_repo_name)
308
- util.schedule_fail(message=str(exc), name=self.name)
377
+ util.schedule_fail(message=str(exc), name=self.name, dry_run=self.args.dry_run)
309
378
  log.info("%s branch: %s %s", suite_repo_name, suite_branch, suite_hash)
310
379
  return suite_hash
311
380
 
@@ -317,12 +386,17 @@ class Run(object):
317
386
  job_config.user = self.user
318
387
  job_config.timestamp = self.timestamp
319
388
  job_config.priority = self.args.priority
389
+ job_config.seed = self.args.seed
390
+ if self.args.subset:
391
+ job_config.subset = '/'.join(str(i) for i in self.args.subset)
320
392
  if self.args.email:
321
393
  job_config.email = self.args.email
322
394
  if self.args.owner:
323
395
  job_config.owner = self.args.owner
324
396
  if self.args.sleep_before_teardown:
325
397
  job_config.sleep_before_teardown = int(self.args.sleep_before_teardown)
398
+ if self.args.rocketchat:
399
+ job_config.rocketchat = self.args.rocketchat
326
400
  return job_config
327
401
 
328
402
  def build_base_args(self):
@@ -349,6 +423,8 @@ class Run(object):
349
423
  if self.args.subset:
350
424
  subset = '/'.join(str(i) for i in self.args.subset)
351
425
  args.extend(['--subset', subset])
426
+ if self.args.no_nested_subset:
427
+ args.extend(['--no-nested-subset'])
352
428
  args.extend(['--seed', str(self.args.seed)])
353
429
  util.teuthology_schedule(
354
430
  args=args,
@@ -397,16 +473,13 @@ class Run(object):
397
473
  def collect_jobs(self, arch, configs, newest=False, limit=0):
398
474
  jobs_to_schedule = []
399
475
  jobs_missing_packages = []
400
- for description, fragment_paths in configs:
476
+ for description, fragment_paths, parsed_yaml in configs:
401
477
  if limit > 0 and len(jobs_to_schedule) >= limit:
402
478
  log.info(
403
479
  'Stopped after {limit} jobs due to --limit={limit}'.format(
404
480
  limit=limit))
405
481
  break
406
482
 
407
- raw_yaml = '\n'.join([open(a, 'r').read() for a in fragment_paths])
408
-
409
- parsed_yaml = yaml.safe_load(raw_yaml)
410
483
  os_type = parsed_yaml.get('os_type') or self.base_config.os_type
411
484
  os_version = parsed_yaml.get('os_version') or self.base_config.os_version
412
485
  exclude_arch = parsed_yaml.get('exclude_arch')
@@ -428,13 +501,16 @@ class Run(object):
428
501
  '--',
429
502
  ])
430
503
  arg.extend(self.base_yaml_paths)
431
- arg.extend(fragment_paths)
504
+
505
+ parsed_yaml_txt = yaml.dump(parsed_yaml)
506
+ arg.append('-')
432
507
 
433
508
  job = dict(
434
509
  yaml=parsed_yaml,
435
510
  desc=description,
436
511
  sha1=self.base_config.sha1,
437
- args=arg
512
+ args=arg,
513
+ stdin=parsed_yaml_txt,
438
514
  )
439
515
 
440
516
  sha1 = self.base_config.sha1
@@ -443,31 +519,16 @@ class Run(object):
443
519
  full_job_config = copy.deepcopy(self.base_config.to_dict())
444
520
  deep_merge(full_job_config, parsed_yaml)
445
521
  flavor = util.get_install_task_flavor(full_job_config)
446
- # Get package versions for this sha1, os_type and flavor. If
447
- # we've already retrieved them in a previous loop, they'll be
448
- # present in package_versions and gitbuilder will not be asked
449
- # again for them.
450
- try:
451
- self.package_versions = util.get_package_versions(
452
- sha1,
453
- os_type,
454
- os_version,
455
- flavor,
456
- self.package_versions
457
- )
458
- except VersionNotFoundError:
459
- pass
460
- if not util.has_packages_for_distro(
461
- sha1, os_type, os_version, flavor, self.package_versions
462
- ):
463
- m = "Packages for os_type '{os}', flavor {flavor} and " + \
464
- "ceph hash '{ver}' not found"
465
- log.error(m.format(os=os_type, flavor=flavor, ver=sha1))
522
+ version = util.package_version_for_hash(sha1, flavor, os_type,
523
+ os_version, self.args.machine_type)
524
+ if not version:
466
525
  jobs_missing_packages.append(job)
526
+ log.error(f"Packages for os_type '{os_type}', flavor {flavor} and "
527
+ f"ceph hash '{sha1}' not found")
467
528
  # optimization: one missing package causes backtrack in newest mode;
468
529
  # no point in continuing the search
469
530
  if newest:
470
- return jobs_missing_packages, None
531
+ return jobs_missing_packages, []
471
532
 
472
533
  jobs_to_schedule.append(job)
473
534
  return jobs_missing_packages, jobs_to_schedule
@@ -481,26 +542,54 @@ class Run(object):
481
542
  log_prefix = ''
482
543
  if job in jobs_missing_packages:
483
544
  log_prefix = "Missing Packages: "
484
- if (
485
- not self.args.dry_run and
486
- not config.suite_allow_missing_packages
487
- ):
545
+ if not config.suite_allow_missing_packages:
488
546
  util.schedule_fail(
489
- "At least one job needs packages that don't exist for "
490
- "hash {sha1}.".format(sha1=self.base_config.sha1),
547
+ "At least one job needs packages that don't exist "
548
+ f"for hash {self.base_config.sha1}.",
491
549
  name,
550
+ dry_run=self.args.dry_run,
492
551
  )
493
552
  util.teuthology_schedule(
494
553
  args=job['args'],
495
554
  dry_run=self.args.dry_run,
496
555
  verbose=self.args.verbose,
497
556
  log_prefix=log_prefix,
557
+ stdin=job['stdin'],
498
558
  )
499
559
  throttle = self.args.throttle
500
560
  if not self.args.dry_run and throttle:
501
561
  log.info("pause between jobs : --throttle " + str(throttle))
502
562
  time.sleep(int(throttle))
503
563
 
564
+ def check_priority(self, jobs_to_schedule):
565
+ priority = self.args.priority
566
+ msg=f'''Unable to schedule {jobs_to_schedule} jobs with priority {priority}.
567
+
568
+ Use the following testing priority
569
+ 10 to 49: Tests which are urgent and blocking other important development.
570
+ 50 to 74: Testing a particular feature/fix with less than 25 jobs and can also be used for urgent release testing.
571
+ 75 to 99: Tech Leads usually schedule integration tests with this priority to verify pull requests against main.
572
+ 100 to 149: QE validation of point releases.
573
+ 150 to 199: Testing a particular feature/fix with less than 100 jobs and results will be available in a day or so.
574
+ 200 to 1000: Large test runs that can be done over the course of a week.
575
+ Note: To force run, use --force-priority'''
576
+ if priority < 50:
577
+ util.schedule_fail(msg, dry_run=self.args.dry_run)
578
+ elif priority < 75 and jobs_to_schedule > 25:
579
+ util.schedule_fail(msg, dry_run=self.args.dry_run)
580
+ elif priority < 150 and jobs_to_schedule > 100:
581
+ util.schedule_fail(msg, dry_run=self.args.dry_run)
582
+
583
+ def check_num_jobs(self, jobs_to_schedule):
584
+ """
585
+ Fail schedule if number of jobs exceeds job threshold.
586
+ """
587
+ threshold = self.args.job_threshold
588
+ msg=f'''Unable to schedule {jobs_to_schedule} jobs, too many jobs, when maximum {threshold} jobs allowed.
589
+
590
+ Note: If you still want to go ahead, use --job-threshold 0'''
591
+ if threshold and jobs_to_schedule > threshold:
592
+ util.schedule_fail(msg, dry_run=self.args.dry_run)
504
593
 
505
594
  def schedule_suite(self):
506
595
  """
@@ -520,11 +609,21 @@ class Run(object):
520
609
  self.base_config.suite.replace(':', '/'),
521
610
  ))
522
611
  log.debug('Suite %s in %s' % (suite_name, suite_path))
612
+ log.debug(f"subset = {self.args.subset}")
613
+ log.debug(f"no_nested_subset = {self.args.no_nested_subset}")
523
614
  configs = build_matrix(suite_path,
524
615
  subset=self.args.subset,
616
+ no_nested_subset=self.args.no_nested_subset,
525
617
  seed=self.args.seed)
526
- log.info('Suite %s in %s generated %d jobs (not yet filtered)' % (
527
- suite_name, suite_path, len(configs)))
618
+ generated = len(configs)
619
+ log.info(f'Suite {suite_name} in {suite_path} generated {generated} jobs (not yet filtered or merged)')
620
+ configs = list(config_merge(configs,
621
+ filter_in=self.args.filter_in,
622
+ filter_out=self.args.filter_out,
623
+ filter_all=self.args.filter_all,
624
+ filter_fragments=self.args.filter_fragments,
625
+ seed=self.args.seed,
626
+ suite_name=suite_name))
528
627
 
529
628
  if self.args.dry_run:
530
629
  log.debug("Base job config:\n%s" % self.base_config)
@@ -561,7 +660,7 @@ class Run(object):
561
660
  'this run for {that_long}? (y/N):'
562
661
  .format(
563
662
  that_long=format_timespan(sleep_before_teardown),
564
- total=len(configs),
663
+ total=generated,
565
664
  maximum=job_limit))
566
665
  while True:
567
666
  insane=(input(are_you_insane) or 'n').lower()
@@ -574,23 +673,18 @@ class Run(object):
574
673
  # if not, do it once
575
674
  backtrack = 0
576
675
  limit = self.args.newest
676
+ sha1s = []
677
+ jobs_to_schedule = []
678
+ jobs_missing_packages = []
577
679
  while backtrack <= limit:
578
680
  jobs_missing_packages, jobs_to_schedule = \
579
- self.collect_jobs(arch,
580
- util.filter_configs(configs,
581
- filter_in=self.args.filter_in,
582
- filter_out=self.args.filter_out,
583
- filter_all=self.args.filter_all,
584
- filter_fragments=self.args.filter_fragments,
585
- suite_name=suite_name),
586
- self.args.newest, job_limit)
681
+ self.collect_jobs(arch, configs, self.args.newest, job_limit)
587
682
  if jobs_missing_packages and self.args.newest:
588
- new_sha1 = \
589
- util.find_git_parent('ceph', self.base_config.sha1)
590
- if new_sha1 is None:
591
- util.schedule_fail('Backtrack for --newest failed', name)
592
- # rebuild the base config to resubstitute sha1
593
- self.config_input['ceph_hash'] = new_sha1
683
+ if not sha1s:
684
+ sha1s = util.find_git_parents('ceph', str(self.base_config.sha1), self.args.newest)
685
+ if not sha1s:
686
+ util.schedule_fail('Backtrack for --newest failed', name, dry_run=self.args.dry_run)
687
+ self.config_input['ceph_hash'] = sha1s.pop(0)
594
688
  self.base_config = self.build_base_config()
595
689
  backtrack += 1
596
690
  continue
@@ -603,31 +697,39 @@ class Run(object):
603
697
  util.schedule_fail(
604
698
  'Exceeded %d backtracks; raise --newest value' % limit,
605
699
  name,
700
+ dry_run=self.args.dry_run,
606
701
  )
607
702
 
608
- if self.args.dry_run:
609
- log.debug("Base job config:\n%s" % self.base_config)
610
-
611
703
  with open(base_yaml_path, 'w+b') as base_yaml:
612
704
  base_yaml.write(str(self.base_config).encode())
613
705
 
614
706
  if jobs_to_schedule:
615
707
  self.write_rerun_memo()
616
708
 
709
+ # Before scheduling jobs, check the priority
710
+ if self.args.priority and jobs_to_schedule and not self.args.force_priority:
711
+ self.check_priority(len(jobs_to_schedule))
712
+
713
+ self.check_num_jobs(len(jobs_to_schedule))
714
+
617
715
  self.schedule_jobs(jobs_missing_packages, jobs_to_schedule, name)
618
716
 
619
717
  os.remove(base_yaml_path)
620
718
 
621
719
  count = len(jobs_to_schedule)
622
720
  missing_count = len(jobs_missing_packages)
721
+ total_count = count
722
+ if self.args.num:
723
+ total_count *= self.args.num
623
724
  log.info(
624
725
  'Suite %s in %s scheduled %d jobs.' %
625
726
  (suite_name, suite_path, count)
626
727
  )
627
728
  log.info('%d/%d jobs were filtered out.',
628
- (len(configs) - count),
629
- len(configs))
729
+ (generated - count),
730
+ generated)
630
731
  if missing_count:
631
- log.warn('Scheduled %d/%d jobs that are missing packages!',
732
+ log.warning('Scheduled %d/%d jobs that are missing packages!',
632
733
  missing_count, count)
734
+ log.info('Scheduled %d jobs in total.', total_count)
633
735
  return count