Add models for VPN service
diff --git a/xos/vpn/__init__.py b/xos/vpn/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/xos/vpn/__init__.py
diff --git a/xos/vpn/admin.py b/xos/vpn/admin.py
new file mode 100644
index 0000000..9d6f6ee
--- /dev/null
+++ b/xos/vpn/admin.py
@@ -0,0 +1,93 @@
+
+from core.admin import ReadOnlyAwareAdmin, SliceInline
+from core.middleware import get_request
+from core.models import User
+from django import forms
+from django.contrib import admin
+from vpn.models import VPNService, VPNTenant, VPN_KIND
+
+class VPNServiceAdmin(ReadOnlyAwareAdmin):
+ model = VPNService
+ verbose_name = "VPN Service"
+
+ list_display = ("backend_status_icon", "name", "enabled")
+
+ list_display_links = ('backend_status_icon', 'name', )
+
+ fieldsets = [(None, {'fields': ['backend_status_text', 'name', 'enabled',
+ 'versionNumber', 'description', "view_url"],
+ 'classes':['suit-tab suit-tab-general']})]
+
+ readonly_fields = ('backend_status_text', )
+
+ inlines = [SliceInline]
+
+ extracontext_registered_admins = True
+
+ user_readonly_fields = ["name", "enabled", "versionNumber", "description"]
+
+ suit_form_tabs = (('general', 'VPN Service Details'),
+ ('administration', 'Tenants'),
+ ('slices', 'Slices'),)
+
+ suit_form_includes = (('vpnserviceadmin.html',
+ 'top',
+ 'administration'),)
+
+ def queryset(self, request):
+ return VPNService.get_service_objects_by_user(request.user)
+
+class VPNTenantForm(forms.ModelForm):
+ creator = forms.ModelChoiceField(queryset=User.objects.all())
+ # They key used to connect to this server
+ server_key = forms.CharField(required=False)
+
+ def __init__(self, *args, **kwargs):
+ super(VPNTenantForm, self).__init__(*args, **kwargs)
+ self.fields['kind'].widget.attrs['readonly'] = True
+ # Make the server_key read only
+ self.fields['server_key'].widget.attrs['readonly'] = True
+
+ self.fields[
+ 'provider_service'].queryset = VPNService.get_service_objects().all()
+
+ self.fields['kind'].initial = VPN_KIND
+
+ if self.instance:
+ self.fields['creator'].initial = self.instance.creator
+ self.fields['server_key'].initial = self.instance.server_key
+
+ # If there is not an instance then we need to set initial values.
+ if (not self.instance) or (not self.instance.pk):
+ self.fields['creator'].initial = get_request().user
+ self.fields['server_key'].initial = "hello world!"
+ if VPNService.get_service_objects().exists():
+ self.fields["provider_service"].initial = VPNService.get_service_objects().all()[0]
+
+ def save(self, commit=True):
+ self.instance.creator = self.cleaned_data.get("creator")
+ self.instance.server_key = self.cleaned_data.get("server_key")
+ return super(VPNTenantForm, self).save(commit=commit)
+
+ class Meta:
+ model = VPNTenant
+
+class VPNTenantAdmin(ReadOnlyAwareAdmin):
+ verbose_name = "VPN Tenant Admin"
+ list_display = ('id', 'backend_status_icon', 'instance')
+ list_display_links = ('id', 'backend_status_icon', 'instance')
+ fieldsets = [(None, {'fields': ['backend_status_text', 'kind',
+ 'provider_service', 'instance', 'creator',
+ 'server_key'],
+ 'classes': ['suit-tab suit-tab-general']})]
+ readonly_fields = ('backend_status_text', 'instance',)
+ form = VPNTenantForm
+
+ suit_form_tabs = (('general', 'Details'),)
+
+ def queryset(self, request):
+ return VPNTenant.get_tenant_objects_by_user(request.user)
+
+# Associate the admin forms with the models.
+admin.site.register(VPNService, VPNServiceAdmin)
+admin.site.register(VPNTenant, VPNTenantAdmin)
diff --git a/xos/vpn/models.py b/xos/vpn/models.py
new file mode 100644
index 0000000..3f23e72
--- /dev/null
+++ b/xos/vpn/models.py
@@ -0,0 +1,86 @@
+from core.models import Service, TenantWithContainer
+from django.db import transaction
+
+VPN_KIND = "vpn"
+
+class VPNService(Service):
+ KIND = VPN_KIND
+
+ class Meta:
+ proxy = True
+ # The name used to find this service, all directories are named this
+ app_label = "vpn"
+ verbose_name = "VPN Service"
+
+class VPNTenantComplete(TenantWithContainer):
+
+ class Meta:
+ proxy = True
+ verbose_name = "VPN Tenant"
+
+ KIND = VPN_KIND
+
+ sync_attributes = ("nat_ip", "nat_mac",)
+
+ default_attributes = {'server_key': 'Error key not found'}
+
+ def __init__(self, *args, **kwargs):
+ vpn_services = VPNService.get_service_objects().all()
+ if vpn_services:
+ self._meta.get_field(
+ "provider_service").default = vpn_services[0].id
+ super(VPNTenant, self).__init__(*args, **kwargs)
+
+ def save(self, *args, **kwargs):
+ super(VPNTenant, self).save(*args, **kwargs)
+ model_policy_vpn_tenant(self.pk)
+
+ def delete(self, *args, **kwargs):
+ self.cleanup_container()
+ super(VPNTenant, self).delete(*args, **kwargs)
+
+ @property
+ def server_key(self):
+ return self.get_attribute(
+ "server_key",
+ self.default_attributes['server_key'])
+
+ @display_message.setter
+ def display_message(self, value):
+ self.set_attribute("server_key", value)
+
+ @property
+ def addresses(self):
+ if (not self.id) or (not self.instance):
+ return {}
+
+ addresses = {}
+ # The ports field refers to networks for the instance.
+ # This loop stores the details for the NAT network that will be
+ # necessary for ansible.
+ for ns in self.instance.ports.all():
+ if "nat" in ns.network.name.lower():
+ addresses["nat"] = (ns.ip, ns.mac)
+ return addresses
+
+ # This getter is necessary because nat_ip is a sync_attribute
+ @property
+ def nat_ip(self):
+ return self.addresses.get("nat", (None, None))[0]
+
+ # This getter is necessary because nat_mac is a sync_attribute
+ @property
+ def nat_mac(self):
+ return self.addresses.get("nat", (None, None))[1]
+
+
+def model_policy_vpn_tenant(pk):
+ # This section of code is atomic to prevent race conditions
+ with transaction.atomic():
+ # We find all of the tenants that are waiting to update
+ tenant = VPNTenant.objects.select_for_update().filter(pk=pk)
+ if not tenant:
+ return
+ # Since this code is atomic it is safe to always use the first tenant
+ tenant = tenant[0]
+ tenant.manage_container()
diff --git a/xos/vpn/templates/vpnserviceadmin.html b/xos/vpn/templates/vpnserviceadmin.html
new file mode 100644
index 0000000..d983771
--- /dev/null
+++ b/xos/vpn/templates/vpnserviceadmin.html
@@ -0,0 +1,10 @@
+<!-- Template used to for the button leading to the HelloWorldTenantComplete form. -->
+<div class = "left-nav">
+ <ul>
+ <li>
+ <a href="/admin/vpn/vpntenant/">
+ VPN Tenants
+ </a>
+ </li>
+ </ul>
+</div>