Skip to content

Commit 8690016

Browse files
authored
Merge branch 'master' into feature/894-rest-api-revisions
2 parents bd604d3 + 8288c23 commit 8690016

File tree

10 files changed

+128
-48
lines changed

10 files changed

+128
-48
lines changed

openwisp_controller/config/base/config.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -272,29 +272,26 @@ def manage_vpn_clients(cls, action, instance, pk_set, **kwargs):
272272
else:
273273
templates = pk_set
274274

275-
# Get current VPNs in use by any template
276-
current_vpns = set(
277-
instance.templates.filter(type='vpn').values_list('vpn_id', flat=True)
278-
)
275+
# Delete VPN clients that are not associated with current templates
276+
instance.vpnclient_set.exclude(
277+
template_id__in=instance.templates.values_list('id', flat=True)
278+
).delete()
279279

280-
# Handle template actions
281-
for template in templates.filter(type='vpn'):
282-
if action == 'post_add':
280+
if action == 'post_add':
281+
for template in templates.filter(type='vpn'):
283282
# Create VPN client if needed
284283
if not vpn_client_model.objects.filter(
285-
config=instance, vpn=template.vpn
284+
config=instance, vpn=template.vpn, template=template
286285
).exists():
287286
client = vpn_client_model(
288287
config=instance,
289288
vpn=template.vpn,
289+
template=template,
290290
auto_cert=template.auto_cert,
291291
)
292292
client.full_clean()
293293
client.save()
294294

