teuthology 1.0.0__py3-none-any.whl → 1.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- scripts/describe.py +1 -0
- scripts/dispatcher.py +62 -0
- scripts/exporter.py +18 -0
- scripts/lock.py +1 -1
- scripts/node_cleanup.py +58 -0
- scripts/openstack.py +9 -9
- scripts/results.py +12 -11
- scripts/run.py +4 -0
- scripts/schedule.py +4 -0
- scripts/suite.py +61 -16
- scripts/supervisor.py +44 -0
- scripts/update_inventory.py +10 -4
- scripts/wait.py +31 -0
- teuthology/__init__.py +24 -21
- teuthology/beanstalk.py +4 -3
- teuthology/config.py +17 -6
- teuthology/contextutil.py +18 -14
- teuthology/describe_tests.py +25 -18
- teuthology/dispatcher/__init__.py +365 -0
- teuthology/dispatcher/supervisor.py +374 -0
- teuthology/exceptions.py +54 -0
- teuthology/exporter.py +347 -0
- teuthology/kill.py +76 -75
- teuthology/lock/cli.py +16 -7
- teuthology/lock/ops.py +276 -70
- teuthology/lock/query.py +61 -44
- teuthology/ls.py +9 -18
- teuthology/misc.py +152 -137
- teuthology/nuke/__init__.py +12 -351
- teuthology/openstack/__init__.py +4 -3
- teuthology/openstack/openstack-centos-7.0-user-data.txt +1 -1
- teuthology/openstack/openstack-centos-7.1-user-data.txt +1 -1
- teuthology/openstack/openstack-centos-7.2-user-data.txt +1 -1
- teuthology/openstack/openstack-debian-8.0-user-data.txt +1 -1
- teuthology/openstack/openstack-opensuse-42.1-user-data.txt +1 -1
- teuthology/openstack/openstack-teuthology.cron +0 -1
- teuthology/orchestra/cluster.py +51 -9
- teuthology/orchestra/connection.py +23 -16
- teuthology/orchestra/console.py +111 -50
- teuthology/orchestra/daemon/cephadmunit.py +23 -5
- teuthology/orchestra/daemon/state.py +10 -3
- teuthology/orchestra/daemon/systemd.py +10 -8
- teuthology/orchestra/opsys.py +32 -11
- teuthology/orchestra/remote.py +369 -152
- teuthology/orchestra/run.py +21 -12
- teuthology/packaging.py +54 -15
- teuthology/provision/__init__.py +30 -10
- teuthology/provision/cloud/openstack.py +12 -6
- teuthology/provision/cloud/util.py +1 -2
- teuthology/provision/downburst.py +83 -29
- teuthology/provision/fog.py +68 -20
- teuthology/provision/openstack.py +5 -4
- teuthology/provision/pelagos.py +13 -5
- teuthology/repo_utils.py +91 -44
- teuthology/report.py +57 -35
- teuthology/results.py +5 -3
- teuthology/run.py +21 -15
- teuthology/run_tasks.py +114 -40
- teuthology/schedule.py +4 -3
- teuthology/scrape.py +28 -22
- teuthology/suite/__init__.py +75 -46
- teuthology/suite/build_matrix.py +34 -24
- teuthology/suite/fragment-merge.lua +105 -0
- teuthology/suite/matrix.py +31 -2
- teuthology/suite/merge.py +175 -0
- teuthology/suite/placeholder.py +8 -8
- teuthology/suite/run.py +204 -102
- teuthology/suite/util.py +67 -211
- teuthology/task/__init__.py +1 -1
- teuthology/task/ansible.py +101 -31
- teuthology/task/buildpackages.py +2 -2
- teuthology/task/ceph_ansible.py +13 -6
- teuthology/task/cephmetrics.py +2 -1
- teuthology/task/clock.py +33 -14
- teuthology/task/exec.py +18 -0
- teuthology/task/hadoop.py +2 -2
- teuthology/task/install/__init__.py +51 -22
- teuthology/task/install/bin/adjust-ulimits +16 -0
- teuthology/task/install/bin/daemon-helper +114 -0
- teuthology/task/install/bin/stdin-killer +263 -0
- teuthology/task/install/deb.py +24 -4
- teuthology/task/install/redhat.py +36 -32
- teuthology/task/install/rpm.py +41 -14
- teuthology/task/install/util.py +48 -22
- teuthology/task/internal/__init__.py +69 -11
- teuthology/task/internal/edit_sudoers.sh +10 -0
- teuthology/task/internal/lock_machines.py +3 -133
- teuthology/task/internal/redhat.py +48 -28
- teuthology/task/internal/syslog.py +31 -8
- teuthology/task/kernel.py +155 -147
- teuthology/task/lockfile.py +1 -1
- teuthology/task/mpi.py +10 -10
- teuthology/task/pcp.py +1 -1
- teuthology/task/selinux.py +17 -8
- teuthology/task/ssh_keys.py +6 -6
- teuthology/task/tests/__init__.py +137 -77
- teuthology/task/tests/test_fetch_coredumps.py +116 -0
- teuthology/task/tests/test_run.py +4 -4
- teuthology/timer.py +3 -3
- teuthology/util/loggerfile.py +19 -0
- teuthology/util/scanner.py +159 -0
- teuthology/util/sentry.py +52 -0
- teuthology/util/time.py +52 -0
- teuthology-1.2.0.data/scripts/adjust-ulimits +16 -0
- teuthology-1.2.0.data/scripts/daemon-helper +114 -0
- teuthology-1.2.0.data/scripts/stdin-killer +263 -0
- teuthology-1.2.0.dist-info/METADATA +89 -0
- teuthology-1.2.0.dist-info/RECORD +174 -0
- {teuthology-1.0.0.dist-info → teuthology-1.2.0.dist-info}/WHEEL +1 -1
- {teuthology-1.0.0.dist-info → teuthology-1.2.0.dist-info}/entry_points.txt +5 -2
- scripts/nuke.py +0 -45
- scripts/worker.py +0 -37
- teuthology/nuke/actions.py +0 -456
- teuthology/openstack/test/__init__.py +0 -0
- teuthology/openstack/test/openstack-integration.py +0 -286
- teuthology/openstack/test/test_config.py +0 -35
- teuthology/openstack/test/test_openstack.py +0 -1695
- teuthology/orchestra/test/__init__.py +0 -0
- teuthology/orchestra/test/integration/__init__.py +0 -0
- teuthology/orchestra/test/integration/test_integration.py +0 -94
- teuthology/orchestra/test/test_cluster.py +0 -240
- teuthology/orchestra/test/test_connection.py +0 -106
- teuthology/orchestra/test/test_console.py +0 -217
- teuthology/orchestra/test/test_opsys.py +0 -404
- teuthology/orchestra/test/test_remote.py +0 -185
- teuthology/orchestra/test/test_run.py +0 -286
- teuthology/orchestra/test/test_systemd.py +0 -54
- teuthology/orchestra/test/util.py +0 -12
- teuthology/sentry.py +0 -18
- teuthology/test/__init__.py +0 -0
- teuthology/test/fake_archive.py +0 -107
- teuthology/test/fake_fs.py +0 -92
- teuthology/test/integration/__init__.py +0 -0
- teuthology/test/integration/test_suite.py +0 -86
- teuthology/test/task/__init__.py +0 -205
- teuthology/test/task/test_ansible.py +0 -624
- teuthology/test/task/test_ceph_ansible.py +0 -176
- teuthology/test/task/test_console_log.py +0 -88
- teuthology/test/task/test_install.py +0 -337
- teuthology/test/task/test_internal.py +0 -57
- teuthology/test/task/test_kernel.py +0 -243
- teuthology/test/task/test_pcp.py +0 -379
- teuthology/test/task/test_selinux.py +0 -35
- teuthology/test/test_config.py +0 -189
- teuthology/test/test_contextutil.py +0 -68
- teuthology/test/test_describe_tests.py +0 -316
- teuthology/test/test_email_sleep_before_teardown.py +0 -81
- teuthology/test/test_exit.py +0 -97
- teuthology/test/test_get_distro.py +0 -47
- teuthology/test/test_get_distro_version.py +0 -47
- teuthology/test/test_get_multi_machine_types.py +0 -27
- teuthology/test/test_job_status.py +0 -60
- teuthology/test/test_ls.py +0 -48
- teuthology/test/test_misc.py +0 -368
- teuthology/test/test_nuke.py +0 -232
- teuthology/test/test_packaging.py +0 -763
- teuthology/test/test_parallel.py +0 -28
- teuthology/test/test_repo_utils.py +0 -204
- teuthology/test/test_report.py +0 -77
- teuthology/test/test_results.py +0 -155
- teuthology/test/test_run.py +0 -238
- teuthology/test/test_safepath.py +0 -55
- teuthology/test/test_schedule.py +0 -45
- teuthology/test/test_scrape.py +0 -167
- teuthology/test/test_timer.py +0 -80
- teuthology/test/test_vps_os_vers_parameter_checking.py +0 -84
- teuthology/test/test_worker.py +0 -303
- teuthology/worker.py +0 -339
- teuthology-1.0.0.dist-info/METADATA +0 -76
- teuthology-1.0.0.dist-info/RECORD +0 -210
- {teuthology-1.0.0.dist-info → teuthology-1.2.0.dist-info}/LICENSE +0 -0
- {teuthology-1.0.0.dist-info → teuthology-1.2.0.dist-info}/top_level.txt +0 -0
teuthology/orchestra/remote.py
CHANGED
@@ -4,17 +4,21 @@ Support for paramiko remote objects.
|
|
4
4
|
|
5
5
|
import teuthology.lock.query
|
6
6
|
import teuthology.lock.util
|
7
|
+
from teuthology.contextutil import safe_while
|
7
8
|
from teuthology.orchestra import run
|
8
9
|
from teuthology.orchestra import connection
|
9
10
|
from teuthology.orchestra import console
|
10
11
|
from teuthology.orchestra.opsys import OS
|
12
|
+
import teuthology.provision
|
11
13
|
from teuthology import misc
|
12
|
-
from teuthology.exceptions import CommandFailedError
|
14
|
+
from teuthology.exceptions import CommandFailedError, UnitTestError
|
15
|
+
from teuthology.util.scanner import UnitTestScanner
|
13
16
|
from teuthology.misc import host_shortname
|
14
|
-
import
|
17
|
+
import errno
|
15
18
|
import re
|
16
19
|
import logging
|
17
20
|
from io import BytesIO
|
21
|
+
from io import StringIO
|
18
22
|
import os
|
19
23
|
import pwd
|
20
24
|
import tempfile
|
@@ -23,8 +27,317 @@ import netaddr
|
|
23
27
|
log = logging.getLogger(__name__)
|
24
28
|
|
25
29
|
|
26
|
-
class
|
30
|
+
class RemoteShell(object):
|
31
|
+
"""
|
32
|
+
Contains methods to run miscellaneous shell commands on remote machines.
|
33
|
+
|
34
|
+
These methods were originally part of orchestra.remote.Remote. The reason
|
35
|
+
for moving these methods from Remote is that applications that use
|
36
|
+
teuthology for testing usually have programs that can run tests locally on
|
37
|
+
a single node machine for development work (for example, vstart_runner.py
|
38
|
+
in case of Ceph). These programs can import and reuse these methods
|
39
|
+
without having to deal SSH stuff. In short, this class serves a shared
|
40
|
+
interface.
|
41
|
+
|
42
|
+
To use these methods, inherit the class here and implement "run()" method in
|
43
|
+
the subclass.
|
44
|
+
"""
|
45
|
+
|
46
|
+
def remove(self, path):
|
47
|
+
self.run(args=['rm', '-fr', path])
|
48
|
+
|
49
|
+
def mkdtemp(self, suffix=None, parentdir=None):
|
50
|
+
"""
|
51
|
+
Create a temporary directory on remote machine and return it's path.
|
52
|
+
"""
|
53
|
+
args = ['mktemp', '-d']
|
54
|
+
|
55
|
+
if suffix:
|
56
|
+
args.append('--suffix=%s' % suffix)
|
57
|
+
if parentdir:
|
58
|
+
args.append('--tmpdir=%s' % parentdir)
|
59
|
+
|
60
|
+
return self.sh(args).strip()
|
61
|
+
|
62
|
+
def mktemp(self, suffix=None, parentdir=None, data=None):
|
63
|
+
"""
|
64
|
+
Make a remote temporary file.
|
65
|
+
|
66
|
+
:param suffix: suffix for the temporary file
|
67
|
+
:param parentdir: parent dir where temp file should be created
|
68
|
+
:param data: write data to the file if provided
|
69
|
+
|
70
|
+
Returns: the path of the temp file created.
|
71
|
+
"""
|
72
|
+
args = ['mktemp']
|
73
|
+
if suffix:
|
74
|
+
args.append('--suffix=%s' % suffix)
|
75
|
+
if parentdir:
|
76
|
+
args.append('--tmpdir=%s' % parentdir)
|
77
|
+
|
78
|
+
path = self.sh(args).strip()
|
79
|
+
|
80
|
+
if data:
|
81
|
+
self.write_file(path=path, data=data)
|
82
|
+
|
83
|
+
return path
|
84
|
+
|
85
|
+
def sh(self, script, **kwargs):
|
86
|
+
"""
|
87
|
+
Shortcut for run method.
|
88
|
+
|
89
|
+
Usage:
|
90
|
+
my_name = remote.sh('whoami')
|
91
|
+
remote_date = remote.sh('date')
|
92
|
+
"""
|
93
|
+
if 'stdout' not in kwargs:
|
94
|
+
kwargs['stdout'] = BytesIO()
|
95
|
+
if 'args' not in kwargs:
|
96
|
+
kwargs['args'] = script
|
97
|
+
proc = self.run(**kwargs)
|
98
|
+
out = proc.stdout.getvalue()
|
99
|
+
if isinstance(out, bytes):
|
100
|
+
return out.decode()
|
101
|
+
else:
|
102
|
+
return out
|
103
|
+
|
104
|
+
def sh_file(self, script, label="script", sudo=False, **kwargs):
|
105
|
+
"""
|
106
|
+
Run shell script after copying its contents to a remote file
|
107
|
+
|
108
|
+
:param script: string with script text, or file object
|
109
|
+
:param sudo: run command with sudo if True,
|
110
|
+
run as user name if string value (defaults to False)
|
111
|
+
:param label: string value which will be part of file name
|
112
|
+
Returns: stdout
|
113
|
+
"""
|
114
|
+
ftempl = '/tmp/teuthology-remote-$(date +%Y%m%d%H%M%S)-{}-XXXX'\
|
115
|
+
.format(label)
|
116
|
+
script_file = self.sh("mktemp %s" % ftempl).strip()
|
117
|
+
self.sh("cat - | tee {script} ; chmod a+rx {script}"\
|
118
|
+
.format(script=script_file), stdin=script)
|
119
|
+
if sudo:
|
120
|
+
if isinstance(sudo, str):
|
121
|
+
command="sudo -u %s %s" % (sudo, script_file)
|
122
|
+
else:
|
123
|
+
command="sudo %s" % script_file
|
124
|
+
else:
|
125
|
+
command="%s" % script_file
|
126
|
+
|
127
|
+
return self.sh(command, **kwargs)
|
128
|
+
|
129
|
+
def chmod(self, file_path, permissions):
|
130
|
+
"""
|
131
|
+
As super-user, set permissions on the remote file specified.
|
132
|
+
"""
|
133
|
+
args = [
|
134
|
+
'sudo',
|
135
|
+
'chmod',
|
136
|
+
permissions,
|
137
|
+
file_path,
|
138
|
+
]
|
139
|
+
self.run(
|
140
|
+
args=args,
|
141
|
+
)
|
142
|
+
|
143
|
+
def chcon(self, file_path, context):
|
144
|
+
"""
|
145
|
+
Set the SELinux context of a given file.
|
146
|
+
|
147
|
+
VMs and non-RPM-based hosts will skip this operation because ours
|
148
|
+
currently have SELinux disabled.
|
149
|
+
|
150
|
+
:param file_path: The path to the file
|
151
|
+
:param context: The SELinux context to be used
|
152
|
+
"""
|
153
|
+
if self.os.package_type != 'rpm' or \
|
154
|
+
self.os.name in ['opensuse', 'sle']:
|
155
|
+
return
|
156
|
+
if teuthology.lock.query.is_vm(self.shortname):
|
157
|
+
return
|
158
|
+
self.run(args="sudo chcon {con} {path}".format(
|
159
|
+
con=context, path=file_path))
|
160
|
+
|
161
|
+
def copy_file(self, src, dst, sudo=False, mode=None, owner=None,
|
162
|
+
mkdir=False, append=False):
|
163
|
+
"""
|
164
|
+
Copy data to remote file
|
165
|
+
|
166
|
+
:param src: source file path on remote host
|
167
|
+
:param dst: destination file path on remote host
|
168
|
+
:param sudo: use sudo to write file, defaults False
|
169
|
+
:param mode: set file mode bits if provided
|
170
|
+
:param owner: set file owner if provided
|
171
|
+
:param mkdir: ensure the destination directory exists, defaults
|
172
|
+
False
|
173
|
+
:param append: append data to the file, defaults False
|
174
|
+
"""
|
175
|
+
dd = 'sudo dd' if sudo else 'dd'
|
176
|
+
args = dd + ' if=' + src + ' of=' + dst
|
177
|
+
if append:
|
178
|
+
args += ' conv=notrunc oflag=append'
|
179
|
+
if mkdir:
|
180
|
+
mkdirp = 'sudo mkdir -p' if sudo else 'mkdir -p'
|
181
|
+
dirpath = os.path.dirname(dst)
|
182
|
+
if dirpath:
|
183
|
+
args = mkdirp + ' ' + dirpath + '\n' + args
|
184
|
+
if mode:
|
185
|
+
chmod = 'sudo chmod' if sudo else 'chmod'
|
186
|
+
args += '\n' + chmod + ' ' + mode + ' ' + dst
|
187
|
+
if owner:
|
188
|
+
chown = 'sudo chown' if sudo else 'chown'
|
189
|
+
args += '\n' + chown + ' ' + owner + ' ' + dst
|
190
|
+
args = 'set -ex' + '\n' + args
|
191
|
+
self.run(args=args)
|
192
|
+
|
193
|
+
def move_file(self, src, dst, sudo=False, mode=None, owner=None,
|
194
|
+
mkdir=False):
|
195
|
+
"""
|
196
|
+
Move data to remote file
|
197
|
+
|
198
|
+
:param src: source file path on remote host
|
199
|
+
:param dst: destination file path on remote host
|
200
|
+
:param sudo: use sudo to write file, defaults False
|
201
|
+
:param mode: set file mode bits if provided
|
202
|
+
:param owner: set file owner if provided
|
203
|
+
:param mkdir: ensure the destination directory exists, defaults
|
204
|
+
False
|
205
|
+
"""
|
206
|
+
mv = 'sudo mv' if sudo else 'mv'
|
207
|
+
args = mv + ' ' + src + ' ' + dst
|
208
|
+
if mkdir:
|
209
|
+
mkdirp = 'sudo mkdir -p' if sudo else 'mkdir -p'
|
210
|
+
dirpath = os.path.dirname(dst)
|
211
|
+
if dirpath:
|
212
|
+
args = mkdirp + ' ' + dirpath + '\n' + args
|
213
|
+
if mode:
|
214
|
+
chmod = 'sudo chmod' if sudo else 'chmod'
|
215
|
+
args += ' && ' + chmod + ' ' + mode + ' ' + dst
|
216
|
+
if owner:
|
217
|
+
chown = 'sudo chown' if sudo else 'chown'
|
218
|
+
args += ' && ' + chown + ' ' + owner + ' ' + dst
|
219
|
+
self.run(args=args)
|
220
|
+
|
221
|
+
def read_file(self, path, sudo=False, stdout=None,
|
222
|
+
offset=0, length=0):
|
223
|
+
"""
|
224
|
+
Read data from remote file
|
225
|
+
|
226
|
+
:param path: file path on remote host
|
227
|
+
:param sudo: use sudo to read the file, defaults False
|
228
|
+
:param stdout: output object, defaults to io.BytesIO()
|
229
|
+
:param offset: number of bytes to skip from the file
|
230
|
+
:param length: number of bytes to read from the file
|
231
|
+
|
232
|
+
:raises: :class:`FileNotFoundError`: there is no such file by the path
|
233
|
+
:raises: :class:`RuntimeError`: unexpected error occurred
|
234
|
+
|
235
|
+
:returns: the file contents in bytes, if stdout is `io.BytesIO`, by
|
236
|
+
default
|
237
|
+
:returns: the file contents in str, if stdout is `io.StringIO`
|
238
|
+
"""
|
239
|
+
dd = 'sudo dd' if sudo else 'dd'
|
240
|
+
args = dd + ' if=' + path + ' of=/dev/stdout'
|
241
|
+
iflags=[]
|
242
|
+
# we have to set defaults here instead of the method's signature,
|
243
|
+
# because python is reusing the object from call to call
|
244
|
+
stdout = stdout or BytesIO()
|
245
|
+
if offset:
|
246
|
+
args += ' skip=' + str(offset)
|
247
|
+
iflags += 'skip_bytes'
|
248
|
+
if length:
|
249
|
+
args += ' count=' + str(length)
|
250
|
+
iflags += 'count_bytes'
|
251
|
+
if iflags:
|
252
|
+
args += ' iflag=' + ','.join(iflags)
|
253
|
+
args = 'set -ex' + '\n' + args
|
254
|
+
proc = self.run(args=args, stdout=stdout, stderr=StringIO(),
|
255
|
+
check_status=False, quiet=True)
|
256
|
+
if proc.returncode:
|
257
|
+
if 'No such file or directory' in proc.stderr.getvalue():
|
258
|
+
raise FileNotFoundError(errno.ENOENT,
|
259
|
+
f"Cannot find file on the remote '{self.name}'", path)
|
260
|
+
else:
|
261
|
+
raise RuntimeError("Unexpected error occurred while trying to "
|
262
|
+
f"read '{path}' file on the remote '{self.name}'")
|
263
|
+
|
264
|
+
return proc.stdout.getvalue()
|
265
|
+
|
266
|
+
|
267
|
+
def write_file(self, path, data, sudo=False, mode=None, owner=None,
|
268
|
+
mkdir=False, append=False):
|
269
|
+
"""
|
270
|
+
Write data to remote file
|
271
|
+
|
272
|
+
:param path: file path on remote host
|
273
|
+
:param data: str, binary or fileobj to be written
|
274
|
+
:param sudo: use sudo to write file, defaults False
|
275
|
+
:param mode: set file mode bits if provided
|
276
|
+
:param owner: set file owner if provided
|
277
|
+
:param mkdir: preliminary create the file directory, defaults False
|
278
|
+
:param append: append data to the file, defaults False
|
279
|
+
"""
|
280
|
+
dd = 'sudo dd' if sudo else 'dd'
|
281
|
+
args = dd + ' of=' + path
|
282
|
+
if append:
|
283
|
+
args += ' conv=notrunc oflag=append'
|
284
|
+
if mkdir:
|
285
|
+
mkdirp = 'sudo mkdir -p' if sudo else 'mkdir -p'
|
286
|
+
dirpath = os.path.dirname(path)
|
287
|
+
if dirpath:
|
288
|
+
args = mkdirp + ' ' + dirpath + '\n' + args
|
289
|
+
if mode:
|
290
|
+
chmod = 'sudo chmod' if sudo else 'chmod'
|
291
|
+
args += '\n' + chmod + ' ' + mode + ' ' + path
|
292
|
+
if owner:
|
293
|
+
chown = 'sudo chown' if sudo else 'chown'
|
294
|
+
args += '\n' + chown + ' ' + owner + ' ' + path
|
295
|
+
args = 'set -ex' + '\n' + args
|
296
|
+
self.run(args=args, stdin=data, quiet=True)
|
297
|
+
|
298
|
+
def sudo_write_file(self, path, data, **kwargs):
|
299
|
+
"""
|
300
|
+
Write data to remote file with sudo, for more info see `write_file()`.
|
301
|
+
"""
|
302
|
+
self.write_file(path, data, sudo=True, **kwargs)
|
303
|
+
|
304
|
+
def is_mounted(self, path):
|
305
|
+
"""
|
306
|
+
Check if the given path is mounted on the remote machine.
|
307
|
+
|
308
|
+
This method checks the contents of "/proc/self/mounts" instead of
|
309
|
+
using "mount" or "findmnt" command since these commands hang when a
|
310
|
+
CephFS client is blocked and its mount point on the remote machine
|
311
|
+
is left unhandled/unmounted.
|
312
|
+
|
313
|
+
:param path: path on remote host
|
314
|
+
"""
|
315
|
+
# XXX: matching newline too is crucial so that "/mnt" does not match
|
316
|
+
# "/mnt/cephfs" if it's present in the output.
|
317
|
+
return f'{path}\n' in self.sh("cat /proc/self/mounts | awk '{print $2}'")
|
318
|
+
|
319
|
+
@property
|
320
|
+
def os(self):
|
321
|
+
if not hasattr(self, '_os'):
|
322
|
+
try:
|
323
|
+
os_release = self.sh('cat /etc/os-release').strip()
|
324
|
+
self._os = OS.from_os_release(os_release)
|
325
|
+
return self._os
|
326
|
+
except CommandFailedError:
|
327
|
+
pass
|
328
|
+
|
329
|
+
lsb_release = self.sh('lsb_release -a').strip()
|
330
|
+
self._os = OS.from_lsb_release(lsb_release)
|
331
|
+
return self._os
|
27
332
|
|
333
|
+
@property
|
334
|
+
def arch(self):
|
335
|
+
if not hasattr(self, '_arch'):
|
336
|
+
self._arch = self.sh('uname -m').strip()
|
337
|
+
return self._arch
|
338
|
+
|
339
|
+
|
340
|
+
class Remote(RemoteShell):
|
28
341
|
"""
|
29
342
|
A connection to a remote host.
|
30
343
|
|
@@ -33,6 +346,7 @@ class Remote(object):
|
|
33
346
|
|
34
347
|
# for unit tests to hook into
|
35
348
|
_runner = staticmethod(run.run)
|
349
|
+
_reimage_types = None
|
36
350
|
|
37
351
|
def __init__(self, name, ssh=None, shortname=None, console=None,
|
38
352
|
host_key=None, keep_alive=True):
|
@@ -53,6 +367,9 @@ class Remote(object):
|
|
53
367
|
self._console = console
|
54
368
|
self.ssh = ssh
|
55
369
|
|
370
|
+
if self._reimage_types is None:
|
371
|
+
Remote._reimage_types = teuthology.provision.get_reimage_types()
|
372
|
+
|
56
373
|
def connect(self, timeout=None, create_key=None, context='connect'):
|
57
374
|
args = dict(user_at_host=self.name, host_key=self._host_key,
|
58
375
|
keep_alive=self.keep_alive, _create_key=create_key)
|
@@ -70,7 +387,7 @@ class Remote(object):
|
|
70
387
|
self.ssh = connection.connect(**args)
|
71
388
|
return self.ssh
|
72
389
|
|
73
|
-
def reconnect(self, timeout=
|
390
|
+
def reconnect(self, timeout=30, socket_timeout=None):
|
74
391
|
"""
|
75
392
|
Attempts to re-establish connection. Returns True for success; False
|
76
393
|
for failure.
|
@@ -79,21 +396,18 @@ class Remote(object):
|
|
79
396
|
self.ssh.close()
|
80
397
|
if not timeout:
|
81
398
|
return self._reconnect(timeout=socket_timeout)
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
sleep_val = min(time_remaining, sleep_time)
|
92
|
-
time.sleep(sleep_val)
|
93
|
-
return success
|
399
|
+
action = "reconnect to {self.shortname}"
|
400
|
+
with safe_while(action=action, timeout=timeout, increment=3, _raise=False) as proceed:
|
401
|
+
success = False
|
402
|
+
while proceed():
|
403
|
+
success = self._reconnect(timeout=socket_timeout)
|
404
|
+
if success:
|
405
|
+
log.info(f"Successfully reconnected to host '{self.name}'")
|
406
|
+
return success
|
407
|
+
return success
|
94
408
|
|
95
409
|
def _reconnect(self, timeout=None):
|
96
|
-
log.info("Trying to reconnect to host")
|
410
|
+
log.info(f"Trying to reconnect to host '{self.name}'")
|
97
411
|
try:
|
98
412
|
self.connect(timeout=timeout, context='reconnect')
|
99
413
|
return self.is_online
|
@@ -150,6 +464,10 @@ class Remote(object):
|
|
150
464
|
self._machine_type = remote_info.get("machine_type", None)
|
151
465
|
return self._machine_type
|
152
466
|
|
467
|
+
@property
|
468
|
+
def is_reimageable(self):
|
469
|
+
return self.machine_type in self._reimage_types
|
470
|
+
|
153
471
|
@property
|
154
472
|
def shortname(self):
|
155
473
|
if self._shortname is None:
|
@@ -173,7 +491,7 @@ class Remote(object):
|
|
173
491
|
return
|
174
492
|
self.connect()
|
175
493
|
if not self.is_online:
|
176
|
-
raise
|
494
|
+
raise ConnectionError(f'Failed to connect to {self.shortname}')
|
177
495
|
|
178
496
|
@property
|
179
497
|
def system_type(self):
|
@@ -200,114 +518,25 @@ class Remote(object):
|
|
200
518
|
if not self.ssh or \
|
201
519
|
not self.ssh.get_transport() or \
|
202
520
|
not self.ssh.get_transport().is_active():
|
203
|
-
self.reconnect()
|
521
|
+
if not self.reconnect():
|
522
|
+
raise ConnectionError(f'Failed to reconnect to {self.shortname}')
|
204
523
|
r = self._runner(client=self.ssh, name=self.shortname, **kwargs)
|
205
524
|
r.remote = self
|
206
525
|
return r
|
207
526
|
|
208
|
-
def
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
def mktemp(self, suffix=None, parentdir=None):
|
222
|
-
"""
|
223
|
-
Make a remote temporary file
|
224
|
-
|
225
|
-
Returns: the path of the temp file created.
|
226
|
-
"""
|
227
|
-
args = ['mktemp']
|
228
|
-
|
229
|
-
if suffix:
|
230
|
-
args.append('--suffix=%s' % suffix)
|
231
|
-
if parentdir:
|
232
|
-
args.append('--tmpdir=%s' % parentdir)
|
233
|
-
|
234
|
-
return self.sh(args).strip()
|
235
|
-
|
236
|
-
def sh(self, script, **kwargs):
|
237
|
-
"""
|
238
|
-
Shortcut for run method.
|
239
|
-
|
240
|
-
Usage:
|
241
|
-
my_name = remote.sh('whoami')
|
242
|
-
remote_date = remote.sh('date')
|
243
|
-
"""
|
244
|
-
if 'stdout' not in kwargs:
|
245
|
-
kwargs['stdout'] = BytesIO()
|
246
|
-
if 'args' not in kwargs:
|
247
|
-
kwargs['args'] = script
|
248
|
-
proc = self.run(**kwargs)
|
249
|
-
out = proc.stdout.getvalue()
|
250
|
-
if isinstance(out, bytes):
|
251
|
-
return out.decode()
|
252
|
-
else:
|
253
|
-
return out
|
254
|
-
|
255
|
-
def sh_file(self, script, label="script", sudo=False, **kwargs):
|
256
|
-
"""
|
257
|
-
Run shell script after copying its contents to a remote file
|
258
|
-
|
259
|
-
:param script: string with script text, or file object
|
260
|
-
:param sudo: run command with sudo if True,
|
261
|
-
run as user name if string value (defaults to False)
|
262
|
-
:param label: string value which will be part of file name
|
263
|
-
Returns: stdout
|
264
|
-
"""
|
265
|
-
ftempl = '/tmp/teuthology-remote-$(date +%Y%m%d%H%M%S)-{}-XXXX'\
|
266
|
-
.format(label)
|
267
|
-
script_file = self.sh("mktemp %s" % ftempl).strip()
|
268
|
-
self.sh("cat - | tee {script} ; chmod a+rx {script}"\
|
269
|
-
.format(script=script_file), stdin=script)
|
270
|
-
if sudo:
|
271
|
-
if isinstance(sudo, str):
|
272
|
-
command="sudo -u %s %s" % (sudo, script_file)
|
273
|
-
else:
|
274
|
-
command="sudo %s" % script_file
|
275
|
-
else:
|
276
|
-
command="%s" % script_file
|
277
|
-
|
278
|
-
return self.sh(command, **kwargs)
|
279
|
-
|
280
|
-
def chmod(self, file_path, permissions):
|
281
|
-
"""
|
282
|
-
As super-user, set permissions on the remote file specified.
|
283
|
-
"""
|
284
|
-
args = [
|
285
|
-
'sudo',
|
286
|
-
'chmod',
|
287
|
-
permissions,
|
288
|
-
file_path,
|
289
|
-
]
|
290
|
-
self.run(
|
291
|
-
args=args,
|
292
|
-
)
|
293
|
-
|
294
|
-
def chcon(self, file_path, context):
|
295
|
-
"""
|
296
|
-
Set the SELinux context of a given file.
|
297
|
-
|
298
|
-
VMs and non-RPM-based hosts will skip this operation because ours
|
299
|
-
currently have SELinux disabled.
|
300
|
-
|
301
|
-
:param file_path: The path to the file
|
302
|
-
:param context: The SELinux context to be used
|
303
|
-
"""
|
304
|
-
if self.os.package_type != 'rpm' or \
|
305
|
-
self.os.name in ['opensuse', 'sle']:
|
306
|
-
return
|
307
|
-
if teuthology.lock.query.is_vm(self.shortname):
|
308
|
-
return
|
309
|
-
self.run(args="sudo chcon {con} {path}".format(
|
310
|
-
con=context, path=file_path))
|
527
|
+
def run_unit_test(self, xml_path_regex, output_yaml, **kwargs):
|
528
|
+
try:
|
529
|
+
r = self.run(**kwargs)
|
530
|
+
except CommandFailedError as exc:
|
531
|
+
if xml_path_regex:
|
532
|
+
error_msg = UnitTestScanner(remote=self).scan_and_write(xml_path_regex, output_yaml)
|
533
|
+
if error_msg:
|
534
|
+
raise UnitTestError(
|
535
|
+
exitstatus=exc.exitstatus, node=exc.node,
|
536
|
+
label=exc.label, message=error_msg
|
537
|
+
)
|
538
|
+
raise exc
|
539
|
+
return r
|
311
540
|
|
312
541
|
def _sftp_put_file(self, local_path, remote_path):
|
313
542
|
"""
|
@@ -329,12 +558,14 @@ class Remote(object):
|
|
329
558
|
sftp.get(remote_path, local_path)
|
330
559
|
return local_path
|
331
560
|
|
332
|
-
def _sftp_open_file(self, remote_path):
|
561
|
+
def _sftp_open_file(self, remote_path, mode=None):
|
333
562
|
"""
|
334
563
|
Use the paramiko.SFTPClient to open a file. Returns a
|
335
564
|
paramiko.SFTPFile object.
|
336
565
|
"""
|
337
566
|
sftp = self.ssh.open_sftp()
|
567
|
+
if mode:
|
568
|
+
return sftp.open(remote_path, mode)
|
338
569
|
return sftp.open(remote_path)
|
339
570
|
|
340
571
|
def _sftp_get_size(self, remote_path):
|
@@ -355,9 +586,6 @@ class Remote(object):
|
|
355
586
|
file_size = file_size / 1024.0
|
356
587
|
return "{:3.0f}{}".format(file_size, unit)
|
357
588
|
|
358
|
-
def remove(self, path):
|
359
|
-
self.run(args=['rm', '-fr', path])
|
360
|
-
|
361
589
|
def put_file(self, path, dest_path, sudo=False):
|
362
590
|
"""
|
363
591
|
Copy a local filename to a remote file
|
@@ -408,7 +636,7 @@ class Remote(object):
|
|
408
636
|
self.remove(path)
|
409
637
|
return local_path
|
410
638
|
|
411
|
-
def get_tar(self, path, to_path, sudo=False):
|
639
|
+
def get_tar(self, path, to_path, sudo=False, compress=True):
|
412
640
|
"""
|
413
641
|
Tar a remote directory and copy it locally
|
414
642
|
"""
|
@@ -418,7 +646,7 @@ class Remote(object):
|
|
418
646
|
args.append('sudo')
|
419
647
|
args.extend([
|
420
648
|
'tar',
|
421
|
-
'cz',
|
649
|
+
'cz' if compress else 'c',
|
422
650
|
'-f', '-',
|
423
651
|
'-C', path,
|
424
652
|
'--',
|
@@ -431,7 +659,7 @@ class Remote(object):
|
|
431
659
|
self._sftp_get_file(remote_temp_path, to_path)
|
432
660
|
self.remove(remote_temp_path)
|
433
661
|
|
434
|
-
def get_tar_stream(self, path, sudo=False):
|
662
|
+
def get_tar_stream(self, path, sudo=False, compress=True):
|
435
663
|
"""
|
436
664
|
Tar-compress a remote directory and return the RemoteProcess
|
437
665
|
for streaming
|
@@ -441,7 +669,7 @@ class Remote(object):
|
|
441
669
|
args.append('sudo')
|
442
670
|
args.extend([
|
443
671
|
'tar',
|
444
|
-
'cz',
|
672
|
+
'cz' if compress else 'c',
|
445
673
|
'-f', '-',
|
446
674
|
'-C', path,
|
447
675
|
'--',
|
@@ -449,26 +677,6 @@ class Remote(object):
|
|
449
677
|
])
|
450
678
|
return self.run(args=args, wait=False, stdout=run.PIPE)
|
451
679
|
|
452
|
-
@property
|
453
|
-
def os(self):
|
454
|
-
if not hasattr(self, '_os'):
|
455
|
-
try:
|
456
|
-
os_release = self.sh('cat /etc/os-release').strip()
|
457
|
-
self._os = OS.from_os_release(os_release)
|
458
|
-
return self._os
|
459
|
-
except CommandFailedError:
|
460
|
-
pass
|
461
|
-
|
462
|
-
lsb_release = self.sh('lsb_release -a').strip()
|
463
|
-
self._os = OS.from_lsb_release(lsb_release)
|
464
|
-
return self._os
|
465
|
-
|
466
|
-
@property
|
467
|
-
def arch(self):
|
468
|
-
if not hasattr(self, '_arch'):
|
469
|
-
self._arch = self.sh('uname -m').strip()
|
470
|
-
return self._arch
|
471
|
-
|
472
680
|
@property
|
473
681
|
def host_key(self):
|
474
682
|
if not self._host_key:
|
@@ -501,6 +709,15 @@ class Remote(object):
|
|
501
709
|
self._is_vm = teuthology.lock.query.is_vm(self.name)
|
502
710
|
return self._is_vm
|
503
711
|
|
712
|
+
@property
|
713
|
+
def is_container(self):
|
714
|
+
if not hasattr(self, '_is_container'):
|
715
|
+
self._is_container = not bool(self.run(
|
716
|
+
args="test -f /run/.containerenv -o -f /.dockerenv",
|
717
|
+
check_status=False,
|
718
|
+
).returncode)
|
719
|
+
return self._is_container
|
720
|
+
|
504
721
|
@property
|
505
722
|
def init_system(self):
|
506
723
|
"""
|
@@ -524,7 +741,7 @@ class Remote(object):
|
|
524
741
|
|
525
742
|
|
526
743
|
def getRemoteConsole(name, ipmiuser=None, ipmipass=None, ipmidomain=None,
|
527
|
-
|
744
|
+
timeout=60):
|
528
745
|
"""
|
529
746
|
Return either VirtualConsole or PhysicalConsole depending on name.
|
530
747
|
"""
|
@@ -534,4 +751,4 @@ def getRemoteConsole(name, ipmiuser=None, ipmipass=None, ipmidomain=None,
|
|
534
751
|
except Exception:
|
535
752
|
return None
|
536
753
|
return console.PhysicalConsole(
|
537
|
-
name, ipmiuser, ipmipass, ipmidomain,
|
754
|
+
name, ipmiuser, ipmipass, ipmidomain, timeout)
|