blob: e56f60511d03fd23cae48a6ba1fa7a3ec713a5c7 [file] [log] [blame]
Siobhan Tully00353f72013-10-08 21:53:27 -04001from django.db import models
Scott Baker008a9962015-04-15 20:58:20 -07002from core.models import PlCoreBase,SingletonModel,PlCoreBaseManager
Tony Mackd84b1ff2015-03-09 13:03:56 -04003from core.models.plcorebase import StrippedCharField
Scott Bakerd921e1c2015-04-20 14:24:29 -07004from xos.exceptions import *
Scott Baker618a4892015-07-06 14:27:31 -07005from operator import attrgetter
Scott Bakerf57e5592015-04-14 17:18:51 -07006import json
Siobhan Tully00353f72013-10-08 21:53:27 -04007
Scott Baker2461bec2015-08-14 09:10:11 -07008COARSE_KIND="coarse"
9
Scott Baker82498c52015-07-13 13:07:27 -070010class 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
Scott Baker74404fe2015-07-13 13:54:06 -070034 @classmethod
35 def setup_simple_attributes(cls):
36 for (attrname, default) in cls.simple_attributes:
Scott Baker096dce82015-07-13 14:27:51 -070037 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))
Scott Baker74404fe2015-07-13 13:54:06 -070041
Scott Baker82498c52015-07-13 13:07:27 -070042class Service(PlCoreBase, AttributeMixin):
Scott Baker008a9962015-04-15 20:58:20 -070043 # when subclassing a service, redefine KIND to describe the new service
44 KIND = "generic"
45
Siobhan Tully00353f72013-10-08 21:53:27 -040046 description = models.TextField(max_length=254,null=True, blank=True,help_text="Description of Service")
47 enabled = models.BooleanField(default=True)
Scott Baker008a9962015-04-15 20:58:20 -070048 kind = StrippedCharField(max_length=30, help_text="Kind of service", default=KIND)
Tony Mackd84b1ff2015-03-09 13:03:56 -040049 name = StrippedCharField(max_length=30, help_text="Service Name")
50 versionNumber = StrippedCharField(max_length=30, help_text="Version of Service Definition")
Siobhan Tullycf04fb62014-01-11 11:25:57 -050051 published = models.BooleanField(default=True)
Tony Mackd84b1ff2015-03-09 13:03:56 -040052 view_url = StrippedCharField(blank=True, null=True, max_length=1024)
53 icon_url = StrippedCharField(blank=True, null=True, max_length=1024)
Scott Baker68944742015-04-30 14:30:56 -070054 public_key = models.TextField(null=True, blank=True, max_length=1024, help_text="Public key string")
Siobhan Tully00353f72013-10-08 21:53:27 -040055
Scott Baker2f0828e2015-07-13 12:33:28 -070056 # Service_specific_attribute and service_specific_id are opaque to XOS
57 service_specific_id = StrippedCharField(max_length=30, blank=True, null=True)
58 service_specific_attribute = models.TextField(blank=True, null=True)
59
Scott Baker008a9962015-04-15 20:58:20 -070060 def __init__(self, *args, **kwargs):
61 # for subclasses, set the default kind appropriately
62 self._meta.get_field("kind").default = self.KIND
63 super(Service, self).__init__(*args, **kwargs)
64
65 @classmethod
66 def get_service_objects(cls):
67 return cls.objects.filter(kind = cls.KIND)
68
Scott Baker16573d32015-07-24 15:36:02 -070069 @classmethod
Scott Baker542cd6f2015-10-19 21:18:53 -070070 def get_deleted_service_objects(cls):
71 return cls.deleted_objects.filter(kind = cls.KIND)
72
73 @classmethod
Scott Baker16573d32015-07-24 15:36:02 -070074 def get_service_objects_by_user(cls, user):
75 return cls.select_by_user(user).filter(kind = cls.KIND)
76
77 @classmethod
78 def select_by_user(cls, user):
79 if user.is_admin:
80 return cls.objects.all()
81 else:
82 service_ids = [sp.slice.id for sp in ServicePrivilege.objects.filter(user=user)]
83 return cls.objects.filter(id__in=service_ids)
84
Siobhan Tully00353f72013-10-08 21:53:27 -040085 def __unicode__(self): return u'%s' % (self.name)
86
Tony Mack9d2ea092015-04-29 12:23:10 -040087 def can_update(self, user):
88 return user.can_update_service(self, allow=['admin'])
Scott Baker618a4892015-07-06 14:27:31 -070089
Scott Baker98436732015-05-11 16:36:41 -070090 def get_scalable_nodes(self, slice, max_per_node=None, exclusive_slices=[]):
91 """
92 Get a list of nodes that can be used to scale up a slice.
93
94 slice - slice to scale up
Tony Mack3de59e32015-08-19 11:58:18 -040095 max_per_node - maximum numbers of instances that 'slice' can have on a single node
Scott Baker98436732015-05-11 16:36:41 -070096 exclusive_slices - list of slices that must have no nodes in common with 'slice'.
97 """
98
Tony Mack3de59e32015-08-19 11:58:18 -040099 from core.models import Node, Instance # late import to get around order-of-imports constraint in __init__.py
Scott Baker98436732015-05-11 16:36:41 -0700100
101 nodes = list(Node.objects.all())
102
Tony Mack3de59e32015-08-19 11:58:18 -0400103 conflicting_instances = Instance.objects.filter(slice__in = exclusive_slices)
104 conflicting_nodes = Node.objects.filter(instances__in = conflicting_instances)
Scott Baker98436732015-05-11 16:36:41 -0700105
106 nodes = [x for x in nodes if x not in conflicting_nodes]
107
Tony Mack3de59e32015-08-19 11:58:18 -0400108 # If max_per_node is set, then limit the number of instances this slice
Scott Baker98436732015-05-11 16:36:41 -0700109 # can have on a single node.
110 if max_per_node:
111 acceptable_nodes = []
112 for node in nodes:
Tony Mack3de59e32015-08-19 11:58:18 -0400113 existing_count = node.instances.filter(slice=slice).count()
Scott Baker98436732015-05-11 16:36:41 -0700114 if existing_count < max_per_node:
115 acceptable_nodes.append(node)
116 nodes = acceptable_nodes
117
118 return nodes
119
120 def pick_node(self, slice, max_per_node=None, exclusive_slices=[]):
121 # Pick the best node to scale up a slice.
122
123 nodes = self.get_scalable_nodes(slice, max_per_node, exclusive_slices)
Tony Mack3de59e32015-08-19 11:58:18 -0400124 nodes = sorted(nodes, key=lambda node: node.instances.all().count())
Scott Baker98436732015-05-11 16:36:41 -0700125 if not nodes:
126 return None
127 return nodes[0]
128
129 def adjust_scale(self, slice_hint, scale, max_per_node=None, exclusive_slices=[]):
Tony Mack3de59e32015-08-19 11:58:18 -0400130 from core.models import Instance # late import to get around order-of-imports constraint in __init__.py
Scott Baker98436732015-05-11 16:36:41 -0700131
132 slices = [x for x in self.slices.all() if slice_hint in x.name]
133 for slice in slices:
Tony Mack3de59e32015-08-19 11:58:18 -0400134 while slice.instances.all().count() > scale:
135 s = slice.instances.all()[0]
136 # print "drop instance", s
Scott Baker98436732015-05-11 16:36:41 -0700137 s.delete()
138
Tony Mack3de59e32015-08-19 11:58:18 -0400139 while slice.instances.all().count() < scale:
Scott Baker98436732015-05-11 16:36:41 -0700140 node = self.pick_node(slice, max_per_node, exclusive_slices)
141 if not node:
142 # no more available nodes
143 break
144
145 image = slice.default_image
146 if not image:
147 raise XOSConfigurationError("No default_image for slice %s" % slice.name)
148
149 flavor = slice.default_flavor
150 if not flavor:
151 raise XOSConfigurationError("No default_flavor for slice %s" % slice.name)
152
Tony Mack3de59e32015-08-19 11:58:18 -0400153 s = Instance(slice=slice,
Scott Baker98436732015-05-11 16:36:41 -0700154 node=node,
155 creator=slice.creator,
156 image=image,
157 flavor=flavor,
158 deployment=node.site_deployment.deployment)
159 s.save()
160
Tony Mack3de59e32015-08-19 11:58:18 -0400161 # print "add instance", s
Tony Mack9d2ea092015-04-29 12:23:10 -0400162
Siobhan Tully00353f72013-10-08 21:53:27 -0400163class ServiceAttribute(PlCoreBase):
164 name = models.SlugField(help_text="Attribute Name", max_length=128)
Tony Mackd84b1ff2015-03-09 13:03:56 -0400165 value = StrippedCharField(help_text="Attribute Value", max_length=1024)
Siobhan Tully00353f72013-10-08 21:53:27 -0400166 service = models.ForeignKey(Service, related_name='serviceattributes', help_text="The Service this attribute is associated with")
167
Tony Mack9d2ea092015-04-29 12:23:10 -0400168class ServiceRole(PlCoreBase):
169 ROLE_CHOICES = (('admin','Admin'),)
170 role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
171
172 def __unicode__(self): return u'%s' % (self.role)
173
174class ServicePrivilege(PlCoreBase):
175 user = models.ForeignKey('User', related_name='serviceprivileges')
176 service = models.ForeignKey('Service', related_name='serviceprivileges')
177 role = models.ForeignKey('ServiceRole',related_name='serviceprivileges')
178
179 class Meta:
Tony Mack9ec754e2015-05-13 12:21:28 -0400180 unique_together = ('user', 'service', 'role')
Tony Mack9d2ea092015-04-29 12:23:10 -0400181
182 def __unicode__(self): return u'%s %s %s' % (self.service, self.user, self.role)
183
184 def can_update(self, user):
185 if not self.service.enabled:
186 raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
187 return self.service.can_update(user)
188
189 def save(self, *args, **kwds):
190 if not self.service.enabled:
191 raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
192 super(ServicePrivilege, self).save(*args, **kwds)
193
194 def delete(self, *args, **kwds):
195 if not self.service.enabled:
196 raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
Scott Baker4587b822015-07-01 18:29:08 -0700197 super(ServicePrivilege, self).delete(*args, **kwds)
198
Scott Baker16573d32015-07-24 15:36:02 -0700199 @classmethod
200 def select_by_user(cls, user):
Tony Mack9d2ea092015-04-29 12:23:10 -0400201 if user.is_admin:
Scott Baker16573d32015-07-24 15:36:02 -0700202 qs = cls.objects.all()
Tony Mack9d2ea092015-04-29 12:23:10 -0400203 else:
Scott Baker16573d32015-07-24 15:36:02 -0700204 qs = cls.objects.filter(user=user)
Scott Baker4587b822015-07-01 18:29:08 -0700205 return qs
206
Scott Baker82498c52015-07-13 13:07:27 -0700207class TenantRoot(PlCoreBase, AttributeMixin):
Scott Baker4587b822015-07-01 18:29:08 -0700208 """ A tenantRoot is one of the things that can sit at the root of a chain
209 of tenancy. This object represents a node.
210 """
211
212 KIND= "generic"
213 kind = StrippedCharField(max_length=30, default=KIND)
Scott Baker618a4892015-07-06 14:27:31 -0700214 name = StrippedCharField(max_length=255, help_text="name", blank=True, null=True)
Scott Baker4587b822015-07-01 18:29:08 -0700215
Scott Bakerefcec632015-07-07 12:12:42 -0700216 service_specific_attribute = models.TextField(blank=True, null=True)
217 service_specific_id = StrippedCharField(max_length=30, blank=True, null=True)
Scott Baker4587b822015-07-01 18:29:08 -0700218
Scott Bakerdb66fd32015-07-07 17:59:44 -0700219 def __init__(self, *args, **kwargs):
220 # for subclasses, set the default kind appropriately
221 self._meta.get_field("kind").default = self.KIND
222 super(TenantRoot, self).__init__(*args, **kwargs)
223
Scott Baker618a4892015-07-06 14:27:31 -0700224 def __unicode__(self):
225 if not self.name:
226 return u"%s-tenant_root-#%s" % (str(self.kind), str(self.id))
227 else:
228 return self.name
229
230 def can_update(self, user):
231 return user.can_update_tenant_root(self, allow=['admin'])
232
Scott Bakerefcec632015-07-07 12:12:42 -0700233 def get_subscribed_tenants(self, tenant_class):
234 ids = self.subscribed_tenants.filter(kind=tenant_class.KIND)
235 return tenant_class.objects.filter(id__in = ids)
236
237 def get_newest_subscribed_tenant(self, kind):
238 st = list(self.get_subscribed_tenants(kind))
239 if not st:
240 return None
241 return sorted(st, key=attrgetter('id'))[0]
242
243 @classmethod
244 def get_tenant_objects(cls):
245 return cls.objects.filter(kind = cls.KIND)
246
Scott Baker16573d32015-07-24 15:36:02 -0700247 @classmethod
248 def get_tenant_objects_by_user(cls, user):
249 return cls.select_by_user(user).filter(kind = cls.KIND)
250
251 @classmethod
252 def select_by_user(cls, user):
253 if user.is_admin:
254 return cls.objects.all()
255 else:
256 tr_ids = [trp.tenant_root.id for trp in TenantRootPrivilege.objects.filter(user=user)]
257 return cls.objects.filter(id__in=tr_ids)
258
Scott Baker82498c52015-07-13 13:07:27 -0700259class Tenant(PlCoreBase, AttributeMixin):
Scott Baker8103d0f2015-04-10 16:42:26 -0700260 """ A tenant is a relationship between two entities, a subscriber and a
Scott Baker4587b822015-07-01 18:29:08 -0700261 provider. This object represents an edge.
Scott Baker8103d0f2015-04-10 16:42:26 -0700262
263 The subscriber can be a User, a Service, or a Tenant.
264
265 The provider is always a Service.
Scott Baker4587b822015-07-01 18:29:08 -0700266
267 TODO: rename "Tenant" to "Tenancy"
Scott Baker8103d0f2015-04-10 16:42:26 -0700268 """
Scott Baker008a9962015-04-15 20:58:20 -0700269
Scott Baker925a8fa2015-04-26 20:30:40 -0700270 CONNECTIVITY_CHOICES = (('public', 'Public'), ('private', 'Private'), ('na', 'Not Applicable'))
271
Scott Baker008a9962015-04-15 20:58:20 -0700272 # when subclassing a service, redefine KIND to describe the new service
273 KIND = "generic"
274
275 kind = StrippedCharField(max_length=30, default=KIND)
Scott Baker4587b822015-07-01 18:29:08 -0700276 provider_service = models.ForeignKey(Service, related_name='provided_tenants')
277
278 # The next four things are the various type of objects that can be subscribers of this Tenancy
279 # relationship. One and only one can be used at a time.
280 subscriber_service = models.ForeignKey(Service, related_name='subscribed_tenants', blank=True, null=True)
281 subscriber_tenant = models.ForeignKey("Tenant", related_name='subscribed_tenants', blank=True, null=True)
282 subscriber_user = models.ForeignKey("User", related_name='subscribed_tenants', blank=True, null=True)
283 subscriber_root = models.ForeignKey("TenantRoot", related_name="subscribed_tenants", blank=True, null=True)
284
285 # Service_specific_attribute and service_specific_id are opaque to XOS
Scott Baker76934d82015-05-06 19:49:31 -0700286 service_specific_id = StrippedCharField(max_length=30, blank=True, null=True)
287 service_specific_attribute = models.TextField(blank=True, null=True)
Scott Baker4587b822015-07-01 18:29:08 -0700288
289 # Connect_method is only used by Coarse tenants
Scott Baker925a8fa2015-04-26 20:30:40 -0700290 connect_method = models.CharField(null=False, blank=False, max_length=30, choices=CONNECTIVITY_CHOICES, default="na")
Scott Baker8103d0f2015-04-10 16:42:26 -0700291
Scott Baker008a9962015-04-15 20:58:20 -0700292 def __init__(self, *args, **kwargs):
293 # for subclasses, set the default kind appropriately
294 self._meta.get_field("kind").default = self.KIND
295 super(Tenant, self).__init__(*args, **kwargs)
296
Scott Baker8103d0f2015-04-10 16:42:26 -0700297 def __unicode__(self):
Scott Bakerf996b762015-05-20 20:42:04 -0700298 return u"%s-tenant-%s" % (str(self.kind), str(self.id))
Scott Baker8103d0f2015-04-10 16:42:26 -0700299
Scott Baker008a9962015-04-15 20:58:20 -0700300 @classmethod
301 def get_tenant_objects(cls):
302 return cls.objects.filter(kind = cls.KIND)
303
Scott Bakere7fc9f52015-05-05 17:52:03 -0700304 @classmethod
Scott Baker16573d32015-07-24 15:36:02 -0700305 def get_tenant_objects_by_user(cls, user):
306 return cls.select_by_user(user).filter(kind = cls.KIND)
307
308 @classmethod
Scott Bakere7fc9f52015-05-05 17:52:03 -0700309 def get_deleted_tenant_objects(cls):
310 return cls.deleted_objects.filter(kind = cls.KIND)
311
Scott Bakerd921e1c2015-04-20 14:24:29 -0700312 # helper function to be used in subclasses that want to ensure service_specific_id is unique
313 def validate_unique_service_specific_id(self):
314 if self.pk is None:
315 if self.service_specific_id is None:
316 raise XOSMissingField("subscriber_specific_id is None, and it's a required field", fields={"service_specific_id": "cannot be none"})
317
318 conflicts = self.get_tenant_objects().filter(service_specific_id=self.service_specific_id)
319 if conflicts:
320 raise XOSDuplicateKey("service_specific_id %s already exists" % self.service_specific_id, fields={"service_specific_id": "duplicate key"})
321
Scott Baker618a4892015-07-06 14:27:31 -0700322 def save(self, *args, **kwargs):
323 subCount = sum( [1 for e in [self.subscriber_service, self.subscriber_tenant, self.subscriber_user, self.subscriber_root] if e is not None])
324 if (subCount > 1):
325 raise XOSConflictingField("Only one of subscriber_service, subscriber_tenant, subscriber_user, subscriber_root should be set")
326
327 super(Tenant, self).save(*args, **kwargs)
328
329 def get_subscribed_tenants(self, tenant_class):
330 ids = self.subscribed_tenants.filter(kind=tenant_class.KIND)
331 return tenant_class.objects.filter(id__in = ids)
332
333 def get_newest_subscribed_tenant(self, kind):
334 st = list(self.get_subscribed_tenants(kind))
335 if not st:
336 return None
337 return sorted(st, key=attrgetter('id'))[0]
338
Scott Bakere458afd2015-09-09 16:36:06 -0700339class TenantWithContainer(Tenant):
340 """ A tenant that manages a container """
341
342 # this is a hack and should be replaced by something smarter...
Scott Baker7288c522015-09-09 16:43:39 -0700343 LOOK_FOR_IMAGES=["ubuntu-vcpe4", # ONOS demo machine -- preferred vcpe image
Scott Bakere458afd2015-09-09 16:36:06 -0700344 "Ubuntu 14.04 LTS", # portal
345 "Ubuntu-14.04-LTS", # ONOS demo machine
Scott Baker7288c522015-09-09 16:43:39 -0700346 "trusty-server-multi-nic", # CloudLab
Scott Bakere458afd2015-09-09 16:36:06 -0700347 ]
348
349 class Meta:
350 proxy = True
351
352 def __init__(self, *args, **kwargs):
353 super(TenantWithContainer, self).__init__(*args, **kwargs)
Tony Mack6a782f92015-09-13 22:50:39 +0000354 self.cached_instance=None
355 self.orig_instance_id = self.get_initial_attribute("instance_id")
Scott Bakere458afd2015-09-09 16:36:06 -0700356
357 @property
Tony Mack6a782f92015-09-13 22:50:39 +0000358 def instance(self):
359 from core.models import Instance
360 if getattr(self, "cached_instance", None):
361 return self.cached_instance
362 instance_id=self.get_attribute("instance_id")
363 if not instance_id:
Scott Bakere458afd2015-09-09 16:36:06 -0700364 return None
Tony Mack6a782f92015-09-13 22:50:39 +0000365 instances=Instance.objects.filter(id=instance_id)
366 if not instances:
Scott Bakere458afd2015-09-09 16:36:06 -0700367 return None
Tony Mack6a782f92015-09-13 22:50:39 +0000368 instance=instances[0]
369 instance.caller = self.creator
370 self.cached_instance = instance
371 return instance
Scott Bakere458afd2015-09-09 16:36:06 -0700372
Tony Mack6a782f92015-09-13 22:50:39 +0000373 @instance.setter
374 def instance(self, value):
Scott Bakere458afd2015-09-09 16:36:06 -0700375 if value:
376 value = value.id
Tony Mack6a782f92015-09-13 22:50:39 +0000377 if (value != self.get_attribute("instance_id", None)):
378 self.cached_instance=None
379 self.set_attribute("instance_id", value)
Scott Bakere458afd2015-09-09 16:36:06 -0700380
381 @property
382 def creator(self):
383 from core.models import User
384 if getattr(self, "cached_creator", None):
385 return self.cached_creator
386 creator_id=self.get_attribute("creator_id")
387 if not creator_id:
388 return None
389 users=User.objects.filter(id=creator_id)
390 if not users:
391 return None
392 user=users[0]
393 self.cached_creator = users[0]
394 return user
395
396 @creator.setter
397 def creator(self, value):
398 if value:
399 value = value.id
400 if (value != self.get_attribute("creator_id", None)):
401 self.cached_creator=None
402 self.set_attribute("creator_id", value)
403
404 @property
405 def image(self):
406 from core.models import Image
407 # Implement the logic here to pick the image that should be used when
408 # instantiating the VM that will hold the container.
409 for image_name in self.LOOK_FOR_IMAGES:
410 images = Image.objects.filter(name = image_name)
411 if images:
412 return images[0]
413
414 raise XOSProgrammingError("No VPCE image (looked for %s)" % str(self.LOOK_FOR_IMAGES))
415
416 def pick_node(self):
417 from core.models import Node
418 nodes = list(Node.objects.all())
419 # TODO: logic to filter nodes by which nodes are up, and which
420 # nodes the slice can instantiate on.
Tony Mack6a782f92015-09-13 22:50:39 +0000421 nodes = sorted(nodes, key=lambda node: node.instances.all().count())
Scott Bakere458afd2015-09-09 16:36:06 -0700422 return nodes[0]
423
424 def manage_container(self):
Tony Mack6a782f92015-09-13 22:50:39 +0000425 from core.models import Instance, Flavor
Scott Bakere458afd2015-09-09 16:36:06 -0700426
427 if self.deleted:
428 return
429
Tony Mack6a782f92015-09-13 22:50:39 +0000430 if (self.instance is not None) and (self.instance.image != self.image):
431 self.instance.delete()
432 self.instance = None
Scott Bakere458afd2015-09-09 16:36:06 -0700433
Tony Mack6a782f92015-09-13 22:50:39 +0000434 if self.instance is None:
Scott Bakere458afd2015-09-09 16:36:06 -0700435 if not self.provider_service.slices.count():
436 raise XOSConfigurationError("The VCPE service has no slices")
437
438 flavors = Flavor.objects.filter(name="m1.small")
439 if not flavors:
440 raise XOSConfigurationError("No m1.small flavor")
441
442 node =self.pick_node()
Tony Mack6a782f92015-09-13 22:50:39 +0000443 instance = Instance(slice = self.provider_service.slices.all()[0],
Scott Bakere458afd2015-09-09 16:36:06 -0700444 node = node,
445 image = self.image,
446 creator = self.creator,
447 deployment = node.site_deployment.deployment,
448 flavor = flavors[0])
Tony Mack6a782f92015-09-13 22:50:39 +0000449 instance.save()
Scott Bakere458afd2015-09-09 16:36:06 -0700450
451 try:
Tony Mack6a782f92015-09-13 22:50:39 +0000452 self.instance = instance
Scott Bakere458afd2015-09-09 16:36:06 -0700453 super(TenantWithContainer, self).save()
454 except:
Tony Mack6a782f92015-09-13 22:50:39 +0000455 instance.delete()
Scott Bakere458afd2015-09-09 16:36:06 -0700456 raise
457
458 def cleanup_container(self):
Tony Mack6a782f92015-09-13 22:50:39 +0000459 if self.instance:
460 # print "XXX cleanup instance", self.instance
461 self.instance.delete()
462 self.instance = None
Scott Baker618a4892015-07-06 14:27:31 -0700463
Scott Baker925a8fa2015-04-26 20:30:40 -0700464class CoarseTenant(Tenant):
Scott Baker4587b822015-07-01 18:29:08 -0700465 """ TODO: rename "CoarseTenant" --> "StaticTenant" """
Scott Baker925a8fa2015-04-26 20:30:40 -0700466 class Meta:
467 proxy = True
Siobhan Tully00353f72013-10-08 21:53:27 -0400468
Scott Baker2461bec2015-08-14 09:10:11 -0700469 KIND = COARSE_KIND
Scott Baker925a8fa2015-04-26 20:30:40 -0700470
471 def save(self, *args, **kwargs):
472 if (not self.subscriber_service):
473 raise XOSValidationError("subscriber_service cannot be null")
474 if (self.subscriber_tenant or self.subscriber_user):
475 raise XOSValidationError("subscriber_tenant and subscriber_user must be null")
476
477 super(CoarseTenant,self).save()
Scott Baker4587b822015-07-01 18:29:08 -0700478
479class Subscriber(TenantRoot):
480 """ Intermediate class for TenantRoots that are to be Subscribers """
481
482 class Meta:
483 proxy = True
484
485 KIND = "Subscriber"
486
487class Provider(TenantRoot):
488 """ Intermediate class for TenantRoots that are to be Providers """
489
490 class Meta:
491 proxy = True
492
493 KIND = "Provider"
494
Scott Baker462a1d92015-10-15 15:59:19 -0700495class TenantAttribute(PlCoreBase):
496 name = models.SlugField(help_text="Attribute Name", max_length=128)
497 value = models.TextField(help_text="Attribute Value")
498 tenant = models.ForeignKey(Tenant, related_name='tenantattributes', help_text="The Tenant this attribute is associated with")
499
Scott Baker4587b822015-07-01 18:29:08 -0700500class TenantRootRole(PlCoreBase):
Scott Baker5116b302015-07-24 15:48:03 -0700501 ROLE_CHOICES = (('admin','Admin'), ('access','Access'))
Scott Baker4587b822015-07-01 18:29:08 -0700502
503 role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
504
505 def __unicode__(self): return u'%s' % (self.role)
506
507class TenantRootPrivilege(PlCoreBase):
508 user = models.ForeignKey('User', related_name="tenant_root_privileges")
509 tenant_root = models.ForeignKey('TenantRoot', related_name="tenant_root_privileges")
510 role = models.ForeignKey('TenantRootRole', related_name="tenant_root_privileges")
511
512 class Meta:
513 unique_together = ('user', 'tenant_root', 'role')
514
Scott Baker618a4892015-07-06 14:27:31 -0700515 def __unicode__(self): return u'%s %s %s' % (self.tenant_root, self.user, self.role)
Scott Baker4587b822015-07-01 18:29:08 -0700516
517 def save(self, *args, **kwds):
518 if not self.user.is_active:
519 raise PermissionDenied, "Cannot modify role(s) of a disabled user"
Scott Baker335882a2015-07-24 10:15:31 -0700520 super(TenantRootPrivilege, self).save(*args, **kwds)
Scott Baker4587b822015-07-01 18:29:08 -0700521
522 def can_update(self, user):
Scott Baker335882a2015-07-24 10:15:31 -0700523 return user.can_update_tenant_root_privilege(self)
Scott Baker4587b822015-07-01 18:29:08 -0700524
Scott Baker16573d32015-07-24 15:36:02 -0700525 @classmethod
526 def select_by_user(cls, user):
Scott Baker4587b822015-07-01 18:29:08 -0700527 if user.is_admin:
Scott Baker5116b302015-07-24 15:48:03 -0700528 return cls.objects.all()
Scott Baker4587b822015-07-01 18:29:08 -0700529 else:
Scott Baker5116b302015-07-24 15:48:03 -0700530 # User can see his own privilege
531 trp_ids = [trp.id for trp in cls.objects.filter(user=user)]
532
533 # A slice admin can see the SlicePrivileges for his Slice
534 for priv in cls.objects.filter(user=user, role__role="admin"):
535 trp_ids.extend( [trp.id for trp in cls.objects.filter(tenant_root=priv.tenant_root)] )
536
537 return cls.objects.filter(id__in=trp_ids)
538
Scott Baker618a4892015-07-06 14:27:31 -0700539