295-
# Clean up any VPN clients that aren't associated with current templates
296-
instance.vpnclient_set.exclude(vpn_id__in=current_vpns).delete()
297-
298295
@classmethod
299296
def clean_templates_org(cls, action, instance, pk_set, raw_data=None, **kwargs):
300297
"""

openwisp_controller/config/base/vpn.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -796,6 +796,10 @@ class AbstractVpnClient(models.Model):
796796
config = models.ForeignKey(
797797
get_model_name('config', 'Config'), on_delete=models.CASCADE
798798
)
799+
template = models.ForeignKey(
800+
get_model_name('config', 'Template'),
801+
on_delete=models.CASCADE,
802+
)
799803
vpn = models.ForeignKey(get_model_name('config', 'Vpn'), on_delete=models.CASCADE)
800804
cert = models.OneToOneField(
801805
get_model_name('django_x509', 'Cert'),
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Generated by Django 4.2.18 on 2025-03-18 15:53
2+
3+
import django.db.models.deletion
4+
from django.conf import settings
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
dependencies = [
10+
("config", "0055_alter_config_status"),
11+
]
12+
13+
operations = [
14+
migrations.AddField(
15+
model_name="vpnclient",
16+
name="template",
17+
field=models.ForeignKey(
18+
default=None,
19+
null=True,
20+
on_delete=django.db.models.deletion.CASCADE,
21+
to=settings.CONFIG_TEMPLATE_MODEL,
22+
),
23+
),
24+
]
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Generated by Django 3.2.15 on 2022-09-16 11:45
2+
3+
4+
from django.db import migrations
5+
6+
7+
def populate_vpnclient_template(apps, schema_editor):
8+
VpnClient = apps.get_model('config', 'VpnClient')
9+
10+
for vpn_client in VpnClient.objects.iterator():
11+
if vpn_client.template is None:
12+
vpn_client.template = vpn_client.config.templates.get(
13+
vpn_id=vpn_client.vpn_id
14+
)
15+
vpn_client.save()
16+
17+
18+
class Migration(migrations.Migration):
19+
dependencies = [
20+
('config', '0056_vpnclient_template'),
21+
]
22+
23+
operations = [
24+
migrations.RunPython(populate_vpnclient_template, migrations.RunPython.noop)
25+
]
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Generated by Django 4.2.18 on 2025-03-18 15:55
2+
3+
import django.db.models.deletion
4+
from django.conf import settings
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
dependencies = [
10+
("config", "0057_populate_vpnclient_template"),
11+
]
12+
13+
operations = [
14+
migrations.AlterField(
15+
model_name="vpnclient",
16+
name="template",
17+
field=models.ForeignKey(
18+
on_delete=django.db.models.deletion.CASCADE,
19+
to=settings.CONFIG_TEMPLATE_MODEL,
20+
),
21+
),
22+
]

openwisp_controller/config/tests/test_admin.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2129,6 +2129,7 @@ def test_vpn_template_switch(self):
21292129
response = self.client.post(path, data=params, follow=True)
21302130
self.assertEqual(response.status_code, 200)
21312131
config.refresh_from_db()
2132+
self.assertEqual(config.vpnclient_set.count(), 1)
21322133
del config.backend_instance
21332134
self.assertEqual(
21342135
config.backend_instance.config['openvpn'][0]['cert'],
@@ -2138,7 +2139,6 @@ def test_vpn_template_switch(self):
21382139
config.backend_instance.config['openvpn'][0]['dev'],
21392140
'tun1',
21402141
)
2141-
self.assertEqual(config.vpnclient_set.count(), 1)
21422142

21432143
with self.subTest('Switch device back to template1'):
21442144
params.update(

openwisp_controller/config/tests/test_config.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,16 @@ def test_clear_vpnclient(self):
424424
self.assertIsNotNone(vpnclient)
425425
self.assertNotEqual(c.vpnclient_set.count(), 0)
426426

427+
def test_deleting_template_deletes_vpnclient(self):
428+
template = self._create_template(
429+
name='test-network', type='vpn', vpn=self._create_vpn(), default=True
430+
)
431+
config = self._create_config(device=self._create_device())
432+
self.assertEqual(config.vpnclient_set.count(), 1)
433+
template.delete()
434+
self.assertEqual(config.templates.count(), 0)
435+
self.assertEqual(config.vpnclient_set.count(), 0)
436+
427437
def test_multiple_vpn_clients(self):
428438
vpn1 = self._create_vpn(name='vpn1')
429439
vpn2 = self._create_vpn(name='vpn2')

openwisp_controller/config/tests/test_vpn.py

Lines changed: 25 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,9 @@ def test_auto_create_cert_with_long_device_name(self):
405405
vpn = self._create_vpn(organization=org)
406406
d = self._create_device(organization=org, name=device_name)
407407
c = self._create_config(device=d)
408-
client = VpnClient(vpn=vpn, config=c, auto_cert=True)
408+
client = VpnClient(
409+
vpn=vpn, config=c, auto_cert=True, template=self._create_template()
410+
)
409411
client.full_clean()
410412
client.save()
411413
# The last 9 characters gets truncated and replaced with unique id
@@ -818,10 +820,17 @@ def test_vpn_peers_changed(self):
818820
class TestVxlan(BaseTestVpn, TestVxlanWireguardVpnMixin, TestCase):
819821
def test_vxlan_config_creation(self):
820822
tunnel, subnet = self._create_vxlan_tunnel()
823+
template = self._create_template(
824+
name='vxlan-wireguard',
825+
type='vpn',
826+
vpn=tunnel,
827+
organization=tunnel.organization,
828+
auto_cert=True,
829+
)
821830
with self.subTest('vni 1'):
822831
d1 = self._create_device()
823832
c1 = self._create_config(device=d1)
824-
client = VpnClient(vpn=tunnel, config=c1, auto_cert=True)
833+
client = VpnClient(vpn=tunnel, config=c1, auto_cert=True, template=template)
825834
client.full_clean()
826835
client.save()
827836
client.refresh_from_db()
@@ -830,7 +839,7 @@ def test_vxlan_config_creation(self):
830839
with self.subTest('vni 2'):
831840
d2 = self._create_device(name='d2', mac_address='16:DB:7F:E8:50:01')
832841
c2 = self._create_config(device=d2)
833-
client = VpnClient(vpn=tunnel, config=c2, auto_cert=True)
842+
client = VpnClient(vpn=tunnel, config=c2, auto_cert=True, template=template)
834843
client.full_clean()
835844
client.save()
836845
client.refresh_from_db()
@@ -853,23 +862,32 @@ def test_vxlan_config_creation(self):
853862
with self.subTest('auto_cert=False'):
854863
d3 = self._create_device(name='d3', mac_address='16:DB:7F:E8:50:03')
855864
c3 = self._create_config(device=d3)
856-
client = VpnClient(vpn=tunnel, config=c3, auto_cert=False)
865+
client = VpnClient(
866+
vpn=tunnel, config=c3, auto_cert=False, template=template
867+
)
857868
client.full_clean()
858869
client.save()
859870
client.refresh_from_db()
860871
self.assertEqual(client.vni, None)
861872

862873
def test_duplicate_vxlan_tunnels_same_vni(self):
863874
tunnel, subnet = self._create_vxlan_tunnel()
875+
template = self._create_template(
876+
name='vxlan-wireguard',
877+
type='vpn',
878+
vpn=tunnel,
879+
organization=tunnel.organization,
880+
auto_cert=True,
881+
)
864882
d1 = self._create_device()
865883
c1 = self._create_config(device=d1)
866-
client = VpnClient(vpn=tunnel, config=c1, vni=1)
884+
client = VpnClient(vpn=tunnel, config=c1, vni=1, template=template)
867885
client.full_clean()
868886
client.save()
869887
with self.subTest('Test server configuration does not define VNI'):
870888
d2 = self._create_device(name='d2', mac_address='16:DB:7F:E8:50:01')
871889
c2 = self._create_config(device=d2)
872-
client = VpnClient(vpn=tunnel, config=c2, vni=1)
890+
client = VpnClient(vpn=tunnel, config=c2, vni=1, template=template)
873891
with self.assertRaises(ValidationError) as context_manager:
874892
client.full_clean()
875893
message_dict = context_manager.exception.message_dict
@@ -883,7 +901,7 @@ def test_duplicate_vxlan_tunnels_same_vni(self):
883901
tunnel.config['vxlan'] = [{'interface': 'vxlan1', 'vni': 1}]
884902
tunnel.full_clean()
885903
tunnel.save()
886-
client = VpnClient(vpn=tunnel, config=c2, vni=1)
904+
client = VpnClient(vpn=tunnel, config=c2, vni=1, template=template)
887905
client.full_clean()
888906
client.save()
889907

@@ -1573,13 +1591,6 @@ def _reset_mocks():
15731591
organization=zt_vpn1.organization,
15741592
auto_cert=True,
15751593
)
1576-
zt_1_copy = self._create_template(
1577-
name='test-zt-template-1-copy',
1578-
type='vpn',
1579-
vpn=zt_vpn1,
1580-
organization=zt_vpn1.organization,
1581-
auto_cert=True,
1582-
)
15831594
zt2 = self._create_template(
15841595
name='test-zt-template-2',
15851596
type='vpn',
@@ -1655,27 +1666,6 @@ def _reset_mocks():
16551666
self.assertEqual(mock_requests.post.call_count, 1)
16561667
_reset_mocks()
16571668

1658-
with self.subTest(
1659-
(
1660-
'Test zt no identity generation when same zt vpn server '
1661-
'vpn client template is already applied to the device'
1662-
)
1663-
):
1664-
device.config.templates.add(zt_1_copy)
1665-
# No new zt vpn client object
1666-
self.assertEqual(vpnclient_qs.count(), 1)
1667-
# Make sure only previously applied 'zt_vpn1' vpn client object is exist
1668-
self.assertEqual(vpnclient_qs.first().vpn, zt_vpn1)
1669-
self.assertEqual(IpAddress.objects.count(), 4)
1670-
# Make sure subprocess is not called for identity generation
1671-
self.assertEqual(mock_subprocess.run.call_count, 0)
1672-
# No configuration changed
1673-
self.assertEqual(mock_info.call_count, 0)
1674-
self.assertEqual(mock_warn.call_count, 0)
1675-
self.assertEqual(mock_error.call_count, 0)
1676-
self.assertEqual(mock_requests.post.call_count, 0)
1677-
_reset_mocks()
1678-
16791669
with self.subTest(
16801670
(
16811671
'Test zt no identity generation when different '

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ openwisp-utils[celery] @ https://github.com/openwisp/openwisp-utils/tarball/1.2
1010
openwisp-notifications @ https://github.com/openwisp/openwisp-notifications/tarball/1.2
1111
openwisp-ipam @ https://github.com/openwisp/openwisp-ipam/tarball/1.2
1212
djangorestframework-gis @ https://github.com/openwisp/django-rest-framework-gis/tarball/1.2
13-
paramiko[ed25519]~=3.5.0
13+
paramiko[ed25519]~=3.5.1
1414
scp~=0.15.0
1515
django-cache-memoize~=0.2.1
1616
shortuuid~=1.0.13

tests/openwisp2/sample_config/migrations/0001_initial.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -630,6 +630,14 @@ class Migration(migrations.Migration):
630630
to='sample_config.TemplateTag',
631631
),
632632
),
633+
migrations.AddField(
634+
model_name="vpnclient",
635+
name="template",
636+
field=models.ForeignKey(
637+
on_delete=django.db.models.deletion.CASCADE,
638+
to='sample_config.template',
639+
),
640+
),
633641
migrations.CreateModel(
634642
name='OrganizationConfigSettings',
635643
fields=[

0 commit comments

Comments
 (0)