forked from openwisp/openwisp-controller
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[feature] Added automatic provisioning of Subnets and IPs openwisp#400
- Added subnet division rules for defining charecterstic of provisioned subnets and IPs - Added "VPN" rule type that provisions subents and IPs for a device when a new VpnClient is created - Added support for adding custom subnet division rule types - Added support for updating subnet divisioon rules Currently only "number_of__ips" and "label" fields can be updated - Added an inline admin for subnet division rules in SubnetAdmin - Added a filter on SubnetAdmin to allow filtering by related device name - Added a filter on DeviceAdmin to allow filtering by related subnet It also allows filtering using parent of master subnet - Added setting for hiding provisioned subnets - Extends SubnetAdmin and IpAdmin from openwisp-ipam NOTE: Adds openwisp-ipam as direct dependency Closes openwisp#400
- Loading branch information
Showing
46 changed files
with
2,300 additions
and
24 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -723,6 +723,7 @@ class VpnAdmin( | |
'organization', | ||
'uuid', | ||
'key', | ||
'subnet', | ||
'ca', | ||
'cert', | ||
'backend', | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
# Generated by Django 3.1.7 on 2021-03-08 12:17 | ||
|
||
import django.db.models.deletion | ||
from django.conf import settings | ||
from django.db import migrations, models | ||
|
||
|
||
class Migration(migrations.Migration): | ||
|
||
dependencies = [ | ||
migrations.swappable_dependency(settings.OPENWISP_IPAM_SUBNET_MODEL), | ||
('config', '0037_alter_taggedtemplate'), | ||
] | ||
|
||
operations = [ | ||
migrations.AddField( | ||
model_name='vpn', | ||
name='subnet', | ||
field=models.ForeignKey( | ||
blank=True, | ||
help_text='Subnet IP addresses used by VPN clients, if applicable', | ||
null=True, | ||
on_delete=django.db.models.deletion.SET_NULL, | ||
to=settings.OPENWISP_IPAM_SUBNET_MODEL, | ||
verbose_name='Subnet', | ||
), | ||
), | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
default_app_config = 'openwisp_controller.subnet_division.apps.SubnetDivisionConfig' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,121 @@ | ||
from django.contrib import admin | ||
from django.urls import reverse | ||
from django.utils.html import mark_safe | ||
from django.utils.translation import gettext_lazy as _ | ||
from openwisp_ipam.admin import IpAddressAdmin as BaseIpAddressAdmin | ||
from openwisp_ipam.admin import SubnetAdmin as BaseSubnetAdmin | ||
from swapper import load_model | ||
|
||
from openwisp_controller.config.admin import DeviceAdmin | ||
from openwisp_users.multitenancy import MultitenantAdminMixin, MultitenantOrgFilter | ||
from openwisp_utils.admin import TimeReadonlyAdminMixin | ||
|
||
from . import settings as app_settings | ||
from .filters import DeviceFilter, SubnetFilter, SubnetListFilter | ||
|
||
SubnetDivisionRule = load_model('subnet_division', 'SubnetDivisionRule') | ||
SubnetDivisionIndex = load_model('subnet_division', 'SubnetDivisionIndex') | ||
Subnet = load_model('openwisp_ipam', 'Subnet') | ||
IpAddress = load_model('openwisp_ipam', 'IpAddress') | ||
Device = load_model('config', 'Device') | ||
|
||
|
||
class SubnetDivisionRuleInlineAdmin( | ||
MultitenantAdminMixin, TimeReadonlyAdminMixin, admin.StackedInline | ||
): | ||
model = SubnetDivisionRule | ||
extra = 0 | ||
|
||
|
||
# Monkey patching DeviceAdmin to allow filtering using subnet | ||
DeviceAdmin.list_filter.append(SubnetFilter) | ||
|
||
# NOTE: Monkey patching SubnetAdmin didn't work for adding readonly_field | ||
# to change_view because of TimeReadonlyAdminMixin. | ||
|
||
admin.site.unregister(Subnet) | ||
admin.site.unregister(IpAddress) | ||
|
||
|
||
@admin.register(Subnet) | ||
class SubnetAdmin(BaseSubnetAdmin): | ||
list_filter = BaseSubnetAdmin.list_filter + [DeviceFilter] | ||
inlines = [SubnetDivisionRuleInlineAdmin] + BaseSubnetAdmin.inlines | ||
|
||
def get_queryset(self, request): | ||
qs = super().get_queryset(request) | ||
subnet_division_index_qs = ( | ||
SubnetDivisionIndex.objects.filter( | ||
subnet_id__in=qs.filter(master_subnet__isnull=False).values('id'), | ||
ip__isnull=True, | ||
) | ||
.select_related('config__device') | ||
.values_list('subnet_id', 'config__device__name') | ||
) | ||
self._lookup = {} | ||
for subnet_id, device_name in subnet_division_index_qs: | ||
self._lookup[subnet_id] = device_name | ||
|
||
if app_settings.HIDE_GENERATED_SUBNETS: | ||
qs = qs.exclude( | ||
id__in=SubnetDivisionIndex.objects.filter( | ||
ip__isnull=True, subnet__isnull=False | ||
).values_list('subnet_id') | ||
) | ||
|
||
return qs | ||
|
||
def get_readonly_fields(self, request, obj=None): | ||
fields = super().get_readonly_fields(request, obj) | ||
if obj is not None and 'related_device' not in fields: | ||
fields = ('related_device',) + fields | ||
return fields | ||
|
||
def get_list_display(self, request): | ||
fields = super().get_list_display(request) | ||
return fields + ['related_device'] | ||
|
||
def related_device(self, obj): | ||
app_label = Device._meta.app_label | ||
url = reverse(f'admin:{app_label}_device_changelist') | ||
if obj.master_subnet is None: | ||
msg_string = _('See all devices') | ||
return mark_safe( | ||
f'<a href="{url}?subnet={str(obj.subnet)}">{msg_string}</a>' | ||
) | ||
else: | ||
device = self._lookup[obj.id] | ||
return mark_safe(f'<a href="{url}?subnet={str(obj.subnet)}">{device}</a>') | ||
|
||
def has_change_permission(self, request, obj=None): | ||
permission = super().has_change_permission(request, obj) | ||
if not obj: | ||
return permission | ||
automated = SubnetDivisionIndex.objects.filter(subnet_id=obj.id).exists() | ||
return permission and not automated | ||
|
||
|
||
@admin.register(IpAddress) | ||
class IpAddressAdmin(BaseIpAddressAdmin): | ||
list_filter = [ | ||
('subnet', SubnetListFilter), | ||
('subnet__organization', MultitenantOrgFilter), | ||
] | ||
|
||
def get_queryset(self, request): | ||
qs = super().get_queryset(request) | ||
|
||
if app_settings.HIDE_GENERATED_SUBNETS: | ||
qs = qs.exclude( | ||
id__in=SubnetDivisionIndex.objects.filter(ip__isnull=False).values_list( | ||
'ip_id' | ||
) | ||
) | ||
return qs | ||
|
||
def has_change_permission(self, request, obj=None): | ||
permission = super().has_change_permission(request, obj) | ||
if not obj: | ||
return permission | ||
automated = SubnetDivisionIndex.objects.filter(ip_id=obj.id).exists() | ||
return permission and not automated |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
from django.apps import AppConfig | ||
from django.utils.module_loading import import_string | ||
from django.utils.translation import gettext_lazy as _ | ||
|
||
|
||
class SubnetDivisionConfig(AppConfig): | ||
name = 'openwisp_controller.subnet_division' | ||
verbose_name = _('Subnet Division') | ||
default_auto_field = 'django.db.models.AutoField' | ||
|
||
def ready(self): | ||
super().ready() | ||
from . import handlers # noqa | ||
from . import settings as app_settings | ||
|
||
for rule_path, name in app_settings.SUBNET_DIVISION_TYPES: | ||
rule_class = import_string(rule_path) | ||
rule_class.validate_rule_type() | ||
rule_class.provision_signal.connect( | ||
receiver=rule_class.provision_receiver, | ||
sender=rule_class.provision_sender, | ||
dispatch_uid=rule_class.provision_dispatch_uid, | ||
) | ||
rule_class.destroyer_signal.connect( | ||
receiver=rule_class.destroyer_receiver, | ||
sender=rule_class.destroyer_sender, | ||
dispatch_uid=rule_class.destroyer_dispatch_uid, | ||
) |
Oops, something went wrong.