From 2b2f60d8d70492119543fafb0fa181277f685acf Mon Sep 17 00:00:00 2001 From: ChrisTimperley Date: Tue, 13 Aug 2019 12:52:49 -0400 Subject: [PATCH 01/11] added shell section --- docs/api.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index 54ff8395..ce0aff49 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -10,6 +10,7 @@ the roswire library. .. autoclass:: ROSWire :members: + System ------ @@ -25,6 +26,15 @@ with a :code:`bash` shell inside the application container. :members: +Shell +----- + +.. py:module:: roswire.proxy.shell +.. autoclass:: ShellProxy() + +.. autoclass:: Popen() + + Descriptions ------------ From ce8470c9b167d50bc3907e0930cffe4225c3aa9c Mon Sep 17 00:00:00 2001 From: ChrisTimperley Date: Tue, 13 Aug 2019 13:49:32 -0400 Subject: [PATCH 02/11] describe members --- docs/api.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/api.rst b/docs/api.rst index ce0aff49..2298a207 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -31,8 +31,10 @@ Shell .. py:module:: roswire.proxy.shell .. autoclass:: ShellProxy() + :members: .. autoclass:: Popen() + :members: Descriptions From 9e3f6640c11fd34272fb142541ebdc4c552ace78 Mon Sep 17 00:00:00 2001 From: ChrisTimperley Date: Tue, 13 Aug 2019 16:34:19 -0400 Subject: [PATCH 03/11] added docstring to send_signal --- src/roswire/proxy/shell.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/roswire/proxy/shell.py b/src/roswire/proxy/shell.py index 8a2bc901..b667c6a6 100644 --- a/src/roswire/proxy/shell.py +++ b/src/roswire/proxy/shell.py @@ -163,6 +163,15 @@ def local_to_host_pid(self, pid_local: int) -> Optional[int]: return None def send_signal(self, pid: int, sig: int) -> None: + """Sends a given signal to a specified process. + + Parameters + ---------- + pid: int + The PID of the process. + sig: int + The signal number. + """ self.execute(f'kill -{sig} {pid}', user='root') def __generate_popen_uid(self, command: str) -> str: From 04c968952f72a0d892d8d566c73327eb6a797d78 Mon Sep 17 00:00:00 2001 From: ChrisTimperley Date: Tue, 13 Aug 2019 16:48:28 -0400 Subject: [PATCH 04/11] added docstring to execute --- src/roswire/proxy/shell.py | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/roswire/proxy/shell.py b/src/roswire/proxy/shell.py index b667c6a6..30982d14 100644 --- a/src/roswire/proxy/shell.py +++ b/src/roswire/proxy/shell.py @@ -250,6 +250,39 @@ def execute(self, time_limit: Optional[int] = None, kill_after: int = 1 ) -> Tuple[int, str, float]: + """Executes a given command and blocks until its completion. + + Parameters + ---------- + command: str + The command that should be executed. + stdout: bool + If :code:`True`, includes stdout as part of output. + stderr: bool + If :code:`True`, includes stderr as part of output. + user: str, optional + The name or UID of the user, inside the container, that should + execute the command. If left unspecified, the default user for + the container will be used. + context: str, optional + The absolute path to the working directory that should be used + when executing the command. If unspecified, the default working + directory for the container will be used. + time_limit: int, optional + The maximum number of seconds that the command is allowed to run + before being terminated via SIGTERM. If unspecified, no time limit + will be imposed on command execution. + kill_after: int + The maximum number of seconds to wait before sending SIGKILL to + the process after attempting termination via SIGTERM. Only applies + when :code:`time_limit` is specified. + + Returns + ------- + Tuple[int, str, float] + The return code, output, and wall-clock running time of the + execution. + """ logger.debug("executing command: %s", command) dockerc = self.__container_docker command = self.instrument(command, time_limit, kill_after) From 22a223746e2b99d844b20539a41fdb8d35f4dadc Mon Sep 17 00:00:00 2001 From: ChrisTimperley Date: Tue, 13 Aug 2019 16:50:04 -0400 Subject: [PATCH 05/11] changed visibility of instrument --- src/roswire/proxy/shell.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/roswire/proxy/shell.py b/src/roswire/proxy/shell.py index 30982d14..957b464c 100644 --- a/src/roswire/proxy/shell.py +++ b/src/roswire/proxy/shell.py @@ -193,12 +193,12 @@ def environ(self, var: str) -> str: raise exceptions.EnvNotFoundError(var) return val - def instrument(self, - command: str, - time_limit: Optional[int] = None, - kill_after: int = 1, - identifier: Optional[str] = None - ) -> str: + def _instrument(self, + command: str, + time_limit: Optional[int] = None, + kill_after: int = 1, + identifier: Optional[str] = None + ) -> str: logger.debug("instrumenting command: %s", command) q = shlex.quote command = f'source /.environment && {command}' @@ -225,7 +225,7 @@ def popen(self, id_container = self.__container_docker.id api_docker = self.__api_docker command_orig = command - command = self.instrument(command, time_limit, kill_after, + command = self._instrument(command, time_limit, kill_after, identifier=uid_popen) exec_resp = api_docker.exec_create(id_container, command, tty=True, @@ -285,7 +285,7 @@ def execute(self, """ logger.debug("executing command: %s", command) dockerc = self.__container_docker - command = self.instrument(command, time_limit, kill_after) + command = self._instrument(command, time_limit, kill_after) timer = Stopwatch() timer.start() From 088ba1917c4f16acb992d8b047b333e2f26a934e Mon Sep 17 00:00:00 2001 From: ChrisTimperley Date: Tue, 13 Aug 2019 16:51:22 -0400 Subject: [PATCH 06/11] added docstring to send_signal --- src/roswire/proxy/shell.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/roswire/proxy/shell.py b/src/roswire/proxy/shell.py index 957b464c..0aef4cd1 100644 --- a/src/roswire/proxy/shell.py +++ b/src/roswire/proxy/shell.py @@ -81,6 +81,13 @@ def returncode(self) -> Optional[int]: return self.__returncode def send_signal(self, sig: int) -> None: + """Sends a given signal to this process. + + Parameters + ---------- + sig: int + The signal number. + """ pid = self.pid if pid: self.__shell.send_signal(pid, sig) From a736f1efc42054173675b994c8b0db9f51d9470e Mon Sep 17 00:00:00 2001 From: ChrisTimperley Date: Tue, 13 Aug 2019 17:03:47 -0400 Subject: [PATCH 07/11] added docs to Popen --- src/roswire/proxy/shell.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/roswire/proxy/shell.py b/src/roswire/proxy/shell.py index 0aef4cd1..ab4628f9 100644 --- a/src/roswire/proxy/shell.py +++ b/src/roswire/proxy/shell.py @@ -23,6 +23,26 @@ class Popen: + """Provides access to a process that is running inside a given shell. + Inspired by the :class:`subprocess.Popen` interface within the Python + standard library. Unlike :class:`subprocess.Popen`, instances of this + class should be generated by :meth:`ShellProxy.popen` rather than via + the constructor. + + Attributes + ---------- + stream: Iterator[str] + An output stream for this process. + args: str + The argument string that was used to generate this process. + pid: int, optional + The PID of this process, if known. + finished: bool + A dynamic flag (i.e., a property) that indicates whether this process + has terminated. + retcode: int, optional + The return code produced by this process, if known. + """ def __init__(self, args: str, uid: str, @@ -71,7 +91,6 @@ def pid(self) -> Optional[int]: @property def finished(self) -> bool: - """True if the process has exited; False if not.""" return self.returncode is not None @property @@ -81,7 +100,7 @@ def returncode(self) -> Optional[int]: return self.__returncode def send_signal(self, sig: int) -> None: - """Sends a given signal to this process. + """Sends a given signal to the process. Parameters ---------- @@ -109,7 +128,7 @@ def wait(self, time_limit: Optional[float] = None) -> int: Parameters ---------- - time_limit: Optional[float] = None + time_limit: float, optional An optional time limit. Raises From 99c45b7f80562954f648b1538b80d099f9b8cba3 Mon Sep 17 00:00:00 2001 From: ChrisTimperley Date: Tue, 13 Aug 2019 17:14:15 -0400 Subject: [PATCH 08/11] described popen --- src/roswire/proxy/shell.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/roswire/proxy/shell.py b/src/roswire/proxy/shell.py index ab4628f9..fae70eaf 100644 --- a/src/roswire/proxy/shell.py +++ b/src/roswire/proxy/shell.py @@ -247,6 +247,17 @@ def popen(self, time_limit: Optional[int] = None, kill_after: int = 1 ) -> Popen: + """Creates a process without blocking, and returns an interface to it. + Inspired by :meth:`subprocess.Popen` in the Python standard library. + This method can be used, for example, to stream the output of a + non-blocking process, or to send a signal (e.g., SIGTERM) to a process + at run-time. + + Returns + ------- + Popen + An interface for interacting with and inspecting the process. + """ uid_popen = self.__generate_popen_uid(command) id_container = self.__container_docker.id api_docker = self.__api_docker From 98f8bdad8629fcf8f9000484e818ae5cc9ee5c43 Mon Sep 17 00:00:00 2001 From: ChrisTimperley Date: Tue, 13 Aug 2019 17:25:00 -0400 Subject: [PATCH 09/11] tidied docs for shell --- src/roswire/proxy/shell.py | 65 +++++++++++++++++++++++--------------- 1 file changed, 40 insertions(+), 25 deletions(-) diff --git a/src/roswire/proxy/shell.py b/src/roswire/proxy/shell.py index fae70eaf..80747cd3 100644 --- a/src/roswire/proxy/shell.py +++ b/src/roswire/proxy/shell.py @@ -162,7 +162,18 @@ def exec_id_to_host_pid(self, exec_id: str) -> int: return self.__api_docker.exec_inspect(exec_id)['Pid'] def local_to_host_pid(self, pid_local: int) -> Optional[int]: - """Finds the host PID for a process inside this shell.""" + """Finds the host PID for a process inside this shell. + + Parameters + ---------- + pid_local: int + The PID of the process inside the container. + + Returns + ------- + int + The PID of the same process on the host machine. + """ ctr_pids = [self.__container_pid] info = self.__api_docker.inspect_container(self.__container_docker.id) ctr_pids += [self.exec_id_to_host_pid(i) for i in info['ExecIDs']] @@ -253,6 +264,31 @@ def popen(self, non-blocking process, or to send a signal (e.g., SIGTERM) to a process at run-time. + Parameters + ---------- + command: str + The command that should be executed. + stdout: bool + If :code:`True`, includes stdout as part of output. + stderr: bool + If :code:`True`, includes stderr as part of output. + user: str, optional + The name or UID of the user, inside the container, that should + execute the command. If left unspecified, the default user for + the container will be used. + context: str, optional + The absolute path to the working directory that should be used + when executing the command. If unspecified, the default working + directory for the container will be used. + time_limit: int, optional + The maximum number of seconds that the command is allowed to run + before being terminated via SIGTERM. If unspecified, no time limit + will be imposed on command execution. + kill_after: int + The maximum number of seconds to wait before sending SIGKILL to + the process after attempting termination via SIGTERM. Only applies + when :code:`time_limit` is specified. + Returns ------- Popen @@ -289,30 +325,9 @@ def execute(self, ) -> Tuple[int, str, float]: """Executes a given command and blocks until its completion. - Parameters - ---------- - command: str - The command that should be executed. - stdout: bool - If :code:`True`, includes stdout as part of output. - stderr: bool - If :code:`True`, includes stderr as part of output. - user: str, optional - The name or UID of the user, inside the container, that should - execute the command. If left unspecified, the default user for - the container will be used. - context: str, optional - The absolute path to the working directory that should be used - when executing the command. If unspecified, the default working - directory for the container will be used. - time_limit: int, optional - The maximum number of seconds that the command is allowed to run - before being terminated via SIGTERM. If unspecified, no time limit - will be imposed on command execution. - kill_after: int - The maximum number of seconds to wait before sending SIGKILL to - the process after attempting termination via SIGTERM. Only applies - when :code:`time_limit` is specified. + Note + ---- + Accepts the same arguments as :meth:`popen`. Returns ------- From f1e82627885d80afc3ccb5c59b202ef9d8509c9c Mon Sep 17 00:00:00 2001 From: ChrisTimperley Date: Tue, 13 Aug 2019 17:27:39 -0400 Subject: [PATCH 10/11] fixed bad style --- src/roswire/proxy/shell.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/roswire/proxy/shell.py b/src/roswire/proxy/shell.py index 80747cd3..d43cc449 100644 --- a/src/roswire/proxy/shell.py +++ b/src/roswire/proxy/shell.py @@ -299,7 +299,7 @@ def popen(self, api_docker = self.__api_docker command_orig = command command = self._instrument(command, time_limit, kill_after, - identifier=uid_popen) + identifier=uid_popen) exec_resp = api_docker.exec_create(id_container, command, tty=True, stdout=stdout, From 7ffcd7fb62f7b0a6f53e080e8dabeb77952e87d2 Mon Sep 17 00:00:00 2001 From: ChrisTimperley Date: Tue, 13 Aug 2019 17:33:05 -0400 Subject: [PATCH 11/11] hide system constructor --- docs/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/api.rst b/docs/api.rst index 2298a207..6001e450 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -22,7 +22,7 @@ through a number of loosely-coupled proxies that are represented as attributes. For example, :attr:`System.shell` exposes a proxy for interacting with a :code:`bash` shell inside the application container. -.. autoclass:: System +.. autoclass:: System() :members: