blob: 8d2180cc8caf4fa4aa132f9bb73f762b4ffb70a2 [file] [log] [blame]
Siobhan Tully00353f72013-10-08 21:53:27 -04001from django.db import models
Scott Baker0d306722015-04-15 20:58:20 -07002from core.models import PlCoreBase,SingletonModel,PlCoreBaseManager
Tony Mack50e12212015-03-09 13:03:56 -04003from core.models.plcorebase import StrippedCharField
Scott Baker7f8ef8f2015-04-20 14:24:29 -07004from xos.exceptions import *
Scott Bakerb2385622015-07-06 14:27:31 -07005from operator import attrgetter
Scott Baker7211f5b2015-04-14 17:18:51 -07006import json
Siobhan Tully00353f72013-10-08 21:53:27 -04007
Scott Bakerc24f86d2015-08-14 09:10:11 -07008COARSE_KIND="coarse"
9
Scott Baker9d1c6d92015-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 Bakereb098e62015-07-13 13:54:06 -070034 @classmethod
35 def setup_simple_attributes(cls):
36 for (attrname, default) in cls.simple_attributes:
Scott Bakere2879d32015-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 Bakereb098e62015-07-13 13:54:06 -070041
Scott Baker9d1c6d92015-07-13 13:07:27 -070042class Service(PlCoreBase, AttributeMixin):
Scott Baker0d306722015-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 Baker0d306722015-04-15 20:58:20 -070048 kind = StrippedCharField(max_length=30, help_text="Kind of service", default=KIND)
Tony Mack50e12212015-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 Mack50e12212015-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 Baker5b044612015-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 Bakerb9040e92015-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 Baker0d306722015-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 Baker27de6012015-07-24 15:36:02 -070069 @classmethod
70 def get_service_objects_by_user(cls, user):
71 return cls.select_by_user(user).filter(kind = cls.KIND)
72
73 @classmethod
74 def select_by_user(cls, user):
75 if user.is_admin:
76 return cls.objects.all()
77 else:
78 service_ids = [sp.slice.id for sp in ServicePrivilege.objects.filter(user=user)]
79 return cls.objects.filter(id__in=service_ids)
80
Siobhan Tully00353f72013-10-08 21:53:27 -040081 def __unicode__(self): return u'%s' % (self.name)
82
Tony Mack950b4492015-04-29 12:23:10 -040083 def can_update(self, user):
84 return user.can_update_service(self, allow=['admin'])
Scott Bakerb2385622015-07-06 14:27:31 -070085
Scott Baker25757222015-05-11 16:36:41 -070086 def get_scalable_nodes(self, slice, max_per_node=None, exclusive_slices=[]):
87 """
88 Get a list of nodes that can be used to scale up a slice.
89
90 slice - slice to scale up
Tony Mackd8515472015-08-19 11:58:18 -040091 max_per_node - maximum numbers of instances that 'slice' can have on a single node
Scott Baker25757222015-05-11 16:36:41 -070092 exclusive_slices - list of slices that must have no nodes in common with 'slice'.
93 """
94
Tony Mackd8515472015-08-19 11:58:18 -040095 from core.models import Node, Instance # late import to get around order-of-imports constraint in __init__.py
Scott Baker25757222015-05-11 16:36:41 -070096
97 nodes = list(Node.objects.all())
98
Tony Mackd8515472015-08-19 11:58:18 -040099 conflicting_instances = Instance.objects.filter(slice__in = exclusive_slices)
100 conflicting_nodes = Node.objects.filter(instances__in = conflicting_instances)
Scott Baker25757222015-05-11 16:36:41 -0700101
102 nodes = [x for x in nodes if x not in conflicting_nodes]
103
Tony Mackd8515472015-08-19 11:58:18 -0400104 # If max_per_node is set, then limit the number of instances this slice
Scott Baker25757222015-05-11 16:36:41 -0700105 # can have on a single node.
106 if max_per_node:
107 acceptable_nodes = []
108 for node in nodes:
Tony Mackd8515472015-08-19 11:58:18 -0400109 existing_count = node.instances.filter(slice=slice).count()
Scott Baker25757222015-05-11 16:36:41 -0700110 if existing_count < max_per_node:
111 acceptable_nodes.append(node)
112 nodes = acceptable_nodes
113
114 return nodes
115
116 def pick_node(self, slice, max_per_node=None, exclusive_slices=[]):
117 # Pick the best node to scale up a slice.
118
119 nodes = self.get_scalable_nodes(slice, max_per_node, exclusive_slices)
Tony Mackd8515472015-08-19 11:58:18 -0400120 nodes = sorted(nodes, key=lambda node: node.instances.all().count())
Scott Baker25757222015-05-11 16:36:41 -0700121 if not nodes:
122 return None
123 return nodes[0]
124
125 def adjust_scale(self, slice_hint, scale, max_per_node=None, exclusive_slices=[]):
Tony Mackd8515472015-08-19 11:58:18 -0400126 from core.models import Instance # late import to get around order-of-imports constraint in __init__.py
Scott Baker25757222015-05-11 16:36:41 -0700127
128 slices = [x for x in self.slices.all() if slice_hint in x.name]
129 for slice in slices:
Tony Mackd8515472015-08-19 11:58:18 -0400130 while slice.instances.all().count() > scale:
131 s = slice.instances.all()[0]
132 # print "drop instance", s
Scott Baker25757222015-05-11 16:36:41 -0700133 s.delete()
134
Tony Mackd8515472015-08-19 11:58:18 -0400135 while slice.instances.all().count() < scale:
Scott Baker25757222015-05-11 16:36:41 -0700136 node = self.pick_node(slice, max_per_node, exclusive_slices)
137 if not node:
138 # no more available nodes
139 break
140
141 image = slice.default_image
142 if not image:
143 raise XOSConfigurationError("No default_image for slice %s" % slice.name)
144
145 flavor = slice.default_flavor
146 if not flavor:
147 raise XOSConfigurationError("No default_flavor for slice %s" % slice.name)
148
Tony Mackd8515472015-08-19 11:58:18 -0400149 s = Instance(slice=slice,
Scott Baker25757222015-05-11 16:36:41 -0700150 node=node,
151 creator=slice.creator,
152 image=image,
153 flavor=flavor,
154 deployment=node.site_deployment.deployment)
155 s.save()
156
Tony Mackd8515472015-08-19 11:58:18 -0400157 # print "add instance", s
Tony Mack950b4492015-04-29 12:23:10 -0400158
Siobhan Tully00353f72013-10-08 21:53:27 -0400159class ServiceAttribute(PlCoreBase):
160 name = models.SlugField(help_text="Attribute Name", max_length=128)
Tony Mack50e12212015-03-09 13:03:56 -0400161 value = StrippedCharField(help_text="Attribute Value", max_length=1024)
Siobhan Tully00353f72013-10-08 21:53:27 -0400162 service = models.ForeignKey(Service, related_name='serviceattributes', help_text="The Service this attribute is associated with")
163
Tony Mack950b4492015-04-29 12:23:10 -0400164class ServiceRole(PlCoreBase):
165 ROLE_CHOICES = (('admin','Admin'),)
166 role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
167
168 def __unicode__(self): return u'%s' % (self.role)
169
170class ServicePrivilege(PlCoreBase):
171 user = models.ForeignKey('User', related_name='serviceprivileges')
172 service = models.ForeignKey('Service', related_name='serviceprivileges')
173 role = models.ForeignKey('ServiceRole',related_name='serviceprivileges')
174
175 class Meta:
Tony Mack02683de2015-05-13 12:21:28 -0400176 unique_together = ('user', 'service', 'role')
Tony Mack950b4492015-04-29 12:23:10 -0400177
178 def __unicode__(self): return u'%s %s %s' % (self.service, self.user, self.role)
179
180 def can_update(self, user):
181 if not self.service.enabled:
182 raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
183 return self.service.can_update(user)
184
185 def save(self, *args, **kwds):
186 if not self.service.enabled:
187 raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
188 super(ServicePrivilege, self).save(*args, **kwds)
189
190 def delete(self, *args, **kwds):
191 if not self.service.enabled:
192 raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
Scott Bakera86489f2015-07-01 18:29:08 -0700193 super(ServicePrivilege, self).delete(*args, **kwds)
194
Scott Baker27de6012015-07-24 15:36:02 -0700195 @classmethod
196 def select_by_user(cls, user):
Tony Mack950b4492015-04-29 12:23:10 -0400197 if user.is_admin:
Scott Baker27de6012015-07-24 15:36:02 -0700198 qs = cls.objects.all()
Tony Mack950b4492015-04-29 12:23:10 -0400199 else:
Scott Baker27de6012015-07-24 15:36:02 -0700200 qs = cls.objects.filter(user=user)
Scott Bakera86489f2015-07-01 18:29:08 -0700201 return qs
202
Scott Baker9d1c6d92015-07-13 13:07:27 -0700203class TenantRoot(PlCoreBase, AttributeMixin):
Scott Bakera86489f2015-07-01 18:29:08 -0700204 """ A tenantRoot is one of the things that can sit at the root of a chain
205 of tenancy. This object represents a node.
206 """
207
208 KIND= "generic"
209 kind = StrippedCharField(max_length=30, default=KIND)
Scott Bakerb2385622015-07-06 14:27:31 -0700210 name = StrippedCharField(max_length=255, help_text="name", blank=True, null=True)
Scott Bakera86489f2015-07-01 18:29:08 -0700211
Scott Baker29415a82015-07-07 12:12:42 -0700212 service_specific_attribute = models.TextField(blank=True, null=True)
213 service_specific_id = StrippedCharField(max_length=30, blank=True, null=True)
Scott Bakera86489f2015-07-01 18:29:08 -0700214
Scott Baker126ad472015-07-07 17:59:44 -0700215 def __init__(self, *args, **kwargs):
216 # for subclasses, set the default kind appropriately
217 self._meta.get_field("kind").default = self.KIND
218 super(TenantRoot, self).__init__(*args, **kwargs)
219
Scott Bakerb2385622015-07-06 14:27:31 -0700220 def __unicode__(self):
221 if not self.name:
222 return u"%s-tenant_root-#%s" % (str(self.kind), str(self.id))
223 else:
224 return self.name
225
226 def can_update(self, user):
227 return user.can_update_tenant_root(self, allow=['admin'])
228
Scott Baker29415a82015-07-07 12:12:42 -0700229 def get_subscribed_tenants(self, tenant_class):
230 ids = self.subscribed_tenants.filter(kind=tenant_class.KIND)
231 return tenant_class.objects.filter(id__in = ids)
232
233 def get_newest_subscribed_tenant(self, kind):
234 st = list(self.get_subscribed_tenants(kind))
235 if not st:
236 return None
237 return sorted(st, key=attrgetter('id'))[0]
238
239 @classmethod
240 def get_tenant_objects(cls):
241 return cls.objects.filter(kind = cls.KIND)
242
Scott Baker27de6012015-07-24 15:36:02 -0700243 @classmethod
244 def get_tenant_objects_by_user(cls, user):
245 return cls.select_by_user(user).filter(kind = cls.KIND)
246
247 @classmethod
248 def select_by_user(cls, user):
249 if user.is_admin:
250 return cls.objects.all()
251 else:
252 tr_ids = [trp.tenant_root.id for trp in TenantRootPrivilege.objects.filter(user=user)]
253 return cls.objects.filter(id__in=tr_ids)
254
Scott Baker9d1c6d92015-07-13 13:07:27 -0700255class Tenant(PlCoreBase, AttributeMixin):
Scott Baker91e85882015-04-10 16:42:26 -0700256 """ A tenant is a relationship between two entities, a subscriber and a
Scott Bakera86489f2015-07-01 18:29:08 -0700257 provider. This object represents an edge.
Scott Baker91e85882015-04-10 16:42:26 -0700258
259 The subscriber can be a User, a Service, or a Tenant.
260
261 The provider is always a Service.
Scott Bakera86489f2015-07-01 18:29:08 -0700262
263 TODO: rename "Tenant" to "Tenancy"
Scott Baker91e85882015-04-10 16:42:26 -0700264 """
Scott Baker0d306722015-04-15 20:58:20 -0700265
Scott Bakeref58a842015-04-26 20:30:40 -0700266 CONNECTIVITY_CHOICES = (('public', 'Public'), ('private', 'Private'), ('na', 'Not Applicable'))
267
Scott Baker0d306722015-04-15 20:58:20 -0700268 # when subclassing a service, redefine KIND to describe the new service
269 KIND = "generic"
270
271 kind = StrippedCharField(max_length=30, default=KIND)
Scott Bakera86489f2015-07-01 18:29:08 -0700272 provider_service = models.ForeignKey(Service, related_name='provided_tenants')
273
274 # The next four things are the various type of objects that can be subscribers of this Tenancy
275 # relationship. One and only one can be used at a time.
276 subscriber_service = models.ForeignKey(Service, related_name='subscribed_tenants', blank=True, null=True)
277 subscriber_tenant = models.ForeignKey("Tenant", related_name='subscribed_tenants', blank=True, null=True)
278 subscriber_user = models.ForeignKey("User", related_name='subscribed_tenants', blank=True, null=True)
279 subscriber_root = models.ForeignKey("TenantRoot", related_name="subscribed_tenants", blank=True, null=True)
280
281 # Service_specific_attribute and service_specific_id are opaque to XOS
Scott Baker1b7c6f12015-05-06 19:49:31 -0700282 service_specific_id = StrippedCharField(max_length=30, blank=True, null=True)
283 service_specific_attribute = models.TextField(blank=True, null=True)
Scott Bakera86489f2015-07-01 18:29:08 -0700284
285 # Connect_method is only used by Coarse tenants
Scott Bakeref58a842015-04-26 20:30:40 -0700286 connect_method = models.CharField(null=False, blank=False, max_length=30, choices=CONNECTIVITY_CHOICES, default="na")
Scott Baker91e85882015-04-10 16:42:26 -0700287
Scott Baker0d306722015-04-15 20:58:20 -0700288 def __init__(self, *args, **kwargs):
289 # for subclasses, set the default kind appropriately
290 self._meta.get_field("kind").default = self.KIND
291 super(Tenant, self).__init__(*args, **kwargs)
292
Scott Baker91e85882015-04-10 16:42:26 -0700293 def __unicode__(self):
Scott Bakerfe91f622015-05-20 20:42:04 -0700294 return u"%s-tenant-%s" % (str(self.kind), str(self.id))
Scott Baker91e85882015-04-10 16:42:26 -0700295
Scott Baker0d306722015-04-15 20:58:20 -0700296 @classmethod
297 def get_tenant_objects(cls):
298 return cls.objects.filter(kind = cls.KIND)
299
Scott Bakereb50ee32015-05-05 17:52:03 -0700300 @classmethod
Scott Baker27de6012015-07-24 15:36:02 -0700301 def get_tenant_objects_by_user(cls, user):
302 return cls.select_by_user(user).filter(kind = cls.KIND)
303
304 @classmethod
Scott Bakereb50ee32015-05-05 17:52:03 -0700305 def get_deleted_tenant_objects(cls):
306 return cls.deleted_objects.filter(kind = cls.KIND)
307
Scott Baker7f8ef8f2015-04-20 14:24:29 -0700308 # helper function to be used in subclasses that want to ensure service_specific_id is unique
309 def validate_unique_service_specific_id(self):
310 if self.pk is None:
311 if self.service_specific_id is None:
312 raise XOSMissingField("subscriber_specific_id is None, and it's a required field", fields={"service_specific_id": "cannot be none"})
313
314 conflicts = self.get_tenant_objects().filter(service_specific_id=self.service_specific_id)
315 if conflicts:
316 raise XOSDuplicateKey("service_specific_id %s already exists" % self.service_specific_id, fields={"service_specific_id": "duplicate key"})
317
Scott Bakerb2385622015-07-06 14:27:31 -0700318 def save(self, *args, **kwargs):
319 subCount = sum( [1 for e in [self.subscriber_service, self.subscriber_tenant, self.subscriber_user, self.subscriber_root] if e is not None])
320 if (subCount > 1):
321 raise XOSConflictingField("Only one of subscriber_service, subscriber_tenant, subscriber_user, subscriber_root should be set")
322
323 super(Tenant, self).save(*args, **kwargs)
324
325 def get_subscribed_tenants(self, tenant_class):
326 ids = self.subscribed_tenants.filter(kind=tenant_class.KIND)
327 return tenant_class.objects.filter(id__in = ids)
328
329 def get_newest_subscribed_tenant(self, kind):
330 st = list(self.get_subscribed_tenants(kind))
331 if not st:
332 return None
333 return sorted(st, key=attrgetter('id'))[0]
334
Scott Bakerc1584b82015-09-09 16:36:06 -0700335class TenantWithContainer(Tenant):
336 """ A tenant that manages a container """
337
338 # this is a hack and should be replaced by something smarter...
Scott Bakerf05c4972015-09-09 16:43:39 -0700339 LOOK_FOR_IMAGES=["ubuntu-vcpe4", # ONOS demo machine -- preferred vcpe image
Scott Bakerc1584b82015-09-09 16:36:06 -0700340 "Ubuntu 14.04 LTS", # portal
341 "Ubuntu-14.04-LTS", # ONOS demo machine
Scott Bakerf05c4972015-09-09 16:43:39 -0700342 "trusty-server-multi-nic", # CloudLab
Scott Bakerc1584b82015-09-09 16:36:06 -0700343 ]
344
345 class Meta:
346 proxy = True
347
348 def __init__(self, *args, **kwargs):
349 super(TenantWithContainer, self).__init__(*args, **kwargs)
Tony Mack32010062015-09-13 22:50:39 +0000350 self.cached_instance=None
351 self.orig_instance_id = self.get_initial_attribute("instance_id")
Scott Bakerc1584b82015-09-09 16:36:06 -0700352
353 @property
Tony Mack32010062015-09-13 22:50:39 +0000354 def instance(self):
355 from core.models import Instance
356 if getattr(self, "cached_instance", None):
357 return self.cached_instance
358 instance_id=self.get_attribute("instance_id")
359 if not instance_id:
Scott Bakerc1584b82015-09-09 16:36:06 -0700360 return None
Tony Mack32010062015-09-13 22:50:39 +0000361 instances=Instance.objects.filter(id=instance_id)
362 if not instances:
Scott Bakerc1584b82015-09-09 16:36:06 -0700363 return None
Tony Mack32010062015-09-13 22:50:39 +0000364 instance=instances[0]
365 instance.caller = self.creator
366 self.cached_instance = instance
367 return instance
Scott Bakerc1584b82015-09-09 16:36:06 -0700368
Tony Mack32010062015-09-13 22:50:39 +0000369 @instance.setter
370 def instance(self, value):
Scott Bakerc1584b82015-09-09 16:36:06 -0700371 if value:
372 value = value.id
Tony Mack32010062015-09-13 22:50:39 +0000373 if (value != self.get_attribute("instance_id", None)):
374 self.cached_instance=None
375 self.set_attribute("instance_id", value)
Scott Bakerc1584b82015-09-09 16:36:06 -0700376
377 @property
378 def creator(self):
379 from core.models import User
380 if getattr(self, "cached_creator", None):
381 return self.cached_creator
382 creator_id=self.get_attribute("creator_id")
383 if not creator_id:
384 return None
385 users=User.objects.filter(id=creator_id)
386 if not users:
387 return None
388 user=users[0]
389 self.cached_creator = users[0]
390 return user
391
392 @creator.setter
393 def creator(self, value):
394 if value:
395 value = value.id
396 if (value != self.get_attribute("creator_id", None)):
397 self.cached_creator=None
398 self.set_attribute("creator_id", value)
399
400 @property
401 def image(self):
402 from core.models import Image
403 # Implement the logic here to pick the image that should be used when
404 # instantiating the VM that will hold the container.
405 for image_name in self.LOOK_FOR_IMAGES:
406 images = Image.objects.filter(name = image_name)
407 if images:
408 return images[0]
409
410 raise XOSProgrammingError("No VPCE image (looked for %s)" % str(self.LOOK_FOR_IMAGES))
411
412 def pick_node(self):
413 from core.models import Node
414 nodes = list(Node.objects.all())
415 # TODO: logic to filter nodes by which nodes are up, and which
416 # nodes the slice can instantiate on.
Tony Mack32010062015-09-13 22:50:39 +0000417 nodes = sorted(nodes, key=lambda node: node.instances.all().count())
Scott Bakerc1584b82015-09-09 16:36:06 -0700418 return nodes[0]
419
420 def manage_container(self):
Tony Mack32010062015-09-13 22:50:39 +0000421 from core.models import Instance, Flavor
Scott Bakerc1584b82015-09-09 16:36:06 -0700422
423 if self.deleted:
424 return
425
Tony Mack32010062015-09-13 22:50:39 +0000426 if (self.instance is not None) and (self.instance.image != self.image):
427 self.instance.delete()
428 self.instance = None
Scott Bakerc1584b82015-09-09 16:36:06 -0700429
Tony Mack32010062015-09-13 22:50:39 +0000430 if self.instance is None:
Scott Bakerc1584b82015-09-09 16:36:06 -0700431 if not self.provider_service.slices.count():
432 raise XOSConfigurationError("The VCPE service has no slices")
433
434 flavors = Flavor.objects.filter(name="m1.small")
435 if not flavors:
436 raise XOSConfigurationError("No m1.small flavor")
437
438 node =self.pick_node()
Tony Mack32010062015-09-13 22:50:39 +0000439 instance = Instance(slice = self.provider_service.slices.all()[0],
Scott Bakerc1584b82015-09-09 16:36:06 -0700440 node = node,
441 image = self.image,
442 creator = self.creator,
443 deployment = node.site_deployment.deployment,
444 flavor = flavors[0])
Tony Mack32010062015-09-13 22:50:39 +0000445 instance.save()
Scott Bakerc1584b82015-09-09 16:36:06 -0700446
447 try:
Tony Mack32010062015-09-13 22:50:39 +0000448 self.instance = instance
Scott Bakerc1584b82015-09-09 16:36:06 -0700449 super(TenantWithContainer, self).save()
450 except:
Tony Mack32010062015-09-13 22:50:39 +0000451 instance.delete()
Scott Bakerc1584b82015-09-09 16:36:06 -0700452 raise
453
454 def cleanup_container(self):
Tony Mack32010062015-09-13 22:50:39 +0000455 if self.instance:
456 # print "XXX cleanup instance", self.instance
457 self.instance.delete()
458 self.instance = None
Scott Bakerb2385622015-07-06 14:27:31 -0700459
Scott Bakeref58a842015-04-26 20:30:40 -0700460class CoarseTenant(Tenant):
Scott Bakera86489f2015-07-01 18:29:08 -0700461 """ TODO: rename "CoarseTenant" --> "StaticTenant" """
Scott Bakeref58a842015-04-26 20:30:40 -0700462 class Meta:
463 proxy = True
Siobhan Tully00353f72013-10-08 21:53:27 -0400464
Scott Bakerc24f86d2015-08-14 09:10:11 -0700465 KIND = COARSE_KIND
Scott Bakeref58a842015-04-26 20:30:40 -0700466
467 def save(self, *args, **kwargs):
468 if (not self.subscriber_service):
469 raise XOSValidationError("subscriber_service cannot be null")
470 if (self.subscriber_tenant or self.subscriber_user):
471 raise XOSValidationError("subscriber_tenant and subscriber_user must be null")
472
473 super(CoarseTenant,self).save()
Scott Bakera86489f2015-07-01 18:29:08 -0700474
475class Subscriber(TenantRoot):
476 """ Intermediate class for TenantRoots that are to be Subscribers """
477
478 class Meta:
479 proxy = True
480
481 KIND = "Subscriber"
482
483class Provider(TenantRoot):
484 """ Intermediate class for TenantRoots that are to be Providers """
485
486 class Meta:
487 proxy = True
488
489 KIND = "Provider"
490
Scott Baker1e7e3482015-10-15 15:59:19 -0700491class TenantAttribute(PlCoreBase):
492 name = models.SlugField(help_text="Attribute Name", max_length=128)
493 value = models.TextField(help_text="Attribute Value")
494 tenant = models.ForeignKey(Tenant, related_name='tenantattributes', help_text="The Tenant this attribute is associated with")
495
Scott Bakera86489f2015-07-01 18:29:08 -0700496class TenantRootRole(PlCoreBase):
Scott Baker1729e342015-07-24 15:48:03 -0700497 ROLE_CHOICES = (('admin','Admin'), ('access','Access'))
Scott Bakera86489f2015-07-01 18:29:08 -0700498
499 role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
500
501 def __unicode__(self): return u'%s' % (self.role)
502
503class TenantRootPrivilege(PlCoreBase):
504 user = models.ForeignKey('User', related_name="tenant_root_privileges")
505 tenant_root = models.ForeignKey('TenantRoot', related_name="tenant_root_privileges")
506 role = models.ForeignKey('TenantRootRole', related_name="tenant_root_privileges")
507
508 class Meta:
509 unique_together = ('user', 'tenant_root', 'role')
510
Scott Bakerb2385622015-07-06 14:27:31 -0700511 def __unicode__(self): return u'%s %s %s' % (self.tenant_root, self.user, self.role)
Scott Bakera86489f2015-07-01 18:29:08 -0700512
513 def save(self, *args, **kwds):
514 if not self.user.is_active:
515 raise PermissionDenied, "Cannot modify role(s) of a disabled user"
Scott Bakerc8e947a2015-07-24 10:15:31 -0700516 super(TenantRootPrivilege, self).save(*args, **kwds)
Scott Bakera86489f2015-07-01 18:29:08 -0700517
518 def can_update(self, user):
Scott Bakerc8e947a2015-07-24 10:15:31 -0700519 return user.can_update_tenant_root_privilege(self)
Scott Bakera86489f2015-07-01 18:29:08 -0700520
Scott Baker27de6012015-07-24 15:36:02 -0700521 @classmethod
522 def select_by_user(cls, user):
Scott Bakera86489f2015-07-01 18:29:08 -0700523 if user.is_admin:
Scott Baker1729e342015-07-24 15:48:03 -0700524 return cls.objects.all()
Scott Bakera86489f2015-07-01 18:29:08 -0700525 else:
Scott Baker1729e342015-07-24 15:48:03 -0700526 # User can see his own privilege
527 trp_ids = [trp.id for trp in cls.objects.filter(user=user)]
528
529 # A slice admin can see the SlicePrivileges for his Slice
530 for priv in cls.objects.filter(user=user, role__role="admin"):
531 trp_ids.extend( [trp.id for trp in cls.objects.filter(tenant_root=priv.tenant_root)] )
532
533 return cls.objects.filter(id__in=trp_ids)
534
Scott Bakerb2385622015-07-06 14:27:31 -0700535