Skip to content

Commit 6578b12

Browse files
Merge branch 'master' into issues/1016-command-connection-check
2 parents d6ec29a + 35dbfe5 commit 6578b12

4 files changed

Lines changed: 59 additions & 20 deletions

File tree

README.rst

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,10 +55,10 @@ Other popular building blocks that are part of the OpenWISP ecosystem are:
5555
- `openwisp-firmware-upgrader
5656
<https://openwisp.io/docs/stable/firmware-upgrader/>`_: automated
5757
firmware upgrades (single devices or mass network upgrades)
58-
- `openwisp-radius <https://openwisp.io/docs/stable/user/radius.html>`_:
59-
based on FreeRADIUS, allows to implement network access authentication
60-
systems like 802.1x WPA2 Enterprise, captive portal authentication,
61-
Hotspot 2.0 (802.11u)
58+
- `openwisp-radius <https://openwisp.io/docs/stable/radius/>`_: based on
59+
FreeRADIUS, allows to implement network access authentication systems
60+
like 802.1x WPA2 Enterprise, captive portal authentication, Hotspot 2.0
61+
(802.11u)
6262
- `openwisp-network-topology
6363
<https://openwisp.io/docs/stable/network-topology/>`_: provides way to
6464
collect and visualize network topology data from dynamic mesh routing

openwisp_controller/connection/admin.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,20 +92,24 @@ def get_queryset(self, request):
9292
return super(admin.StackedInline, self).get_queryset(request)
9393

9494

95+
class LimitedCommandResults(forms.models.BaseInlineFormSet):
96+
"""Limits results to 30"""
97+
98+
def get_queryset(self):
99+
return super().get_queryset()[0:30]
100+
101+
95102
class CommandInline(admin.StackedInline):
96103
model = Command
97104
verbose_name = _('Recent Commands')
98105
verbose_name_plural = verbose_name
99106
fields = ['status', 'type', 'input_data', 'output_data', 'created', 'modified']
100107
readonly_fields = ['input_data', 'output_data']
101-
# hack for openwisp-monitoring integration
102-
# TODO: remove when this issue solved:
103-
# https://github.com/theatlantic/django-nested-admin/issues/128#issuecomment-665833142
104-
sortable_options = {'disabled': True}
108+
formset = LimitedCommandResults
105109

106110
def get_queryset(self, request, select_related=True):
107111
"""
108-
Return recent commands for this device
112+
Return the most recent commands for this device
109113
(created within the last 7 days)
110114
"""
111115
qs = super().get_queryset(request)

openwisp_controller/connection/channels/consumers.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import json
2+
from copy import deepcopy
23

34
from swapper import load_model
45

@@ -9,5 +10,6 @@
910

1011
class CommandConsumer(BaseDeviceConsumer):
1112
def send_update(self, event):
12-
event.pop('type')
13-
self.send(json.dumps(event))
13+
data = deepcopy(event)
14+
data.pop('type')
15+
self.send(json.dumps(data))

openwisp_controller/connection/tests/pytest.py

Lines changed: 42 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -36,11 +36,7 @@ async def _get_communicator(self, admin_client, device_id):
3636
assert connected is True
3737
return communicator
3838

39-
@mock.patch('paramiko.SSHClient.connect')
40-
async def test_new_command_created(self, admin_user, admin_client):
41-
device_conn = await database_sync_to_async(self._create_device_connection)()
42-
communicator = await self._get_communicator(admin_client, device_conn.device_id)
43-
39+
async def _create_command(self, device_conn):
4440
command = Command(
4541
device_id=device_conn.device_id,
4642
connection=device_conn,
@@ -52,11 +48,12 @@ async def test_new_command_created(self, admin_user, admin_client):
5248
mocked_exec_command.return_value = self._exec_command_return_value(
5349
stdout='test'
5450
)
55-
await database_sync_to_async(command.save)()
56-
await database_sync_to_async(command.refresh_from_db)()
51+
await database_sync_to_async(command.save)()
52+
await database_sync_to_async(command.refresh_from_db)()
53+
return command
5754

58-
response = await communicator.receive_json_from()
59-
expected_response = {
55+
def _get_expected_response(self, command):
56+
return {
6057
'model': 'Command',
6158
'data': {
6259
'id': str(command.id),
@@ -70,5 +67,41 @@ async def test_new_command_created(self, admin_user, admin_client):
7067
'connection': str(command.connection_id),
7168
},
7269
}
70+
71+
@mock.patch('paramiko.SSHClient.connect')
72+
async def test_new_command_created(self, admin_user, admin_client):
73+
device_conn = await database_sync_to_async(self._create_device_connection)()
74+
communicator = await self._get_communicator(admin_client, device_conn.device_id)
75+
command = await self._create_command(device_conn)
76+
response = await communicator.receive_json_from()
77+
expected_response = self._get_expected_response(command)
7378
assert response == expected_response
7479
await communicator.disconnect()
80+
81+
async def test_multiple_connections_receive_updates_with_redis(
82+
self, admin_user, admin_client, settings
83+
):
84+
settings.CHANNEL_LAYERS = {
85+
'default': {
86+
'BACKEND': 'channels_redis.core.RedisChannelLayer',
87+
'CONFIG': {
88+
'hosts': [('localhost', 6379)],
89+
},
90+
},
91+
}
92+
93+
device_conn = await database_sync_to_async(self._create_device_connection)()
94+
communicator1 = await self._get_communicator(
95+
admin_client, device_conn.device_id
96+
)
97+
communicator2 = await self._get_communicator(
98+
admin_client, device_conn.device_id
99+
)
100+
command = await self._create_command(device_conn)
101+
response1 = await communicator1.receive_json_from()
102+
response2 = await communicator2.receive_json_from()
103+
expected_response = self._get_expected_response(command)
104+
assert response1 == expected_response
105+
assert response2 == expected_response
106+
await communicator1.disconnect()
107+
await communicator2.disconnect()

0 commit comments

Comments
 (0)