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.
- scripts/node_cleanup.py +18 -2
- scripts/suite.py +2 -0
- teuthology/__init__.py +0 -1
- teuthology/config.py +28 -7
- teuthology/dispatcher/supervisor.py +9 -6
- teuthology/lock/cli.py +4 -2
- teuthology/lock/ops.py +10 -9
- teuthology/lock/query.py +28 -4
- teuthology/lock/util.py +1 -1
- teuthology/misc.py +13 -58
- teuthology/openstack/__init__.py +202 -176
- teuthology/openstack/setup-openstack.sh +52 -27
- teuthology/orchestra/opsys.py +15 -0
- teuthology/orchestra/remote.py +54 -2
- teuthology/orchestra/run.py +8 -2
- teuthology/provision/downburst.py +84 -43
- teuthology/provision/fog.py +2 -2
- teuthology/repo_utils.py +3 -1
- teuthology/run.py +1 -1
- teuthology/scrape.py +5 -2
- teuthology/suite/merge.py +3 -1
- teuthology/suite/run.py +51 -37
- teuthology/suite/util.py +2 -2
- teuthology/task/install/rpm.py +8 -16
- teuthology/task/internal/__init__.py +2 -1
- teuthology/task/internal/syslog.py +17 -13
- teuthology/task/kernel.py +1 -1
- {teuthology-1.2.1.dist-info → teuthology-1.2.2.dist-info}/METADATA +10 -8
- {teuthology-1.2.1.dist-info → teuthology-1.2.2.dist-info}/RECORD +36 -36
- {teuthology-1.2.1.dist-info → teuthology-1.2.2.dist-info}/WHEEL +1 -1
- {teuthology-1.2.1.data → teuthology-1.2.2.data}/scripts/adjust-ulimits +0 -0
- {teuthology-1.2.1.data → teuthology-1.2.2.data}/scripts/daemon-helper +0 -0
- {teuthology-1.2.1.data → teuthology-1.2.2.data}/scripts/stdin-killer +0 -0
- {teuthology-1.2.1.dist-info → teuthology-1.2.2.dist-info}/entry_points.txt +0 -0
- {teuthology-1.2.1.dist-info → teuthology-1.2.2.dist-info/licenses}/LICENSE +0 -0
- {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
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
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
|
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 "
|
183
|
-
-e "s|^job_log_href_templ =
|
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
|
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
|
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
|
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
|
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 !
|
505
|
-
echo
|
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
|
-
|
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
|
724
|
-
ip=$(ip
|
725
|
-
|
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
|
-
|
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
|
teuthology/orchestra/opsys.py
CHANGED
@@ -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
|
|
teuthology/orchestra/remote.py
CHANGED
@@ -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'):
|
teuthology/orchestra/run.py
CHANGED
@@ -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.
|
111
|
-
|
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
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
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
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
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
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
296
|
-
|
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
|
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
|
-
|
319
|
-
return
|
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
|
363
|
+
log.info('Using default values for supported os_type/os_version or values from previous call...')
|
364
|
+
return _known_downburst_distros
|
teuthology/provision/fog.py
CHANGED
@@ -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
|
123
|
-
self.log.error("
|
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.
|
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.
|
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
|
-
|
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
|
-
|
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:
|