add admin models AND TOSCA resources to vPGWC

Change-Id: I77a65411c36c9701b56abc0b764410efa2a2d6c8
diff --git a/xos/admin.py b/xos/admin.py
new file mode 100644
index 0000000..55d6a56
--- /dev/null
+++ b/xos/admin.py
@@ -0,0 +1,141 @@
+
+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 services.vpgwc.models import VPGWCService, VPGWCComponent, MCORD_KIND
+
+# The class to provide an admin interface on the web for the service.
+# We do only configuration here and don't change any logic because the logic
+# is taken care of for us by ReadOnlyAwareAdmin
+class VPGWCServiceAdmin(ReadOnlyAwareAdmin):
+    # We must set the model so that the admin knows what fields to use
+    model = VPGWCService
+    verbose_name = "vPGWC Service"
+    verbose_name_plural = "vPGWC Services"
+
+    # Setting list_display creates columns on the admin page, each value here
+    # is a column, the column is populated for every instance of the model.
+    list_display = ("backend_status_icon", "name", "enabled")
+
+    # Used to indicate which values in the columns of the admin form are links.
+    list_display_links = ('backend_status_icon', 'name', )
+
+    # Denotes the sections of the form, the fields in the section, and the
+    # CSS classes used to style them. We represent this as a set of tuples, each
+    # tuple as a name (or None) and a set of fields and classes.
+    # Here the first section does not have a name so we use none. That first
+    # section has several fields indicated in the 'fields' attribute, and styled
+    # by the classes indicated in the 'classes' attribute. The classes given
+    # here are important for rendering the tabs on the form. To give the tabs
+    # we must assign the classes suit-tab and suit-tab-<name> where
+    # where <name> will be used later.
+    fieldsets = [(None, {'fields': ['backend_status_text', 'name', 'enabled',
+                                    'versionNumber', 'description', "view_url"],
+                         'classes':['suit-tab suit-tab-general']})]
+
+    # Denotes the fields that are readonly and cannot be changed.
+    readonly_fields = ('backend_status_text', )
+
+    # Inlines are used to denote other models that can be edited on the same
+    # form as this one. In this case the service form also allows changes
+    # to slices.
+    inlines = [SliceInline]
+
+    extracontext_registered_admins = True
+
+    # Denotes the fields that can be changed by an admin but not be all users
+    user_readonly_fields = ["name", "enabled", "versionNumber", "description"]
+
+    # Associates fieldsets from this form and from the inlines.
+    # The format here are tuples, of (<name>, tab title). <name> comes from the
+    # <name> in the fieldsets.
+    suit_form_tabs = (('general', 'vPGWC Service Details'),
+                      ('administration', 'Components'),
+                      ('slices', 'Slices'),)
+
+    # Used to include a template for a tab. Here we include the
+    # helloworldserviceadmin template in the top position for the administration
+    # tab.
+    suit_form_includes = (('mcordadmin.html',
+                           'top',
+                           'administration'),)
+
+    # Used to get the objects for this model that are associated with the
+    # requesting user.
+    def queryset(self, request):
+        return VPGWCService.get_service_objects_by_user(request.user)
+
+# Class to represent the form to add and edit tenants.
+# We need to define this instead of just using an admin like we did for the
+# service because tenants vary more than services and there isn't a common form.
+# This allows us to change the python behavior for the admin form to save extra
+# fields and control defaults.
+class VPGWCComponentForm(forms.ModelForm):
+    # Defines a field for the creator of this service. It is a dropdown which
+    # is populated with all of the users.
+    creator = forms.ModelChoiceField(queryset=User.objects.all())
+    # Defines a text field for the display message, it is not required.
+    display_message = forms.CharField(required=False)
+
+    def __init__(self, *args, **kwargs):
+        super(VPGWCComponentForm, self).__init__(*args, **kwargs)
+        # Set the kind field to readonly
+        self.fields['kind'].widget.attrs['readonly'] = True
+        # Define the logic for obtaining the objects for the provider_service
+        # dropdown of the tenant form.
+        self.fields[
+            'provider_service'].queryset = VPGWCService.get_service_objects().all()
+        # Set the initial kind to HELLO_WORLD_KIND for this tenant.
+        self.fields['kind'].initial = MCORD_KIND
+        # If there is an instance of this model then we can set the initial
+        # form values to the existing values.
+        if self.instance:
+            self.fields['creator'].initial = self.instance.creator
+            self.fields[
+                'display_message'].initial = self.instance.display_message
+
+        # 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
+            if VPGWCService.get_service_objects().exists():
+                self.fields["provider_service"].initial = VPGWCService.get_service_objects().all()[0]
+
+    # This function describes what happens when the save button is pressed on
+    # the tenant form. In this case we set the values for the instance that were
+    # entered.
+    def save(self, commit=True):
+        self.instance.creator = self.cleaned_data.get("creator")
+        self.instance.display_message = self.cleaned_data.get(
+            "display_message")
+        return super(VPGWCComponentForm, self).save(commit=commit)
+
+    class Meta:
+        model = VPGWCComponent
+        fields = '__all__'
+
+
+# Define the admin form for the tenant. This uses a similar structure as the
+# service but uses HelloWorldTenantCompleteForm to change the python behavior.
+class VPGWCComponentAdmin(ReadOnlyAwareAdmin):
+    verbose_name = "vPGWC Component"
+    verbose_name_plural = "vPGWC Components"
+    list_display = ('id', 'backend_status_icon', 'instance', 'display_message')
+    list_display_links = ('backend_status_icon', 'instance', 'display_message',
+                          'id')
+    fieldsets = [(None, {'fields': ['backend_status_text', 'kind',
+                                    'provider_service', 'instance', 'creator',
+                                    'display_message'],
+                         'classes': ['suit-tab suit-tab-general']})]
+    readonly_fields = ('backend_status_text', 'instance',)
+    form = VPGWCComponentForm
+
+    suit_form_tabs = (('general', 'Details'),)
+
+    def queryset(self, request):
+        return VPGWCComponent.get_tenant_objects_by_user(request.user)
+
+# Associate the admin forms with the models.
+admin.site.register(VPGWCService, VPGWCServiceAdmin)
+admin.site.register(VPGWCComponent, VPGWCComponentAdmin)
diff --git a/xos/models.py b/xos/models.py
new file mode 100644
index 0000000..102e3e3
--- /dev/null
+++ b/xos/models.py
@@ -0,0 +1,237 @@
+from django.db import models
+from core.models import Service, PlCoreBase, Slice, Instance, Tenant, TenantWithContainer, Node, Image, User, Flavor, Subscriber, NetworkParameter, NetworkParameterType, AddressPool, Port
+from core.models.plcorebase import StrippedCharField
+import os
+from django.db import models, transaction
+from django.forms.models import model_to_dict
+from django.db.models import Q
+from operator import itemgetter, attrgetter, methodcaller
+from core.models import Tag
+from core.models.service import LeastLoadedNodeScheduler
+import traceback
+from xos.exceptions import *
+from core.models import SlicePrivilege, SitePrivilege
+from sets import Set
+from xos.config import Config
+
+MCORD_KIND = "RAN" # This should be changed later I did it fo demo
+MCORD_USE_VTN = getattr(Config(), "networking_use_vtn", False)
+VBBU_KIND = "RAN"
+VSGW_KIND = "vSGW"
+VPGWC_KIND = "RAN"
+vbbu_net_types = ("s1u", "s1mme", "rru")
+vpgwc_net_types = ("s5s8")
+# The class to represent the service. Most of the service logic is given for us
+# in the Service class but, we have some configuration that is specific for
+# this example.
+class VPGWCService(Service):
+    KIND = MCORD_KIND
+
+    class Meta:
+        # When the proxy field is set to True the model is represented as
+        # it's superclass in the database, but we can still change the python
+        # behavior. In this case HelloWorldServiceComplete is a Service in the
+        # database.
+        proxy = True
+        # The name used to find this service, all directories are named this
+        app_label = "vpgwc"
+        verbose_name = "vPGWC Service"
+
+# This is the class to represent the tenant. Most of the logic is given to use
+# in TenantWithContainer, however there is some configuration and logic that
+# we need to define for this example.
+class VPGWCComponent(TenantWithContainer):
+
+    class Meta:
+        # Same as a above, HelloWorldTenantComplete is represented as a
+        # TenantWithContainer, but we change the python behavior.
+        proxy = True
+        verbose_name = "VPGWC Service Component"
+
+    # The kind of the service is used on forms to differentiate this service
+    # from the other services.
+    KIND = VPGWC_KIND
+
+    # Ansible requires that the sync_attributes field contain nat_ip and nat_mac
+    # these will be used to determine where to SSH to for ansible.
+    # Getters must be defined for every attribute specified here.
+    sync_attributes = ("s5s8_pgw_ip", "s5s8_pgw_mac")
+
+    # default_attributes is used cleanly indicate what the default values for
+    # the fields are.
+    default_attributes = {"display_message": "New vPGWC Component", "s5s8_pgw_tag": "300"}
+    def __init__(self, *args, **kwargs):
+        mcord_services = VPGWCService.get_service_objects().all()
+        # When the tenant is created the default service in the form is set
+        # to be the first created HelloWorldServiceComplete
+        if mcord_services:
+            self._meta.get_field(
+                "provider_service").default = mcord_services[0].id
+        super(VPGWCComponent, self).__init__(*args, **kwargs)
+
+    def can_update(self, user):
+        #Allow creation of this model instances for non-admin users also
+        return True
+
+    def save(self, *args, **kwargs):
+        if not self.creator:
+            if not getattr(self, "caller", None):
+                # caller must be set when creating a monitoring channel since it creates a slice
+                raise XOSProgrammingError("ServiceComponents's self.caller was not set")
+            self.creator = self.caller
+            if not self.creator:
+                raise XOSProgrammingError("ServiceComponents's self.creator was not set")
+
+        super(VPGWCComponent, self).save(*args, **kwargs)
+        # This call needs to happen so that an instance is created for this
+        # tenant is created in the slice. One instance is created per tenant.
+        model_policy_mcord_servicecomponent(self.pk)
+
+    def save_instance(self, instance):
+        with transaction.atomic():
+            super(VPGWCComponent, self).save_instance(instance)
+            if instance.isolation in ["vm"]:
+                for ntype in vpgwc_net_types:
+                    lan_network = self.get_lan_network(instance, ntype)
+                    port = self.find_or_make_port(instance,lan_network)
+                    if (ntype == "s5s8"):
+                        port.set_parameter("s_tag", self.s5s8_pgw_tag)
+                        port.set_parameter("neutron_port_name", "stag-%s" % self.s5s8_pgw_tag)
+                        port.save()
+                    else:
+			return True
+
+    def delete(self, *args, **kwargs):
+        # Delete the instance that was created for this tenant
+        self.cleanup_container()
+        super(VPGWCComponent, self).delete(*args, **kwargs)
+
+    def find_or_make_port(self, instance, network, **kwargs):
+        port = Port.objects.filter(instance=instance, network=network)
+        if port:
+            port = port[0]
+            print "port already exist", port[0]
+        else:
+            port = Port(instance=instance, network=network, **kwargs)
+            print "NETWORK", network, "MAKE_PORT", port 
+            port.save()
+        return port
+
+    def get_lan_network(self, instance, ntype):
+        slice = self.provider_service.slices.all()[0]
+        lan_networks = [x for x in slice.networks.all() if ntype in x.name]
+        if not lan_networks:
+            raise XOSProgrammingError("No lan_network")
+        return lan_networks[0]
+
+    def manage_container(self):
+        from core.models import Instance, Flavor
+
+        if self.deleted:
+            return
+
+        # For container or container_vm isolation, use what TenantWithCotnainer
+        # provides us
+        slice = self.get_slice()
+        if slice.default_isolation in ["container_vm", "container"]:
+            super(VPGWCComponent,self).manage_container()
+            return
+
+        if not self.s5s8_pgw_tag:
+            raise XOSConfigurationError("S5S8_PGW_TAG is missed")
+
+        if self.instance:
+            # We're good.
+            return
+
+        instance = self.make_instance()
+        self.instance = instance
+        super(TenantWithContainer, self).save()
+
+    def get_slice(self):
+        if not self.provider_service.slices.count():
+            raise XOSConfigurationError("The service has no slices")
+        slice = self.provider_service.slices.all()[0]
+        return slice
+
+    def make_instance(self):
+        slice = self.provider_service.slices.all()[0]            
+        flavors = Flavor.objects.filter(name=slice.default_flavor)
+#        flavors = Flavor.objects.filter(name="m1.xlarge")
+        if not flavors:
+            raise XOSConfigurationError("No default flavor")
+        default_flavor = slice.default_flavor
+        slice = self.provider_service.slices.all()[0]
+        if slice.default_isolation == "container_vm":
+            (node, parent) = ContainerVmScheduler(slice).pick()
+        else:
+            (node, parent) = LeastLoadedNodeScheduler(slice).pick()
+        instance = Instance(slice = slice,
+                        node = node,
+                        image = self.image,
+                        creator = self.creator,
+                        deployment = node.site_deployment.deployment,
+                        flavor = flavors[0],
+                        isolation = slice.default_isolation,
+                        parent = parent)
+        self.save_instance(instance)
+        return instance
+
+    def ip_to_mac(self, ip):
+        (a, b, c, d) = ip.split('.')
+        return "02:42:%02x:%02x:%02x:%02x" % (int(a), int(b), int(c), int(d))
+
+    # Getter for the message that will appear on the webpage
+    # By default it is "Hello World!"
+    @property
+    def display_message(self):
+        return self.get_attribute(
+            "display_message",
+            self.default_attributes['display_message'])
+
+    @display_message.setter
+    def display_message(self, value):
+        self.set_attribute("display_message", value)
+
+    @property
+    def s5s8_pgw_tag(self):
+        return self.get_attribute(
+            "s5s8_pgw_tag",
+            self.default_attributes['s5s8_pgw_tag'])
+
+    @s5s8_pgw_tag.setter
+    def s5s8_pgw_tag(self, value):
+        self.set_attribute("s5s8_pgw_tag", value)
+
+
+    @property
+    def addresses(self):
+        if (not self.id) or (not self.instance):
+            return {}
+
+        addresses = {}
+        for ns in self.instance.ports.all():
+            if "s5s8_pgw" in ns.network.name.lower():
+                addresses["s5s8_pgw"] = (ns.ip, ns.mac)
+        return addresses
+
+
+    @property
+    def s5s8_pgw_ip(self):
+        return self.addresses.get("s5s8_pgw", (None, None))[0]
+    @property
+    def s5s8_pgw_mac(self):
+        return self.addresses.get("s5s8_pgw", (None, None))[1]
+
+
+
+def model_policy_mcord_servicecomponent(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
+        component = VPGWCComponent.objects.select_for_update().filter(pk=pk)
+        if not component:
+            return
+        # Since this code is atomic it is safe to always use the first tenant
+        component = component[0]
+        component.manage_container()
diff --git a/xos/synchronizer/steps/sync_vpgwc.py b/xos/synchronizer/steps/sync_vpgwc.py
index 48d80c9..70056b3 100644
--- a/xos/synchronizer/steps/sync_vpgwc.py
+++ b/xos/synchronizer/steps/sync_vpgwc.py
@@ -1,7 +1,7 @@
 import os
 import sys
 from django.db.models import Q, F
-from services.vpgwc.models import MCORDService, VPGWCComponent
+from services.vpgwc.models import VPGWCService, VPGWCComponent
 from synchronizers.base.SyncInstanceUsingAnsible import SyncInstanceUsingAnsible
 
 parentdir = os.path.join(os.path.dirname(__file__), "..")
diff --git a/xos/tosca/resources/VPGWCComponent.py b/xos/tosca/resources/VPGWCComponent.py
new file mode 100644
index 0000000..cbea988
--- /dev/null
+++ b/xos/tosca/resources/VPGWCComponent.py
@@ -0,0 +1,40 @@
+import os
+import pdb
+import sys
+import tempfile
+sys.path.append("/opt/tosca")
+from translator.toscalib.tosca_template import ToscaTemplate
+import pdb
+
+from services.vpgwc.models import VPGWCComponent, MCORDService
+
+from xosresource import XOSResource
+
+class XOSVPGWCComponent(XOSResource):
+    provides = "tosca.nodes.VPGWCComponent"
+    xos_model = VPGWCComponent
+    copyin_props = ["s5s8_pgw_tag", "display_message"]
+    name_field = None
+
+    def get_xos_args(self, throw_exception=True):
+        args = super(XOSVPGWCComponent, self).get_xos_args()
+
+        provider_name = self.get_requirement("tosca.relationships.MemberOfService", throw_exception=throw_exception)
+        if provider_name:
+            args["provider_service"] = self.get_xos_object(MCORDService, throw_exception=throw_exception, name=provider_name)
+
+        return args
+
+    def get_existing_objs(self):
+        args = self.get_xos_args(throw_exception=False)
+        provider_service = args.get("provider", None)
+        if provider_service:
+            return [ self.get_xos_object(provider_service=provider_service) ]
+        return []
+
+    def postprocess(self, obj):
+        pass
+
+    def can_delete(self, obj):
+        return super(XOSVPGWCComponent, self).can_delete(obj)
+
diff --git a/xos/tosca/resources/VPGWCService.py b/xos/tosca/resources/VPGWCService.py
new file mode 100644
index 0000000..7ee36dc
--- /dev/null
+++ b/xos/tosca/resources/VPGWCService.py
@@ -0,0 +1,19 @@
+import os
+import pdb
+import sys
+import tempfile
+sys.path.append("/opt/tosca")
+from translator.toscalib.tosca_template import ToscaTemplate
+import pdb
+
+from services.vpgwc.models import VPGWCService
+
+from service import XOSService
+
+class XOSVPGWCService(XOSService):
+    provides = "tosca.nodes.VPGWCService"
+    xos_model = VPGWCService
+    copyin_props = ["view_url", "icon_url", "enabled", "published", "public_key",
+                    "private_key_fn", "versionNumber",
+                    ]
+