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