Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 10 additions & 1 deletion openwisp_controller/connection/connectors/ssh.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import logging
import socket
import time
from io import BytesIO, StringIO

import paramiko
Expand Down Expand Up @@ -186,6 +187,7 @@ def exec_command(
logger.info("Executing command: {0}".format(command))
# execute commmand
try:
start_cmd = time.perf_counter()
stdin, stdout, stderr = self.shell.exec_command(command, timeout=timeout)
# re-raise socket.timeout to avoid being catched
# by the subsequent `except Exception as e` block
Expand All @@ -196,7 +198,14 @@ def exec_command(
logger.exception(e)
raise e
# store command exit status
exit_status = stdout.channel.recv_exit_status()
# workaround https://github.com/paramiko/paramiko/issues/1815
# workaround https://github.com/paramiko/paramiko/issues/1787
# Ref. https://docs.paramiko.org/en/stable/api/channel.html#paramiko.channel.Channel.recv_exit_status # noqa
stdout.channel.status_event.wait(
timeout=timeout - int(time.perf_counter() - start_cmd)
)
assert stdout.channel.status_event.is_set()
exit_status = stdout.channel.exit_status
# log standard output
# try to decode to UTF-8, ignoring unconvertible characters
# https://docs.python.org/3/howto/unicode.html#the-string-type
Expand Down
5 changes: 3 additions & 2 deletions openwisp_controller/connection/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import socket
from unittest import mock
from unittest.mock import PropertyMock

import paramiko
from django.contrib.auth.models import ContentType
Expand Down Expand Up @@ -46,7 +47,7 @@ def _exec_command_return_value(
stderr_ = mock.Mock()
stdin_.read().decode.return_value = stdin
stdout_.read().decode.return_value = stdout
stdout_.channel.recv_exit_status.return_value = exit_code
type(stdout_.channel).exit_status = PropertyMock(return_value=exit_code)
stderr_.read().decode.return_value = stderr
return (stdin_, stdout_, stderr_)

Expand Down Expand Up @@ -1009,7 +1010,7 @@ def _assert_applying_conf_test_command(mocked_exec):
# 1. Checking openwisp_config returns with 0
# 2. Testing presence of /tmp/openwisp/applying_conf returns with 1
# 3. Restarting openwisp_config returns with 0 exit code
stdout.channel.recv_exit_status.side_effect = [0, 1, 1]
type(stdout.channel).exit_status = PropertyMock(side_effect=[0, 1, 1])
mocked_exec_command.return_value = (stdin, stdout, stderr)
conf.save()
self.assertEqual(mocked_exec_command.call_count, 3)
Expand Down
Loading