teuthology 1.2.1__py3-none-any.whl → 1.2.2__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 (36) hide show
  1. scripts/node_cleanup.py +18 -2
  2. scripts/suite.py +2 -0
  3. teuthology/__init__.py +0 -1
  4. teuthology/config.py +28 -7
  5. teuthology/dispatcher/supervisor.py +9 -6
  6. teuthology/lock/cli.py +4 -2
  7. teuthology/lock/ops.py +10 -9
  8. teuthology/lock/query.py +28 -4
  9. teuthology/lock/util.py +1 -1
  10. teuthology/misc.py +13 -58
  11. teuthology/openstack/__init__.py +202 -176
  12. teuthology/openstack/setup-openstack.sh +52 -27
  13. teuthology/orchestra/opsys.py +15 -0
  14. teuthology/orchestra/remote.py +54 -2
  15. teuthology/orchestra/run.py +8 -2
  16. teuthology/provision/downburst.py +84 -43
  17. teuthology/provision/fog.py +2 -2
  18. teuthology/repo_utils.py +3 -1
  19. teuthology/run.py +1 -1
  20. teuthology/scrape.py +5 -2
  21. teuthology/suite/merge.py +3 -1
  22. teuthology/suite/run.py +51 -37
  23. teuthology/suite/util.py +2 -2
  24. teuthology/task/install/rpm.py +8 -16
  25. teuthology/task/internal/__init__.py +2 -1
  26. teuthology/task/internal/syslog.py +17 -13
  27. teuthology/task/kernel.py +1 -1
  28. {teuthology-1.2.1.dist-info → teuthology-1.2.2.dist-info}/METADATA +10 -8
  29. {teuthology-1.2.1.dist-info → teuthology-1.2.2.dist-info}/RECORD +36 -36
  30. {teuthology-1.2.1.dist-info → teuthology-1.2.2.dist-info}/WHEEL +1 -1
  31. {teuthology-1.2.1.data → teuthology-1.2.2.data}/scripts/adjust-ulimits +0 -0
  32. {teuthology-1.2.1.data → teuthology-1.2.2.data}/scripts/daemon-helper +0 -0
  33. {teuthology-1.2.1.data → teuthology-1.2.2.data}/scripts/stdin-killer +0 -0
  34. {teuthology-1.2.1.dist-info → teuthology-1.2.2.dist-info}/entry_points.txt +0 -0
  35. {teuthology-1.2.1.dist-info → teuthology-1.2.2.dist-info/licenses}/LICENSE +0 -0
  36. {teuthology-1.2.1.dist-info → teuthology-1.2.2.dist-info}/top_level.txt +0 -0
