blob: 00193bc6dc60cdb43a45372d22e73d4bc5cbb5b3 [file] [log] [blame]
from django.db import models
from core.models import Service, PlCoreBase, Slice, Instance, Tenant, TenantWithContainer, Node, Image, User, Flavor, NetworkParameter, NetworkParameterType, Port, AddressPool, User
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
from services.vrouter.models import VRouterService, VRouterTenant
import traceback
from xos.exceptions import *
from xos.config import Config
class ConfigurationError(Exception):
pass
VCPE_KIND = "vCPE"
CORD_SUBSCRIBER_KIND = "CordSubscriberRoot"
# -------------------------------------------
# VCPE
# -------------------------------------------
class VSGService(Service):
KIND = VCPE_KIND
URL_FILTER_KIND_CHOICES = ( (None, "None"), ("safebrowsing", "Safe Browsing"), ("answerx", "AnswerX") )
url_filter_kind = StrippedCharField(max_length=30, choices=URL_FILTER_KIND_CHOICES, null=True, blank=True)
dns_servers = StrippedCharField(max_length=255, default="8.8.8.8")
node_label = StrippedCharField(max_length=30, null=True, blank=True)
docker_image_name = StrippedCharField(max_length=255, default="docker.io/xosproject/vsg")
docker_insecure_registry = models.BooleanField(default=False)
class Meta:
app_label = "vsg"
verbose_name = "vSG Service"
class VSGTenant(TenantWithContainer):
KIND = VCPE_KIND
# it's not clear we still need this field...
last_ansible_hash = StrippedCharField(max_length=128, null=True, blank=True)
class Meta:
app_label = "vsg"
sync_attributes = ("wan_container_ip", "wan_container_mac", "wan_container_netbits",
"wan_container_gateway_ip", "wan_container_gateway_mac",
"wan_vm_ip", "wan_vm_mac")
def __init__(self, *args, **kwargs):
super(VSGTenant, self).__init__(*args, **kwargs)
self.cached_vrouter=None
@property
def vrouter(self):
vrouter = self.get_newest_subscribed_tenant(VRouterTenant)
if not vrouter:
return None
# always return the same object when possible
if (self.cached_vrouter) and (self.cached_vrouter.id == vrouter.id):
return self.cached_vrouter
vrouter.caller = self.creator
self.cached_vrouter = vrouter
return vrouter
@vrouter.setter
def vrouter(self, value):
raise XOSConfigurationError("VSGTenant.vrouter setter is not implemented")
@property
def volt(self):
from services.volt.models import VOLTTenant
if not self.subscriber_tenant:
return None
volts = VOLTTenant.objects.filter(id=self.subscriber_tenant.id)
if not volts:
return None
return volts[0]
@volt.setter
def volt(self, value):
raise XOSConfigurationError("VSGTenant.volt setter is not implemented")
@property
def ssh_command(self):
if self.instance:
return self.instance.get_ssh_command()
else:
return "no-instance"
def get_vrouter_field(self, name, default=None):
if self.vrouter:
return getattr(self.vrouter, name, default)
else:
return default
@property
def wan_container_ip(self):
return self.get_vrouter_field("public_ip", None)
@property
def wan_container_mac(self):
return self.get_vrouter_field("public_mac", None)
@property
def wan_container_netbits(self):
return self.get_vrouter_field("netbits", None)
@property
def wan_container_gateway_ip(self):
return self.get_vrouter_field("gateway_ip", None)
@property
def wan_container_gateway_mac(self):
return self.get_vrouter_field("gateway_mac", None)
@property
def wan_vm_ip(self):
tags = Tag.select_by_content_object(self.instance).filter(name="vm_vrouter_tenant")
if tags:
tenant = VRouterTenant.objects.get(id=tags[0].value)
return tenant.public_ip
else:
raise Exception("no vm_vrouter_tenant tag for instance %s" % o.instance)
@property
def wan_vm_mac(self):
tags = Tag.select_by_content_object(self.instance).filter(name="vm_vrouter_tenant")
if tags:
tenant = VRouterTenant.objects.get(id=tags[0].value)
return tenant.public_mac
else:
raise Exception("no vm_vrouter_tenant tag for instance %s" % o.instance)
@property
def is_synced(self):
return (self.enacted is not None) and (self.enacted >= self.updated)
@is_synced.setter
def is_synced(self, value):
pass
def get_vrouter_service(self):
vrouterServices = VRouterService.get_service_objects().all()
if not vrouterServices:
raise XOSConfigurationError("No VROUTER Services available")
return vrouterServices[0]
def manage_vrouter(self):
# Each vCPE object owns exactly one vRouterTenant object
if self.deleted:
return
if self.vrouter is None:
vrouter = self.get_vrouter_service().get_tenant(address_pool_name="addresses_vsg", subscriber_tenant = self)
vrouter.caller = self.creator
vrouter.save()
def cleanup_vrouter(self):
if self.vrouter:
# print "XXX cleanup vrouter", self.vrouter
self.vrouter.delete()
def cleanup_orphans(self):
# ensure vCPE only has one vRouter
cur_vrouter = self.vrouter
for vrouter in list(self.get_subscribed_tenants(VRouterTenant)):
if (not cur_vrouter) or (vrouter.id != cur_vrouter.id):
# print "XXX clean up orphaned vrouter", vrouter
vrouter.delete()
if self.orig_instance_id and (self.orig_instance_id != self.get_attribute("instance_id")):
instances=Instance.objects.filter(id=self.orig_instance_id)
if instances:
# print "XXX clean up orphaned instance", instances[0]
instances[0].delete()
def get_slice(self):
if not self.provider_service.slices.count():
print self, "dio porco"
raise XOSConfigurationError("The service has no slices")
slice = self.provider_service.slices.all()[0]
return slice
def get_vsg_service(self):
return VSGService.get_service_objects().get(id=self.provider_service.id)
def find_instance_for_s_tag(self, s_tag):
#s_tags = STagBlock.objects.find(s_s_tag)
#if s_tags:
# return s_tags[0].instance
tags = Tag.objects.filter(name="s_tag", value=s_tag)
if tags:
return tags[0].content_object
return None
def find_or_make_instance_for_s_tag(self, s_tag):
instance = self.find_instance_for_s_tag(self.volt.s_tag)
if instance:
return instance
flavors = Flavor.objects.filter(name="m1.small")
if not flavors:
raise XOSConfigurationError("No m1.small flavor")
slice = self.provider_service.slices.all()[0]
if slice.default_isolation == "container_vm":
(node, parent) = ContainerVmScheduler(slice).pick()
else:
(node, parent) = LeastLoadedNodeScheduler(slice, label=self.get_vsg_service().node_label).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 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(VSGTenant,self).manage_container()
return
if not self.volt:
raise XOSConfigurationError("This vCPE container has no volt")
if self.instance:
# We're good.
return
instance = self.find_or_make_instance_for_s_tag(self.volt.s_tag)
self.instance = instance
super(TenantWithContainer, self).save()
def cleanup_container(self):
if self.get_slice().default_isolation in ["container_vm", "container"]:
super(VSGTenant,self).cleanup_container()
# To-do: cleanup unused instances
pass
def find_or_make_port(self, instance, network, **kwargs):
port = Port.objects.filter(instance=instance, network=network)
if port:
port = port[0]
else:
port = Port(instance=instance, network=network, **kwargs)
port.save()
return port
def get_lan_network(self, instance):
slice = self.provider_service.slices.all()[0]
# there should only be one network private network, and its template should not be the management template
lan_networks = [x for x in slice.networks.all() if x.template.visibility=="private" and (not "management" in x.template.name)]
if len(lan_networks)>1:
raise XOSProgrammingError("The vSG slice should only have one non-management private network")
if not lan_networks:
raise XOSProgrammingError("No lan_network")
return lan_networks[0]
def save_instance(self, instance):
with transaction.atomic():
instance.volumes = "/etc/dnsmasq.d,/etc/ufw"
super(VSGTenant, self).save_instance(instance)
if instance.isolation in ["container", "container_vm"]:
lan_network = self.get_lan_network(instance)
port = self.find_or_make_port(instance, lan_network, ip="192.168.0.1", port_id="unmanaged")
port.set_parameter("c_tag", self.volt.c_tag)
port.set_parameter("s_tag", self.volt.s_tag)
port.set_parameter("device", "eth1")
port.set_parameter("bridge", "br-lan")
wan_networks = [x for x in instance.slice.networks.all() if "wan" in x.name]
if not wan_networks:
raise XOSProgrammingError("No wan_network")
port = self.find_or_make_port(instance, wan_networks[0])
port.set_parameter("next_hop", value="10.0.1.253") # FIX ME
port.set_parameter("device", "eth0")
if instance.isolation in ["vm"]:
lan_network = self.get_lan_network(instance)
port = self.find_or_make_port(instance, lan_network)
port.set_parameter("c_tag", self.volt.c_tag)
port.set_parameter("s_tag", self.volt.s_tag)
port.set_parameter("neutron_port_name", "stag-%s" % self.volt.s_tag)
port.save()
# tag the instance with the s-tag, so we can easily find the
# instance later
if self.volt and self.volt.s_tag:
tags = Tag.objects.filter(name="s_tag", value=self.volt.s_tag)
if not tags:
tag = Tag(service=self.provider_service, content_object=instance, name="s_tag", value=self.volt.s_tag)
tag.save()
# VTN-CORD needs a WAN address for the VM, so that the VM can
# be configured.
tags = Tag.select_by_content_object(instance).filter(name="vm_vrouter_tenant")
if not tags:
vrouter = self.get_vrouter_service().get_tenant(address_pool_name="addresses_vsg", subscriber_service = self.provider_service)
vrouter.set_attribute("tenant_for_instance_id", instance.id)
vrouter.save()
tag = Tag(service=self.provider_service, content_object=instance, name="vm_vrouter_tenant", value="%d" % vrouter.id)
tag.save()
def save(self, *args, **kwargs):
if not self.creator:
if not getattr(self, "caller", None):
# caller must be set when creating a vCPE since it creates a slice
raise XOSProgrammingError("VSGTenant's self.caller was not set")
self.creator = self.caller
if not self.creator:
raise XOSProgrammingError("VSGTenant's self.creator was not set")
super(VSGTenant, self).save(*args, **kwargs)
model_policy_vcpe(self.pk)
def delete(self, *args, **kwargs):
self.cleanup_vrouter()
self.cleanup_container()
super(VSGTenant, self).delete(*args, **kwargs)
def model_policy_vcpe(pk):
# TODO: this should be made in to a real model_policy
with transaction.atomic():
vcpe = VSGTenant.objects.select_for_update().filter(pk=pk)
if not vcpe:
return
vcpe = vcpe[0]
vcpe.manage_container()
vcpe.manage_vrouter()
vcpe.cleanup_orphans()