blob: 78342a672ab99ff23e248e13ac82a5ff16c78d74 [file] [log] [blame]
Scott Baker761e1062016-06-20 17:18:17 -07001from django.db import models
2from core.models import Service, PlCoreBase, Slice, Instance, Tenant, TenantWithContainer, Node, Image, User, Flavor, Subscriber, NetworkParameter, NetworkParameterType, Port, AddressPool, User
3from core.models.plcorebase import StrippedCharField
4import os
5from django.db import models, transaction
6from django.forms.models import model_to_dict
7from django.db.models import Q
8from operator import itemgetter, attrgetter, methodcaller
9from core.models import Tag
10from core.models.service import LeastLoadedNodeScheduler
11from services.vrouter.models import VRouterService, VRouterTenant
12import traceback
13from xos.exceptions import *
14from xos.config import Config
15
16class ConfigurationError(Exception):
17 pass
18
19VCPE_KIND = "vCPE"
20CORD_SUBSCRIBER_KIND = "CordSubscriberRoot"
21
Scott Baker761e1062016-06-20 17:18:17 -070022# -------------------------------------------
23# VCPE
24# -------------------------------------------
25
26class VSGService(Service):
27 KIND = VCPE_KIND
28
29 URL_FILTER_KIND_CHOICES = ( (None, "None"), ("safebrowsing", "Safe Browsing"), ("answerx", "AnswerX") )
30
Scott Bakerecee9b12017-03-08 09:56:20 -080031 url_filter_kind = StrippedCharField(max_length=30, choices=URL_FILTER_KIND_CHOICES, null=True, blank=True)
32 dns_servers = StrippedCharField(max_length=255, default="8.8.8.8")
33 node_label = StrippedCharField(max_length=30, null=True, blank=True)
34 docker_image_name = StrippedCharField(max_length=255, default="docker.io/xosproject/vsg")
35 docker_insecure_registry = models.BooleanField(default=False)
Scott Baker761e1062016-06-20 17:18:17 -070036
37 class Meta:
38 app_label = "vsg"
39 verbose_name = "vSG Service"
Scott Baker761e1062016-06-20 17:18:17 -070040
41class VSGTenant(TenantWithContainer):
Scott Baker761e1062016-06-20 17:18:17 -070042 KIND = VCPE_KIND
43
Scott Bakerecee9b12017-03-08 09:56:20 -080044 # it's not clear we still need this field...
45 last_ansible_hash = StrippedCharField(max_length=128, null=True, blank=True)
46
47 class Meta:
48 app_label = "vsg"
49
Scott Baker761e1062016-06-20 17:18:17 -070050 sync_attributes = ("wan_container_ip", "wan_container_mac", "wan_container_netbits",
51 "wan_container_gateway_ip", "wan_container_gateway_mac",
52 "wan_vm_ip", "wan_vm_mac")
53
Scott Baker761e1062016-06-20 17:18:17 -070054 def __init__(self, *args, **kwargs):
55 super(VSGTenant, self).__init__(*args, **kwargs)
56 self.cached_vrouter=None
57
58 @property
Scott Baker761e1062016-06-20 17:18:17 -070059 def vrouter(self):
60 vrouter = self.get_newest_subscribed_tenant(VRouterTenant)
61 if not vrouter:
62 return None
63
64 # always return the same object when possible
65 if (self.cached_vrouter) and (self.cached_vrouter.id == vrouter.id):
66 return self.cached_vrouter
67
68 vrouter.caller = self.creator
69 self.cached_vrouter = vrouter
70 return vrouter
71
72 @vrouter.setter
73 def vrouter(self, value):
Scott Bakerecee9b12017-03-08 09:56:20 -080074 raise XOSConfigurationError("VSGTenant.vrouter setter is not implemented")
Scott Baker761e1062016-06-20 17:18:17 -070075
76 @property
77 def volt(self):
78 from services.volt.models import VOLTTenant
79 if not self.subscriber_tenant:
80 return None
81 volts = VOLTTenant.objects.filter(id=self.subscriber_tenant.id)
82 if not volts:
83 return None
84 return volts[0]
85
Scott Bakerecee9b12017-03-08 09:56:20 -080086 @volt.setter
87 def volt(self, value):
88 raise XOSConfigurationError("VSGTenant.volt setter is not implemented")
Scott Baker761e1062016-06-20 17:18:17 -070089
90 @property
91 def ssh_command(self):
92 if self.instance:
93 return self.instance.get_ssh_command()
94 else:
95 return "no-instance"
96
Scott Baker761e1062016-06-20 17:18:17 -070097 def get_vrouter_field(self, name, default=None):
98 if self.vrouter:
99 return getattr(self.vrouter, name, default)
100 else:
101 return default
102
103 @property
104 def wan_container_ip(self):
105 return self.get_vrouter_field("public_ip", None)
106
107 @property
108 def wan_container_mac(self):
109 return self.get_vrouter_field("public_mac", None)
110
111 @property
112 def wan_container_netbits(self):
113 return self.get_vrouter_field("netbits", None)
114
115 @property
116 def wan_container_gateway_ip(self):
117 return self.get_vrouter_field("gateway_ip", None)
118
119 @property
120 def wan_container_gateway_mac(self):
121 return self.get_vrouter_field("gateway_mac", None)
122
123 @property
124 def wan_vm_ip(self):
125 tags = Tag.select_by_content_object(self.instance).filter(name="vm_vrouter_tenant")
126 if tags:
127 tenant = VRouterTenant.objects.get(id=tags[0].value)
128 return tenant.public_ip
129 else:
130 raise Exception("no vm_vrouter_tenant tag for instance %s" % o.instance)
131
132 @property
133 def wan_vm_mac(self):
134 tags = Tag.select_by_content_object(self.instance).filter(name="vm_vrouter_tenant")
135 if tags:
136 tenant = VRouterTenant.objects.get(id=tags[0].value)
137 return tenant.public_mac
138 else:
139 raise Exception("no vm_vrouter_tenant tag for instance %s" % o.instance)
140
141 @property
142 def is_synced(self):
143 return (self.enacted is not None) and (self.enacted >= self.updated)
144
145 @is_synced.setter
146 def is_synced(self, value):
147 pass
148
149 def get_vrouter_service(self):
150 vrouterServices = VRouterService.get_service_objects().all()
151 if not vrouterServices:
152 raise XOSConfigurationError("No VROUTER Services available")
153 return vrouterServices[0]
154
155 def manage_vrouter(self):
156 # Each vCPE object owns exactly one vRouterTenant object
157
158 if self.deleted:
159 return
160
161 if self.vrouter is None:
162 vrouter = self.get_vrouter_service().get_tenant(address_pool_name="addresses_vsg", subscriber_tenant = self)
163 vrouter.caller = self.creator
164 vrouter.save()
165
166 def cleanup_vrouter(self):
167 if self.vrouter:
168 # print "XXX cleanup vrouter", self.vrouter
169 self.vrouter.delete()
170
171 def cleanup_orphans(self):
172 # ensure vCPE only has one vRouter
173 cur_vrouter = self.vrouter
174 for vrouter in list(self.get_subscribed_tenants(VRouterTenant)):
175 if (not cur_vrouter) or (vrouter.id != cur_vrouter.id):
176 # print "XXX clean up orphaned vrouter", vrouter
177 vrouter.delete()
178
179 if self.orig_instance_id and (self.orig_instance_id != self.get_attribute("instance_id")):
180 instances=Instance.objects.filter(id=self.orig_instance_id)
181 if instances:
182 # print "XXX clean up orphaned instance", instances[0]
183 instances[0].delete()
184
185 def get_slice(self):
186 if not self.provider_service.slices.count():
187 print self, "dio porco"
188 raise XOSConfigurationError("The service has no slices")
189 slice = self.provider_service.slices.all()[0]
190 return slice
191
192 def get_vsg_service(self):
193 return VSGService.get_service_objects().get(id=self.provider_service.id)
194
195 def find_instance_for_s_tag(self, s_tag):
196 #s_tags = STagBlock.objects.find(s_s_tag)
197 #if s_tags:
198 # return s_tags[0].instance
199
200 tags = Tag.objects.filter(name="s_tag", value=s_tag)
201 if tags:
202 return tags[0].content_object
203
204 return None
205
206 def find_or_make_instance_for_s_tag(self, s_tag):
207 instance = self.find_instance_for_s_tag(self.volt.s_tag)
208 if instance:
209 return instance
210
211 flavors = Flavor.objects.filter(name="m1.small")
212 if not flavors:
213 raise XOSConfigurationError("No m1.small flavor")
214
215 slice = self.provider_service.slices.all()[0]
216
217 if slice.default_isolation == "container_vm":
218 (node, parent) = ContainerVmScheduler(slice).pick()
219 else:
220 (node, parent) = LeastLoadedNodeScheduler(slice, label=self.get_vsg_service().node_label).pick()
221
222 instance = Instance(slice = slice,
223 node = node,
224 image = self.image,
225 creator = self.creator,
226 deployment = node.site_deployment.deployment,
227 flavor = flavors[0],
228 isolation = slice.default_isolation,
229 parent = parent)
230
231 self.save_instance(instance)
232
233 return instance
234
235 def manage_container(self):
236 from core.models import Instance, Flavor
237
238 if self.deleted:
239 return
240
241 # For container or container_vm isolation, use what TenantWithCotnainer
242 # provides us
243 slice = self.get_slice()
244 if slice.default_isolation in ["container_vm", "container"]:
245 super(VSGTenant,self).manage_container()
246 return
247
248 if not self.volt:
249 raise XOSConfigurationError("This vCPE container has no volt")
250
251 if self.instance:
252 # We're good.
253 return
254
255 instance = self.find_or_make_instance_for_s_tag(self.volt.s_tag)
256 self.instance = instance
257 super(TenantWithContainer, self).save()
258
259 def cleanup_container(self):
260 if self.get_slice().default_isolation in ["container_vm", "container"]:
261 super(VSGTenant,self).cleanup_container()
262
263 # To-do: cleanup unused instances
264 pass
265
Scott Baker761e1062016-06-20 17:18:17 -0700266 def find_or_make_port(self, instance, network, **kwargs):
267 port = Port.objects.filter(instance=instance, network=network)
268 if port:
269 port = port[0]
270 else:
271 port = Port(instance=instance, network=network, **kwargs)
272 port.save()
273 return port
274
275 def get_lan_network(self, instance):
276 slice = self.provider_service.slices.all()[0]
Scott Bakerecee9b12017-03-08 09:56:20 -0800277 # there should only be one network private network, and its template should not be the management template
278 lan_networks = [x for x in slice.networks.all() if x.template.visibility=="private" and (not "management" in x.template.name)]
279 if len(lan_networks)>1:
280 raise XOSProgrammingError("The vSG slice should only have one non-management private network")
Scott Baker761e1062016-06-20 17:18:17 -0700281 if not lan_networks:
282 raise XOSProgrammingError("No lan_network")
283 return lan_networks[0]
284
285 def save_instance(self, instance):
286 with transaction.atomic():
287 instance.volumes = "/etc/dnsmasq.d,/etc/ufw"
288 super(VSGTenant, self).save_instance(instance)
289
290 if instance.isolation in ["container", "container_vm"]:
291 lan_network = self.get_lan_network(instance)
292 port = self.find_or_make_port(instance, lan_network, ip="192.168.0.1", port_id="unmanaged")
293 port.set_parameter("c_tag", self.volt.c_tag)
294 port.set_parameter("s_tag", self.volt.s_tag)
295 port.set_parameter("device", "eth1")
296 port.set_parameter("bridge", "br-lan")
297
298 wan_networks = [x for x in instance.slice.networks.all() if "wan" in x.name]
299 if not wan_networks:
300 raise XOSProgrammingError("No wan_network")
301 port = self.find_or_make_port(instance, wan_networks[0])
302 port.set_parameter("next_hop", value="10.0.1.253") # FIX ME
303 port.set_parameter("device", "eth0")
304
305 if instance.isolation in ["vm"]:
306 lan_network = self.get_lan_network(instance)
307 port = self.find_or_make_port(instance, lan_network)
308 port.set_parameter("c_tag", self.volt.c_tag)
309 port.set_parameter("s_tag", self.volt.s_tag)
310 port.set_parameter("neutron_port_name", "stag-%s" % self.volt.s_tag)
311 port.save()
312
313 # tag the instance with the s-tag, so we can easily find the
314 # instance later
315 if self.volt and self.volt.s_tag:
316 tags = Tag.objects.filter(name="s_tag", value=self.volt.s_tag)
317 if not tags:
318 tag = Tag(service=self.provider_service, content_object=instance, name="s_tag", value=self.volt.s_tag)
319 tag.save()
320
321 # VTN-CORD needs a WAN address for the VM, so that the VM can
322 # be configured.
Scott Bakerecee9b12017-03-08 09:56:20 -0800323 tags = Tag.select_by_content_object(instance).filter(name="vm_vrouter_tenant")
324 if not tags:
325 vrouter = self.get_vrouter_service().get_tenant(address_pool_name="addresses_vsg", subscriber_service = self.provider_service)
326 vrouter.set_attribute("tenant_for_instance_id", instance.id)
327 vrouter.save()
328 tag = Tag(service=self.provider_service, content_object=instance, name="vm_vrouter_tenant", value="%d" % vrouter.id)
329 tag.save()
Scott Baker761e1062016-06-20 17:18:17 -0700330
331 def save(self, *args, **kwargs):
332 if not self.creator:
333 if not getattr(self, "caller", None):
334 # caller must be set when creating a vCPE since it creates a slice
335 raise XOSProgrammingError("VSGTenant's self.caller was not set")
336 self.creator = self.caller
337 if not self.creator:
338 raise XOSProgrammingError("VSGTenant's self.creator was not set")
339
340 super(VSGTenant, self).save(*args, **kwargs)
341 model_policy_vcpe(self.pk)
342
343 def delete(self, *args, **kwargs):
344 self.cleanup_vrouter()
345 self.cleanup_container()
346 super(VSGTenant, self).delete(*args, **kwargs)
347
348def model_policy_vcpe(pk):
349 # TODO: this should be made in to a real model_policy
350 with transaction.atomic():
351 vcpe = VSGTenant.objects.select_for_update().filter(pk=pk)
352 if not vcpe:
353 return
354 vcpe = vcpe[0]
355 vcpe.manage_container()
356 vcpe.manage_vrouter()
Scott Baker761e1062016-06-20 17:18:17 -0700357 vcpe.cleanup_orphans()
358
359