@@ -41,6 +41,8 @@ function create_config() {
41
41
  local server_group="${11}"
42
42
  local worker_group="${12}"
43
43
  local package_repo="${13}"
44
+ local teuthology_branch="${14}"
45
+ local teuthology_git_url="${15}"
44
46
 
45
47
  if test "$network" ; then
46
48
  network="network: $network"
@@ -65,6 +67,8 @@ queue_host: localhost
65
67
  lab_domain: $labdomain
66
68
  max_job_time: 32400 # 9 hours
67
69
  teuthology_path: .
70
+ teuthology_branch: $teuthology_branch
71
+ teuthology_git_url: $teuthology_git_url
68
72
  canonical_tags: $canonical_tags
69
73
  openstack:
70
74
  clone: git clone http://github.com/ceph/teuthology
@@ -113,13 +117,23 @@ function apt_get_update() {
113
117
  }
114
118
 
115
119
  function setup_docker() {
116
- if test -f /etc/apt/sources.list.d/docker.list ; then
117
- echo "OK docker is installed"
118
- else
119
- sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D
120
- echo deb https://apt.dockerproject.org/repo ubuntu-trusty main | sudo tee -a /etc/apt/sources.list.d/docker.list
121
- sudo apt-get -qq install -y docker-engine
120
+ source /etc/os-release
121
+ if ! $VERSION_CODENAME; then
122
+ echo "ERROR: VERSION_CODENAME is not set. Cannot proceed with Docker installation."
123
+ return
124
+ fi
125
+ if !command -v docker &> /dev/null; then
126
+ curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \
127
+ sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
128
+ echo \
129
+ "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \
130
+ https://download.docker.com/linux/ubuntu $VERSION_CODENAME stable" | \
131
+ sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
132
+ sudo apt-get update
133
+ sudo apt-get install -y docker-ce docker-ce-cli containerd.io
122
134
  echo "INSTALLED docker"
135
+ else
136
+ echo "OK docker is installed"
123
137
  fi
124
138
  }
125
139
 
@@ -163,7 +177,7 @@ function setup_paddles() {
163
177
  git clone https://github.com/ceph/paddles.git $paddles_dir || return 1
164
178
  fi
165
179
 
166
- sudo apt-get -qq install -y --force-yes beanstalkd postgresql postgresql-contrib postgresql-server-dev-all supervisor
180
+ sudo apt-get -qq install -y beanstalkd postgresql postgresql-contrib postgresql-server-dev-all supervisor
167
181
 
168
182
  if ! sudo /etc/init.d/postgresql status ; then
169
183
  sudo mkdir -p /etc/postgresql
@@ -179,10 +193,10 @@ function setup_paddles() {
179
193
  cd $paddles_dir || return 1
180
194
  git pull --rebase
181
195
  git clean -ffqdx
182
- sed -e "s|^address.*|address = 'http://localhost'|" \
183
- -e "s|^job_log_href_templ = 'http://qa-proxy.ceph.com/teuthology|job_log_href_templ = 'http://$public_ip|" \
196
+ sed -e "/^address = os.environ.get(/,/^)/c\address = os.environ.get('PADDLES_ADDRESS', 'http://localhost')" \
197
+ -e "s|^job_log_href_templ = os.environ.get(.*)|job_log_href_templ = os.environ.get('PADDLES_JOB_LOG_HREF_TEMPL', 'http://$public_ip')|" \
184
198
  -e "/sqlite/d" \
185
- -e "s|.*'postgresql+psycop.*'|'url': 'postgresql://paddles:paddles@localhost/paddles'|" \
199
+ -e "s|^ *'url':.*|'url': 'postgresql://paddles:paddles@localhost/paddles',|" \
186
200
  -e "s/'host': '127.0.0.1'/'host': '0.0.0.0'/" \
187
201
  < config.py.in > config.py
188
202
  virtualenv ./virtualenv
@@ -258,7 +272,7 @@ function setup_pulpito() {
258
272
  git clone https://github.com/ceph/pulpito.git $pulpito_dir || return 1
259
273
  fi
260
274
 
261
- sudo apt-get -qq install -y --force-yes nginx
275
+ sudo apt-get -qq install -y nginx
262
276
  local nginx_conf=/etc/nginx/sites-available/default
263
277
  sudo sed -i '/text\/plain/a\ text\/plain log;' \
264
278
  /etc/nginx/mime.types
@@ -278,7 +292,7 @@ function setup_pulpito() {
278
292
  virtualenv ./virtualenv
279
293
  source ./virtualenv/bin/activate
280
294
  pip install --upgrade pip
281
- pip install 'setuptools==18.2.0'
295
+ pip install 'setuptools>=58.0.0'
282
296
  pip install -r requirements.txt
283
297
  python run.py &
284
298
  )
@@ -408,7 +422,7 @@ function setup_dnsmasq() {
408
422
 
409
423
  if ! test -f /etc/dnsmasq.d/resolv ; then
410
424
  resolver=$(grep nameserver /etc/resolv.conf | head -1 | perl -ne 'print $1 if(/\s*nameserver\s+([\d\.]+)/)')
411
- sudo apt-get -qq install -y --force-yes dnsmasq resolvconf
425
+ sudo apt-get -qq install -y dnsmasq resolvconf
412
426
  # FIXME: this opens up dnsmasq to DNS reflection/amplification attacks, and can be reverted
413
427
  # FIXME: once we figure out how to configure dnsmasq to accept DNS queries from all subnets
414
428
  sudo perl -pi -e 's/--local-service//' /etc/init.d/dnsmasq
@@ -500,22 +514,27 @@ function remove_images() {
500
514
  }
501
515
 
502
516
  function install_packages() {
503
-
504
- if ! test -f /etc/apt/sources.list.d/trusty-backports.list ; then
505
- echo deb http://archive.ubuntu.com/ubuntu trusty-backports main universe | sudo tee /etc/apt/sources.list.d/trusty-backports.list
517
+ source /etc/os-release
518
+ if ! $VERSION_CODENAME; then
519
+ echo "ERROR: VERSION_CODENAME is not set. Cannot proceed with Docker installation."
520
+ return
521
+ fi
522
+ local codename=$VERSION_CODENAME
523
+ local backports_file="/etc/apt/sources.list.d/${codename}-backports.list"
524
+ if [ ! -f "$backports_file" ]; then
525
+ echo "Adding backports repo for $codename..."
526
+ echo "deb http://archive.ubuntu.com/ubuntu ${codename}-backports main universe" | sudo tee "$backports_file"
506
527
  sudo apt-get update
507
528
  fi
508
-
509
- local packages="jq realpath curl"
510
- sudo apt-get -qq install -y --force-yes $packages
511
-
529
+ local packages="jq curl"
530
+ sudo apt-get -qq install -y $packages
512
531
  echo "INSTALL required packages $packages"
513
532
  }
514
533
 
515
534
  CAT=${CAT:-cat}
516
535
 
517
536
  function verify_openstack() {
518
- if ! openstack server list > /dev/null ; then
537
+ if ! openstack server list --format json > /dev/null ; then
519
538
  echo ERROR: the credentials from ~/openrc.sh are not working >&2
520
539
  return 1
521
540
  fi
@@ -701,13 +720,14 @@ function main() {
701
720
  # assume the first available IPv4 subnet is going to be used to assign IP to the instance
702
721
  #
703
722
  [ -z "$network" ] && {
704
- local default_subnets=$(openstack subnet list --ip-version 4 -f json | jq -r '.[] | .Subnet' | sort | uniq)
723
+ local default_subnets=$(openstack subnet list --ip-version 4 -f json | jq -r '.[] | select(.Name != null) | .Subnet' | sort | uniq)
705
724
  } || {
706
725
  local network_id=$(openstack network list -f json | jq -r ".[] | select(.Name == \"$network\") | .ID")
707
726
  local default_subnets=$(openstack subnet list --ip-version 4 -f json \
708
727
  | jq -r ".[] | select(.Network == \"$network_id\") | .Subnet" | sort | uniq)
709
728
  }
710
729
  subnets=$(echo $subnets $default_subnets)
730
+ echo "subnets: $subnets"
711
731
 
712
732
  case $provider in
713
733
  entercloudsuite)
@@ -720,16 +740,21 @@ function main() {
720
740
  esac
721
741
 
722
742
  local ip
723
- for dev in eth0 ens3 ; do
724
- ip=$(ip a show dev $dev 2>/dev/null | sed -n "s:.*inet \(.*\)/.*:\1:p")
725
- test "$ip" && break
743
+ for dev in $(ip -o link show | awk -F': ' '{print $2}' | grep -v '^lo$'); do
744
+ ip=$(ip -4 addr show dev "$dev" | awk '/inet / {print $2}' | cut -d/ -f1)
745
+ if [ -n "$ip" ]; then
746
+ nameserver="$ip"
747
+ break
748
+ fi
726
749
  done
727
- : ${nameserver:=$ip}
750
+
751
+ local teuthology_branch="$(git -C $(dirname $0)/../../../teuthology rev-parse --abbrev-ref HEAD)"
752
+ local teuthology_git_url="$(git -C $(dirname $0)/../../../teuthology config --get remote.origin.url)"
728
753
 
729
754
  if $do_create_config ; then
730
755
  create_config "$network" "$subnets" "$nameserver" "$labdomain" "$ip" \
731
756
  "$archive_upload" "$canonical_tags" "$selfname" "$keypair" \
732
- "$server_name" "$server_group" "$worker_group" "$package_repo" || return 1
757
+ "$server_name" "$server_group" "$worker_group" "$package_repo" "$teuthology_branch" "$teuthology_git_url" || return 1
733
758
  setup_ansible "$subnets" $labdomain || return 1
734
759
  setup_ssh_config || return 1
735
760
  setup_authorized_keys || return 1
@@ -34,6 +34,16 @@ DISTRO_CODENAME_MAP = {
34
34
  "7": "maipo",
35
35
  "6": "santiago",
36
36
  },
37
+ "alma": {
38
+ "8.10": "alma",
39
+ "9.6": "alma",
40
+ "10.0": "alma",
41
+ },
42
+ "rocky": {
43
+ "8.10": "rocky",
44
+ "9.6": "rocky",
45
+ "10.0": "rocky",
46
+ },
37
47
  "centos": {
38
48
  "10": "stream",
39
49
  "9": "stream",
@@ -53,6 +63,7 @@ DISTRO_CODENAME_MAP = {
53
63
  "20": "heisenbug",
54
64
  },
55
65
  "opensuse": {
66
+ "1.0": "tumbleweed",
56
67
  "15.0": "leap",
57
68
  "15.1": "leap",
58
69
  "15.2": "leap",
@@ -82,6 +93,8 @@ DEFAULT_OS_VERSION = dict(
82
93
  opensuse="15.4",
83
94
  sle="15.2",
84
95
  rhel="8.6",
96
+ rocky="9.6",
97
+ alma="9.6",
85
98
  debian='8.0'
86
99
  )
87
100
 
@@ -187,6 +200,8 @@ class OS(object):
187
200
  elif name == 'centos':
188
201
  if parse_version(version) >= Version("8.0"):
189
202
  version = f"{version}.stream"
203
+ elif name == 'almalinux':
204
+ name = 'alma'
190
205
  obj = cls(name=name, version=version)
191
206
  return obj
192
207
 
@@ -265,10 +265,13 @@ class RemoteShell(object):
265
265
 
266
266
 
267
267
  def write_file(self, path, data, sudo=False, mode=None, owner=None,
268
- mkdir=False, append=False):
268
+ mkdir=False, append=False, bs=None,
269
+ offset=None, sync=False):
269
270
  """
270
271
  Write data to remote file
271
272
 
273
+ The data written in 512-byte blocks, provide `bs` to use bigger blocks.
274
+
272
275
  :param path: file path on remote host
273
276
  :param data: str, binary or fileobj to be written
274
277
  :param sudo: use sudo to write file, defaults False
@@ -276,11 +279,20 @@ class RemoteShell(object):
276
279
  :param owner: set file owner if provided
277
280
  :param mkdir: preliminary create the file directory, defaults False
278
281
  :param append: append data to the file, defaults False
282
+ :param bs: write up to N bytes at a time if provided, default is 512 in `dd`
283
+ :param offset: number of bs blocks to seek to in file, defaults 0
284
+ :param sync: sync file after write is complete if provided
279
285
  """
280
286
  dd = 'sudo dd' if sudo else 'dd'
281
287
  args = dd + ' of=' + path
282
288
  if append:
283
289
  args += ' conv=notrunc oflag=append'
290
+ if bs:
291
+ args += ' bs=' + str(bs)
292
+ if offset:
293
+ args += ' seek=' + str(offset)
294
+ if sync:
295
+ args += ' conv=sync'
284
296
  if mkdir:
285
297
  mkdirp = 'sudo mkdir -p' if sudo else 'mkdir -p'
286
298
  dirpath = os.path.dirname(path)
@@ -396,7 +408,7 @@ class Remote(RemoteShell):
396
408
  self.ssh.close()
397
409
  if not timeout:
398
410
  return self._reconnect(timeout=socket_timeout)
399
- action = "reconnect to {self.shortname}"
411
+ action = f"reconnect to {self.shortname}"
400
412
  with safe_while(action=action, timeout=timeout, increment=3, _raise=False) as proceed:
401
413
  success = False
402
414
  while proceed():
@@ -449,6 +461,46 @@ class Remote(RemoteShell):
449
461
  return
450
462
  raise RuntimeError("Could not determine interface/CIDR!")
451
463
 
464
+
465
+ def resolve_ip(self, name=None, ipv='4') -> str:
466
+ """
467
+ Resolve IP address of the remote host via remote host
468
+
469
+ Because remote object maybe behind bastion host we need
470
+ the remote host address resolvable from remote side.
471
+ So in order to the ip address we just call `host` remotely
472
+ and parse output like:
473
+ 'smithi001.front.sepia.ceph.com has address 172.21.15.1\n'
474
+
475
+ :param name: hostname to resolve, by defaults remote host itself.
476
+ :param ipv: the IP version, 4 or 6, defaults to 4.
477
+
478
+ :raises: :class:`Exception`: when the hostname cannot be resolved.
479
+ :raises: :class:`ValueError`: when the ipv argument mismatch 4 or 6.
480
+
481
+ :returns: str object, the ip addres of the remote host.
482
+ """
483
+ hostname = name or self.hostname
484
+ version = str(ipv)
485
+ if version in ['4', '6']:
486
+ remote_host_ip = self.sh(f'host -{ipv} {hostname}')
487
+ else:
488
+ raise ValueError(f'Unknown IP version {ipv}, expected 4 or 6')
489
+ # `host -4` or `host -6` may have multiline output: a host can have
490
+ # several address; thus try and find the first suitable
491
+ for info in remote_host_ip.split("\n"):
492
+ if version == '4' and 'has address' in info:
493
+ (host, ip) = info.strip().split(' has address ')
494
+ if hostname in host:
495
+ return ip
496
+ elif version == '6' and 'has IPv6 address' in info:
497
+ (host, ip) = info.strip().split(' has IPv6 address ')
498
+ if hostname in host:
499
+ return ip
500
+ else:
501
+ raise Exception(f'Cannot get IPv{ipv} address for the host "{hostname}" via remote "{self.hostname}"')
502
+
503
+
452
504
  @property
453
505
  def hostname(self):
454
506
  if not hasattr(self, '_hostname'):
@@ -242,15 +242,21 @@ class Raw(object):
242
242
 
243
243
  def quote(args):
244
244
  """
245
- Internal quote wrapper.
245
+ Internal quote wrapper. None arguments are not allowed.
246
+
247
+ :param args: list of str or Raw objects
248
+
249
+ :raises: :class:`RuntimeError`: if one of the args is None.
246
250
  """
247
251
  def _quote(args):
248
252
  """
249
253
  Handle quoted string, testing for raw charaters.
250
254
  """
251
- for a in args:
255
+ for i, a in enumerate(args):
252
256
  if isinstance(a, Raw):
253
257
  yield a.value
258
+ elif a is None:
259
+ raise RuntimeError(f"Argument at position {i} is None: {args}")
254
260
  else:
255
261
  yield shlex.quote(a)
256
262
  if isinstance(args, list):
@@ -14,6 +14,15 @@ from teuthology.lock import query
14
14
  log = logging.getLogger(__name__)
15
15
 
16
16
 
17
+ def get_types():
18
+ types = ['vps']
19
+ if 'downburst' in config and 'machine' in config.downburst:
20
+ machine = config.downburst.get('machine')
21
+ if isinstance(machine, list):
22
+ types = list(m.get('type') for m in machine)
23
+ return types
24
+
25
+
17
26
  def downburst_executable():
18
27
  """
19
28
  First check for downburst in the user's path.
@@ -107,8 +116,9 @@ class Downburst(object):
107
116
  self.destroy()
108
117
  else:
109
118
  success = False
110
- log.info("Downburst failed on %s: %s" % (
111
- self.name, stderr.strip()))
119
+ log.error("Downburst failed on %s" % self.name)
120
+ for i in stderr.split('\n'):
121
+ log.error(f">>> {i}")
112
122
  break
113
123
  return success
114
124
 
@@ -185,24 +195,32 @@ class Downburst(object):
185
195
  os_version = self.os_version.lower()
186
196
 
187
197
  mac_address = self.status['mac_address']
188
- defaults = dict(
189
- downburst=dict(
190
- machine=dict(
191
- disk=os.environ.get('DOWNBURST_DISK_SIZE', '100G'),
192
- ram=os.environ.get('DOWNBURST_RAM_SIZE', '3.8G'),
193
- cpus=int(os.environ.get('DOWNBURST_CPUS', 1)),
194
- volumes=dict(
195
- count=int(os.environ.get('DOWNBURST_EXTRA_DISK_NUMBER', 4)),
196
- size=os.environ.get('DOWNBURST_EXTRA_DISK_SIZE', '100G'),
197
- ),
198
- ),
199
- )
198
+ machine = dict(
199
+ disk=os.environ.get('DOWNBURST_DISK_SIZE', '100G'),
200
+ ram=os.environ.get('DOWNBURST_RAM_SIZE', '3.8G'),
201
+ cpus=int(os.environ.get('DOWNBURST_CPUS', 1)),
202
+ volumes=dict(
203
+ count=int(os.environ.get('DOWNBURST_EXTRA_DISK_NUMBER', 4)),
204
+ size=os.environ.get('DOWNBURST_EXTRA_DISK_SIZE', '100G'),
205
+ ),
200
206
  )
201
- downburst_config = defaults['downburst']
202
- if config.downburst and isinstance(config.downburst, dict):
203
- deep_merge(downburst_config, config.downburst)
204
- log.debug('downburst_config: %s', downburst_config)
205
- machine = downburst_config['machine']
207
+ def belongs_machine_type(machine_config: dict, machine_type: str) -> bool:
208
+ if isinstance(machine_config, dict):
209
+ t = machine_config.get('type', None)
210
+ if isinstance(t, str):
211
+ return machine_type == t
212
+ elif isinstance(t, list):
213
+ return machine_type in t
214
+ return False
215
+ if isinstance(config.downburst, dict) and isinstance(config.downburst.get('machine'), list):
216
+ machine_type = self.status['machine_type']
217
+ machine_config = next((m for m in config.downburst.get('machine')
218
+ if belongs_machine_type(m, machine_type)), None)
219
+ if machine_config is None:
220
+ raise RuntimeError(f"Cannot find config for machine type {machine_type}.")
221
+ elif isinstance(config.downburst, dict) and isinstance(config.downburst.get('machine'), dict):
222
+ machine_config = config.downburst.get('machine')
223
+ deep_merge(machine, machine_config)
206
224
  log.debug('Using machine config: %s', machine)
207
225
  file_info = {
208
226
  'disk-size': machine['disk'],
@@ -245,18 +263,30 @@ class Downburst(object):
245
263
  'git',
246
264
  'wget',
247
265
  ])
266
+ if os_type in ('centos', 'opensuse'):
267
+ user_info['packages'].extend([
268
+ 'chrony',
269
+ ])
270
+ if os_type in ('ubuntu', 'debian'):
271
+ user_info['packages'].extend([
272
+ 'ntp',
273
+ ])
274
+
248
275
  # On CentOS/RHEL/Fedora, write the correct mac address and
249
- # install redhab-lsb-core for `lsb_release`
250
276
  if os_type in ['centos', 'rhel', 'fedora']:
251
277
  user_info['runcmd'].extend([
252
278
  ['sed', '-ie', 's/HWADDR=".*"/HWADDR="%s"/' % mac_address,
253
279
  '/etc/sysconfig/network-scripts/ifcfg-eth0'],
254
280
  ])
255
- user_info['packages'].append('redhat-lsb-core')
256
281
  # On Ubuntu, starting with 16.04, and Fedora, starting with 24, we need
257
282
  # to install 'python' to get python2.7, which ansible needs
258
283
  if os_type in ('ubuntu', 'fedora'):
259
284
  user_info['packages'].append('python')
285
+ if os_type in ('centos'):
286
+ user_info['packages'].extend([
287
+ 'python3-pip',
288
+ 'bind-utils',
289
+ ])
260
290
  user_fd = tempfile.NamedTemporaryFile(delete=False, mode='wt')
261
291
  user_str = "#cloud-config\n" + yaml.safe_dump(user_info)
262
292
  user_fd.write(user_str)
@@ -281,43 +311,54 @@ class Downburst(object):
281
311
  self.remove_config()
282
312
 
283
313
 
314
+ _known_downburst_distros = {
315
+ 'rhel_minimal': ['6.4', '6.5'],
316
+ 'centos': ['9.stream', '10.stream'],
317
+ 'centos_minimal': ['6.4', '6.5'],
318
+ 'debian': ['6.0', '7.0', '7.9', '8.0'],
319
+ 'fedora': ['41', '42'],
320
+ 'opensuse': ['1.0(tumbleweed)',
321
+ '15.5(leap)', '15.6(leap)',
322
+ '16.0(leap)',
323
+ ],
324
+ 'sles': ['12-sp3', '15-sp1', '15-sp2'],
325
+ 'alma': ['10.0', '8.10', '9.6'],
326
+ 'rocky': ['10.0', '8.10', '9.6'],
327
+ 'ubuntu': ['20.04(focal)', '20.10(groovy)',
328
+ '21.04(hirsute)', '21.10(impish)',
329
+ '22.04(jammy)', '22.10(kinetic)',
330
+ '23.04(lunar)', '23.10(mantic)',
331
+ '24.04(noble)', '24.10(oracular)',
332
+ '25.04(plucky)',
333
+ ],
334
+ }
335
+
284
336
  def get_distro_from_downburst():
285
337
  """
286
338
  Return a table of valid distros.
287
339
 
288
340
  If downburst is in path use it. If either downburst is unavailable,
289
341
  or if downburst is unable to produce a json list, then use a default
290
- table.
342
+ table or a table from previous successful call.
291
343
  """
292
- default_table = {'rhel_minimal': ['6.4', '6.5'],
293
- 'fedora': ['17', '18', '19', '20', '22'],
294
- 'centos': ['6.3', '6.4', '6.5', '7.0',
295
- '7.2', '7.4', '8.2'],
296
- 'centos_minimal': ['6.4', '6.5'],
297
- 'ubuntu': ['8.04(hardy)', '9.10(karmic)',
298
- '10.04(lucid)', '10.10(maverick)',
299
- '11.04(natty)', '11.10(oneiric)',
300
- '12.04(precise)', '12.10(quantal)',
301
- '13.04(raring)', '13.10(saucy)',
302
- '14.04(trusty)', 'utopic(utopic)',
303
- '16.04(xenial)', '18.04(bionic)',
304
- '20.04(focal)'],
305
- 'sles': ['12-sp3', '15-sp1', '15-sp2'],
306
- 'opensuse': ['12.3', '15.1', '15.2'],
307
- 'debian': ['6.0', '7.0', '8.0']}
344
+ # because sometimes downburst fails to complete list-json
345
+ # due to temporary issues with vendor site accessibility
346
+ # we cache known downburst distros from previous call
347
+ # to be reused in such cases of outage
348
+ global _known_downburst_distros
308
349
  executable_cmd = downburst_executable()
309
350
  environment_dict = downburst_environment()
310
351
  if not executable_cmd:
311
352
  log.warning("Downburst not found!")
312
353
  log.info('Using default values for supported os_type/os_version')
313
- return default_table
354
+ return _known_downburst_distros
314
355
  try:
315
356
  log.debug(executable_cmd)
316
357
  output = subprocess.check_output([executable_cmd, 'list-json'],
317
358
  env=environment_dict)
318
- downburst_data = json.loads(output)
319
- return downburst_data
359
+ _known_downburst_distros = json.loads(output)
360
+ return _known_downburst_distros
320
361
  except (subprocess.CalledProcessError, OSError):
321
362
  log.exception("Error calling downburst!")
322
- log.info('Using default values for supported os_type/os_version')
323
- return default_table
363
+ log.info('Using default values for supported os_type/os_version or values from previous call...')
364
+ return _known_downburst_distros
@@ -119,8 +119,8 @@ class FOG(object):
119
119
  )
120
120
  prepped = req.prepare()
121
121
  resp = requests.Session().send(prepped)
122
- if not resp.ok and resp.text:
123
- self.log.error("%s: %s", resp.status_code, resp.text)
122
+ if not resp.ok:
123
+ self.log.error(f"Got status {resp.status_code} from {url_suffix}: '{resp.text}'")
124
124
  if verify:
125
125
  resp.raise_for_status()
126
126
  return resp
teuthology/repo_utils.py CHANGED
@@ -51,6 +51,8 @@ def build_git_url(project, project_owner='ceph'):
51
51
  base = config.get_ceph_cm_ansible_git_url()
52
52
  elif project == 'ceph':
53
53
  base = config.get_ceph_git_url()
54
+ elif project == 'teuthology':
55
+ base = config.get_teuthology_git_url()
54
56
  else:
55
57
  base = 'https://github.com/{project_owner}/{project}'
56
58
  url_templ = re.sub(r'\.git$', '', base)
@@ -435,7 +437,7 @@ def fetch_teuthology(branch, commit=None, lock=True):
435
437
  :param commit: The sha1 to checkout. Defaults to None, which uses HEAD of the branch.
436
438
  :returns: The destination path
437
439
  """
438
- url = config.ceph_git_base_url + 'teuthology.git'
440
+ url = config.get_teuthology_git_url()
439
441
  return fetch_repo(url, branch, commit, bootstrap_teuthology, lock)
440
442
 
441
443
 
teuthology/run.py CHANGED
@@ -402,7 +402,7 @@ def main(args):
402
402
  # FIXME this should become more generic, and the keys should use
403
403
  # '_' uniformly
404
404
  if fake_ctx.config.get('interactive-on-error'):
405
- teuth_config.config.ctx = fake_ctx
405
+ teuth_config.ctx = fake_ctx
406
406
 
407
407
  try:
408
408
  run_tasks(tasks=config['tasks'], ctx=fake_ctx)
teuthology/scrape.py CHANGED
@@ -3,7 +3,7 @@
3
3
 
4
4
  import difflib
5
5
  from errno import ENOENT
6
- from gzip import GzipFile
6
+ import gzip
7
7
  import sys
8
8
  import os
9
9
  import yaml
@@ -361,6 +361,8 @@ class Job(object):
361
361
  return
362
362
 
363
363
  for line in grep(tlog_path, "command crashed with signal"):
364
+ if not line:
365
+ continue
364
366
  log.debug("Found a crash indication: {0}".format(line))
365
367
  # tasks.ceph.osd.1.plana82.stderr
366
368
  match = re.search(r"tasks.ceph.([^\.]+).([^\.]+).([^\.]+).stderr", line)
@@ -387,7 +389,8 @@ class Job(object):
387
389
  ))
388
390
  continue
389
391
 
390
- bt, ass = self._search_backtrace(GzipFile(gzipped_log_path))
392
+ with gzip.open(gzipped_log_path, 'rt', errors='ignore') as f:
393
+ bt, ass = self._search_backtrace(f)
391
394
  if ass and not self.assertion:
392
395
  self.assertion = ass
393
396
  if bt:
teuthology/suite/merge.py CHANGED
@@ -5,6 +5,7 @@ import os
5
5
  from types import MappingProxyType
6
6
  import yaml
7
7
 
8
+ from teuthology.config import JobConfig
8
9
  from teuthology.suite.build_matrix import combine_path
9
10
  from teuthology.suite.util import strip_fragment_path
10
11
  from teuthology.misc import deep_merge
@@ -115,6 +116,7 @@ def config_merge(configs, suite_name=None, **kwargs):
115
116
  the entire job (config) from the list.
116
117
  """
117
118
  seed = kwargs.setdefault('seed', 1)
119
+ base_config = kwargs.setdefault('base_config', JobConfig())
118
120
  if not isinstance(seed, int):
119
121
  log.debug("no valid seed input: using 1")
120
122
  seed = 1
@@ -128,7 +130,7 @@ def config_merge(configs, suite_name=None, **kwargs):
128
130
  if suite_name is not None:
129
131
  desc = combine_path(suite_name, desc)
130
132
 
131
- yaml_complete_obj = {}
133
+ yaml_complete_obj = copy.deepcopy(base_config.to_dict())
132
134
  deep_merge(yaml_complete_obj, dict(TEUTHOLOGY_TEMPLATE))
133
135
  for path in paths:
134
136
  if path not in yaml_cache: