Merge pull request #152 from girishgc/vPGWc_Service
added pgwc service changes
diff --git a/xos/services/mcord/admin.py b/xos/services/mcord/admin.py
index b496ef7..ee19c7e 100644
--- a/xos/services/mcord/admin.py
+++ b/xos/services/mcord/admin.py
@@ -4,7 +4,7 @@
from core.models import User
from django import forms
from django.contrib import admin
-from services.mcord.models import MCORDService, VBBUComponent, MCORD_KIND
+from services.mcord.models import MCORDService, VBBUComponent, 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
@@ -114,6 +114,55 @@
class Meta:
model = VBBUComponent
+# 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 = MCORDService.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 MCORDService.get_service_objects().exists():
+ self.fields["provider_service"].initial = MCORDService.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
+
+
+
# Define the admin form for the tenant. This uses a similar structure as the
# service but uses HelloWorldTenantCompleteForm to change the python behavior.
@@ -136,6 +185,28 @@
def queryset(self, request):
return VBBUComponent.get_tenant_objects_by_user(request.user)
+
+# 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(MCORDService, MCORDServiceAdmin)
admin.site.register(VBBUComponent, VBBUComponentAdmin)
+admin.site.register(VPGWCComponent, VPGWCComponentAdmin)
diff --git a/xos/services/mcord/models.py b/xos/services/mcord/models.py
index 1b2bd65..6d070d8 100644
--- a/xos/services/mcord/models.py
+++ b/xos/services/mcord/models.py
@@ -18,8 +18,9 @@
MCORD_USE_VTN = getattr(Config(), "networking_use_vtn", False)
VBBU_KIND = "RAN"
VSGW_KIND = "vSGW"
-VPGW_KIND = "vPGW"
-net_types = ("s1u", "s1mme", "rru")
+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.
@@ -91,7 +92,7 @@
with transaction.atomic():
super(VBBUComponent, self).save_instance(instance)
if instance.isolation in ["vm"]:
- for ntype in net_types:
+ for ntype in vbbu_net_types:
lan_network = self.get_lan_network(instance, ntype)
port = self.find_or_make_port(instance,lan_network)
if (ntype == "s1u"):
@@ -271,6 +272,193 @@
def rru_mac(self):
return self.addresses.get("rru", (None, None))[1]
+
+# 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 MCORD 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 = MCORDService.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():
@@ -281,3 +469,15 @@
# Since this code is atomic it is safe to always use the first tenant
component = component[0]
component.manage_container()
+
+
+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()