blob: ad25c9826bf3618b9e519d34e9c5c07e57c08ae6 [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
22CORD_USE_VTN = getattr(Config(), "networking_use_vtn", False)
23
24# -------------------------------------------
25# VCPE
26# -------------------------------------------
27
28class VSGService(Service):
29 KIND = VCPE_KIND
30
31 URL_FILTER_KIND_CHOICES = ( (None, "None"), ("safebrowsing", "Safe Browsing"), ("answerx", "AnswerX") )
32
33 simple_attributes = ( ("bbs_api_hostname", None),
34 ("bbs_api_port", None),
35 ("bbs_server", None),
36 ("backend_network_label", "hpc_client"),
37 ("dns_servers", "8.8.8.8"),
38 ("url_filter_kind", None),
39 ("node_label", None) )
40
41 def __init__(self, *args, **kwargs):
42 super(VSGService, self).__init__(*args, **kwargs)
43
44 class Meta:
45 app_label = "vsg"
46 verbose_name = "vSG Service"
47 proxy = True
48
49 def allocate_bbs_account(self):
50 vcpes = VSGTenant.get_tenant_objects().all()
51 bbs_accounts = [vcpe.bbs_account for vcpe in vcpes]
52
53 # There's a bit of a race here; some other user could be trying to
54 # allocate a bbs_account at the same time we are.
55
56 for i in range(2,21):
57 account_name = "bbs%02d@onlab.us" % i
58 if (account_name not in bbs_accounts):
59 return account_name
60
61 raise XOSConfigurationError("We've run out of available broadbandshield accounts. Delete some vcpe and try again.")
62
63 @property
64 def bbs_slice(self):
65 bbs_slice_id=self.get_attribute("bbs_slice_id")
66 if not bbs_slice_id:
67 return None
68 bbs_slices=Slice.objects.filter(id=bbs_slice_id)
69 if not bbs_slices:
70 return None
71 return bbs_slices[0]
72
73 @bbs_slice.setter
74 def bbs_slice(self, value):
75 if value:
76 value = value.id
77 self.set_attribute("bbs_slice_id", value)
78
79VSGService.setup_simple_attributes()
80
81class VSGTenant(TenantWithContainer):
82 class Meta:
83 proxy = True
84
85 KIND = VCPE_KIND
86
87 sync_attributes = ("wan_container_ip", "wan_container_mac", "wan_container_netbits",
88 "wan_container_gateway_ip", "wan_container_gateway_mac",
89 "wan_vm_ip", "wan_vm_mac")
90
91 default_attributes = {"instance_id": None,
92 "container_id": None,
93 "users": [],
94 "bbs_account": None,
95 "last_ansible_hash": None,
96 "wan_container_ip": None}
97
98 def __init__(self, *args, **kwargs):
99 super(VSGTenant, self).__init__(*args, **kwargs)
100 self.cached_vrouter=None
101
102 @property
103 def vbng(self):
104 # not supported
105 return None
106
107 @vbng.setter
108 def vbng(self, value):
109 raise XOSConfigurationError("vCPE.vBNG cannot be set this way -- create a new vBNG object and set it's subscriber_tenant instead")
110
111 @property
112 def vrouter(self):
113 vrouter = self.get_newest_subscribed_tenant(VRouterTenant)
114 if not vrouter:
115 return None
116
117 # always return the same object when possible
118 if (self.cached_vrouter) and (self.cached_vrouter.id == vrouter.id):
119 return self.cached_vrouter
120
121 vrouter.caller = self.creator
122 self.cached_vrouter = vrouter
123 return vrouter
124
125 @vrouter.setter
126 def vrouter(self, value):
127 raise XOSConfigurationError("vCPE.vRouter cannot be set this way -- create a new vRuter object and set its subscriber_tenant instead")
128
129 @property
130 def volt(self):
131 from services.volt.models import VOLTTenant
132 if not self.subscriber_tenant:
133 return None
134 volts = VOLTTenant.objects.filter(id=self.subscriber_tenant.id)
135 if not volts:
136 return None
137 return volts[0]
138
139 @property
140 def bbs_account(self):
141 return self.get_attribute("bbs_account", self.default_attributes["bbs_account"])
142
143 @bbs_account.setter
144 def bbs_account(self, value):
145 return self.set_attribute("bbs_account", value)
146
147 @property
148 def last_ansible_hash(self):
149 return self.get_attribute("last_ansible_hash", self.default_attributes["last_ansible_hash"])
150
151 @last_ansible_hash.setter
152 def last_ansible_hash(self, value):
153 return self.set_attribute("last_ansible_hash", value)
154
155 @property
156 def ssh_command(self):
157 if self.instance:
158 return self.instance.get_ssh_command()
159 else:
160 return "no-instance"
161
162 @ssh_command.setter
163 def ssh_command(self, value):
164 pass
165
166 def get_vrouter_field(self, name, default=None):
167 if self.vrouter:
168 return getattr(self.vrouter, name, default)
169 else:
170 return default
171
172 @property
173 def wan_container_ip(self):
174 return self.get_vrouter_field("public_ip", None)
175
176 @property
177 def wan_container_mac(self):
178 return self.get_vrouter_field("public_mac", None)
179
180 @property
181 def wan_container_netbits(self):
182 return self.get_vrouter_field("netbits", None)
183
184 @property
185 def wan_container_gateway_ip(self):
186 return self.get_vrouter_field("gateway_ip", None)
187
188 @property
189 def wan_container_gateway_mac(self):
190 return self.get_vrouter_field("gateway_mac", None)
191
192 @property
193 def wan_vm_ip(self):
194 tags = Tag.select_by_content_object(self.instance).filter(name="vm_vrouter_tenant")
195 if tags:
196 tenant = VRouterTenant.objects.get(id=tags[0].value)
197 return tenant.public_ip
198 else:
199 raise Exception("no vm_vrouter_tenant tag for instance %s" % o.instance)
200
201 @property
202 def wan_vm_mac(self):
203 tags = Tag.select_by_content_object(self.instance).filter(name="vm_vrouter_tenant")
204 if tags:
205 tenant = VRouterTenant.objects.get(id=tags[0].value)
206 return tenant.public_mac
207 else:
208 raise Exception("no vm_vrouter_tenant tag for instance %s" % o.instance)
209
210 @property
211 def is_synced(self):
212 return (self.enacted is not None) and (self.enacted >= self.updated)
213
214 @is_synced.setter
215 def is_synced(self, value):
216 pass
217
218 def get_vrouter_service(self):
219 vrouterServices = VRouterService.get_service_objects().all()
220 if not vrouterServices:
221 raise XOSConfigurationError("No VROUTER Services available")
222 return vrouterServices[0]
223
224 def manage_vrouter(self):
225 # Each vCPE object owns exactly one vRouterTenant object
226
227 if self.deleted:
228 return
229
230 if self.vrouter is None:
231 vrouter = self.get_vrouter_service().get_tenant(address_pool_name="addresses_vsg", subscriber_tenant = self)
232 vrouter.caller = self.creator
233 vrouter.save()
234
235 def cleanup_vrouter(self):
236 if self.vrouter:
237 # print "XXX cleanup vrouter", self.vrouter
238 self.vrouter.delete()
239
240 def cleanup_orphans(self):
241 # ensure vCPE only has one vRouter
242 cur_vrouter = self.vrouter
243 for vrouter in list(self.get_subscribed_tenants(VRouterTenant)):
244 if (not cur_vrouter) or (vrouter.id != cur_vrouter.id):
245 # print "XXX clean up orphaned vrouter", vrouter
246 vrouter.delete()
247
248 if self.orig_instance_id and (self.orig_instance_id != self.get_attribute("instance_id")):
249 instances=Instance.objects.filter(id=self.orig_instance_id)
250 if instances:
251 # print "XXX clean up orphaned instance", instances[0]
252 instances[0].delete()
253
254 def get_slice(self):
255 if not self.provider_service.slices.count():
256 print self, "dio porco"
257 raise XOSConfigurationError("The service has no slices")
258 slice = self.provider_service.slices.all()[0]
259 return slice
260
261 def get_vsg_service(self):
262 return VSGService.get_service_objects().get(id=self.provider_service.id)
263
264 def find_instance_for_s_tag(self, s_tag):
265 #s_tags = STagBlock.objects.find(s_s_tag)
266 #if s_tags:
267 # return s_tags[0].instance
268
269 tags = Tag.objects.filter(name="s_tag", value=s_tag)
270 if tags:
271 return tags[0].content_object
272
273 return None
274
275 def find_or_make_instance_for_s_tag(self, s_tag):
276 instance = self.find_instance_for_s_tag(self.volt.s_tag)
277 if instance:
278 return instance
279
280 flavors = Flavor.objects.filter(name="m1.small")
281 if not flavors:
282 raise XOSConfigurationError("No m1.small flavor")
283
284 slice = self.provider_service.slices.all()[0]
285
286 if slice.default_isolation == "container_vm":
287 (node, parent) = ContainerVmScheduler(slice).pick()
288 else:
289 (node, parent) = LeastLoadedNodeScheduler(slice, label=self.get_vsg_service().node_label).pick()
290
291 instance = Instance(slice = slice,
292 node = node,
293 image = self.image,
294 creator = self.creator,
295 deployment = node.site_deployment.deployment,
296 flavor = flavors[0],
297 isolation = slice.default_isolation,
298 parent = parent)
299
300 self.save_instance(instance)
301
302 return instance
303
304 def manage_container(self):
305 from core.models import Instance, Flavor
306
307 if self.deleted:
308 return
309
310 # For container or container_vm isolation, use what TenantWithCotnainer
311 # provides us
312 slice = self.get_slice()
313 if slice.default_isolation in ["container_vm", "container"]:
314 super(VSGTenant,self).manage_container()
315 return
316
317 if not self.volt:
318 raise XOSConfigurationError("This vCPE container has no volt")
319
320 if self.instance:
321 # We're good.
322 return
323
324 instance = self.find_or_make_instance_for_s_tag(self.volt.s_tag)
325 self.instance = instance
326 super(TenantWithContainer, self).save()
327
328 def cleanup_container(self):
329 if self.get_slice().default_isolation in ["container_vm", "container"]:
330 super(VSGTenant,self).cleanup_container()
331
332 # To-do: cleanup unused instances
333 pass
334
335 def manage_bbs_account(self):
336 if self.deleted:
337 return
338
339 if self.volt and self.volt.subscriber and self.volt.subscriber.url_filter_enable:
340 if not self.bbs_account:
341 # make sure we use the proxied VSGService object, not the generic Service object
342 vcpe_service = VSGService.objects.get(id=self.provider_service.id)
343 self.bbs_account = vcpe_service.allocate_bbs_account()
344 super(VSGTenant, self).save()
345 else:
346 if self.bbs_account:
347 self.bbs_account = None
348 super(VSGTenant, self).save()
349
350 def find_or_make_port(self, instance, network, **kwargs):
351 port = Port.objects.filter(instance=instance, network=network)
352 if port:
353 port = port[0]
354 else:
355 port = Port(instance=instance, network=network, **kwargs)
356 port.save()
357 return port
358
359 def get_lan_network(self, instance):
360 slice = self.provider_service.slices.all()[0]
361 if CORD_USE_VTN:
362 # there should only be one network private network, and its template should not be the management template
363 lan_networks = [x for x in slice.networks.all() if x.template.visibility=="private" and (not "management" in x.template.name)]
364 if len(lan_networks)>1:
365 raise XOSProgrammingError("The vSG slice should only have one non-management private network")
366 else:
367 lan_networks = [x for x in slice.networks.all() if "lan" in x.name]
368 if not lan_networks:
369 raise XOSProgrammingError("No lan_network")
370 return lan_networks[0]
371
372 def save_instance(self, instance):
373 with transaction.atomic():
374 instance.volumes = "/etc/dnsmasq.d,/etc/ufw"
375 super(VSGTenant, self).save_instance(instance)
376
377 if instance.isolation in ["container", "container_vm"]:
378 lan_network = self.get_lan_network(instance)
379 port = self.find_or_make_port(instance, lan_network, ip="192.168.0.1", port_id="unmanaged")
380 port.set_parameter("c_tag", self.volt.c_tag)
381 port.set_parameter("s_tag", self.volt.s_tag)
382 port.set_parameter("device", "eth1")
383 port.set_parameter("bridge", "br-lan")
384
385 wan_networks = [x for x in instance.slice.networks.all() if "wan" in x.name]
386 if not wan_networks:
387 raise XOSProgrammingError("No wan_network")
388 port = self.find_or_make_port(instance, wan_networks[0])
389 port.set_parameter("next_hop", value="10.0.1.253") # FIX ME
390 port.set_parameter("device", "eth0")
391
392 if instance.isolation in ["vm"]:
393 lan_network = self.get_lan_network(instance)
394 port = self.find_or_make_port(instance, lan_network)
395 port.set_parameter("c_tag", self.volt.c_tag)
396 port.set_parameter("s_tag", self.volt.s_tag)
397 port.set_parameter("neutron_port_name", "stag-%s" % self.volt.s_tag)
398 port.save()
399
400 # tag the instance with the s-tag, so we can easily find the
401 # instance later
402 if self.volt and self.volt.s_tag:
403 tags = Tag.objects.filter(name="s_tag", value=self.volt.s_tag)
404 if not tags:
405 tag = Tag(service=self.provider_service, content_object=instance, name="s_tag", value=self.volt.s_tag)
406 tag.save()
407
408 # VTN-CORD needs a WAN address for the VM, so that the VM can
409 # be configured.
410 if CORD_USE_VTN:
411 tags = Tag.select_by_content_object(instance).filter(name="vm_vrouter_tenant")
412 if not tags:
413 vrouter = self.get_vrouter_service().get_tenant(address_pool_name="addresses_vsg", subscriber_service = self.provider_service)
414 vrouter.set_attribute("tenant_for_instance_id", instance.id)
415 vrouter.save()
416 tag = Tag(service=self.provider_service, content_object=instance, name="vm_vrouter_tenant", value="%d" % vrouter.id)
417 tag.save()
418
419 def save(self, *args, **kwargs):
420 if not self.creator:
421 if not getattr(self, "caller", None):
422 # caller must be set when creating a vCPE since it creates a slice
423 raise XOSProgrammingError("VSGTenant's self.caller was not set")
424 self.creator = self.caller
425 if not self.creator:
426 raise XOSProgrammingError("VSGTenant's self.creator was not set")
427
428 super(VSGTenant, self).save(*args, **kwargs)
429 model_policy_vcpe(self.pk)
430
431 def delete(self, *args, **kwargs):
432 self.cleanup_vrouter()
433 self.cleanup_container()
434 super(VSGTenant, self).delete(*args, **kwargs)
435
436def model_policy_vcpe(pk):
437 # TODO: this should be made in to a real model_policy
438 with transaction.atomic():
439 vcpe = VSGTenant.objects.select_for_update().filter(pk=pk)
440 if not vcpe:
441 return
442 vcpe = vcpe[0]
443 vcpe.manage_container()
444 vcpe.manage_vrouter()
445 vcpe.manage_bbs_account()
446 vcpe.cleanup_orphans()
447
448