Skip to content

Commit 464b04f

Browse files
authored
[chores:fix] Fixed data migrations breaking due to JSONField change #1061
Historical migration models can expose old JSONField values as strings or nulls. Several data migrations assumed dict values or live model properties, which broke upgrades with older controller databases. Normalized legacy JSON values before mutating configs, skip malformed nested entries, and compute checksums inside the migration without depending on the live Config.checksum property. Also avoid loading stale template relations during VPN client template population and tolerate VPN rows with missing DH parameters. Related to #1061
1 parent 0518b35 commit 464b04f

6 files changed

Lines changed: 330 additions & 21 deletions

openwisp_controller/config/migrations/0042_multiple_wireguard_tunnels.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from django.db import migrations
44

55
from ..migrations import get_swapped_model
6+
from . import resolve_config
67

78

89
def get_wireguard_and_vxlan_wireguard_templates(apps):
@@ -13,11 +14,13 @@ def get_wireguard_and_vxlan_wireguard_templates(apps):
1314
def allow_multiple_wireguard_tunneling(apps, schema_editor):
1415
templates = get_wireguard_and_vxlan_wireguard_templates(apps).iterator()
1516
for template in templates:
16-
config = template.config
17-
interfaces = config["interfaces"]
17+
config = resolve_config(template.config)
18+
interfaces = config.get("interfaces", [])
1819
vpn_id = template.vpn.pk.hex
1920
changed = False
2021
for interface in interfaces:
22+
if not isinstance(interface, dict):
23+
continue
2124
interface_type = interface.get("type", None)
2225
private_key = interface.get("private_key", None)
2326
if interface_type != "wireguard" or not private_key:
@@ -38,11 +41,13 @@ def allow_multiple_wireguard_tunneling(apps, schema_editor):
3841
def disallow_multiple_wireguard_tunneling(apps, schema_editor):
3942
templates = get_wireguard_and_vxlan_wireguard_templates(apps).iterator()
4043
for template in templates:
41-
config = template.config
42-
interfaces = config["interfaces"]
44+
config = resolve_config(template.config)
45+
interfaces = config.get("interfaces", [])
4346
vpn_id = template.vpn.pk.hex
4447
changed = False
4548
for interface in interfaces:
49+
if not isinstance(interface, dict):
50+
continue
4651
interface_type = interface.get("type", None)
4752
private_key = interface.get("private_key", None)
4853
if interface_type != "wireguard" or not private_key:

openwisp_controller/config/migrations/0048_wifi_radio_band_migration.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from django.db import migrations
55
from netjsonconfig import channels
66

7+
from . import resolve_config
8+
79

810
def hwmode_to_band(radio):
911
"""
@@ -67,39 +69,44 @@ def band_to_hwmode(radio):
6769
# 802.11ax and 802.11ad were not supported in OpenWrt < 21.
6870
# Hence, we ignore "6g" and "60g" values.
6971
if band == "2g":
70-
if radio["protocol"] == "802.11b":
72+
if radio.get("protocol") == "802.11b":
7173
return "11b"
7274
else:
7375
return "11g"
7476
elif band == "5g":
7577
return "11a"
7678
# Use protocol to infer "hwmode"
77-
protocol = radio["protocol"]
79+
protocol = radio.get("protocol")
7880
if protocol in ["802.11a", "802.11b", "802.11g"]:
7981
# return 11a, 11b or 11g
8082
return protocol[4:]
8183
if protocol == "802.11ac":
8284
return "11a"
8385
# determine hwmode depending on channel used
84-
if radio["channel"] == 0:
86+
channel = radio.get("channel")
87+
if channel == 0:
8588
# when using automatic channel selection, we need an
8689
# additional parameter to determine the frequency band
8790
return radio.get("hwmode")
88-
elif radio["channel"] <= 13:
91+
elif channel is not None and channel <= 13:
8992
return "11g"
90-
else:
93+
elif channel is not None:
9194
return "11a"
9295

9396

9497
def update_config_for_model(Model, func, field):
9598
updated_objects = []
9699
for obj in Model.objects.iterator(chunk_size=100):
97100
update_obj = False
98-
for radio in obj.config.get("radios", []):
101+
config = resolve_config(obj.config)
102+
for radio in config.get("radios", []):
103+
if not isinstance(radio, dict):
104+
continue
99105
if not radio.get(field):
100106
radio[field] = func(radio)
101107
update_obj = True
102108
if update_obj:
109+
obj.config = config
103110
updated_objects.append(obj)
104111
if len(updated_objects) > 100:
105112
Model.objects.bulk_update(updated_objects, fields=["config"])

openwisp_controller/config/migrations/0057_populate_vpnclient_template.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,19 @@
66

77
def populate_vpnclient_template(apps, schema_editor):
88
VpnClient = apps.get_model("config", "VpnClient")
9+
Config = apps.get_model("config", "Config")
10+
Template = apps.get_model("config", "Template")
911

1012
for vpn_client in VpnClient.objects.iterator():
11-
if vpn_client.template is None:
13+
if vpn_client.template_id is not None:
14+
continue
15+
try:
1216
vpn_client.template = vpn_client.config.templates.get(
1317
vpn_id=vpn_client.vpn_id
1418
)
15-
vpn_client.save()
19+
except (Config.DoesNotExist, Template.DoesNotExist):
20+
continue
21+
vpn_client.save()
1622

1723

1824
class Migration(migrations.Migration):

openwisp_controller/config/migrations/0059_zerotier_templates_ow_zt_to_global.py

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,22 @@
11
from django.db import migrations
22

3+
from . import resolve_config
4+
35

46
def change_owzt_to_global(apps, schema_editor):
57
Template = apps.get_model("config", "Template")
68
updated_templates = set()
79
for template in Template.objects.filter(
810
type="vpn", vpn__backend="openwisp_controller.vpn_backends.ZeroTier"
911
).iterator():
10-
if "zerotier" in template.config:
11-
for item in template.config.get("zerotier", []):
12-
if item.get("name") == "ow_zt":
13-
item["name"] = "global"
14-
updated_templates.add(template)
12+
config = resolve_config(template.config)
13+
for item in config.get("zerotier", []):
14+
if not isinstance(item, dict):
15+
continue
16+
if item.get("name") == "ow_zt":
17+
item["name"] = "global"
18+
template.config = config
19+
updated_templates.add(template)
1520
Template.objects.bulk_update(updated_templates, ["config"])
1621

1722

0 commit comments

Comments
 (0)