blob: d3b6a7d2a3ed8280ea7de7ab15e25d4f8107bc58 [file] [log] [blame]
Pingping Linfa30bae2016-03-03 09:52:24 -08001from django.db import models
2from core.models import PlCoreBase,SingletonModel,PlCoreBaseManager
3from core.models.plcorebase import StrippedCharField
4from xos.exceptions import *
5from operator import attrgetter
6import json
7
8COARSE_KIND="coarse"
9
10class AttributeMixin(object):
11 # helper for extracting things from a json-encoded service_specific_attribute
12 def get_attribute(self, name, default=None):
13 if self.service_specific_attribute:
14 attributes = json.loads(self.service_specific_attribute)
15 else:
16 attributes = {}
17 return attributes.get(name, default)
18
19 def set_attribute(self, name, value):
20 if self.service_specific_attribute:
21 attributes = json.loads(self.service_specific_attribute)
22 else:
23 attributes = {}
24 attributes[name]=value
25 self.service_specific_attribute = json.dumps(attributes)
26
27 def get_initial_attribute(self, name, default=None):
28 if self._initial["service_specific_attribute"]:
29 attributes = json.loads(self._initial["service_specific_attribute"])
30 else:
31 attributes = {}
32 return attributes.get(name, default)
33
34 @classmethod
35 def setup_simple_attributes(cls):
36 for (attrname, default) in cls.simple_attributes:
37 setattr(cls, attrname, property(lambda self, attrname=attrname, default=default: self.get_attribute(attrname, default),
38 lambda self, value, attrname=attrname: self.set_attribute(attrname, value),
39 None,
40 attrname))
41
42class Service(PlCoreBase, AttributeMixin):
43 # when subclassing a service, redefine KIND to describe the new service
44 KIND = "generic"
45
46 description = models.TextField(max_length=254,null=True, blank=True,help_text="Description of Service")
47 enabled = models.BooleanField(default=True)
48 kind = StrippedCharField(max_length=30, help_text="Kind of service", default=KIND)
49 name = StrippedCharField(max_length=30, help_text="Service Name")
50 versionNumber = StrippedCharField(max_length=30, help_text="Version of Service Definition")
51 published = models.BooleanField(default=True)
52 view_url = StrippedCharField(blank=True, null=True, max_length=1024)
53 icon_url = StrippedCharField(blank=True, null=True, max_length=1024)
54 public_key = models.TextField(null=True, blank=True, max_length=1024, help_text="Public key string")
55 private_key_fn = StrippedCharField(blank=True, null=True, max_length=1024)
56
57 # Service_specific_attribute and service_specific_id are opaque to XOS
58 service_specific_id = StrippedCharField(max_length=30, blank=True, null=True)
59 service_specific_attribute = models.TextField(blank=True, null=True)
60
61 def __init__(self, *args, **kwargs):
62 # for subclasses, set the default kind appropriately
63 self._meta.get_field("kind").default = self.KIND
64 super(Service, self).__init__(*args, **kwargs)
65
66 @classmethod
67 def get_service_objects(cls):
68 return cls.objects.filter(kind = cls.KIND)
69
70 @classmethod
71 def get_deleted_service_objects(cls):
72 return cls.deleted_objects.filter(kind = cls.KIND)
73
74 @classmethod
75 def get_service_objects_by_user(cls, user):
76 return cls.select_by_user(user).filter(kind = cls.KIND)
77
78 @classmethod
79 def select_by_user(cls, user):
80 if user.is_admin:
81 return cls.objects.all()
82 else:
83 service_ids = [sp.slice.id for sp in ServicePrivilege.objects.filter(user=user)]
84 return cls.objects.filter(id__in=service_ids)
85
86 @property
87 def serviceattribute_dict(self):
88 attrs = {}
89 for attr in self.serviceattributes.all():
90 attrs[attr.name] = attr.value
91 return attrs
92
93 def __unicode__(self): return u'%s' % (self.name)
94
95 def can_update(self, user):
96 return user.can_update_service(self, allow=['admin'])
97
98 def get_scalable_nodes(self, slice, max_per_node=None, exclusive_slices=[]):
99 """
100 Get a list of nodes that can be used to scale up a slice.
101
102 slice - slice to scale up
103 max_per_node - maximum numbers of instances that 'slice' can have on a single node
104 exclusive_slices - list of slices that must have no nodes in common with 'slice'.
105 """
106
107 from core.models import Node, Instance # late import to get around order-of-imports constraint in __init__.py
108
109 nodes = list(Node.objects.all())
110
111 conflicting_instances = Instance.objects.filter(slice__in = exclusive_slices)
112 conflicting_nodes = Node.objects.filter(instances__in = conflicting_instances)
113
114 nodes = [x for x in nodes if x not in conflicting_nodes]
115
116 # If max_per_node is set, then limit the number of instances this slice
117 # can have on a single node.
118 if max_per_node:
119 acceptable_nodes = []
120 for node in nodes:
121 existing_count = node.instances.filter(slice=slice).count()
122 if existing_count < max_per_node:
123 acceptable_nodes.append(node)
124 nodes = acceptable_nodes
125
126 return nodes
127
128 def pick_node(self, slice, max_per_node=None, exclusive_slices=[]):
129 # Pick the best node to scale up a slice.
130
131 nodes = self.get_scalable_nodes(slice, max_per_node, exclusive_slices)
132 nodes = sorted(nodes, key=lambda node: node.instances.all().count())
133 if not nodes:
134 return None
135 return nodes[0]
136
137 def adjust_scale(self, slice_hint, scale, max_per_node=None, exclusive_slices=[]):
138 from core.models import Instance # late import to get around order-of-imports constraint in __init__.py
139
140 slices = [x for x in self.slices.all() if slice_hint in x.name]
141 for slice in slices:
142 while slice.instances.all().count() > scale:
143 s = slice.instances.all()[0]
144 # print "drop instance", s
145 s.delete()
146
147 while slice.instances.all().count() < scale:
148 node = self.pick_node(slice, max_per_node, exclusive_slices)
149 if not node:
150 # no more available nodes
151 break
152
153 image = slice.default_image
154 if not image:
155 raise XOSConfigurationError("No default_image for slice %s" % slice.name)
156
157 flavor = slice.default_flavor
158 if not flavor:
159 raise XOSConfigurationError("No default_flavor for slice %s" % slice.name)
160
161 s = Instance(slice=slice,
162 node=node,
163 creator=slice.creator,
164 image=image,
165 flavor=flavor,
166 deployment=node.site_deployment.deployment)
167 s.save()
168
169 # print "add instance", s
170
171 def get_vtn_src_nets(self):
172 nets=[]
173 for slice in self.slices.all():
174 for ns in slice.networkslices.all():
175 if not ns.network:
176 continue
177# if ns.network.template.access in ["direct", "indirect"]:
178# # skip access networks; we want to use the private network
179# continue
180 if ns.network.name in ["wan_network", "lan_network"]:
181 # we don't want to attach to the vCPE's lan or wan network
182 # we only want to attach to its private network
183 # TODO: fix hard-coding of network name
184 continue
185 for cn in ns.network.controllernetworks.all():
186 if cn.net_id:
187 net = {"name": ns.network.name, "net_id": cn.net_id}
188 nets.append(net)
189 return nets
190
191 def get_vtn_nets(self):
192 nets=[]
193 for slice in self.slices.all():
194 for ns in slice.networkslices.all():
195 if not ns.network:
196 continue
197 if ns.network.template.access not in ["direct", "indirect"]:
198 # skip anything that's not an access network
199 continue
200 for cn in ns.network.controllernetworks.all():
201 if cn.net_id:
202 net = {"name": ns.network.name, "net_id": cn.net_id}
203 nets.append(net)
204 return nets
205
206 def get_vtn_dependencies_nets(self):
207 provider_nets = []
208 for tenant in self.subscribed_tenants.all():
209 if tenant.provider_service:
210 for net in tenant.provider_service.get_vtn_nets():
211 if not net in provider_nets:
212 provider_nets.append(net)
213 return provider_nets
214
215 def get_vtn_dependencies_ids(self):
216 return [x["net_id"] for x in self.get_vtn_dependencies_nets()]
217
218 def get_vtn_dependencies_names(self):
219 return [x["name"]+"_"+x["net_id"] for x in self.get_vtn_dependencies_nets()]
220
221 def get_vtn_src_ids(self):
222 return [x["net_id"] for x in self.get_vtn_src_nets()]
223
224 def get_vtn_src_names(self):
225 return [x["name"]+"_"+x["net_id"] for x in self.get_vtn_src_nets()]
226
227
228class ServiceAttribute(PlCoreBase):
229 name = models.CharField(help_text="Attribute Name", max_length=128)
230 value = StrippedCharField(help_text="Attribute Value", max_length=1024)
231 service = models.ForeignKey(Service, related_name='serviceattributes', help_text="The Service this attribute is associated with")
232
233class ServiceRole(PlCoreBase):
234 ROLE_CHOICES = (('admin','Admin'),)
235 role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
236
237 def __unicode__(self): return u'%s' % (self.role)
238
239class ServicePrivilege(PlCoreBase):
240 user = models.ForeignKey('User', related_name='serviceprivileges')
241 service = models.ForeignKey('Service', related_name='serviceprivileges')
242 role = models.ForeignKey('ServiceRole',related_name='serviceprivileges')
243
244 class Meta:
245 unique_together = ('user', 'service', 'role')
246
247 def __unicode__(self): return u'%s %s %s' % (self.service, self.user, self.role)
248
249 def can_update(self, user):
250 if not self.service.enabled:
251 raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
252 return self.service.can_update(user)
253
254 def save(self, *args, **kwds):
255 if not self.service.enabled:
256 raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
257 super(ServicePrivilege, self).save(*args, **kwds)
258
259 def delete(self, *args, **kwds):
260 if not self.service.enabled:
261 raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
262 super(ServicePrivilege, self).delete(*args, **kwds)
263
264 @classmethod
265 def select_by_user(cls, user):
266 if user.is_admin:
267 qs = cls.objects.all()
268 else:
269 qs = cls.objects.filter(user=user)
270 return qs
271
272class TenantRoot(PlCoreBase, AttributeMixin):
273 """ A tenantRoot is one of the things that can sit at the root of a chain
274 of tenancy. This object represents a node.
275 """
276
277 KIND= "generic"
278 kind = StrippedCharField(max_length=30, default=KIND)
279 name = StrippedCharField(max_length=255, help_text="name", blank=True, null=True)
280
281 service_specific_attribute = models.TextField(blank=True, null=True)
282 service_specific_id = StrippedCharField(max_length=30, blank=True, null=True)
283
284 def __init__(self, *args, **kwargs):
285 # for subclasses, set the default kind appropriately
286 self._meta.get_field("kind").default = self.KIND
287 super(TenantRoot, self).__init__(*args, **kwargs)
288
289 def __unicode__(self):
290 if not self.name:
291 return u"%s-tenant_root-#%s" % (str(self.kind), str(self.id))
292 else:
293 return self.name
294
295 def can_update(self, user):
296 return user.can_update_tenant_root(self, allow=['admin'])
297
298 def get_subscribed_tenants(self, tenant_class):
299 ids = self.subscribed_tenants.filter(kind=tenant_class.KIND)
300 return tenant_class.objects.filter(id__in = ids)
301
302 def get_newest_subscribed_tenant(self, kind):
303 st = list(self.get_subscribed_tenants(kind))
304 if not st:
305 return None
306 return sorted(st, key=attrgetter('id'))[0]
307
308 @classmethod
309 def get_tenant_objects(cls):
310 return cls.objects.filter(kind = cls.KIND)
311
312 @classmethod
313 def get_tenant_objects_by_user(cls, user):
314 return cls.select_by_user(user).filter(kind = cls.KIND)
315
316 @classmethod
317 def select_by_user(cls, user):
318 if user.is_admin:
319 return cls.objects.all()
320 else:
321 tr_ids = [trp.tenant_root.id for trp in TenantRootPrivilege.objects.filter(user=user)]
322 return cls.objects.filter(id__in=tr_ids)
323
324class Tenant(PlCoreBase, AttributeMixin):
325 """ A tenant is a relationship between two entities, a subscriber and a
326 provider. This object represents an edge.
327
328 The subscriber can be a User, a Service, or a Tenant.
329
330 The provider is always a Service.
331
332 TODO: rename "Tenant" to "Tenancy"
333 """
334
335 CONNECTIVITY_CHOICES = (('public', 'Public'), ('private', 'Private'), ('na', 'Not Applicable'))
336
337 # when subclassing a service, redefine KIND to describe the new service
338 KIND = "generic"
339
340 kind = StrippedCharField(max_length=30, default=KIND)
341 provider_service = models.ForeignKey(Service, related_name='provided_tenants')
342
343 # The next four things are the various type of objects that can be subscribers of this Tenancy
344 # relationship. One and only one can be used at a time.
345 subscriber_service = models.ForeignKey(Service, related_name='subscribed_tenants', blank=True, null=True)
346 subscriber_tenant = models.ForeignKey("Tenant", related_name='subscribed_tenants', blank=True, null=True)
347 subscriber_user = models.ForeignKey("User", related_name='subscribed_tenants', blank=True, null=True)
348 subscriber_root = models.ForeignKey("TenantRoot", related_name="subscribed_tenants", blank=True, null=True)
349
350 # Service_specific_attribute and service_specific_id are opaque to XOS
351 service_specific_id = StrippedCharField(max_length=30, blank=True, null=True)
352 service_specific_attribute = models.TextField(blank=True, null=True)
353
354 # Connect_method is only used by Coarse tenants
355 connect_method = models.CharField(null=False, blank=False, max_length=30, choices=CONNECTIVITY_CHOICES, default="na")
356
357 def __init__(self, *args, **kwargs):
358 # for subclasses, set the default kind appropriately
359 self._meta.get_field("kind").default = self.KIND
360 super(Tenant, self).__init__(*args, **kwargs)
361
362 def __unicode__(self):
363 return u"%s-tenant-%s" % (str(self.kind), str(self.id))
364
365 @classmethod
366 def get_tenant_objects(cls):
367 return cls.objects.filter(kind = cls.KIND)
368
369 @classmethod
370 def get_tenant_objects_by_user(cls, user):
371 return cls.select_by_user(user).filter(kind = cls.KIND)
372
373 @classmethod
374 def get_deleted_tenant_objects(cls):
375 return cls.deleted_objects.filter(kind = cls.KIND)
376
377 @property
378 def tenantattribute_dict(self):
379 attrs = {}
380 for attr in self.tenantattributes.all():
381 attrs[attr.name] = attr.value
382 return attrs
383
384 # helper function to be used in subclasses that want to ensure service_specific_id is unique
385 def validate_unique_service_specific_id(self):
386 if self.pk is None:
387 if self.service_specific_id is None:
388 raise XOSMissingField("subscriber_specific_id is None, and it's a required field", fields={"service_specific_id": "cannot be none"})
389
390 conflicts = self.get_tenant_objects().filter(service_specific_id=self.service_specific_id)
391 if conflicts:
392 raise XOSDuplicateKey("service_specific_id %s already exists" % self.service_specific_id, fields={"service_specific_id": "duplicate key"})
393
394 def save(self, *args, **kwargs):
395 subCount = sum( [1 for e in [self.subscriber_service, self.subscriber_tenant, self.subscriber_user, self.subscriber_root] if e is not None])
396 if (subCount > 1):
397 raise XOSConflictingField("Only one of subscriber_service, subscriber_tenant, subscriber_user, subscriber_root should be set")
398
399 super(Tenant, self).save(*args, **kwargs)
400
401 def get_subscribed_tenants(self, tenant_class):
402 ids = self.subscribed_tenants.filter(kind=tenant_class.KIND)
403 return tenant_class.objects.filter(id__in = ids)
404
405 def get_newest_subscribed_tenant(self, kind):
406 st = list(self.get_subscribed_tenants(kind))
407 if not st:
408 return None
409 return sorted(st, key=attrgetter('id'))[0]
410
411class Scheduler(object):
412 # XOS Scheduler Abstract Base Class
413 # Used to implement schedulers that pick which node to put instances on
414
415 def __init__(self, slice):
416 self.slice = slice
417
418 def pick(self):
419 # this method should return a tuple (node, parent)
420 # node is the node to instantiate on
421 # parent is for container_vm instances only, and is the VM that will
422 # hold the container
423
424 raise Exception("Abstract Base")
425
426class LeastLoadedNodeScheduler(Scheduler):
427 # This scheduler always return the node with the fewest number of instances.
428
429 def __init__(self, slice):
430 super(LeastLoadedNodeScheduler, self).__init__(slice)
431
432 def pick(self):
433 from core.models import Node
434# nodes = list(Node.objects.all())
435 if not self.slice.default_node:
436 nodes = list(Node.objects.all())
437 nodes = sorted(nodes, key=lambda node: node.instances.all().count())
438 else:
439 nodes = list(Node.objects.filter(name = self.slice.default_node))
440 # TODO: logic to filter nodes by which nodes are up, and which
441 # nodes the slice can instantiate on.
442# nodes = sorted(nodes, key=lambda node: node.instances.all().count())
443 return [nodes[0], None]
444
445class ContainerVmScheduler(Scheduler):
446 # This scheduler picks a VM in the slice with the fewest containers inside
447 # of it. If no VMs are suitable, then it creates a VM.
448
449 # this is a hack and should be replaced by something smarter...
450 LOOK_FOR_IMAGES=["ubuntu-vcpe4", # ONOS demo machine -- preferred vcpe image
451 "Ubuntu 14.04 LTS", # portal
452 "Ubuntu-14.04-LTS", # ONOS demo machine
453 "trusty-server-multi-nic", # CloudLab
454 ]
455
456 MAX_VM_PER_CONTAINER = 10
457
458 def __init__(self, slice):
459 super(ContainerVmScheduler, self).__init__(slice)
460
461 @property
462 def image(self):
463 from core.models import Image
464
465 look_for_images = self.LOOK_FOR_IMAGES
466 for image_name in look_for_images:
467 images = Image.objects.filter(name = image_name)
468 if images:
469 return images[0]
470
471 raise XOSProgrammingError("No ContainerVM image (looked for %s)" % str(look_for_images))
472
473 def make_new_instance(self):
474 from core.models import Instance, Flavor
475
476 flavors = Flavor.objects.filter(name="m1.small")
477 if not flavors:
478 raise XOSConfigurationError("No m1.small flavor")
479
480 (node,parent) = LeastLoadedNodeScheduler(self.slice).pick()
481
482 instance = Instance(slice = self.slice,
483 node = node,
484 image = self.image,
485 creator = self.slice.creator,
486 deployment = node.site_deployment.deployment,
487 flavor = flavors[0],
488 isolation = "vm",
489 parent = parent)
490 instance.save()
491 # We rely on a special naming convention to identify the VMs that will
492 # hole containers.
493 instance.name = "%s-outer-%s" % (instance.slice.name, instance.id)
494 instance.save()
495 return instance
496
497 def pick(self):
498 from core.models import Instance, Flavor
499
500 for vm in self.slice.instances.filter(isolation="vm"):
501 avail_vms = []
502 if (vm.name.startswith("%s-outer-" % self.slice.name)):
503 container_count = Instance.objects.filter(parent=vm).count()
504 if (container_count < self.MAX_VM_PER_CONTAINER):
505 avail_vms.append( (vm, container_count) )
506 # sort by least containers-per-vm
507 avail_vms = sorted(avail_vms, key = lambda x: x[1])
508 print "XXX", avail_vms
509 if avail_vms:
510 instance = avail_vms[0][0]
511 return (instance.node, instance)
512
513 instance = self.make_new_instance()
514 return (instance.node, instance)
515
516class TenantWithContainer(Tenant):
517 """ A tenant that manages a container """
518
519 # this is a hack and should be replaced by something smarter...
520 LOOK_FOR_IMAGES=["ubuntu-vcpe4", # ONOS demo machine -- preferred vcpe image
521 "Ubuntu 14.04 LTS", # portal
522 "Ubuntu-14.04-LTS", # ONOS demo machine
523 "trusty-server-multi-nic", # CloudLab
524 ]
525
526 LOOK_FOR_CONTAINER_IMAGES=["docker-vcpe"]
527
528 class Meta:
529 proxy = True
530
531 def __init__(self, *args, **kwargs):
532 super(TenantWithContainer, self).__init__(*args, **kwargs)
533 self.cached_instance=None
534 self.orig_instance_id = self.get_initial_attribute("instance_id")
535
536 @property
537 def instance(self):
538 from core.models import Instance
539 if getattr(self, "cached_instance", None):
540 return self.cached_instance
541 instance_id=self.get_attribute("instance_id")
542 if not instance_id:
543 return None
544 instances=Instance.objects.filter(id=instance_id)
545 if not instances:
546 return None
547 instance=instances[0]
548 instance.caller = self.creator
549 self.cached_instance = instance
550 return instance
551
552 @instance.setter
553 def instance(self, value):
554 if value:
555 value = value.id
556 if (value != self.get_attribute("instance_id", None)):
557 self.cached_instance=None
558 self.set_attribute("instance_id", value)
559
560 @property
561 def external_hostname(self):
562 return self.get_attribute("external_hostname", "")
563
564 @external_hostname.setter
565 def external_hostname(self, value):
566 self.set_attribute("external_hostname", value)
567
568 @property
569 def external_container(self):
570 return self.get_attribute("external_container", "")
571
572 @external_container.setter
573 def external_container(self, value):
574 self.set_attribute("external_container", value)
575
576 @property
577 def creator(self):
578 from core.models import User
579 if getattr(self, "cached_creator", None):
580 return self.cached_creator
581 creator_id=self.get_attribute("creator_id")
582 if not creator_id:
583 return None
584 users=User.objects.filter(id=creator_id)
585 if not users:
586 return None
587 user=users[0]
588 self.cached_creator = users[0]
589 return user
590
591 @creator.setter
592 def creator(self, value):
593 if value:
594 value = value.id
595 if (value != self.get_attribute("creator_id", None)):
596 self.cached_creator=None
597 self.set_attribute("creator_id", value)
598
599 @property
600 def image(self):
601 from core.models import Image
602 # Implement the logic here to pick the image that should be used when
603 # instantiating the VM that will hold the container.
604
605 slice = self.provider_service.slices.all()
606 if not slice:
607 raise XOSProgrammingError("provider service has no slice")
608 slice = slice[0]
609
610 if slice.default_isolation in ["container", "container_vm"]:
611 look_for_images = self.LOOK_FOR_CONTAINER_IMAGES
612 else:
613 look_for_images = self.LOOK_FOR_IMAGES
614
615 for image_name in look_for_images:
616 images = Image.objects.filter(name = image_name)
617 if images:
618 return images[0]
619
620 raise XOSProgrammingError("No VPCE image (looked for %s)" % str(look_for_images))
621
622 def save_instance(self, instance):
623 # Override this function to do custom pre-save or post-save processing,
624 # such as creating ports for containers.
625 instance.save()
626
627 def pick_least_loaded_instance_in_slice(self, slices):
628 for slice in slices:
629 if slice.instances.all().count() > 0:
630 for instance in slice.instances.all():
631 #Pick the first instance that has lesser than 5 tenants
632 if self.count_of_tenants_of_an_instance(instance) < 5:
633 return instance
634 return None
635
636 #TODO: Ideally the tenant count for an instance should be maintained using a
637 #many-to-one relationship attribute, however this model being proxy, it does
638 #not permit any new attributes to be defined. Find if any better solutions
639 def count_of_tenants_of_an_instance(self, instance):
640 tenant_count = 0
641 for tenant in self.get_tenant_objects().all():
642 if tenant.get_attribute("instance_id", None) == instance.id:
643 tenant_count += 1
644 return tenant_count
645
646 def manage_container(self):
647 from core.models import Instance, Flavor
648
649 if self.deleted:
650 return
651
652 if (self.instance is not None): # and (self.instance.image != self.image):
653 self.instance.delete()
654 self.instance = None
655
656 if self.instance is None:
657 if not self.provider_service.slices.count():
658 raise XOSConfigurationError("The service has no slices")
659
660 new_instance_created = False
661 instance = None
662 if self.get_attribute("use_same_instance_for_multiple_tenants", default=False):
663 #Find if any existing instances can be used for this tenant
664 slices = self.provider_service.slices.all()
665 instance = self.pick_least_loaded_instance_in_slice(slices)
666
667 if not instance:
668 flavors = Flavor.objects.filter(name="m1.small")
669 if not flavors:
670 raise XOSConfigurationError("No m1.small flavor")
671
672 slice = self.provider_service.slices.all()[0]
673
674 if slice.default_isolation == "container_vm":
675 (node, parent) = ContainerVmScheduler(slice).pick()
676 else:
677 (node, parent) = LeastLoadedNodeScheduler(slice).pick()
678
679 instance = Instance(slice = slice,
680 node = node,
681# image = slice.default_image,
682 image = self.image,
683 creator = self.creator,
684 deployment = node.site_deployment.deployment,
685# flavor = flavors[0],
686 flavor = slice.default_flavor,
687 isolation = slice.default_isolation,
688 parent = parent)
689 self.save_instance(instance)
690 new_instance_created = True
691
692 try:
693 self.instance = instance
694 super(TenantWithContainer, self).save()
695 except:
696 if new_instance_created:
697 instance.delete()
698 raise
699
700 def cleanup_container(self):
701 if self.instance:
702 if self.get_attribute("use_same_instance_for_multiple_tenants", default=False):
703 #Delete the instance only if this is last tenant in that instance
704 tenant_count = self.count_of_tenants_of_an_instance(self.instance)
705 if tenant_count == 0:
706 self.instance.delete()
707 else:
708 self.instance.delete()
709 self.instance = None
710
711 def save(self, *args, **kwargs):
712 if (not self.creator) and (hasattr(self, "caller")) and (self.caller):
713 self.creator = self.caller
714 super(TenantWithContainer, self).save(*args, **kwargs)
715
716class CoarseTenant(Tenant):
717 """ TODO: rename "CoarseTenant" --> "StaticTenant" """
718 class Meta:
719 proxy = True
720
721 KIND = COARSE_KIND
722
723 def save(self, *args, **kwargs):
724 if (not self.subscriber_service):
725 raise XOSValidationError("subscriber_service cannot be null")
726 if (self.subscriber_tenant or self.subscriber_user):
727 raise XOSValidationError("subscriber_tenant and subscriber_user must be null")
728
729 super(CoarseTenant,self).save()
730
731class Subscriber(TenantRoot):
732 """ Intermediate class for TenantRoots that are to be Subscribers """
733
734 class Meta:
735 proxy = True
736
737 KIND = "Subscriber"
738
739class Provider(TenantRoot):
740 """ Intermediate class for TenantRoots that are to be Providers """
741
742 class Meta:
743 proxy = True
744
745 KIND = "Provider"
746
747class TenantAttribute(PlCoreBase):
748 name = models.CharField(help_text="Attribute Name", max_length=128)
749 value = models.TextField(help_text="Attribute Value")
750 tenant = models.ForeignKey(Tenant, related_name='tenantattributes', help_text="The Tenant this attribute is associated with")
751
752class TenantRootRole(PlCoreBase):
753 ROLE_CHOICES = (('admin','Admin'), ('access','Access'))
754
755 role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
756
757 def __unicode__(self): return u'%s' % (self.role)
758
759class TenantRootPrivilege(PlCoreBase):
760 user = models.ForeignKey('User', related_name="tenant_root_privileges")
761 tenant_root = models.ForeignKey('TenantRoot', related_name="tenant_root_privileges")
762 role = models.ForeignKey('TenantRootRole', related_name="tenant_root_privileges")
763
764 class Meta:
765 unique_together = ('user', 'tenant_root', 'role')
766
767 def __unicode__(self): return u'%s %s %s' % (self.tenant_root, self.user, self.role)
768
769 def save(self, *args, **kwds):
770 if not self.user.is_active:
771 raise PermissionDenied, "Cannot modify role(s) of a disabled user"
772 super(TenantRootPrivilege, self).save(*args, **kwds)
773
774 def can_update(self, user):
775 return user.can_update_tenant_root_privilege(self)
776
777 @classmethod
778 def select_by_user(cls, user):
779 if user.is_admin:
780 return cls.objects.all()
781 else:
782 # User can see his own privilege
783 trp_ids = [trp.id for trp in cls.objects.filter(user=user)]
784
785 # A slice admin can see the SlicePrivileges for his Slice
786 for priv in cls.objects.filter(user=user, role__role="admin"):
787 trp_ids.extend( [trp.id for trp in cls.objects.filter(tenant_root=priv.tenant_root)] )
788
789 return cls.objects.filter(id__in=trp_ids)
790
791