blob: 9b03f66cdd5f316e58abc432b8dc3e176753c6ad [file] [log] [blame]
Scott Bakerf57e5592015-04-14 17:18:51 -07001import json
Jeremy Mowery11ed8992016-04-17 20:56:54 -07002from operator import attrgetter
Siobhan Tully00353f72013-10-08 21:53:27 -04003
Scott Baker80d65b12016-05-26 20:20:47 -07004from core.models import PlCoreBase, PlCoreBaseManager, SingletonModel, XOS
Jeremy Mowery11ed8992016-04-17 20:56:54 -07005from core.models.plcorebase import StrippedCharField
6from django.db import models
7from xos.exceptions import *
8
9COARSE_KIND = "coarse"
10
Scott Baker80d65b12016-05-26 20:20:47 -070011def get_xos():
12 xos = XOS.objects.all()
13
14 if xos:
15 return xos[0]
16 else:
17 return None
18
Scott Baker2461bec2015-08-14 09:10:11 -070019
Scott Baker82498c52015-07-13 13:07:27 -070020class AttributeMixin(object):
Jeremy Mowery11ed8992016-04-17 20:56:54 -070021 # helper for extracting things from a json-encoded
22 # service_specific_attribute
23
Scott Baker82498c52015-07-13 13:07:27 -070024 def get_attribute(self, name, default=None):
25 if self.service_specific_attribute:
26 attributes = json.loads(self.service_specific_attribute)
27 else:
28 attributes = {}
29 return attributes.get(name, default)
30
31 def set_attribute(self, name, value):
32 if self.service_specific_attribute:
33 attributes = json.loads(self.service_specific_attribute)
34 else:
35 attributes = {}
Jeremy Mowery11ed8992016-04-17 20:56:54 -070036 attributes[name] = value
Scott Baker82498c52015-07-13 13:07:27 -070037 self.service_specific_attribute = json.dumps(attributes)
38
39 def get_initial_attribute(self, name, default=None):
40 if self._initial["service_specific_attribute"]:
Jeremy Mowery11ed8992016-04-17 20:56:54 -070041 attributes = json.loads(
42 self._initial["service_specific_attribute"])
Scott Baker82498c52015-07-13 13:07:27 -070043 else:
44 attributes = {}
45 return attributes.get(name, default)
46
Scott Baker74404fe2015-07-13 13:54:06 -070047 @classmethod
Scott Baker6dfde212016-03-01 20:10:24 -080048 def get_default_attribute(cls, name):
49 for (attrname, default) in cls.simple_attributes:
Jeremy Mowery11ed8992016-04-17 20:56:54 -070050 if attrname == name:
Scott Baker6dfde212016-03-01 20:10:24 -080051 return default
Jeremy Mowery11ed8992016-04-17 20:56:54 -070052 if hasattr(cls, "default_attributes"):
Scott Bakerf180ff32016-03-31 11:46:02 -070053 if name in cls.default_attributes:
54 return cls.default_attributes[name]
55
56 return None
Scott Baker6dfde212016-03-01 20:10:24 -080057
58 @classmethod
Scott Baker74404fe2015-07-13 13:54:06 -070059 def setup_simple_attributes(cls):
60 for (attrname, default) in cls.simple_attributes:
Scott Baker096dce82015-07-13 14:27:51 -070061 setattr(cls, attrname, property(lambda self, attrname=attrname, default=default: self.get_attribute(attrname, default),
Jeremy Mowery11ed8992016-04-17 20:56:54 -070062 lambda self, value, attrname=attrname: self.set_attribute(
63 attrname, value),
Scott Baker096dce82015-07-13 14:27:51 -070064 None,
65 attrname))
Scott Baker74404fe2015-07-13 13:54:06 -070066
Scott Baker80d65b12016-05-26 20:20:47 -070067class ServiceController(PlCoreBase):
68 xos = models.ForeignKey(XOS, related_name='servicecontrolers', help_text="Pointer to XOS", default=get_xos)
69 name = StrippedCharField(max_length=30, help_text="Service Name")
Scott Baker1e559822016-05-26 20:57:10 -070070 base_url = StrippedCharField(max_length=1024, help_text="Base URL, allows use of relative URLs for resources", null=True, blank=True)
Scott Baker80d65b12016-05-26 20:20:47 -070071
Scott Baker147cace2016-05-26 21:11:37 -070072 def __unicode__(self): return u'%s' % (self.name)
73
Scott Baker80d65b12016-05-26 20:20:47 -070074class ServiceControllerResource(PlCoreBase):
75 KIND_CHOICES = (('models', 'Models'),
76 ('admin', 'Admin'),
77 ('django_library', 'Django Library'),
Scott Baker1ea8cb52016-05-26 22:19:35 -070078 ('synchronizer', 'Synchronizer'),
Scott Baker80d65b12016-05-26 20:20:47 -070079 ('rest', 'REST API'),
80 ('tosca_custom_types', 'Tosca Custom Types'),
81 ('tosca_resource', 'Tosca Resource'))
82
83 FORMAT_CHOICES = (('python', 'Python'),
84 ('manifest', 'Manifest'),
85 ('docker', 'Docker Container'))
86
87 service_controller = models.ForeignKey(ServiceController, related_name='servicecontrolerresources',
88 help_text="The Service Controller this resource is associated with")
89
90 name = StrippedCharField(max_length=30, help_text="Object Name")
91 kind = StrippedCharField(choices=KIND_CHOICES, max_length=30)
92 format = StrippedCharField(choices=FORMAT_CHOICES, max_length=30)
Scott Baker1e559822016-05-26 20:57:10 -070093 url = StrippedCharField(max_length=1024, help_text="URL of resource", null=True, blank=True)
Jeremy Mowery11ed8992016-04-17 20:56:54 -070094
Scott Baker147cace2016-05-26 21:11:37 -070095 def __unicode__(self): return u'%s' % (self.name)
96
Scott Baker82498c52015-07-13 13:07:27 -070097class Service(PlCoreBase, AttributeMixin):
Scott Baker008a9962015-04-15 20:58:20 -070098 # when subclassing a service, redefine KIND to describe the new service
99 KIND = "generic"
100
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700101 description = models.TextField(
102 max_length=254, null=True, blank=True, help_text="Description of Service")
Siobhan Tully00353f72013-10-08 21:53:27 -0400103 enabled = models.BooleanField(default=True)
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700104 kind = StrippedCharField(
105 max_length=30, help_text="Kind of service", default=KIND)
Tony Mackd84b1ff2015-03-09 13:03:56 -0400106 name = StrippedCharField(max_length=30, help_text="Service Name")
Scott Bakerd3e30292016-05-02 09:38:24 -0700107 versionNumber = StrippedCharField(blank=True, null=True,
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700108 max_length=30, help_text="Version of Service Definition")
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500109 published = models.BooleanField(default=True)
Tony Mackd84b1ff2015-03-09 13:03:56 -0400110 view_url = StrippedCharField(blank=True, null=True, max_length=1024)
111 icon_url = StrippedCharField(blank=True, null=True, max_length=1024)
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700112 public_key = models.TextField(
113 null=True, blank=True, max_length=1024, help_text="Public key string")
Scott Bakerf60c0102015-11-12 16:22:52 -0800114 private_key_fn = StrippedCharField(blank=True, null=True, max_length=1024)
Siobhan Tully00353f72013-10-08 21:53:27 -0400115
Scott Baker2f0828e2015-07-13 12:33:28 -0700116 # Service_specific_attribute and service_specific_id are opaque to XOS
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700117 service_specific_id = StrippedCharField(
118 max_length=30, blank=True, null=True)
Scott Baker2f0828e2015-07-13 12:33:28 -0700119 service_specific_attribute = models.TextField(blank=True, null=True)
120
Scott Baker80d65b12016-05-26 20:20:47 -0700121 controller = models.ForeignKey(ServiceController, related_name='services',
122 help_text="The Service Controller this Service uses")
123
Scott Baker008a9962015-04-15 20:58:20 -0700124 def __init__(self, *args, **kwargs):
125 # for subclasses, set the default kind appropriately
126 self._meta.get_field("kind").default = self.KIND
127 super(Service, self).__init__(*args, **kwargs)
128
129 @classmethod
130 def get_service_objects(cls):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700131 return cls.objects.filter(kind=cls.KIND)
Scott Baker008a9962015-04-15 20:58:20 -0700132
Scott Baker16573d32015-07-24 15:36:02 -0700133 @classmethod
Scott Baker542cd6f2015-10-19 21:18:53 -0700134 def get_deleted_service_objects(cls):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700135 return cls.deleted_objects.filter(kind=cls.KIND)
Scott Baker542cd6f2015-10-19 21:18:53 -0700136
137 @classmethod
Scott Baker16573d32015-07-24 15:36:02 -0700138 def get_service_objects_by_user(cls, user):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700139 return cls.select_by_user(user).filter(kind=cls.KIND)
Scott Baker16573d32015-07-24 15:36:02 -0700140
141 @classmethod
142 def select_by_user(cls, user):
143 if user.is_admin:
144 return cls.objects.all()
145 else:
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700146 service_ids = [
147 sp.slice.id for sp in ServicePrivilege.objects.filter(user=user)]
Scott Baker16573d32015-07-24 15:36:02 -0700148 return cls.objects.filter(id__in=service_ids)
149
Scott Bakercce158d2015-12-07 22:20:40 -0800150 @property
151 def serviceattribute_dict(self):
152 attrs = {}
153 for attr in self.serviceattributes.all():
154 attrs[attr.name] = attr.value
155 return attrs
156
Siobhan Tully00353f72013-10-08 21:53:27 -0400157 def __unicode__(self): return u'%s' % (self.name)
158
Tony Mack9d2ea092015-04-29 12:23:10 -0400159 def can_update(self, user):
160 return user.can_update_service(self, allow=['admin'])
Scott Baker618a4892015-07-06 14:27:31 -0700161
Scott Baker98436732015-05-11 16:36:41 -0700162 def get_scalable_nodes(self, slice, max_per_node=None, exclusive_slices=[]):
163 """
164 Get a list of nodes that can be used to scale up a slice.
165
166 slice - slice to scale up
Tony Mack3de59e32015-08-19 11:58:18 -0400167 max_per_node - maximum numbers of instances that 'slice' can have on a single node
Scott Baker98436732015-05-11 16:36:41 -0700168 exclusive_slices - list of slices that must have no nodes in common with 'slice'.
169 """
170
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700171 # late import to get around order-of-imports constraint in __init__.py
172 from core.models import Node, Instance
Scott Baker98436732015-05-11 16:36:41 -0700173
174 nodes = list(Node.objects.all())
175
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700176 conflicting_instances = Instance.objects.filter(
177 slice__in=exclusive_slices)
178 conflicting_nodes = Node.objects.filter(
179 instances__in=conflicting_instances)
Scott Baker98436732015-05-11 16:36:41 -0700180
181 nodes = [x for x in nodes if x not in conflicting_nodes]
182
Tony Mack3de59e32015-08-19 11:58:18 -0400183 # If max_per_node is set, then limit the number of instances this slice
Scott Baker98436732015-05-11 16:36:41 -0700184 # can have on a single node.
185 if max_per_node:
186 acceptable_nodes = []
187 for node in nodes:
Tony Mack3de59e32015-08-19 11:58:18 -0400188 existing_count = node.instances.filter(slice=slice).count()
Scott Baker98436732015-05-11 16:36:41 -0700189 if existing_count < max_per_node:
190 acceptable_nodes.append(node)
191 nodes = acceptable_nodes
192
193 return nodes
194
195 def pick_node(self, slice, max_per_node=None, exclusive_slices=[]):
196 # Pick the best node to scale up a slice.
197
198 nodes = self.get_scalable_nodes(slice, max_per_node, exclusive_slices)
Tony Mack3de59e32015-08-19 11:58:18 -0400199 nodes = sorted(nodes, key=lambda node: node.instances.all().count())
Scott Baker98436732015-05-11 16:36:41 -0700200 if not nodes:
201 return None
202 return nodes[0]
203
204 def adjust_scale(self, slice_hint, scale, max_per_node=None, exclusive_slices=[]):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700205 # late import to get around order-of-imports constraint in __init__.py
206 from core.models import Instance
Scott Baker98436732015-05-11 16:36:41 -0700207
208 slices = [x for x in self.slices.all() if slice_hint in x.name]
209 for slice in slices:
Tony Mack3de59e32015-08-19 11:58:18 -0400210 while slice.instances.all().count() > scale:
211 s = slice.instances.all()[0]
212 # print "drop instance", s
Scott Baker98436732015-05-11 16:36:41 -0700213 s.delete()
214
Tony Mack3de59e32015-08-19 11:58:18 -0400215 while slice.instances.all().count() < scale:
Scott Baker98436732015-05-11 16:36:41 -0700216 node = self.pick_node(slice, max_per_node, exclusive_slices)
217 if not node:
218 # no more available nodes
219 break
220
221 image = slice.default_image
222 if not image:
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700223 raise XOSConfigurationError(
224 "No default_image for slice %s" % slice.name)
Scott Baker98436732015-05-11 16:36:41 -0700225
226 flavor = slice.default_flavor
227 if not flavor:
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700228 raise XOSConfigurationError(
229 "No default_flavor for slice %s" % slice.name)
Scott Baker98436732015-05-11 16:36:41 -0700230
Tony Mack3de59e32015-08-19 11:58:18 -0400231 s = Instance(slice=slice,
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700232 node=node,
233 creator=slice.creator,
234 image=image,
235 flavor=flavor,
236 deployment=node.site_deployment.deployment)
Scott Baker98436732015-05-11 16:36:41 -0700237 s.save()
238
Tony Mack3de59e32015-08-19 11:58:18 -0400239 # print "add instance", s
Tony Mack9d2ea092015-04-29 12:23:10 -0400240
Scott Baker8a67ae72015-12-09 22:54:52 -0800241 def get_vtn_src_nets(self):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700242 nets = []
Scott Baker8a67ae72015-12-09 22:54:52 -0800243 for slice in self.slices.all():
244 for ns in slice.networkslices.all():
245 if not ns.network:
246 continue
Scott Bakerf63cf1c2016-01-12 19:59:12 -0800247# if ns.network.template.access in ["direct", "indirect"]:
248# # skip access networks; we want to use the private network
249# continue
Scott Bakere6e78552016-04-11 21:26:39 -0700250 if "management" in ns.network.name:
251 # don't try to connect the management network to anything
252 continue
Scott Baker8a67ae72015-12-09 22:54:52 -0800253 if ns.network.name in ["wan_network", "lan_network"]:
254 # we don't want to attach to the vCPE's lan or wan network
255 # we only want to attach to its private network
256 # TODO: fix hard-coding of network name
257 continue
258 for cn in ns.network.controllernetworks.all():
259 if cn.net_id:
260 net = {"name": ns.network.name, "net_id": cn.net_id}
261 nets.append(net)
262 return nets
263
Scott Bakercbd718e2015-12-08 21:31:18 -0800264 def get_vtn_nets(self):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700265 nets = []
Scott Bakercbd718e2015-12-08 21:31:18 -0800266 for slice in self.slices.all():
267 for ns in slice.networkslices.all():
268 if not ns.network:
269 continue
Scott Baker8a67ae72015-12-09 22:54:52 -0800270 if ns.network.template.access not in ["direct", "indirect"]:
271 # skip anything that's not an access network
272 continue
Scott Bakercbd718e2015-12-08 21:31:18 -0800273 for cn in ns.network.controllernetworks.all():
274 if cn.net_id:
275 net = {"name": ns.network.name, "net_id": cn.net_id}
276 nets.append(net)
277 return nets
278
279 def get_vtn_dependencies_nets(self):
280 provider_nets = []
Scott Baker5380a442015-12-08 19:27:50 -0800281 for tenant in self.subscribed_tenants.all():
282 if tenant.provider_service:
Scott Bakercbd718e2015-12-08 21:31:18 -0800283 for net in tenant.provider_service.get_vtn_nets():
284 if not net in provider_nets:
285 provider_nets.append(net)
286 return provider_nets
287
288 def get_vtn_dependencies_ids(self):
289 return [x["net_id"] for x in self.get_vtn_dependencies_nets()]
290
291 def get_vtn_dependencies_names(self):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700292 return [x["name"] + "_" + x["net_id"] for x in self.get_vtn_dependencies_nets()]
Scott Bakercbd718e2015-12-08 21:31:18 -0800293
Scott Baker8a67ae72015-12-09 22:54:52 -0800294 def get_vtn_src_ids(self):
295 return [x["net_id"] for x in self.get_vtn_src_nets()]
Scott Bakercbd718e2015-12-08 21:31:18 -0800296
Scott Baker8a67ae72015-12-09 22:54:52 -0800297 def get_vtn_src_names(self):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700298 return [x["name"] + "_" + x["net_id"] for x in self.get_vtn_src_nets()]
Scott Baker5380a442015-12-08 19:27:50 -0800299
300
Siobhan Tully00353f72013-10-08 21:53:27 -0400301class ServiceAttribute(PlCoreBase):
Scott Bakercce158d2015-12-07 22:20:40 -0800302 name = models.CharField(help_text="Attribute Name", max_length=128)
Tony Mackd84b1ff2015-03-09 13:03:56 -0400303 value = StrippedCharField(help_text="Attribute Value", max_length=1024)
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700304 service = models.ForeignKey(Service, related_name='serviceattributes',
305 help_text="The Service this attribute is associated with")
306
Siobhan Tully00353f72013-10-08 21:53:27 -0400307
Tony Mack9d2ea092015-04-29 12:23:10 -0400308class ServiceRole(PlCoreBase):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700309 ROLE_CHOICES = (('admin', 'Admin'),)
Tony Mack9d2ea092015-04-29 12:23:10 -0400310 role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
311
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700312 def __unicode__(self): return u'%s' % (self.role)
313
Tony Mack9d2ea092015-04-29 12:23:10 -0400314
315class ServicePrivilege(PlCoreBase):
316 user = models.ForeignKey('User', related_name='serviceprivileges')
317 service = models.ForeignKey('Service', related_name='serviceprivileges')
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700318 role = models.ForeignKey('ServiceRole', related_name='serviceprivileges')
Tony Mack9d2ea092015-04-29 12:23:10 -0400319
320 class Meta:
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700321 unique_together = ('user', 'service', 'role')
Tony Mack9d2ea092015-04-29 12:23:10 -0400322
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700323 def __unicode__(self): return u'%s %s %s' % (
324 self.service, self.user, self.role)
Tony Mack9d2ea092015-04-29 12:23:10 -0400325
326 def can_update(self, user):
327 if not self.service.enabled:
328 raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
329 return self.service.can_update(user)
330
331 def save(self, *args, **kwds):
332 if not self.service.enabled:
333 raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
334 super(ServicePrivilege, self).save(*args, **kwds)
335
336 def delete(self, *args, **kwds):
337 if not self.service.enabled:
338 raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
Scott Baker4587b822015-07-01 18:29:08 -0700339 super(ServicePrivilege, self).delete(*args, **kwds)
340
Scott Baker16573d32015-07-24 15:36:02 -0700341 @classmethod
342 def select_by_user(cls, user):
Tony Mack9d2ea092015-04-29 12:23:10 -0400343 if user.is_admin:
Scott Baker16573d32015-07-24 15:36:02 -0700344 qs = cls.objects.all()
Tony Mack9d2ea092015-04-29 12:23:10 -0400345 else:
Scott Baker16573d32015-07-24 15:36:02 -0700346 qs = cls.objects.filter(user=user)
Scott Baker4587b822015-07-01 18:29:08 -0700347 return qs
348
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700349
Scott Baker82498c52015-07-13 13:07:27 -0700350class TenantRoot(PlCoreBase, AttributeMixin):
Scott Baker4587b822015-07-01 18:29:08 -0700351 """ A tenantRoot is one of the things that can sit at the root of a chain
352 of tenancy. This object represents a node.
353 """
354
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700355 KIND = "generic"
Scott Baker4587b822015-07-01 18:29:08 -0700356 kind = StrippedCharField(max_length=30, default=KIND)
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700357 name = StrippedCharField(
358 max_length=255, help_text="name", blank=True, null=True)
Scott Baker4587b822015-07-01 18:29:08 -0700359
Scott Bakerefcec632015-07-07 12:12:42 -0700360 service_specific_attribute = models.TextField(blank=True, null=True)
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700361 service_specific_id = StrippedCharField(
362 max_length=30, blank=True, null=True)
Scott Baker4587b822015-07-01 18:29:08 -0700363
Scott Bakerdb66fd32015-07-07 17:59:44 -0700364 def __init__(self, *args, **kwargs):
365 # for subclasses, set the default kind appropriately
366 self._meta.get_field("kind").default = self.KIND
367 super(TenantRoot, self).__init__(*args, **kwargs)
368
Scott Baker618a4892015-07-06 14:27:31 -0700369 def __unicode__(self):
370 if not self.name:
371 return u"%s-tenant_root-#%s" % (str(self.kind), str(self.id))
372 else:
373 return self.name
374
375 def can_update(self, user):
376 return user.can_update_tenant_root(self, allow=['admin'])
377
Scott Bakerefcec632015-07-07 12:12:42 -0700378 def get_subscribed_tenants(self, tenant_class):
379 ids = self.subscribed_tenants.filter(kind=tenant_class.KIND)
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700380 return tenant_class.objects.filter(id__in=ids)
Scott Bakerefcec632015-07-07 12:12:42 -0700381
382 def get_newest_subscribed_tenant(self, kind):
383 st = list(self.get_subscribed_tenants(kind))
384 if not st:
385 return None
386 return sorted(st, key=attrgetter('id'))[0]
387
388 @classmethod
389 def get_tenant_objects(cls):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700390 return cls.objects.filter(kind=cls.KIND)
Scott Bakerefcec632015-07-07 12:12:42 -0700391
Scott Baker16573d32015-07-24 15:36:02 -0700392 @classmethod
393 def get_tenant_objects_by_user(cls, user):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700394 return cls.select_by_user(user).filter(kind=cls.KIND)
Scott Baker16573d32015-07-24 15:36:02 -0700395
396 @classmethod
397 def select_by_user(cls, user):
398 if user.is_admin:
399 return cls.objects.all()
400 else:
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700401 tr_ids = [
402 trp.tenant_root.id for trp in TenantRootPrivilege.objects.filter(user=user)]
Scott Baker16573d32015-07-24 15:36:02 -0700403 return cls.objects.filter(id__in=tr_ids)
404
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700405 # helper function to be used in subclasses that want to ensure
406 # service_specific_id is unique
Scott Bakerdc880f32016-03-31 14:45:31 -0700407 def validate_unique_service_specific_id(self, none_okay=False):
408 if not none_okay and (self.service_specific_id is None):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700409 raise XOSMissingField("subscriber_specific_id is None, and it's a required field", fields={
410 "service_specific_id": "cannot be none"})
Scott Bakerdc880f32016-03-31 14:45:31 -0700411
412 if self.service_specific_id:
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700413 conflicts = self.get_tenant_objects().filter(
414 service_specific_id=self.service_specific_id)
Scott Bakerdc880f32016-03-31 14:45:31 -0700415 if self.pk:
Scott Bakere4c9d872016-04-01 16:28:41 -0700416 conflicts = conflicts.exclude(pk=self.pk)
Scott Bakerdc880f32016-03-31 14:45:31 -0700417 if conflicts:
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700418 raise XOSDuplicateKey("service_specific_id %s already exists" % self.service_specific_id, fields={
419 "service_specific_id": "duplicate key"})
420
Scott Bakerdc880f32016-03-31 14:45:31 -0700421
Scott Baker82498c52015-07-13 13:07:27 -0700422class Tenant(PlCoreBase, AttributeMixin):
Scott Baker8103d0f2015-04-10 16:42:26 -0700423 """ A tenant is a relationship between two entities, a subscriber and a
Scott Baker4587b822015-07-01 18:29:08 -0700424 provider. This object represents an edge.
Scott Baker8103d0f2015-04-10 16:42:26 -0700425
426 The subscriber can be a User, a Service, or a Tenant.
427
428 The provider is always a Service.
Scott Baker4587b822015-07-01 18:29:08 -0700429
430 TODO: rename "Tenant" to "Tenancy"
Scott Baker8103d0f2015-04-10 16:42:26 -0700431 """
Scott Baker008a9962015-04-15 20:58:20 -0700432
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700433 CONNECTIVITY_CHOICES = (('public', 'Public'),
434 ('private', 'Private'), ('na', 'Not Applicable'))
Scott Baker925a8fa2015-04-26 20:30:40 -0700435
Scott Baker008a9962015-04-15 20:58:20 -0700436 # when subclassing a service, redefine KIND to describe the new service
437 KIND = "generic"
438
439 kind = StrippedCharField(max_length=30, default=KIND)
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700440 provider_service = models.ForeignKey(
441 Service, related_name='provided_tenants')
Scott Baker4587b822015-07-01 18:29:08 -0700442
443 # The next four things are the various type of objects that can be subscribers of this Tenancy
444 # relationship. One and only one can be used at a time.
Scott Baker97ca9102016-04-13 16:56:39 -0700445 # XXX these should really be changed to GenericForeignKey
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700446 subscriber_service = models.ForeignKey(
447 Service, related_name='subscribed_tenants', blank=True, null=True)
448 subscriber_tenant = models.ForeignKey(
449 "Tenant", related_name='subscribed_tenants', blank=True, null=True)
450 subscriber_user = models.ForeignKey(
451 "User", related_name='subscribed_tenants', blank=True, null=True)
452 subscriber_root = models.ForeignKey(
453 "TenantRoot", related_name="subscribed_tenants", blank=True, null=True)
454 subscriber_network = models.ForeignKey(
455 "Network", related_name="subscribed_tenants", blank=True, null=True)
Scott Baker4587b822015-07-01 18:29:08 -0700456
457 # Service_specific_attribute and service_specific_id are opaque to XOS
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700458 service_specific_id = StrippedCharField(
459 max_length=30, blank=True, null=True)
Scott Baker76934d82015-05-06 19:49:31 -0700460 service_specific_attribute = models.TextField(blank=True, null=True)
Scott Baker4587b822015-07-01 18:29:08 -0700461
462 # Connect_method is only used by Coarse tenants
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700463 connect_method = models.CharField(
464 null=False, blank=False, max_length=30, choices=CONNECTIVITY_CHOICES, default="na")
Scott Baker8103d0f2015-04-10 16:42:26 -0700465
Scott Baker008a9962015-04-15 20:58:20 -0700466 def __init__(self, *args, **kwargs):
467 # for subclasses, set the default kind appropriately
468 self._meta.get_field("kind").default = self.KIND
469 super(Tenant, self).__init__(*args, **kwargs)
470
Scott Baker8103d0f2015-04-10 16:42:26 -0700471 def __unicode__(self):
Scott Bakerf996b762015-05-20 20:42:04 -0700472 return u"%s-tenant-%s" % (str(self.kind), str(self.id))
Scott Baker8103d0f2015-04-10 16:42:26 -0700473
Scott Baker008a9962015-04-15 20:58:20 -0700474 @classmethod
475 def get_tenant_objects(cls):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700476 return cls.objects.filter(kind=cls.KIND)
Scott Baker008a9962015-04-15 20:58:20 -0700477
Scott Bakere7fc9f52015-05-05 17:52:03 -0700478 @classmethod
Scott Baker16573d32015-07-24 15:36:02 -0700479 def get_tenant_objects_by_user(cls, user):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700480 return cls.select_by_user(user).filter(kind=cls.KIND)
Scott Baker16573d32015-07-24 15:36:02 -0700481
482 @classmethod
Scott Bakere7fc9f52015-05-05 17:52:03 -0700483 def get_deleted_tenant_objects(cls):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700484 return cls.deleted_objects.filter(kind=cls.KIND)
Scott Bakere7fc9f52015-05-05 17:52:03 -0700485
Scott Bakercce158d2015-12-07 22:20:40 -0800486 @property
487 def tenantattribute_dict(self):
488 attrs = {}
489 for attr in self.tenantattributes.all():
490 attrs[attr.name] = attr.value
491 return attrs
492
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700493 # helper function to be used in subclasses that want to ensure
494 # service_specific_id is unique
Scott Bakerd921e1c2015-04-20 14:24:29 -0700495 def validate_unique_service_specific_id(self):
496 if self.pk is None:
497 if self.service_specific_id is None:
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700498 raise XOSMissingField("subscriber_specific_id is None, and it's a required field", fields={
499 "service_specific_id": "cannot be none"})
Scott Bakerd921e1c2015-04-20 14:24:29 -0700500
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700501 conflicts = self.get_tenant_objects().filter(
502 service_specific_id=self.service_specific_id)
Scott Bakerd921e1c2015-04-20 14:24:29 -0700503 if conflicts:
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700504 raise XOSDuplicateKey("service_specific_id %s already exists" % self.service_specific_id, fields={
505 "service_specific_id": "duplicate key"})
Scott Bakerd921e1c2015-04-20 14:24:29 -0700506
Scott Baker618a4892015-07-06 14:27:31 -0700507 def save(self, *args, **kwargs):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700508 subCount = sum([1 for e in [self.subscriber_service, self.subscriber_tenant,
509 self.subscriber_user, self.subscriber_root] if e is not None])
Scott Baker618a4892015-07-06 14:27:31 -0700510 if (subCount > 1):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700511 raise XOSConflictingField(
512 "Only one of subscriber_service, subscriber_tenant, subscriber_user, subscriber_root should be set")
Scott Baker618a4892015-07-06 14:27:31 -0700513
514 super(Tenant, self).save(*args, **kwargs)
515
516 def get_subscribed_tenants(self, tenant_class):
517 ids = self.subscribed_tenants.filter(kind=tenant_class.KIND)
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700518 return tenant_class.objects.filter(id__in=ids)
Scott Baker618a4892015-07-06 14:27:31 -0700519
520 def get_newest_subscribed_tenant(self, kind):
521 st = list(self.get_subscribed_tenants(kind))
522 if not st:
523 return None
524 return sorted(st, key=attrgetter('id'))[0]
525
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700526
Scott Baker534dd2a2015-11-18 20:58:08 -0800527class Scheduler(object):
528 # XOS Scheduler Abstract Base Class
529 # Used to implement schedulers that pick which node to put instances on
530
531 def __init__(self, slice):
532 self.slice = slice
533
534 def pick(self):
535 # this method should return a tuple (node, parent)
536 # node is the node to instantiate on
537 # parent is for container_vm instances only, and is the VM that will
538 # hold the container
539
540 raise Exception("Abstract Base")
541
Scott Baker534dd2a2015-11-18 20:58:08 -0800542
Scott Baker534dd2a2015-11-18 20:58:08 -0800543class LeastLoadedNodeScheduler(Scheduler):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700544 # This scheduler always return the node with the fewest number of
545 # instances.
Scott Baker534dd2a2015-11-18 20:58:08 -0800546
Scott Baker6526f552016-03-04 11:29:02 -0800547 def __init__(self, slice, label=None):
Scott Baker534dd2a2015-11-18 20:58:08 -0800548 super(LeastLoadedNodeScheduler, self).__init__(slice)
Scott Baker6526f552016-03-04 11:29:02 -0800549 self.label = label
Scott Baker534dd2a2015-11-18 20:58:08 -0800550
551 def pick(self):
552 from core.models import Node
Pingping Linfa30bae2016-03-03 09:52:24 -0800553 if not self.slice.default_node:
554 nodes = list(Node.objects.all())
555 nodes = sorted(nodes, key=lambda node: node.instances.all().count())
556 else:
557 nodes = list(Node.objects.filter(name = self.slice.default_node))
Matteo Scandolo1d7ff812016-04-21 09:02:08 -0700558
Scott Baker6526f552016-03-04 11:29:02 -0800559 if self.label:
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700560 nodes = nodes.filter(nodelabels__name=self.label)
Scott Baker6526f552016-03-04 11:29:02 -0800561
562 nodes = list(nodes)
563
564 if not nodes:
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700565 raise Exception(
566 "LeastLoadedNodeScheduler: No suitable nodes to pick from")
Scott Bakercce158d2015-12-07 22:20:40 -0800567
Scott Baker534dd2a2015-11-18 20:58:08 -0800568 # TODO: logic to filter nodes by which nodes are up, and which
569 # nodes the slice can instantiate on.
Pingping Linfa30bae2016-03-03 09:52:24 -0800570# nodes = sorted(nodes, key=lambda node: node.instances.all().count())
Scott Baker534dd2a2015-11-18 20:58:08 -0800571 return [nodes[0], None]
572
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700573
Scott Baker534dd2a2015-11-18 20:58:08 -0800574class ContainerVmScheduler(Scheduler):
575 # This scheduler picks a VM in the slice with the fewest containers inside
576 # of it. If no VMs are suitable, then it creates a VM.
577
Scott Baker534dd2a2015-11-18 20:58:08 -0800578 MAX_VM_PER_CONTAINER = 10
579
580 def __init__(self, slice):
581 super(ContainerVmScheduler, self).__init__(slice)
582
583 @property
584 def image(self):
585 from core.models import Image
586
Scott Bakera8188a72016-05-19 17:54:52 -0700587 # If slice has default_image set then use it
588 if self.slice.default_image:
589 return self.slice.default_image
Scott Baker534dd2a2015-11-18 20:58:08 -0800590
Scott Bakera8188a72016-05-19 17:54:52 -0700591 raise XOPSProgrammingError("Please set a default image for %s" % self.slice.name)
Scott Baker534dd2a2015-11-18 20:58:08 -0800592
593 def make_new_instance(self):
594 from core.models import Instance, Flavor
595
596 flavors = Flavor.objects.filter(name="m1.small")
597 if not flavors:
598 raise XOSConfigurationError("No m1.small flavor")
599
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700600 (node, parent) = LeastLoadedNodeScheduler(self.slice).pick()
Scott Baker534dd2a2015-11-18 20:58:08 -0800601
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700602 instance = Instance(slice=self.slice,
603 node=node,
604 image=self.image,
605 creator=self.slice.creator,
606 deployment=node.site_deployment.deployment,
607 flavor=flavors[0],
608 isolation="vm",
609 parent=parent)
Scott Baker534dd2a2015-11-18 20:58:08 -0800610 instance.save()
611 # We rely on a special naming convention to identify the VMs that will
612 # hole containers.
613 instance.name = "%s-outer-%s" % (instance.slice.name, instance.id)
614 instance.save()
615 return instance
616
617 def pick(self):
618 from core.models import Instance, Flavor
619
620 for vm in self.slice.instances.filter(isolation="vm"):
621 avail_vms = []
622 if (vm.name.startswith("%s-outer-" % self.slice.name)):
623 container_count = Instance.objects.filter(parent=vm).count()
624 if (container_count < self.MAX_VM_PER_CONTAINER):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700625 avail_vms.append((vm, container_count))
Scott Baker534dd2a2015-11-18 20:58:08 -0800626 # sort by least containers-per-vm
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700627 avail_vms = sorted(avail_vms, key=lambda x: x[1])
Scott Baker534dd2a2015-11-18 20:58:08 -0800628 print "XXX", avail_vms
629 if avail_vms:
630 instance = avail_vms[0][0]
631 return (instance.node, instance)
632
633 instance = self.make_new_instance()
634 return (instance.node, instance)
635
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700636
Scott Bakere458afd2015-09-09 16:36:06 -0700637class TenantWithContainer(Tenant):
638 """ A tenant that manages a container """
639
Scott Bakere458afd2015-09-09 16:36:06 -0700640 class Meta:
641 proxy = True
642
643 def __init__(self, *args, **kwargs):
644 super(TenantWithContainer, self).__init__(*args, **kwargs)
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700645 self.cached_instance = None
Tony Mack6a782f92015-09-13 22:50:39 +0000646 self.orig_instance_id = self.get_initial_attribute("instance_id")
Scott Bakere274f6b2015-11-02 20:54:28 -0800647
Scott Bakere458afd2015-09-09 16:36:06 -0700648 @property
Tony Mack6a782f92015-09-13 22:50:39 +0000649 def instance(self):
650 from core.models import Instance
651 if getattr(self, "cached_instance", None):
652 return self.cached_instance
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700653 instance_id = self.get_attribute("instance_id")
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600654 if not instance_id:
655 return None
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700656 instances = Instance.objects.filter(id=instance_id)
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600657 if not instances:
658 return None
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700659 instance = instances[0]
Tony Mack6a782f92015-09-13 22:50:39 +0000660 instance.caller = self.creator
661 self.cached_instance = instance
662 return instance
Scott Bakere458afd2015-09-09 16:36:06 -0700663
Tony Mack6a782f92015-09-13 22:50:39 +0000664 @instance.setter
665 def instance(self, value):
Scott Bakere458afd2015-09-09 16:36:06 -0700666 if value:
667 value = value.id
Tony Mack6a782f92015-09-13 22:50:39 +0000668 if (value != self.get_attribute("instance_id", None)):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700669 self.cached_instance = None
Tony Mack6a782f92015-09-13 22:50:39 +0000670 self.set_attribute("instance_id", value)
Scott Bakere458afd2015-09-09 16:36:06 -0700671
Scott Bakere274f6b2015-11-02 20:54:28 -0800672 @property
Scott Bakerbdda0be2016-02-10 12:23:53 -0800673 def external_hostname(self):
674 return self.get_attribute("external_hostname", "")
675
676 @external_hostname.setter
677 def external_hostname(self, value):
678 self.set_attribute("external_hostname", value)
679
680 @property
681 def external_container(self):
682 return self.get_attribute("external_container", "")
683
684 @external_container.setter
685 def external_container(self, value):
686 self.set_attribute("external_container", value)
687
688 @property
Scott Bakere458afd2015-09-09 16:36:06 -0700689 def creator(self):
690 from core.models import User
691 if getattr(self, "cached_creator", None):
692 return self.cached_creator
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700693 creator_id = self.get_attribute("creator_id")
Scott Bakere458afd2015-09-09 16:36:06 -0700694 if not creator_id:
695 return None
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700696 users = User.objects.filter(id=creator_id)
Scott Bakere458afd2015-09-09 16:36:06 -0700697 if not users:
698 return None
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700699 user = users[0]
Scott Bakere458afd2015-09-09 16:36:06 -0700700 self.cached_creator = users[0]
701 return user
702
703 @creator.setter
704 def creator(self, value):
705 if value:
706 value = value.id
707 if (value != self.get_attribute("creator_id", None)):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700708 self.cached_creator = None
Scott Bakere458afd2015-09-09 16:36:06 -0700709 self.set_attribute("creator_id", value)
710
711 @property
712 def image(self):
713 from core.models import Image
714 # Implement the logic here to pick the image that should be used when
715 # instantiating the VM that will hold the container.
Scott Bakerabad4c92016-05-25 10:02:29 -0700716
Scott Bakercfaf0d32015-11-16 22:51:02 -0800717 slice = self.provider_service.slices.all()
718 if not slice:
719 raise XOSProgrammingError("provider service has no slice")
720 slice = slice[0]
721
Scott Bakerabad4c92016-05-25 10:02:29 -0700722 # If slice has default_image set then use it
723 if slice.default_image:
724 return slice.default_image
Scott Bakere458afd2015-09-09 16:36:06 -0700725
Scott Bakera8188a72016-05-19 17:54:52 -0700726 raise XOPSProgrammingError("Please set a default image for %s" % self.slice.name)
Scott Bakere274f6b2015-11-02 20:54:28 -0800727
Scott Bakercfaf0d32015-11-16 22:51:02 -0800728 def save_instance(self, instance):
729 # Override this function to do custom pre-save or post-save processing,
730 # such as creating ports for containers.
731 instance.save()
Scott Bakere274f6b2015-11-02 20:54:28 -0800732
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600733 def pick_least_loaded_instance_in_slice(self, slices):
734 for slice in slices:
735 if slice.instances.all().count() > 0:
736 for instance in slice.instances.all():
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700737 # Pick the first instance that has lesser than 5 tenants
738 if self.count_of_tenants_of_an_instance(instance) < 5:
739 return instance
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600740 return None
741
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700742 # TODO: Ideally the tenant count for an instance should be maintained using a
743 # many-to-one relationship attribute, however this model being proxy, it does
744 # not permit any new attributes to be defined. Find if any better solutions
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600745 def count_of_tenants_of_an_instance(self, instance):
746 tenant_count = 0
747 for tenant in self.get_tenant_objects().all():
748 if tenant.get_attribute("instance_id", None) == instance.id:
749 tenant_count += 1
750 return tenant_count
751
Scott Bakercfaf0d32015-11-16 22:51:02 -0800752 def manage_container(self):
Tony Mack6a782f92015-09-13 22:50:39 +0000753 from core.models import Instance, Flavor
Scott Bakere458afd2015-09-09 16:36:06 -0700754
755 if self.deleted:
756 return
757
Tony Mack6a782f92015-09-13 22:50:39 +0000758 if (self.instance is not None) and (self.instance.image != self.image):
759 self.instance.delete()
760 self.instance = None
Scott Bakere458afd2015-09-09 16:36:06 -0700761
Tony Mack6a782f92015-09-13 22:50:39 +0000762 if self.instance is None:
Scott Bakere458afd2015-09-09 16:36:06 -0700763 if not self.provider_service.slices.count():
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600764 raise XOSConfigurationError("The service has no slices")
Scott Bakere458afd2015-09-09 16:36:06 -0700765
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600766 new_instance_created = False
767 instance = None
768 if self.get_attribute("use_same_instance_for_multiple_tenants", default=False):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700769 # Find if any existing instances can be used for this tenant
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600770 slices = self.provider_service.slices.all()
771 instance = self.pick_least_loaded_instance_in_slice(slices)
Scott Bakere458afd2015-09-09 16:36:06 -0700772
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600773 if not instance:
Srikanth Vavilapalli71120a92015-11-17 13:41:38 -0600774 slice = self.provider_service.slices.all()[0]
Scott Bakercfaf0d32015-11-16 22:51:02 -0800775
Srikanth Vavilapalli2ab2d072016-02-15 01:18:09 -0500776 flavor = slice.default_flavor
777 if not flavor:
778 flavors = Flavor.objects.filter(name="m1.small")
779 if not flavors:
780 raise XOSConfigurationError("No m1.small flavor")
781 flavor = flavors[0]
782
Srikanth Vavilapalli71120a92015-11-17 13:41:38 -0600783 if slice.default_isolation == "container_vm":
Scott Baker534dd2a2015-11-18 20:58:08 -0800784 (node, parent) = ContainerVmScheduler(slice).pick()
Srikanth Vavilapalli71120a92015-11-17 13:41:38 -0600785 else:
Scott Baker534dd2a2015-11-18 20:58:08 -0800786 (node, parent) = LeastLoadedNodeScheduler(slice).pick()
Scott Bakercfaf0d32015-11-16 22:51:02 -0800787
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700788 instance = Instance(slice=slice,
789 node=node,
790 image=self.image,
791 creator=self.creator,
792 deployment=node.site_deployment.deployment,
793 flavor=flavor,
794 isolation=slice.default_isolation,
795 parent=parent)
Srikanth Vavilapalli71120a92015-11-17 13:41:38 -0600796 self.save_instance(instance)
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600797 new_instance_created = True
Scott Bakere458afd2015-09-09 16:36:06 -0700798
799 try:
Tony Mack6a782f92015-09-13 22:50:39 +0000800 self.instance = instance
Scott Bakere458afd2015-09-09 16:36:06 -0700801 super(TenantWithContainer, self).save()
802 except:
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600803 if new_instance_created:
804 instance.delete()
Scott Bakere458afd2015-09-09 16:36:06 -0700805 raise
806
807 def cleanup_container(self):
Tony Mack6a782f92015-09-13 22:50:39 +0000808 if self.instance:
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600809 if self.get_attribute("use_same_instance_for_multiple_tenants", default=False):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700810 # Delete the instance only if this is last tenant in that
811 # instance
812 tenant_count = self.count_of_tenants_of_an_instance(
813 self.instance)
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600814 if tenant_count == 0:
815 self.instance.delete()
816 else:
817 self.instance.delete()
Tony Mack6a782f92015-09-13 22:50:39 +0000818 self.instance = None
Scott Baker618a4892015-07-06 14:27:31 -0700819
Scott Bakeree83f2a2015-12-10 23:23:07 -0800820 def save(self, *args, **kwargs):
821 if (not self.creator) and (hasattr(self, "caller")) and (self.caller):
822 self.creator = self.caller
823 super(TenantWithContainer, self).save(*args, **kwargs)
824
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700825
Scott Baker925a8fa2015-04-26 20:30:40 -0700826class CoarseTenant(Tenant):
Scott Baker4587b822015-07-01 18:29:08 -0700827 """ TODO: rename "CoarseTenant" --> "StaticTenant" """
Scott Baker925a8fa2015-04-26 20:30:40 -0700828 class Meta:
829 proxy = True
Siobhan Tully00353f72013-10-08 21:53:27 -0400830
Scott Baker2461bec2015-08-14 09:10:11 -0700831 KIND = COARSE_KIND
Scott Baker925a8fa2015-04-26 20:30:40 -0700832
833 def save(self, *args, **kwargs):
834 if (not self.subscriber_service):
835 raise XOSValidationError("subscriber_service cannot be null")
836 if (self.subscriber_tenant or self.subscriber_user):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700837 raise XOSValidationError(
838 "subscriber_tenant and subscriber_user must be null")
Scott Baker925a8fa2015-04-26 20:30:40 -0700839
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700840 super(CoarseTenant, self).save()
841
Scott Baker4587b822015-07-01 18:29:08 -0700842
843class Subscriber(TenantRoot):
844 """ Intermediate class for TenantRoots that are to be Subscribers """
845
846 class Meta:
847 proxy = True
848
849 KIND = "Subscriber"
850
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700851
Scott Baker4587b822015-07-01 18:29:08 -0700852class Provider(TenantRoot):
853 """ Intermediate class for TenantRoots that are to be Providers """
854
855 class Meta:
856 proxy = True
857
858 KIND = "Provider"
859
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700860
Scott Baker462a1d92015-10-15 15:59:19 -0700861class TenantAttribute(PlCoreBase):
Scott Baker907f9912015-10-20 17:12:36 -0700862 name = models.CharField(help_text="Attribute Name", max_length=128)
Scott Baker462a1d92015-10-15 15:59:19 -0700863 value = models.TextField(help_text="Attribute Value")
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700864 tenant = models.ForeignKey(Tenant, related_name='tenantattributes',
865 help_text="The Tenant this attribute is associated with")
Scott Baker462a1d92015-10-15 15:59:19 -0700866
Scott Baker1271a4d2016-04-06 14:34:49 -0700867 def __unicode__(self): return u'%s-%s' % (self.name, self.id)
868
Scott Baker4587b822015-07-01 18:29:08 -0700869
870class TenantRootRole(PlCoreBase):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700871 ROLE_CHOICES = (('admin', 'Admin'), ('access', 'Access'))
Scott Baker4587b822015-07-01 18:29:08 -0700872
873 role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
874
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700875 def __unicode__(self): return u'%s' % (self.role)
876
Scott Baker4587b822015-07-01 18:29:08 -0700877
878class TenantRootPrivilege(PlCoreBase):
879 user = models.ForeignKey('User', related_name="tenant_root_privileges")
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700880 tenant_root = models.ForeignKey(
881 'TenantRoot', related_name="tenant_root_privileges")
882 role = models.ForeignKey(
883 'TenantRootRole', related_name="tenant_root_privileges")
Scott Baker4587b822015-07-01 18:29:08 -0700884
885 class Meta:
886 unique_together = ('user', 'tenant_root', 'role')
887
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700888 def __unicode__(self): return u'%s %s %s' % (
889 self.tenant_root, self.user, self.role)
Scott Baker4587b822015-07-01 18:29:08 -0700890
891 def save(self, *args, **kwds):
892 if not self.user.is_active:
893 raise PermissionDenied, "Cannot modify role(s) of a disabled user"
Scott Baker335882a2015-07-24 10:15:31 -0700894 super(TenantRootPrivilege, self).save(*args, **kwds)
Scott Baker4587b822015-07-01 18:29:08 -0700895
896 def can_update(self, user):
Scott Baker335882a2015-07-24 10:15:31 -0700897 return user.can_update_tenant_root_privilege(self)
Scott Baker4587b822015-07-01 18:29:08 -0700898
Scott Baker16573d32015-07-24 15:36:02 -0700899 @classmethod
900 def select_by_user(cls, user):
Scott Baker4587b822015-07-01 18:29:08 -0700901 if user.is_admin:
Scott Baker5116b302015-07-24 15:48:03 -0700902 return cls.objects.all()
Scott Baker4587b822015-07-01 18:29:08 -0700903 else:
Scott Baker5116b302015-07-24 15:48:03 -0700904 # User can see his own privilege
905 trp_ids = [trp.id for trp in cls.objects.filter(user=user)]
906
907 # A slice admin can see the SlicePrivileges for his Slice
908 for priv in cls.objects.filter(user=user, role__role="admin"):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700909 trp_ids.extend(
910 [trp.id for trp in cls.objects.filter(tenant_root=priv.tenant_root)])
Scott Baker5116b302015-07-24 15:48:03 -0700911
912 return cls.objects.filter(id__in=trp_ids)
913
Jeremy Mowery95635132016-04-15 17:39:49 -0700914
Jeremy Moweryb31bd9e2016-03-14 23:59:11 -0700915class TenantRole(PlCoreBase):
Jeremy Mowery98e97d72016-04-15 00:05:27 -0700916 """A TenantRole option."""
Jeremy Mowery95635132016-04-15 17:39:49 -0700917 ROLE_CHOICES = (('admin', 'Admin'), ('access', 'Access'))
Jeremy Moweryb31bd9e2016-03-14 23:59:11 -0700918 role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
Jeremy Mowery95635132016-04-15 17:39:49 -0700919
920 def __unicode__(self): return u'%s' % (self.role)
921
Jeremy Moweryb31bd9e2016-03-14 23:59:11 -0700922
923class TenantPrivilege(PlCoreBase):
Jeremy Mowery98e97d72016-04-15 00:05:27 -0700924 """"A TenantPrivilege which defines how users can access a particular Tenant.
925
926 Attributes:
927 id (models.AutoField): The ID of the privilege.
928 user (models.ForeignKey): A Foreign Key to the a User.
929 tenant (models.ForeignKey): A ForeignKey to the Tenant.
930 role (models.ForeignKey): A ForeignKey to the TenantRole.
931 """
Jeremy Moweryc86352a2016-04-10 23:00:54 -0700932 id = models.AutoField(primary_key=True)
Jeremy Mowery5d06a232016-04-04 22:30:44 -0700933 user = models.ForeignKey('User', related_name="tenantprivileges")
934 tenant = models.ForeignKey('Tenant', related_name="tenantprivileges")
935 role = models.ForeignKey('TenantRole', related_name="tenantprivileges")
Jeremy Moweryb31bd9e2016-03-14 23:59:11 -0700936
Jeremy Mowery95635132016-04-15 17:39:49 -0700937 def __unicode__(self): return u'%s %s %s' % (
938 self.tenant, self.user, self.role)
Jeremy Moweryb31bd9e2016-03-14 23:59:11 -0700939
940 def save(self, *args, **kwds):
941 if not self.user.is_active:
942 raise PermissionDenied, "Cannot modify role(s) of a disabled user"
943 super(TenantPrivilege, self).save(*args, **kwds)
944
945 def can_update(self, user):
946 return user.can_update_tenant_privilege(self)
947
948 @classmethod
949 def select_by_user(cls, user):
950 if user.is_admin:
951 return cls.objects.all()
952 else:
953 # User can see his own privilege
954 trp_ids = [trp.id for trp in cls.objects.filter(user=user)]
955
956 # A tenant admin can see the TenantPrivileges for their Tenants
957 for priv in cls.objects.filter(user=user, role__role="admin"):
Jeremy Mowery95635132016-04-15 17:39:49 -0700958 trp_ids.extend(
959 [trp.id for trp in cls.objects.filter(tenant=priv.tenant)])
Jeremy Moweryb31bd9e2016-03-14 23:59:11 -0700960
961 return cls.objects.filter(id__in=trp_ids)