Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
9 changes: 6 additions & 3 deletions openwisp_controller/config/base/vpn.py
Original file line number Diff line number Diff line change
Expand Up @@ -892,12 +892,15 @@ def _get_common_name(self):
"""
d = self.config.device
end = 63 - len(d.mac_address)
d.name = d.name[:end]
name = d.name[:end]
unique_slug = shortuuid.ShortUUID().random(length=8)
cn_format = app_settings.COMMON_NAME_FORMAT
if cn_format == "{mac_address}-{name}" and d.name == d.mac_address:

if cn_format == "{mac_address}-{name}" and name == d.mac_address:
cn_format = "{mac_address}"
common_name = cn_format.format(**d.__dict__)[:55]

data = {**d.__dict__, "name": name}
common_name = cn_format.format(**data)[:55]
common_name = f"{common_name}-{unique_slug}"
return common_name

Expand Down
100 changes: 0 additions & 100 deletions openwisp_controller/connection/tasks.py
Original file line number Diff line number Diff line change
@@ -1,101 +1 @@
import logging
import time

import swapper
from celery import current_app, shared_task
from celery.exceptions import SoftTimeLimitExceeded
from django.core.exceptions import ObjectDoesNotExist
from django.utils.translation import gettext_lazy as _
from swapper import load_model

from . import settings as app_settings
from .connectors.exceptions import CommandTimeoutException
from .exceptions import NoWorkingDeviceConnectionError

logger = logging.getLogger(__name__)
_TASK_NAME = "openwisp_controller.connection.tasks.update_config"


def _is_update_in_progress(device_id, current_task_id=None):
active = current_app.control.inspect().active()
if not active:
return False
# check if there's any other running task before adding it
# exclude the current task by comparing task IDs
for task_list in active.values():
for task in task_list:
if (
task["name"] == _TASK_NAME
and str(device_id) in task["args"]
and task["id"] != current_task_id
):
return True
return False


@shared_task(bind=True)
def update_config(self, device_id):
"""
Launches the ``update_config()`` operation
of a specific device in the background
"""
Device = swapper.load_model(*swapper.split(app_settings.UPDATE_CONFIG_MODEL))
DeviceConnection = swapper.load_model("connection", "DeviceConnection")
# wait for the saving operations of this device to complete
# (there may be multiple ones happening at the same time)
time.sleep(2)
try:
device = Device.objects.select_related("config").get(pk=device_id)
# abort operation if device shouldn't be updated
if not device.can_be_updated():
logger.info(f"{device} (pk: {device_id}) is not going to be updated")
return
except ObjectDoesNotExist as e:
logger.warning(f'update_config("{device_id}") failed: {e}')
return
if _is_update_in_progress(device_id, current_task_id=self.request.id):
return
try:
device_conn = DeviceConnection.get_working_connection(device)
except NoWorkingDeviceConnectionError:
return
else:
logger.info(f"Updating {device} (pk: {device_id})")
device_conn.update_config()


# task timeout is SSH_COMMAND_TIMEOUT plus a 20% margin
@shared_task(soft_time_limit=app_settings.SSH_COMMAND_TIMEOUT * 1.2)
def launch_command(command_id):
"""
Launches execution of commands in the background
"""
Command = load_model("connection", "Command")
try:
command = Command.objects.get(pk=command_id)
except Command.DoesNotExist as e:
logger.warning(f'launch_command("{command_id}") failed: {e}')
return
try:
command.execute()
except SoftTimeLimitExceeded:
command.status = "failed"
command._add_output(_("Background task time limit exceeded."))
command.save()
except CommandTimeoutException as e:
command.status = "failed"
command._add_output(_(f"The command took longer than expected: {e}"))
command.save()
except Exception as e:
logger.exception(
f"An exception was raised while executing command {command_id}"
)
command.status = "failed"
command._add_output(_(f"Internal system error: {e}"))
command.save()


@shared_task(soft_time_limit=3600)
def auto_add_credentials_to_devices(credential_id, organization_id):
Credentials = load_model("connection", "Credentials")
Credentials.auto_add_to_devices(credential_id, organization_id)
29 changes: 29 additions & 0 deletions openwisp_controller/connection/tests/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,3 +156,32 @@ def test_update_config_hostname_changed_on_reregister(self, mocked_update_config
)
self.assertEqual(response.status_code, 201)
mocked_update_config.assert_not_called()

def test_update_config_closes_connection_on_exception(self):
with mock.patch(
"openwisp_controller.connection.tasks.DeviceConnection.get_working_connection"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix flake8 E501 on patch target line.

Line 162 is over 88 chars and is currently failing CI. Please split the dotted path into adjacent string literals.

💡 Proposed fix
-            "openwisp_controller.connection.tasks.DeviceConnection.get_working_connection"
+            "openwisp_controller.connection.tasks."
+            "DeviceConnection.get_working_connection"

Based on learnings, in this repository flake8 enforces E501 (max-line-length = 88) and # noqa: E501 should be used sparingly.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"openwisp_controller.connection.tasks.DeviceConnection.get_working_connection"
"openwisp_controller.connection.tasks."
"DeviceConnection.get_working_connection"
🧰 Tools
🪛 GitHub Actions: OpenWISP Controller CI Build

[error] 162-162: Flake8 (E501) line too long (90 > 88 characters)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openwisp_controller/connection/tests/test_tasks.py` at line 162, The long
dotted path
"openwisp_controller.connection.tasks.DeviceConnection.get_working_connection"
exceeds the 88-char limit; fix it by splitting the string into adjacent string
literals (for example split between modules and class name), e.g.
"openwisp_controller.connection.tasks."
"DeviceConnection.get_working_connection" where that dotted path appears so
flake8 E501 no longer triggers while preserving the exact concatenated value
used by the test.

) as mocked_get_conn, mock.patch(
"openwisp_controller.connection.tasks.Device.objects.select_related"
) as mocked_device, mock.patch(
"openwisp_controller.connection.tasks._is_update_in_progress",
return_value=False,
):
device = mock.MagicMock()
device.can_be_updated.return_value = True
mocked_device.return_value.get.return_value = device

device_conn = mock.MagicMock()
device_conn.update_config.side_effect = Exception("Test exception")
mocked_get_conn.return_value = device_conn

from openwisp_controller.connection.tasks import update_config

try:
update_config(device_id=1)
except Exception:
pass

self.assertTrue(
device_conn.disconnect.called or device_conn.close.called,
"Connection was not properly closed",
)
Loading