Merge branch 'master' of https://github.com/open-cloud/xos into AddVPNService
diff --git a/xos/configurations/common/Dockerfile.common b/xos/configurations/common/Dockerfile.common
index 26b753d..8db457c 100644
--- a/xos/configurations/common/Dockerfile.common
+++ b/xos/configurations/common/Dockerfile.common
@@ -28,7 +28,8 @@
     python-dev \
     libyaml-dev \
     pkg-config \
-    python-pycurl
+    python-pycurl \
+    openvpn
 
 RUN pip install django==1.7
 RUN pip install djangorestframework==2.4.4
diff --git a/xos/configurations/devel/Dockerfile.devel b/xos/configurations/devel/Dockerfile.devel
index 60c0143..dff8c0c 100644
--- a/xos/configurations/devel/Dockerfile.devel
+++ b/xos/configurations/devel/Dockerfile.devel
@@ -5,6 +5,7 @@
 ADD xos/configurations/common/cloudlab-nodes.yaml /opt/xos/configurations/commmon/
 ADD xos/configurations/common/id_rsa.pub /root/setup/padmin_public_key
 ADD xos/configurations/common/id_rsa /opt/xos/observers/helloworldservice_complete/helloworldservice_private_key
+ADD xos/configurations/common/id_rsa /opt/xos/observers/vpn/vpn_private_key
 ADD xos/configurations/common/node_key.pub /root/setup/node_key.pub
 ADD xos/configurations/common/node_key /root/setup/node_key
 ADD xos/configurations/common/ceilometer_url /root/setup/ceilometer_url
diff --git a/xos/observers/vpn/model-deps b/xos/observers/vpn/model-deps
new file mode 100644
index 0000000..0967ef4
--- /dev/null
+++ b/xos/observers/vpn/model-deps
@@ -0,0 +1 @@
+{}
diff --git a/xos/observers/vpn/run.sh b/xos/observers/vpn/run.sh
new file mode 100755
index 0000000..2fc0ca5
--- /dev/null
+++ b/xos/observers/vpn/run.sh
@@ -0,0 +1,2 @@
+export XOS_DIR=/opt/xos
+python vpn-observer.py  -C $XOS_DIR/observers/vpn/vpn_config
diff --git a/xos/observers/vpn/steps/sync_vpntenant.py b/xos/observers/vpn/steps/sync_vpntenant.py
new file mode 100644
index 0000000..4632c8c
--- /dev/null
+++ b/xos/observers/vpn/steps/sync_vpntenant.py
@@ -0,0 +1,30 @@
+import os
+import sys
+from django.db.models import Q, F
+from helloworldservice_complete.models import VPNService, VPNTenant
+from observers.base.SyncInstanceUsingAnsible import SyncInstanceUsingAnsible
+
+parentdir = os.path.join(os.path.dirname(__file__), "..")
+sys.path.insert(0, parentdir)
+
+class SyncVPNTenant(SyncInstanceUsingAnsible):
+    provides = [VPNTenant]
+    observes = VPNTenant
+    requested_interval = 0
+    template_name = "sync_vpntenant.yaml"
+    service_key_name = "/opt/xos/observers/vpn/vpn_private_key"
+
+    def __init__(self, *args, **kwargs):
+        super(SyncVPNTenant, self).__init__(*args, **kwargs)
+
+    def fetch_pending(self, deleted):
+        if (not deleted):
+            objs = VPNTenant.get_tenant_objects().filter(
+                Q(enacted__lt=F('updated')) | Q(enacted=None), Q(lazy_blocked=False))
+        else:
+            objs = VPNTenant.get_deleted_tenant_objects()
+
+        return objs
+
+    def get_extra_attributes(self, o):
+        return {"server_key": o.server_key}
diff --git a/xos/observers/vpn/steps/sync_vpntenant.yaml b/xos/observers/vpn/steps/sync_vpntenant.yaml
new file mode 100644
index 0000000..82193d0
--- /dev/null
+++ b/xos/observers/vpn/steps/sync_vpntenant.yaml
@@ -0,0 +1,18 @@
+---
+- hosts: {{ instance_name }}
+  gather_facts: False
+  connection: ssh
+  user: ubuntu
+  sudo: yes
+  tasks:
+  - name: install apache
+    apt: name=apache2 state=present update_cache=yes
+
+  - name: write message
+    shell: echo "{{ server_key }}" > /var/www/html/index.html
+
+  - name: stop apache
+    service: name=apache2 state=stopped
+
+  - name: start apache
+    service: name=apache2 state=started
diff --git a/xos/observers/vpn/stop.sh b/xos/observers/vpn/stop.sh
new file mode 100755
index 0000000..5feca3c
--- /dev/null
+++ b/xos/observers/vpn/stop.sh
@@ -0,0 +1,2 @@
+# Kill the observer
+pkill -9 -f vpn-observer.py
diff --git a/xos/observers/vpn/vpn-observer.py b/xos/observers/vpn/vpn-observer.py
new file mode 100755
index 0000000..2d7644f
--- /dev/null
+++ b/xos/observers/vpn/vpn-observer.py
@@ -0,0 +1,10 @@
+#!/usr/bin/env python
+
+import importlib
+import os
+import sys
+observer_path = os.path.join(os.path.dirname(
+    os.path.realpath(__file__)), "../..")
+sys.path.append(observer_path)
+mod = importlib.import_module("xos-observer")
+mod.main()
diff --git a/xos/observers/vpn/vpn_config b/xos/observers/vpn/vpn_config
new file mode 100644
index 0000000..8165ae1
--- /dev/null
+++ b/xos/observers/vpn/vpn_config
@@ -0,0 +1,23 @@
+# Required by XOS
+[db]
+name=xos
+user=postgres
+password=password
+host=localhost
+port=5432
+
+# Required by XOS
+[api]
+nova_enabled=True
+
+# Sets options for the observer
+[observer]
+name=vpn
+dependency_graph=/opt/xos/observers/vpn/model-deps
+steps_dir=/opt/xos/observers/vpn/steps
+sys_dir=/opt/xos/observers/vpn/sys
+logfile=/var/log/xos_backend.log
+pretend=False
+backoff_disabled=True
+save_ansible_output=True
+proxy_ssh=False
diff --git a/xos/scripts/opencloud b/xos/scripts/opencloud
index 6b3e737..966ad9b 100755
--- a/xos/scripts/opencloud
+++ b/xos/scripts/opencloud
@@ -60,13 +60,13 @@
         echo Waiting for postgres to start
         sleep 1
         sudo -u postgres psql -c '\q'
-    done 
+    done
 }
 
 function db_exists {
-   sudo -u postgres psql $DBNAME -c '\q' 2>/dev/null    
+   sudo -u postgres psql $DBNAME -c '\q' 2>/dev/null
    return $?
-} 
+}
 
 function createdb {
     wait_postgres
@@ -145,6 +145,7 @@
     python ./manage.py makemigrations cord
     python ./manage.py makemigrations ceilometer
     python ./manage.py makemigrations helloworldservice_complete
+    python ./manage.py makemigrations vpn
     python ./manage.py makemigrations onos
     #python ./manage.py makemigrations servcomp
 }
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>
diff --git a/xos/xos/settings.py b/xos/xos/settings.py
index 4c0f779..709a2b6 100644
--- a/xos/xos/settings.py
+++ b/xos/xos/settings.py
@@ -175,6 +175,7 @@
     'hpc',
     'cord',
     'helloworldservice_complete',
+    'vpn',
     'services.onos',
     'ceilometer',
     'requestrouter',