-
Notifications
You must be signed in to change notification settings - Fork 78
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Integrate buildroot and add support for QEMU Linux target #667
Changes from all commits
918c19c
3b7fb5b
1253903
82a722d
a4b3547
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -14,6 +14,7 @@ | |
# | ||
|
||
import asyncio | ||
from contextlib import contextmanager | ||
import io | ||
import base64 | ||
import functools | ||
|
@@ -493,7 +494,7 @@ async def setup(self, executables=None): | |
# Check for platform dependent setup procedures | ||
self.platform.setup(self) | ||
|
||
# Initialize modules which requires Buxybox (e.g. shutil dependent tasks) | ||
# Initialize modules which requires Busybox (e.g. shutil dependent tasks) | ||
self._update_modules('setup') | ||
|
||
await self.execute.asyn('mkdir -p {}'.format(quote(self._file_transfer_cache))) | ||
|
@@ -1071,6 +1072,38 @@ async def write_value(self, path, value, verify=True, as_root=True): | |
else: | ||
raise | ||
|
||
@contextmanager | ||
def make_temp(self, is_directory=True, directory='', prefix='devlib-test'): | ||
""" | ||
Creates temporary file/folder on target and deletes it once it's done. | ||
|
||
:param is_directory: Specifies if temporary object is a directory, defaults to True. | ||
:type is_directory: bool, optional | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This
The typical way an optional parameter is implemented in Python is by using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I do see There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That seems to be the right doc, but I think that either the doc is wrong or it's actually the doc on how to contribute to Sphinx itself, which may use something else than plain autodoc. That syntax is part of the numpy style, which requires the napoleon Sphinx extension. Apparently that napoleon extension pre-processes the docstring before autodoc sees it.
You build the doc and check for warnings ( Either way, if the function accepts a type T or [1] You can make a parameter actually optional without any default value with the variable keyword argument syntax, but that's definitely not something you'd do on a daily basis. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Another way would be to setup a test sphinx project, which is very easy too. Or port devlib to use autodoc. It's not particularly difficult, just an invasive PR (all existing function documentations need to be moved back as docstrings). If you were to do that, it would probably take ~1week (or maybe less) and I think it would be reasonable to as @marcbonnici to freeze merging any other stuff so you don't end up with conflicts while working on it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I will not go for updating entire devlib repo now :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes just go for replacing There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If implementing a temporary code freeze to make such changes would be beneficial I'm happy to coordinate to do so. |
||
|
||
:param directory: Temp object will be created under this directory, | ||
metin-arm marked this conversation as resolved.
Show resolved
Hide resolved
|
||
defaults to :attr:`Target.working_directory`. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you checked that
actually works when building a Sphinx project ? This would requires
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
:type directory: str, optional | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
|
||
:param prefix: Prefix of temp object's name, defaults to 'devlib-test'. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
:type prefix: str, optional | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
|
||
:yield: Full path of temp object. | ||
:rtype: str | ||
""" | ||
|
||
directory = directory or self.working_directory | ||
temp_obj = None | ||
try: | ||
cmd = f'mktemp -p {directory} {prefix}-XXXXXX' | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
if is_directory: | ||
cmd += ' -d' | ||
|
||
temp_obj = self.execute(cmd).strip() | ||
yield temp_obj | ||
finally: | ||
if temp_obj is not None: | ||
self.remove(temp_obj) | ||
|
||
def reset(self): | ||
try: | ||
self.execute('reboot', as_root=self.needs_su, timeout=2) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,267 @@ | ||
# Copyright 2024 ARM Limited | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure we want to expose that module publicly. This is certainly not needed for the "normal devlib operation", and for now is really only needed for testing devlib/WA/LISA. These 3 projects could just use a private module on a best-effort basis without having to fully commit in front of the entire world to backward compatibility in any of these features. This is especially important given that android-related things tend to change on a regular basis, and that overall this emulation business is pretty complex, so we may want at some point to use it in a different way, or switch to a different backend (e.g. use VirtualBox) or whatever. We don't want to be constrained by backward compat when doing such changes. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
# | ||
|
||
""" | ||
Target runner and related classes are implemented here. | ||
""" | ||
|
||
import logging | ||
import os | ||
import signal | ||
import subprocess | ||
import time | ||
from platform import machine | ||
|
||
from devlib.exception import (TargetStableError, HostError) | ||
from devlib.target import LinuxTarget | ||
from devlib.utils.misc import get_subprocess, which | ||
from devlib.utils.ssh import SshConnection | ||
|
||
|
||
class TargetRunner: | ||
""" | ||
A generic class for interacting with targets runners. | ||
|
||
It mainly aims to provide framework support for QEMU like target runners | ||
(e.g., :class:`QEMUTargetRunner`). | ||
|
||
:param runner_cmd: The command to start runner process (e.g., | ||
``qemu-system-aarch64 -kernel Image -append "console=ttyAMA0" ...``). | ||
:type runner_cmd: str | ||
|
||
:param target: Specifies type of target per :class:`Target` based classes. | ||
:type target: Target | ||
|
||
:param connect: Specifies if :class:`TargetRunner` should try to connect | ||
target after launching it, defaults to True. | ||
:type connect: bool, optional | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
|
||
:param boot_timeout: Timeout for target's being ready for SSH access in | ||
seconds, defaults to 60. | ||
:type boot_timeout: int, optional | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto |
||
|
||
:raises HostError: if it cannot execute runner command successfully. | ||
|
||
:raises TargetStableError: if Target is inaccessible. | ||
""" | ||
|
||
def __init__(self, | ||
runner_cmd, | ||
target, | ||
connect=True, | ||
boot_timeout=60): | ||
self.boot_timeout = boot_timeout | ||
self.target = target | ||
|
||
self.logger = logging.getLogger(self.__class__.__name__) | ||
|
||
self.logger.info('runner_cmd: %s', runner_cmd) | ||
|
||
try: | ||
self.runner_process = get_subprocess(list(runner_cmd.split())) | ||
except Exception as ex: | ||
raise HostError(f'Error while running "{runner_cmd}": {ex}') from ex | ||
|
||
if connect: | ||
self.wait_boot_complete() | ||
|
||
def __enter__(self): | ||
""" | ||
Complementary method for contextmanager. | ||
|
||
:return: Self object. | ||
:rtype: TargetRunner | ||
""" | ||
|
||
return self | ||
|
||
def __exit__(self, *_): | ||
""" | ||
Exit routine for contextmanager. | ||
|
||
Ensure :attr:`TargetRunner.runner_process` is terminated on exit. | ||
""" | ||
|
||
self.terminate() | ||
|
||
def wait_boot_complete(self): | ||
""" | ||
Wait for target OS to finish boot up and become accessible over SSH in at most | ||
:attr:`TargetRunner.boot_timeout` seconds. | ||
|
||
:raises TargetStableError: In case of timeout. | ||
""" | ||
|
||
start_time = time.time() | ||
elapsed = 0 | ||
while self.boot_timeout >= elapsed: | ||
try: | ||
self.target.connect(timeout=self.boot_timeout - elapsed) | ||
self.logger.info('Target is ready.') | ||
return | ||
# pylint: disable=broad-except | ||
except BaseException as ex: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the rational for catching There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Replaced it with |
||
self.logger.info('Cannot connect target: %s', ex) | ||
|
||
time.sleep(1) | ||
elapsed = time.time() - start_time | ||
|
||
self.terminate() | ||
raise TargetStableError(f'Target is inaccessible for {self.boot_timeout} seconds!') | ||
|
||
def terminate(self): | ||
""" | ||
Terminate :attr:`TargetRunner.runner_process`. | ||
""" | ||
|
||
if self.runner_process is None: | ||
return | ||
|
||
try: | ||
self.runner_process.stdin.close() | ||
self.runner_process.stdout.close() | ||
self.runner_process.stderr.close() | ||
|
||
if self.runner_process.poll() is None: | ||
self.logger.debug('Terminating target runner...') | ||
os.killpg(self.runner_process.pid, signal.SIGTERM) | ||
# Wait 3 seconds before killing the runner. | ||
self.runner_process.wait(timeout=3) | ||
except subprocess.TimeoutExpired: | ||
self.logger.info('Killing target runner...') | ||
os.killpg(self.runner_process.pid, signal.SIGKILL) | ||
Comment on lines
+133
to
+144
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unless there is a good reason to do so, this code should use Popen.kill() rather than fetching the PID and manually passing it to Also, even better: use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We first try to terminate the process gracefully, and then forcefully kill it if the process does not exit in 3 seconds. Replaced the logic in There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could call
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done on #677 . |
||
|
||
|
||
class QEMUTargetRunner(TargetRunner): | ||
""" | ||
Class for interacting with QEMU runners. | ||
|
||
:class:`QEMUTargetRunner` is a subclass of :class:`TargetRunner` which performs necessary | ||
groundwork for launching a guest OS on QEMU. | ||
|
||
:param qemu_settings: A dictionary which has QEMU related parameters. The full list | ||
of QEMU parameters is below: | ||
* ``kernel_image``: This is the location of kernel image (e.g., ``Image``) which | ||
will be used as target's kernel. | ||
|
||
* ``arch``: Architecture type. Defaults to ``aarch64``. | ||
|
||
* ``cpu_types``: List of CPU ids for QEMU. The list only contains ``cortex-a72`` by | ||
default. This parameter is valid for Arm architectures only. | ||
|
||
* ``initrd_image``: This points to the location of initrd image (e.g., | ||
``rootfs.cpio.xz``) which will be used as target's root filesystem if kernel | ||
does not include one already. | ||
|
||
* ``mem_size``: Size of guest memory in MiB. | ||
|
||
* ``num_cores``: Number of CPU cores. Guest will have ``2`` cores by default. | ||
|
||
* ``num_threads``: Number of CPU threads. Set to ``2`` by defaults. | ||
|
||
* ``cmdline``: Kernel command line parameter. It only specifies console device in | ||
default (i.e., ``console=ttyAMA0``) which is valid for Arm architectures. | ||
May be changed to ``ttyS0`` for x86 platforms. | ||
|
||
* ``enable_kvm``: Specifies if KVM will be used as accelerator in QEMU or not. | ||
Enabled by default if host architecture matches with target's for improving | ||
QEMU performance. | ||
:type qemu_settings: Dict | ||
|
||
:param connection_settings: the dictionary to store connection settings | ||
of :attr:`Target.connection_settings`, defaults to None. | ||
:type connection_settings: Dict, optional | ||
|
||
:param make_target: Lambda function for creating :class:`Target` based | ||
object, defaults to :func:`lambda **kwargs: LinuxTarget(**kwargs)`. | ||
:type make_target: func, optional | ||
|
||
:Variable positional arguments: Forwarded to :class:`TargetRunner`. | ||
|
||
:raises FileNotFoundError: if QEMU executable, kernel or initrd image cannot be found. | ||
""" | ||
|
||
def __init__(self, | ||
qemu_settings, | ||
connection_settings=None, | ||
# pylint: disable=unnecessary-lambda | ||
make_target=lambda **kwargs: LinuxTarget(**kwargs), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can be eta-reduced (i.e. the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
**args): | ||
self.connection_settings = { | ||
'host': '127.0.0.1', | ||
'port': 8022, | ||
'username': 'root', | ||
'password': 'root', | ||
'strict_host_check': False, | ||
} | ||
|
||
if connection_settings is not None: | ||
self.connection_settings = self.connection_settings | connection_settings | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Union of dict is brought by PEP 584, which was implemented in Python 3.8. For now, devlib supports down to 3.7. The backward-compatible equivalent would be:
and you can further avoid a separate path with:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
||
qemu_args = { | ||
'kernel_image': '', | ||
'arch': 'aarch64', | ||
'cpu_type': 'cortex-a72', | ||
'initrd_image': '', | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems the consuming code checks to see if the value is fals-y. This could be avoided by simply not having the key by default. That typically results in cleaner code, as "having a value" means "the value must be used", and "not having a value" will result in a KeyError that can be explicitly handled. Otherwise, you end up with in-band signaling (some values are "good to use" and others are "not good to use"), on top of always having the risk of a KeyError because it's just a dict after all. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed. Removed empty values. |
||
'mem_size': 512, | ||
'num_cores': 2, | ||
'num_threads': 2, | ||
'cmdline': 'console=ttyAMA0', | ||
'enable_kvm': True, | ||
} | ||
|
||
qemu_args = qemu_args | qemu_settings | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ditto on PEP 584 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
|
||
qemu_executable = f'qemu-system-{qemu_args["arch"]}' | ||
qemu_path = which(qemu_executable) | ||
if qemu_path is None: | ||
raise FileNotFoundError(f'Cannot find {qemu_executable} executable!') | ||
|
||
if not os.path.exists(qemu_args["kernel_image"]): | ||
raise FileNotFoundError(f'{qemu_args["kernel_image"]} does not exist!') | ||
|
||
# pylint: disable=consider-using-f-string | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd agree with pylint here, that would make the code more readable. If that was not possible for whatever reason, I'd still use the keyword arguments of format() to provide named placeholders, rather than positional ones. Otherwise it's very easy to shift everything by one location and break the code, making it needlessly harder to modify in the future There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Changed it to f-string. |
||
qemu_cmd = '''\ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Prevents prepending leading spaces in following line. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. AFAIK having the space or not won't change anything. If it does, it's probably a bug in devlib. You can also just use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Having leading spaces is not a functional issue. Just a cosmetic one. |
||
{} -kernel {} -append "{}" -m {} -smp cores={},threads={} -netdev user,id=net0,hostfwd=tcp::{}-:22 \ | ||
-device virtio-net-pci,netdev=net0 --nographic\ | ||
'''.format( | ||
qemu_path, | ||
qemu_args["kernel_image"], | ||
qemu_args["cmdline"], | ||
qemu_args["mem_size"], | ||
qemu_args["num_cores"], | ||
qemu_args["num_threads"], | ||
self.connection_settings["port"], | ||
) | ||
|
||
if qemu_args["initrd_image"]: | ||
if not os.path.exists(qemu_args["initrd_image"]): | ||
raise FileNotFoundError(f'{qemu_args["initrd_image"]} does not exist!') | ||
|
||
qemu_cmd += f' -initrd {qemu_args["initrd_image"]}' | ||
|
||
if qemu_args["arch"] == machine(): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are we sure that whatever Python returns in platform.machine() actually agrees with qemu arch names ? This is typically a can of worm, where half the world uses There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Relaxed the check by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it correct though ? I know that for now arm64 hosts are not really supported in LISA, but devlib might have a different standing there, and we also don't want to prevent it from working the day the rest (e.g. adb) is actually ready. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is the new version of the code in #677: if qemu_args['arch'].startswith('x86') and machine().startswith('x86'):
if qemu_args["enable_kvm"]:
qemu_cmd += ' --enable-kvm'
else:
qemu_cmd += f' -machine virt -cpu {qemu_args["cpu_type"]}' I want to enable KVM only for x86 and specify There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Is KVM not working on aarch64 ?
What does There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Works for sure, but there are some restrictions: https://stackoverflow.com/a/71220201.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I had a look at those restrictions and they don't seem to be too problematic. No-one is running an arm 32bits host these days for any CI system AFAIK, and
Fair enough, so that's really attached to x86 target itself and not assuming the host is x86. Make sure that in both cases, you add a comment stating the reason why this code takes those decisions so it can be revisited easily in the future (e.g. one day x86_64 might support virt). There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Made a small improvement regarding the host/guest checks. If we don't have any open comments left here, let's continue reviewing on #677 please. |
||
if qemu_args["enable_kvm"]: | ||
qemu_cmd += ' --enable-kvm' | ||
else: | ||
qemu_cmd += f' -machine virt -cpu {qemu_args["cpu_type"]}' | ||
|
||
self.target = make_target(connect=False, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. assigning to self.target is the responsibility of the base class's There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
conn_cls=SshConnection, | ||
connection_settings=self.connection_settings) | ||
|
||
super().__init__(runner_cmd=qemu_cmd, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. All user input that makes it to qemu_cmd must be escaped using shlex.quote(). I know this is cumbersome, but the only other way to deal with that problem is to ask for the command as a list like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I thought this is addressed by There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This is broken. Either the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Converted |
||
target=self.target, | ||
**args) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -367,25 +367,32 @@ def __init__(self, | |
else: | ||
logger.debug('Using SFTP for file transfer') | ||
|
||
self.client = self._make_client() | ||
atexit.register(self.close) | ||
|
||
# Use a marker in the output so that we will be able to differentiate | ||
# target connection issues with "password needed". | ||
# Also, sudo might not be installed at all on the target (but | ||
# everything will work as long as we login as root). If sudo is still | ||
# needed, it will explode when someone tries to use it. After all, the | ||
# user might not be interested in being root at all. | ||
self._sudo_needs_password = ( | ||
'NEED_PASSWORD' in | ||
self.execute( | ||
# sudo -n is broken on some versions on MacOSX, revisit that if | ||
# someone ever cares | ||
'sudo -n true || echo NEED_PASSWORD', | ||
as_root=False, | ||
check_exit_code=False, | ||
self.client = None | ||
try: | ||
self.client = self._make_client() | ||
atexit.register(self.close) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unrelated to this PR but we should probably fix this: if we keep creating connection objects (and we do because of the multithreading support), we end up:
let me know if you want to get your hands dirty in low level Python, otherwise I'll do it There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will leave it up to you :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Here we are #679 |
||
|
||
# Use a marker in the output so that we will be able to differentiate | ||
# target connection issues with "password needed". | ||
# Also, sudo might not be installed at all on the target (but | ||
# everything will work as long as we login as root). If sudo is still | ||
# needed, it will explode when someone tries to use it. After all, the | ||
# user might not be interested in being root at all. | ||
self._sudo_needs_password = ( | ||
'NEED_PASSWORD' in | ||
self.execute( | ||
# sudo -n is broken on some versions on MacOSX, revisit that if | ||
# someone ever cares | ||
'sudo -n true || echo NEED_PASSWORD', | ||
as_root=False, | ||
check_exit_code=False, | ||
) | ||
) | ||
) | ||
|
||
except BaseException: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is one of the cases where
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I see following error log if I include the snippet above:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a bug that needs to be fixed separately. When I'm not quite sure why that problem is masked by your initial code though, but I suppose it doesn't really matter There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. EDIT: actually there is an issue with my code, it should be: except BaseException as e:
try:
if self.client is not None:
self.client.close()
finally:
raise e The reason you saw that issue is because the exception was discarded and never re-raised in the self.client.close() path. This means that the metaprogramming There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Applied this patch. |
||
if self.client is not None: | ||
self.client.close() | ||
raise | ||
|
||
def _make_client(self): | ||
if self.strict_host_check: | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,7 @@ Contents: | |
derived_measurements | ||
platform | ||
connection | ||
tools | ||
|
||
Indices and tables | ||
================== | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: Would
path
be a more descriptive parameter name?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But it's working directory?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my mind the function is designed to create a new temp directory/file under the path provided from the user. The default location is just set to the working directory.
Having said this, after checking the documentation for
tempfile.mkstemp
I see they usedir
as parameter name so not a strong opinion on this.