blob: 104b4cf1afbb8dd54a3d06ae0f0c41871b8c2a04 [file] [log] [blame]
Scott Baker7211f5b2015-04-14 17:18:51 -07001import json
Jeremy Mowery46d96d12016-04-17 20:56:54 -07002from operator import attrgetter
Siobhan Tully00353f72013-10-08 21:53:27 -04003
Jeremy Mowery46d96d12016-04-17 20:56:54 -07004from core.models import PlCoreBase, PlCoreBaseManager, SingletonModel
5from core.models.plcorebase import StrippedCharField
6from django.db import models
7from xos.exceptions import *
8
9COARSE_KIND = "coarse"
10
Scott Bakerc24f86d2015-08-14 09:10:11 -070011
Scott Baker9d1c6d92015-07-13 13:07:27 -070012class AttributeMixin(object):
Jeremy Mowery46d96d12016-04-17 20:56:54 -070013 # helper for extracting things from a json-encoded
14 # service_specific_attribute
15
Scott Baker9d1c6d92015-07-13 13:07:27 -070016 def get_attribute(self, name, default=None):
17 if self.service_specific_attribute:
18 attributes = json.loads(self.service_specific_attribute)
19 else:
20 attributes = {}
21 return attributes.get(name, default)
22
23 def set_attribute(self, name, value):
24 if self.service_specific_attribute:
25 attributes = json.loads(self.service_specific_attribute)
26 else:
27 attributes = {}
Jeremy Mowery46d96d12016-04-17 20:56:54 -070028 attributes[name] = value
Scott Baker9d1c6d92015-07-13 13:07:27 -070029 self.service_specific_attribute = json.dumps(attributes)
30
31 def get_initial_attribute(self, name, default=None):
32 if self._initial["service_specific_attribute"]:
Jeremy Mowery46d96d12016-04-17 20:56:54 -070033 attributes = json.loads(
34 self._initial["service_specific_attribute"])
Scott Baker9d1c6d92015-07-13 13:07:27 -070035 else:
36 attributes = {}
37 return attributes.get(name, default)
38
Scott Bakereb098e62015-07-13 13:54:06 -070039 @classmethod
Scott Baker440d1152016-03-01 20:10:24 -080040 def get_default_attribute(cls, name):
41 for (attrname, default) in cls.simple_attributes:
Jeremy Mowery46d96d12016-04-17 20:56:54 -070042 if attrname == name:
Scott Baker440d1152016-03-01 20:10:24 -080043 return default
Jeremy Mowery46d96d12016-04-17 20:56:54 -070044 if hasattr(cls, "default_attributes"):
Scott Baker5d45b8a2016-03-31 11:46:02 -070045 if name in cls.default_attributes:
46 return cls.default_attributes[name]
47
48 return None
Scott Baker440d1152016-03-01 20:10:24 -080049
50 @classmethod
Scott Bakereb098e62015-07-13 13:54:06 -070051 def setup_simple_attributes(cls):
52 for (attrname, default) in cls.simple_attributes:
Scott Bakere2879d32015-07-13 14:27:51 -070053 setattr(cls, attrname, property(lambda self, attrname=attrname, default=default: self.get_attribute(attrname, default),
Jeremy Mowery46d96d12016-04-17 20:56:54 -070054 lambda self, value, attrname=attrname: self.set_attribute(
55 attrname, value),
Scott Bakere2879d32015-07-13 14:27:51 -070056 None,
57 attrname))
Scott Bakereb098e62015-07-13 13:54:06 -070058
Jeremy Mowery46d96d12016-04-17 20:56:54 -070059
Scott Baker9d1c6d92015-07-13 13:07:27 -070060class Service(PlCoreBase, AttributeMixin):
Scott Baker0d306722015-04-15 20:58:20 -070061 # when subclassing a service, redefine KIND to describe the new service
62 KIND = "generic"
63
Jeremy Mowery46d96d12016-04-17 20:56:54 -070064 description = models.TextField(
65 max_length=254, null=True, blank=True, help_text="Description of Service")
Siobhan Tully00353f72013-10-08 21:53:27 -040066 enabled = models.BooleanField(default=True)
Jeremy Mowery46d96d12016-04-17 20:56:54 -070067 kind = StrippedCharField(
68 max_length=30, help_text="Kind of service", default=KIND)
Tony Mack50e12212015-03-09 13:03:56 -040069 name = StrippedCharField(max_length=30, help_text="Service Name")
Scott Bakere332de52016-05-02 09:38:24 -070070 versionNumber = StrippedCharField(blank=True, null=True,
Jeremy Mowery46d96d12016-04-17 20:56:54 -070071 max_length=30, help_text="Version of Service Definition")
Siobhan Tullycf04fb62014-01-11 11:25:57 -050072 published = models.BooleanField(default=True)
Tony Mack50e12212015-03-09 13:03:56 -040073 view_url = StrippedCharField(blank=True, null=True, max_length=1024)
74 icon_url = StrippedCharField(blank=True, null=True, max_length=1024)
Jeremy Mowery46d96d12016-04-17 20:56:54 -070075 public_key = models.TextField(
76 null=True, blank=True, max_length=1024, help_text="Public key string")
Scott Bakerdc63fb32015-11-12 16:22:52 -080077 private_key_fn = StrippedCharField(blank=True, null=True, max_length=1024)
Siobhan Tully00353f72013-10-08 21:53:27 -040078
Scott Bakerb9040e92015-07-13 12:33:28 -070079 # Service_specific_attribute and service_specific_id are opaque to XOS
Jeremy Mowery46d96d12016-04-17 20:56:54 -070080 service_specific_id = StrippedCharField(
81 max_length=30, blank=True, null=True)
Scott Bakerb9040e92015-07-13 12:33:28 -070082 service_specific_attribute = models.TextField(blank=True, null=True)
83
Scott Baker0d306722015-04-15 20:58:20 -070084 def __init__(self, *args, **kwargs):
85 # for subclasses, set the default kind appropriately
86 self._meta.get_field("kind").default = self.KIND
87 super(Service, self).__init__(*args, **kwargs)
88
89 @classmethod
90 def get_service_objects(cls):
Jeremy Mowery46d96d12016-04-17 20:56:54 -070091 return cls.objects.filter(kind=cls.KIND)
Scott Baker0d306722015-04-15 20:58:20 -070092
Scott Baker27de6012015-07-24 15:36:02 -070093 @classmethod
Scott Bakercd32ad02015-10-19 21:18:53 -070094 def get_deleted_service_objects(cls):
Jeremy Mowery46d96d12016-04-17 20:56:54 -070095 return cls.deleted_objects.filter(kind=cls.KIND)
Scott Bakercd32ad02015-10-19 21:18:53 -070096
97 @classmethod
Scott Baker27de6012015-07-24 15:36:02 -070098 def get_service_objects_by_user(cls, user):
Jeremy Mowery46d96d12016-04-17 20:56:54 -070099 return cls.select_by_user(user).filter(kind=cls.KIND)
Scott Baker27de6012015-07-24 15:36:02 -0700100
101 @classmethod
102 def select_by_user(cls, user):
103 if user.is_admin:
104 return cls.objects.all()
105 else:
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700106 service_ids = [
107 sp.slice.id for sp in ServicePrivilege.objects.filter(user=user)]
Scott Baker27de6012015-07-24 15:36:02 -0700108 return cls.objects.filter(id__in=service_ids)
109
Scott Bakerbcea8cf2015-12-07 22:20:40 -0800110 @property
111 def serviceattribute_dict(self):
112 attrs = {}
113 for attr in self.serviceattributes.all():
114 attrs[attr.name] = attr.value
115 return attrs
116
Siobhan Tully00353f72013-10-08 21:53:27 -0400117 def __unicode__(self): return u'%s' % (self.name)
118
Tony Mack950b4492015-04-29 12:23:10 -0400119 def can_update(self, user):
120 return user.can_update_service(self, allow=['admin'])
Scott Bakerb2385622015-07-06 14:27:31 -0700121
Scott Baker25757222015-05-11 16:36:41 -0700122 def get_scalable_nodes(self, slice, max_per_node=None, exclusive_slices=[]):
123 """
124 Get a list of nodes that can be used to scale up a slice.
125
126 slice - slice to scale up
Tony Mackd8515472015-08-19 11:58:18 -0400127 max_per_node - maximum numbers of instances that 'slice' can have on a single node
Scott Baker25757222015-05-11 16:36:41 -0700128 exclusive_slices - list of slices that must have no nodes in common with 'slice'.
129 """
130
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700131 # late import to get around order-of-imports constraint in __init__.py
132 from core.models import Node, Instance
Scott Baker25757222015-05-11 16:36:41 -0700133
134 nodes = list(Node.objects.all())
135
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700136 conflicting_instances = Instance.objects.filter(
137 slice__in=exclusive_slices)
138 conflicting_nodes = Node.objects.filter(
139 instances__in=conflicting_instances)
Scott Baker25757222015-05-11 16:36:41 -0700140
141 nodes = [x for x in nodes if x not in conflicting_nodes]
142
Tony Mackd8515472015-08-19 11:58:18 -0400143 # If max_per_node is set, then limit the number of instances this slice
Scott Baker25757222015-05-11 16:36:41 -0700144 # can have on a single node.
145 if max_per_node:
146 acceptable_nodes = []
147 for node in nodes:
Tony Mackd8515472015-08-19 11:58:18 -0400148 existing_count = node.instances.filter(slice=slice).count()
Scott Baker25757222015-05-11 16:36:41 -0700149 if existing_count < max_per_node:
150 acceptable_nodes.append(node)
151 nodes = acceptable_nodes
152
153 return nodes
154
155 def pick_node(self, slice, max_per_node=None, exclusive_slices=[]):
156 # Pick the best node to scale up a slice.
157
158 nodes = self.get_scalable_nodes(slice, max_per_node, exclusive_slices)
Tony Mackd8515472015-08-19 11:58:18 -0400159 nodes = sorted(nodes, key=lambda node: node.instances.all().count())
Scott Baker25757222015-05-11 16:36:41 -0700160 if not nodes:
161 return None
162 return nodes[0]
163
164 def adjust_scale(self, slice_hint, scale, max_per_node=None, exclusive_slices=[]):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700165 # late import to get around order-of-imports constraint in __init__.py
166 from core.models import Instance
Scott Baker25757222015-05-11 16:36:41 -0700167
168 slices = [x for x in self.slices.all() if slice_hint in x.name]
169 for slice in slices:
Tony Mackd8515472015-08-19 11:58:18 -0400170 while slice.instances.all().count() > scale:
171 s = slice.instances.all()[0]
172 # print "drop instance", s
Scott Baker25757222015-05-11 16:36:41 -0700173 s.delete()
174
Tony Mackd8515472015-08-19 11:58:18 -0400175 while slice.instances.all().count() < scale:
Scott Baker25757222015-05-11 16:36:41 -0700176 node = self.pick_node(slice, max_per_node, exclusive_slices)
177 if not node:
178 # no more available nodes
179 break
180
181 image = slice.default_image
182 if not image:
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700183 raise XOSConfigurationError(
184 "No default_image for slice %s" % slice.name)
Scott Baker25757222015-05-11 16:36:41 -0700185
186 flavor = slice.default_flavor
187 if not flavor:
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700188 raise XOSConfigurationError(
189 "No default_flavor for slice %s" % slice.name)
Scott Baker25757222015-05-11 16:36:41 -0700190
Tony Mackd8515472015-08-19 11:58:18 -0400191 s = Instance(slice=slice,
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700192 node=node,
193 creator=slice.creator,
194 image=image,
195 flavor=flavor,
196 deployment=node.site_deployment.deployment)
Scott Baker25757222015-05-11 16:36:41 -0700197 s.save()
198
Tony Mackd8515472015-08-19 11:58:18 -0400199 # print "add instance", s
Tony Mack950b4492015-04-29 12:23:10 -0400200
Scott Bakercbf4c782015-12-09 22:54:52 -0800201 def get_vtn_src_nets(self):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700202 nets = []
Scott Bakercbf4c782015-12-09 22:54:52 -0800203 for slice in self.slices.all():
204 for ns in slice.networkslices.all():
205 if not ns.network:
206 continue
Scott Bakereb3bad92016-01-12 19:59:12 -0800207# if ns.network.template.access in ["direct", "indirect"]:
208# # skip access networks; we want to use the private network
209# continue
Scott Bakeracae4bd2016-04-11 21:26:39 -0700210 if "management" in ns.network.name:
211 # don't try to connect the management network to anything
212 continue
Scott Bakercbf4c782015-12-09 22:54:52 -0800213 if ns.network.name in ["wan_network", "lan_network"]:
214 # we don't want to attach to the vCPE's lan or wan network
215 # we only want to attach to its private network
216 # TODO: fix hard-coding of network name
217 continue
218 for cn in ns.network.controllernetworks.all():
219 if cn.net_id:
220 net = {"name": ns.network.name, "net_id": cn.net_id}
221 nets.append(net)
222 return nets
223
Scott Baker1b7d98b2015-12-08 21:31:18 -0800224 def get_vtn_nets(self):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700225 nets = []
Scott Baker1b7d98b2015-12-08 21:31:18 -0800226 for slice in self.slices.all():
227 for ns in slice.networkslices.all():
228 if not ns.network:
229 continue
Scott Bakercbf4c782015-12-09 22:54:52 -0800230 if ns.network.template.access not in ["direct", "indirect"]:
231 # skip anything that's not an access network
232 continue
Scott Baker1b7d98b2015-12-08 21:31:18 -0800233 for cn in ns.network.controllernetworks.all():
234 if cn.net_id:
235 net = {"name": ns.network.name, "net_id": cn.net_id}
236 nets.append(net)
237 return nets
238
239 def get_vtn_dependencies_nets(self):
240 provider_nets = []
Scott Baker012c54b2015-12-08 19:27:50 -0800241 for tenant in self.subscribed_tenants.all():
242 if tenant.provider_service:
Scott Baker1b7d98b2015-12-08 21:31:18 -0800243 for net in tenant.provider_service.get_vtn_nets():
244 if not net in provider_nets:
245 provider_nets.append(net)
246 return provider_nets
247
248 def get_vtn_dependencies_ids(self):
249 return [x["net_id"] for x in self.get_vtn_dependencies_nets()]
250
251 def get_vtn_dependencies_names(self):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700252 return [x["name"] + "_" + x["net_id"] for x in self.get_vtn_dependencies_nets()]
Scott Baker1b7d98b2015-12-08 21:31:18 -0800253
Scott Bakercbf4c782015-12-09 22:54:52 -0800254 def get_vtn_src_ids(self):
255 return [x["net_id"] for x in self.get_vtn_src_nets()]
Scott Baker1b7d98b2015-12-08 21:31:18 -0800256
Scott Bakercbf4c782015-12-09 22:54:52 -0800257 def get_vtn_src_names(self):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700258 return [x["name"] + "_" + x["net_id"] for x in self.get_vtn_src_nets()]
Scott Baker012c54b2015-12-08 19:27:50 -0800259
260
Siobhan Tully00353f72013-10-08 21:53:27 -0400261class ServiceAttribute(PlCoreBase):
Scott Bakerbcea8cf2015-12-07 22:20:40 -0800262 name = models.CharField(help_text="Attribute Name", max_length=128)
Tony Mack50e12212015-03-09 13:03:56 -0400263 value = StrippedCharField(help_text="Attribute Value", max_length=1024)
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700264 service = models.ForeignKey(Service, related_name='serviceattributes',
265 help_text="The Service this attribute is associated with")
266
Siobhan Tully00353f72013-10-08 21:53:27 -0400267
Tony Mack950b4492015-04-29 12:23:10 -0400268class ServiceRole(PlCoreBase):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700269 ROLE_CHOICES = (('admin', 'Admin'),)
Tony Mack950b4492015-04-29 12:23:10 -0400270 role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
271
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700272 def __unicode__(self): return u'%s' % (self.role)
273
Tony Mack950b4492015-04-29 12:23:10 -0400274
275class ServicePrivilege(PlCoreBase):
276 user = models.ForeignKey('User', related_name='serviceprivileges')
277 service = models.ForeignKey('Service', related_name='serviceprivileges')
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700278 role = models.ForeignKey('ServiceRole', related_name='serviceprivileges')
Tony Mack950b4492015-04-29 12:23:10 -0400279
280 class Meta:
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700281 unique_together = ('user', 'service', 'role')
Tony Mack950b4492015-04-29 12:23:10 -0400282
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700283 def __unicode__(self): return u'%s %s %s' % (
284 self.service, self.user, self.role)
Tony Mack950b4492015-04-29 12:23:10 -0400285
286 def can_update(self, user):
287 if not self.service.enabled:
288 raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
289 return self.service.can_update(user)
290
291 def save(self, *args, **kwds):
292 if not self.service.enabled:
293 raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
294 super(ServicePrivilege, self).save(*args, **kwds)
295
296 def delete(self, *args, **kwds):
297 if not self.service.enabled:
298 raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
Scott Bakera86489f2015-07-01 18:29:08 -0700299 super(ServicePrivilege, self).delete(*args, **kwds)
300
Scott Baker27de6012015-07-24 15:36:02 -0700301 @classmethod
302 def select_by_user(cls, user):
Tony Mack950b4492015-04-29 12:23:10 -0400303 if user.is_admin:
Scott Baker27de6012015-07-24 15:36:02 -0700304 qs = cls.objects.all()
Tony Mack950b4492015-04-29 12:23:10 -0400305 else:
Scott Baker27de6012015-07-24 15:36:02 -0700306 qs = cls.objects.filter(user=user)
Scott Bakera86489f2015-07-01 18:29:08 -0700307 return qs
308
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700309
Scott Baker9d1c6d92015-07-13 13:07:27 -0700310class TenantRoot(PlCoreBase, AttributeMixin):
Scott Bakera86489f2015-07-01 18:29:08 -0700311 """ A tenantRoot is one of the things that can sit at the root of a chain
312 of tenancy. This object represents a node.
313 """
314
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700315 KIND = "generic"
Scott Bakera86489f2015-07-01 18:29:08 -0700316 kind = StrippedCharField(max_length=30, default=KIND)
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700317 name = StrippedCharField(
318 max_length=255, help_text="name", blank=True, null=True)
Scott Bakera86489f2015-07-01 18:29:08 -0700319
Scott Baker29415a82015-07-07 12:12:42 -0700320 service_specific_attribute = models.TextField(blank=True, null=True)
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700321 service_specific_id = StrippedCharField(
322 max_length=30, blank=True, null=True)
Scott Bakera86489f2015-07-01 18:29:08 -0700323
Scott Baker126ad472015-07-07 17:59:44 -0700324 def __init__(self, *args, **kwargs):
325 # for subclasses, set the default kind appropriately
326 self._meta.get_field("kind").default = self.KIND
327 super(TenantRoot, self).__init__(*args, **kwargs)
328
Scott Bakerb2385622015-07-06 14:27:31 -0700329 def __unicode__(self):
330 if not self.name:
331 return u"%s-tenant_root-#%s" % (str(self.kind), str(self.id))
332 else:
333 return self.name
334
335 def can_update(self, user):
336 return user.can_update_tenant_root(self, allow=['admin'])
337
Scott Baker29415a82015-07-07 12:12:42 -0700338 def get_subscribed_tenants(self, tenant_class):
339 ids = self.subscribed_tenants.filter(kind=tenant_class.KIND)
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700340 return tenant_class.objects.filter(id__in=ids)
Scott Baker29415a82015-07-07 12:12:42 -0700341
342 def get_newest_subscribed_tenant(self, kind):
343 st = list(self.get_subscribed_tenants(kind))
344 if not st:
345 return None
346 return sorted(st, key=attrgetter('id'))[0]
347
348 @classmethod
349 def get_tenant_objects(cls):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700350 return cls.objects.filter(kind=cls.KIND)
Scott Baker29415a82015-07-07 12:12:42 -0700351
Scott Baker27de6012015-07-24 15:36:02 -0700352 @classmethod
353 def get_tenant_objects_by_user(cls, user):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700354 return cls.select_by_user(user).filter(kind=cls.KIND)
Scott Baker27de6012015-07-24 15:36:02 -0700355
356 @classmethod
357 def select_by_user(cls, user):
358 if user.is_admin:
359 return cls.objects.all()
360 else:
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700361 tr_ids = [
362 trp.tenant_root.id for trp in TenantRootPrivilege.objects.filter(user=user)]
Scott Baker27de6012015-07-24 15:36:02 -0700363 return cls.objects.filter(id__in=tr_ids)
364
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700365 # helper function to be used in subclasses that want to ensure
366 # service_specific_id is unique
Scott Bakerf3735762016-03-31 14:45:31 -0700367 def validate_unique_service_specific_id(self, none_okay=False):
368 if not none_okay and (self.service_specific_id is None):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700369 raise XOSMissingField("subscriber_specific_id is None, and it's a required field", fields={
370 "service_specific_id": "cannot be none"})
Scott Bakerf3735762016-03-31 14:45:31 -0700371
372 if self.service_specific_id:
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700373 conflicts = self.get_tenant_objects().filter(
374 service_specific_id=self.service_specific_id)
Scott Bakerf3735762016-03-31 14:45:31 -0700375 if self.pk:
Scott Baker64a16582016-04-01 16:28:41 -0700376 conflicts = conflicts.exclude(pk=self.pk)
Scott Bakerf3735762016-03-31 14:45:31 -0700377 if conflicts:
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700378 raise XOSDuplicateKey("service_specific_id %s already exists" % self.service_specific_id, fields={
379 "service_specific_id": "duplicate key"})
380
Scott Bakerf3735762016-03-31 14:45:31 -0700381
Scott Baker9d1c6d92015-07-13 13:07:27 -0700382class Tenant(PlCoreBase, AttributeMixin):
Scott Baker91e85882015-04-10 16:42:26 -0700383 """ A tenant is a relationship between two entities, a subscriber and a
Scott Bakera86489f2015-07-01 18:29:08 -0700384 provider. This object represents an edge.
Scott Baker91e85882015-04-10 16:42:26 -0700385
386 The subscriber can be a User, a Service, or a Tenant.
387
388 The provider is always a Service.
Scott Bakera86489f2015-07-01 18:29:08 -0700389
390 TODO: rename "Tenant" to "Tenancy"
Scott Baker91e85882015-04-10 16:42:26 -0700391 """
Scott Baker0d306722015-04-15 20:58:20 -0700392
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700393 CONNECTIVITY_CHOICES = (('public', 'Public'),
394 ('private', 'Private'), ('na', 'Not Applicable'))
Scott Bakeref58a842015-04-26 20:30:40 -0700395
Scott Baker0d306722015-04-15 20:58:20 -0700396 # when subclassing a service, redefine KIND to describe the new service
397 KIND = "generic"
398
399 kind = StrippedCharField(max_length=30, default=KIND)
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700400 provider_service = models.ForeignKey(
401 Service, related_name='provided_tenants')
Scott Bakera86489f2015-07-01 18:29:08 -0700402
403 # The next four things are the various type of objects that can be subscribers of this Tenancy
404 # relationship. One and only one can be used at a time.
Scott Baker5d268e52016-04-13 16:56:39 -0700405 # XXX these should really be changed to GenericForeignKey
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700406 subscriber_service = models.ForeignKey(
407 Service, related_name='subscribed_tenants', blank=True, null=True)
408 subscriber_tenant = models.ForeignKey(
409 "Tenant", related_name='subscribed_tenants', blank=True, null=True)
410 subscriber_user = models.ForeignKey(
411 "User", related_name='subscribed_tenants', blank=True, null=True)
412 subscriber_root = models.ForeignKey(
413 "TenantRoot", related_name="subscribed_tenants", blank=True, null=True)
414 subscriber_network = models.ForeignKey(
415 "Network", related_name="subscribed_tenants", blank=True, null=True)
Scott Bakera86489f2015-07-01 18:29:08 -0700416
417 # Service_specific_attribute and service_specific_id are opaque to XOS
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700418 service_specific_id = StrippedCharField(
419 max_length=30, blank=True, null=True)
Scott Baker1b7c6f12015-05-06 19:49:31 -0700420 service_specific_attribute = models.TextField(blank=True, null=True)
Scott Bakera86489f2015-07-01 18:29:08 -0700421
422 # Connect_method is only used by Coarse tenants
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700423 connect_method = models.CharField(
424 null=False, blank=False, max_length=30, choices=CONNECTIVITY_CHOICES, default="na")
Scott Baker91e85882015-04-10 16:42:26 -0700425
Scott Baker0d306722015-04-15 20:58:20 -0700426 def __init__(self, *args, **kwargs):
427 # for subclasses, set the default kind appropriately
428 self._meta.get_field("kind").default = self.KIND
429 super(Tenant, self).__init__(*args, **kwargs)
430
Scott Baker91e85882015-04-10 16:42:26 -0700431 def __unicode__(self):
Scott Bakerfe91f622015-05-20 20:42:04 -0700432 return u"%s-tenant-%s" % (str(self.kind), str(self.id))
Scott Baker91e85882015-04-10 16:42:26 -0700433
Scott Baker0d306722015-04-15 20:58:20 -0700434 @classmethod
435 def get_tenant_objects(cls):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700436 return cls.objects.filter(kind=cls.KIND)
Scott Baker0d306722015-04-15 20:58:20 -0700437
Scott Bakereb50ee32015-05-05 17:52:03 -0700438 @classmethod
Scott Baker27de6012015-07-24 15:36:02 -0700439 def get_tenant_objects_by_user(cls, user):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700440 return cls.select_by_user(user).filter(kind=cls.KIND)
Scott Baker27de6012015-07-24 15:36:02 -0700441
442 @classmethod
Scott Bakereb50ee32015-05-05 17:52:03 -0700443 def get_deleted_tenant_objects(cls):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700444 return cls.deleted_objects.filter(kind=cls.KIND)
Scott Bakereb50ee32015-05-05 17:52:03 -0700445
Scott Bakerbcea8cf2015-12-07 22:20:40 -0800446 @property
447 def tenantattribute_dict(self):
448 attrs = {}
449 for attr in self.tenantattributes.all():
450 attrs[attr.name] = attr.value
451 return attrs
452
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700453 # helper function to be used in subclasses that want to ensure
454 # service_specific_id is unique
Scott Baker7f8ef8f2015-04-20 14:24:29 -0700455 def validate_unique_service_specific_id(self):
456 if self.pk is None:
457 if self.service_specific_id is None:
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700458 raise XOSMissingField("subscriber_specific_id is None, and it's a required field", fields={
459 "service_specific_id": "cannot be none"})
Scott Baker7f8ef8f2015-04-20 14:24:29 -0700460
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700461 conflicts = self.get_tenant_objects().filter(
462 service_specific_id=self.service_specific_id)
Scott Baker7f8ef8f2015-04-20 14:24:29 -0700463 if conflicts:
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700464 raise XOSDuplicateKey("service_specific_id %s already exists" % self.service_specific_id, fields={
465 "service_specific_id": "duplicate key"})
Scott Baker7f8ef8f2015-04-20 14:24:29 -0700466
Scott Bakerb2385622015-07-06 14:27:31 -0700467 def save(self, *args, **kwargs):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700468 subCount = sum([1 for e in [self.subscriber_service, self.subscriber_tenant,
469 self.subscriber_user, self.subscriber_root] if e is not None])
Scott Bakerb2385622015-07-06 14:27:31 -0700470 if (subCount > 1):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700471 raise XOSConflictingField(
472 "Only one of subscriber_service, subscriber_tenant, subscriber_user, subscriber_root should be set")
Scott Bakerb2385622015-07-06 14:27:31 -0700473
474 super(Tenant, self).save(*args, **kwargs)
475
476 def get_subscribed_tenants(self, tenant_class):
477 ids = self.subscribed_tenants.filter(kind=tenant_class.KIND)
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700478 return tenant_class.objects.filter(id__in=ids)
Scott Bakerb2385622015-07-06 14:27:31 -0700479
480 def get_newest_subscribed_tenant(self, kind):
481 st = list(self.get_subscribed_tenants(kind))
482 if not st:
483 return None
484 return sorted(st, key=attrgetter('id'))[0]
485
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700486
Scott Bakerc8914bf2015-11-18 20:58:08 -0800487class Scheduler(object):
488 # XOS Scheduler Abstract Base Class
489 # Used to implement schedulers that pick which node to put instances on
490
491 def __init__(self, slice):
492 self.slice = slice
493
494 def pick(self):
495 # this method should return a tuple (node, parent)
496 # node is the node to instantiate on
497 # parent is for container_vm instances only, and is the VM that will
498 # hold the container
499
500 raise Exception("Abstract Base")
501
Scott Bakerc8914bf2015-11-18 20:58:08 -0800502
Scott Bakerc8914bf2015-11-18 20:58:08 -0800503class LeastLoadedNodeScheduler(Scheduler):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700504 # This scheduler always return the node with the fewest number of
505 # instances.
Scott Bakerc8914bf2015-11-18 20:58:08 -0800506
Scott Baker67074ab2016-03-04 11:29:02 -0800507 def __init__(self, slice, label=None):
Scott Bakerc8914bf2015-11-18 20:58:08 -0800508 super(LeastLoadedNodeScheduler, self).__init__(slice)
Scott Baker67074ab2016-03-04 11:29:02 -0800509 self.label = label
Scott Bakerc8914bf2015-11-18 20:58:08 -0800510
511 def pick(self):
512 from core.models import Node
Pingping Linfc9256a2016-03-03 09:52:24 -0800513 if not self.slice.default_node:
514 nodes = list(Node.objects.all())
515 nodes = sorted(nodes, key=lambda node: node.instances.all().count())
516 else:
517 nodes = list(Node.objects.filter(name = self.slice.default_node))
Matteo Scandolo3678a3d2016-04-21 09:02:08 -0700518
Scott Baker67074ab2016-03-04 11:29:02 -0800519 if self.label:
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700520 nodes = nodes.filter(nodelabels__name=self.label)
Scott Baker67074ab2016-03-04 11:29:02 -0800521
522 nodes = list(nodes)
523
524 if not nodes:
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700525 raise Exception(
526 "LeastLoadedNodeScheduler: No suitable nodes to pick from")
Scott Bakerbcea8cf2015-12-07 22:20:40 -0800527
Scott Bakerc8914bf2015-11-18 20:58:08 -0800528 # TODO: logic to filter nodes by which nodes are up, and which
529 # nodes the slice can instantiate on.
Pingping Linfc9256a2016-03-03 09:52:24 -0800530# nodes = sorted(nodes, key=lambda node: node.instances.all().count())
Scott Bakerc8914bf2015-11-18 20:58:08 -0800531 return [nodes[0], None]
532
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700533
Scott Bakerc8914bf2015-11-18 20:58:08 -0800534class ContainerVmScheduler(Scheduler):
535 # This scheduler picks a VM in the slice with the fewest containers inside
536 # of it. If no VMs are suitable, then it creates a VM.
537
Scott Bakerc8914bf2015-11-18 20:58:08 -0800538 MAX_VM_PER_CONTAINER = 10
539
540 def __init__(self, slice):
541 super(ContainerVmScheduler, self).__init__(slice)
542
543 @property
544 def image(self):
545 from core.models import Image
546
Scott Bakerc2cf8522016-05-19 17:54:52 -0700547 # If slice has default_image set then use it
548 if self.slice.default_image:
549 return self.slice.default_image
Scott Bakerc8914bf2015-11-18 20:58:08 -0800550
Scott Bakerc2cf8522016-05-19 17:54:52 -0700551 raise XOPSProgrammingError("Please set a default image for %s" % self.slice.name)
Scott Bakerc8914bf2015-11-18 20:58:08 -0800552
553 def make_new_instance(self):
554 from core.models import Instance, Flavor
555
556 flavors = Flavor.objects.filter(name="m1.small")
557 if not flavors:
558 raise XOSConfigurationError("No m1.small flavor")
559
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700560 (node, parent) = LeastLoadedNodeScheduler(self.slice).pick()
Scott Bakerc8914bf2015-11-18 20:58:08 -0800561
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700562 instance = Instance(slice=self.slice,
563 node=node,
564 image=self.image,
565 creator=self.slice.creator,
566 deployment=node.site_deployment.deployment,
567 flavor=flavors[0],
568 isolation="vm",
569 parent=parent)
Scott Bakerc8914bf2015-11-18 20:58:08 -0800570 instance.save()
571 # We rely on a special naming convention to identify the VMs that will
572 # hole containers.
573 instance.name = "%s-outer-%s" % (instance.slice.name, instance.id)
574 instance.save()
575 return instance
576
577 def pick(self):
578 from core.models import Instance, Flavor
579
580 for vm in self.slice.instances.filter(isolation="vm"):
581 avail_vms = []
582 if (vm.name.startswith("%s-outer-" % self.slice.name)):
583 container_count = Instance.objects.filter(parent=vm).count()
584 if (container_count < self.MAX_VM_PER_CONTAINER):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700585 avail_vms.append((vm, container_count))
Scott Bakerc8914bf2015-11-18 20:58:08 -0800586 # sort by least containers-per-vm
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700587 avail_vms = sorted(avail_vms, key=lambda x: x[1])
Scott Bakerc8914bf2015-11-18 20:58:08 -0800588 print "XXX", avail_vms
589 if avail_vms:
590 instance = avail_vms[0][0]
591 return (instance.node, instance)
592
593 instance = self.make_new_instance()
594 return (instance.node, instance)
595
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700596
Scott Bakerc1584b82015-09-09 16:36:06 -0700597class TenantWithContainer(Tenant):
598 """ A tenant that manages a container """
599
Scott Bakerc1584b82015-09-09 16:36:06 -0700600 class Meta:
601 proxy = True
602
603 def __init__(self, *args, **kwargs):
604 super(TenantWithContainer, self).__init__(*args, **kwargs)
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700605 self.cached_instance = None
Tony Mack32010062015-09-13 22:50:39 +0000606 self.orig_instance_id = self.get_initial_attribute("instance_id")
Scott Baker5c125e42015-11-02 20:54:28 -0800607
Scott Bakerc1584b82015-09-09 16:36:06 -0700608 @property
Tony Mack32010062015-09-13 22:50:39 +0000609 def instance(self):
610 from core.models import Instance
611 if getattr(self, "cached_instance", None):
612 return self.cached_instance
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700613 instance_id = self.get_attribute("instance_id")
Srikanth Vavilapalli17b5a3c2015-11-17 12:21:02 -0600614 if not instance_id:
615 return None
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700616 instances = Instance.objects.filter(id=instance_id)
Srikanth Vavilapalli17b5a3c2015-11-17 12:21:02 -0600617 if not instances:
618 return None
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700619 instance = instances[0]
Tony Mack32010062015-09-13 22:50:39 +0000620 instance.caller = self.creator
621 self.cached_instance = instance
622 return instance
Scott Bakerc1584b82015-09-09 16:36:06 -0700623
Tony Mack32010062015-09-13 22:50:39 +0000624 @instance.setter
625 def instance(self, value):
Scott Bakerc1584b82015-09-09 16:36:06 -0700626 if value:
627 value = value.id
Tony Mack32010062015-09-13 22:50:39 +0000628 if (value != self.get_attribute("instance_id", None)):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700629 self.cached_instance = None
Tony Mack32010062015-09-13 22:50:39 +0000630 self.set_attribute("instance_id", value)
Scott Bakerc1584b82015-09-09 16:36:06 -0700631
Scott Baker5c125e42015-11-02 20:54:28 -0800632 @property
Scott Baker268e2aa2016-02-10 12:23:53 -0800633 def external_hostname(self):
634 return self.get_attribute("external_hostname", "")
635
636 @external_hostname.setter
637 def external_hostname(self, value):
638 self.set_attribute("external_hostname", value)
639
640 @property
641 def external_container(self):
642 return self.get_attribute("external_container", "")
643
644 @external_container.setter
645 def external_container(self, value):
646 self.set_attribute("external_container", value)
647
648 @property
Scott Bakerc1584b82015-09-09 16:36:06 -0700649 def creator(self):
650 from core.models import User
651 if getattr(self, "cached_creator", None):
652 return self.cached_creator
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700653 creator_id = self.get_attribute("creator_id")
Scott Bakerc1584b82015-09-09 16:36:06 -0700654 if not creator_id:
655 return None
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700656 users = User.objects.filter(id=creator_id)
Scott Bakerc1584b82015-09-09 16:36:06 -0700657 if not users:
658 return None
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700659 user = users[0]
Scott Bakerc1584b82015-09-09 16:36:06 -0700660 self.cached_creator = users[0]
661 return user
662
663 @creator.setter
664 def creator(self, value):
665 if value:
666 value = value.id
667 if (value != self.get_attribute("creator_id", None)):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700668 self.cached_creator = None
Scott Bakerc1584b82015-09-09 16:36:06 -0700669 self.set_attribute("creator_id", value)
670
671 @property
672 def image(self):
673 from core.models import Image
674 # Implement the logic here to pick the image that should be used when
675 # instantiating the VM that will hold the container.
Scott Bakera759fe32015-11-16 22:51:02 -0800676 slice = self.provider_service.slices.all()
677 if not slice:
678 raise XOSProgrammingError("provider service has no slice")
679 slice = slice[0]
680
681 if slice.default_isolation in ["container", "container_vm"]:
682 look_for_images = self.LOOK_FOR_CONTAINER_IMAGES
683 else:
684 look_for_images = self.LOOK_FOR_IMAGES
685
686 for image_name in look_for_images:
Matteo Scandolo3678a3d2016-04-21 09:02:08 -0700687
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700688 images = Image.objects.filter(name=image_name)
Matteo Scandolo3678a3d2016-04-21 09:02:08 -0700689
Scott Bakerc1584b82015-09-09 16:36:06 -0700690 if images:
691 return images[0]
Pingping Lin99604be2016-04-20 18:02:10 -0700692 else:
693 images = Image.objects.filter(name = slice.default_image)
694 if images:
695 return images[0]
Scott Bakerc1584b82015-09-09 16:36:06 -0700696
Scott Bakerc2cf8522016-05-19 17:54:52 -0700697 raise XOPSProgrammingError("Please set a default image for %s" % self.slice.name)
Scott Baker5c125e42015-11-02 20:54:28 -0800698
Scott Bakera759fe32015-11-16 22:51:02 -0800699 def save_instance(self, instance):
700 # Override this function to do custom pre-save or post-save processing,
701 # such as creating ports for containers.
702 instance.save()
Scott Baker5c125e42015-11-02 20:54:28 -0800703
Srikanth Vavilapalli17b5a3c2015-11-17 12:21:02 -0600704 def pick_least_loaded_instance_in_slice(self, slices):
705 for slice in slices:
706 if slice.instances.all().count() > 0:
707 for instance in slice.instances.all():
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700708 # Pick the first instance that has lesser than 5 tenants
709 if self.count_of_tenants_of_an_instance(instance) < 5:
710 return instance
Srikanth Vavilapalli17b5a3c2015-11-17 12:21:02 -0600711 return None
712
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700713 # TODO: Ideally the tenant count for an instance should be maintained using a
714 # many-to-one relationship attribute, however this model being proxy, it does
715 # not permit any new attributes to be defined. Find if any better solutions
Srikanth Vavilapalli17b5a3c2015-11-17 12:21:02 -0600716 def count_of_tenants_of_an_instance(self, instance):
717 tenant_count = 0
718 for tenant in self.get_tenant_objects().all():
719 if tenant.get_attribute("instance_id", None) == instance.id:
720 tenant_count += 1
721 return tenant_count
722
Scott Bakera759fe32015-11-16 22:51:02 -0800723 def manage_container(self):
Tony Mack32010062015-09-13 22:50:39 +0000724 from core.models import Instance, Flavor
Scott Bakerc1584b82015-09-09 16:36:06 -0700725
726 if self.deleted:
727 return
728
Tony Mack32010062015-09-13 22:50:39 +0000729 if (self.instance is not None) and (self.instance.image != self.image):
730 self.instance.delete()
731 self.instance = None
Scott Bakerc1584b82015-09-09 16:36:06 -0700732
Tony Mack32010062015-09-13 22:50:39 +0000733 if self.instance is None:
Scott Bakerc1584b82015-09-09 16:36:06 -0700734 if not self.provider_service.slices.count():
Srikanth Vavilapalli17b5a3c2015-11-17 12:21:02 -0600735 raise XOSConfigurationError("The service has no slices")
Scott Bakerc1584b82015-09-09 16:36:06 -0700736
Srikanth Vavilapalli17b5a3c2015-11-17 12:21:02 -0600737 new_instance_created = False
738 instance = None
739 if self.get_attribute("use_same_instance_for_multiple_tenants", default=False):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700740 # Find if any existing instances can be used for this tenant
Srikanth Vavilapalli17b5a3c2015-11-17 12:21:02 -0600741 slices = self.provider_service.slices.all()
742 instance = self.pick_least_loaded_instance_in_slice(slices)
Scott Bakerc1584b82015-09-09 16:36:06 -0700743
Srikanth Vavilapalli17b5a3c2015-11-17 12:21:02 -0600744 if not instance:
Srikanth Vavilapalli3406fbb2015-11-17 13:41:38 -0600745 slice = self.provider_service.slices.all()[0]
Pingping Lin99604be2016-04-20 18:02:10 -0700746 flavors = Flavor.objects.filter(name=slice.default_flavor) #MCORD
Scott Bakera759fe32015-11-16 22:51:02 -0800747
Srikanth Vavilapalli204a5c42016-02-15 01:18:09 -0500748 flavor = slice.default_flavor
749 if not flavor:
750 flavors = Flavor.objects.filter(name="m1.small")
751 if not flavors:
752 raise XOSConfigurationError("No m1.small flavor")
753 flavor = flavors[0]
Pingping Lin99604be2016-04-20 18:02:10 -0700754# default_flavor = slice.default_flavor #MCORD
755
Srikanth Vavilapalli204a5c42016-02-15 01:18:09 -0500756
Srikanth Vavilapalli3406fbb2015-11-17 13:41:38 -0600757 if slice.default_isolation == "container_vm":
Scott Bakerc8914bf2015-11-18 20:58:08 -0800758 (node, parent) = ContainerVmScheduler(slice).pick()
Srikanth Vavilapalli3406fbb2015-11-17 13:41:38 -0600759 else:
Scott Bakerc8914bf2015-11-18 20:58:08 -0800760 (node, parent) = LeastLoadedNodeScheduler(slice).pick()
Scott Bakera759fe32015-11-16 22:51:02 -0800761
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700762 instance = Instance(slice=slice,
763 node=node,
764 image=self.image,
765 creator=self.creator,
766 deployment=node.site_deployment.deployment,
767 flavor=flavor,
768 isolation=slice.default_isolation,
769 parent=parent)
Srikanth Vavilapalli3406fbb2015-11-17 13:41:38 -0600770 self.save_instance(instance)
Srikanth Vavilapalli17b5a3c2015-11-17 12:21:02 -0600771 new_instance_created = True
Scott Bakerc1584b82015-09-09 16:36:06 -0700772
773 try:
Tony Mack32010062015-09-13 22:50:39 +0000774 self.instance = instance
Scott Bakerc1584b82015-09-09 16:36:06 -0700775 super(TenantWithContainer, self).save()
776 except:
Srikanth Vavilapalli17b5a3c2015-11-17 12:21:02 -0600777 if new_instance_created:
778 instance.delete()
Scott Bakerc1584b82015-09-09 16:36:06 -0700779 raise
780
781 def cleanup_container(self):
Tony Mack32010062015-09-13 22:50:39 +0000782 if self.instance:
Srikanth Vavilapalli17b5a3c2015-11-17 12:21:02 -0600783 if self.get_attribute("use_same_instance_for_multiple_tenants", default=False):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700784 # Delete the instance only if this is last tenant in that
785 # instance
786 tenant_count = self.count_of_tenants_of_an_instance(
787 self.instance)
Srikanth Vavilapalli17b5a3c2015-11-17 12:21:02 -0600788 if tenant_count == 0:
789 self.instance.delete()
790 else:
791 self.instance.delete()
Tony Mack32010062015-09-13 22:50:39 +0000792 self.instance = None
Scott Bakerb2385622015-07-06 14:27:31 -0700793
Scott Baker88fa6732015-12-10 23:23:07 -0800794 def save(self, *args, **kwargs):
795 if (not self.creator) and (hasattr(self, "caller")) and (self.caller):
796 self.creator = self.caller
797 super(TenantWithContainer, self).save(*args, **kwargs)
798
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700799
Scott Bakeref58a842015-04-26 20:30:40 -0700800class CoarseTenant(Tenant):
Scott Bakera86489f2015-07-01 18:29:08 -0700801 """ TODO: rename "CoarseTenant" --> "StaticTenant" """
Scott Bakeref58a842015-04-26 20:30:40 -0700802 class Meta:
803 proxy = True
Siobhan Tully00353f72013-10-08 21:53:27 -0400804
Scott Bakerc24f86d2015-08-14 09:10:11 -0700805 KIND = COARSE_KIND
Scott Bakeref58a842015-04-26 20:30:40 -0700806
807 def save(self, *args, **kwargs):
808 if (not self.subscriber_service):
809 raise XOSValidationError("subscriber_service cannot be null")
810 if (self.subscriber_tenant or self.subscriber_user):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700811 raise XOSValidationError(
812 "subscriber_tenant and subscriber_user must be null")
Scott Bakeref58a842015-04-26 20:30:40 -0700813
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700814 super(CoarseTenant, self).save()
815
Scott Bakera86489f2015-07-01 18:29:08 -0700816
817class Subscriber(TenantRoot):
818 """ Intermediate class for TenantRoots that are to be Subscribers """
819
820 class Meta:
821 proxy = True
822
823 KIND = "Subscriber"
824
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700825
Scott Bakera86489f2015-07-01 18:29:08 -0700826class Provider(TenantRoot):
827 """ Intermediate class for TenantRoots that are to be Providers """
828
829 class Meta:
830 proxy = True
831
832 KIND = "Provider"
833
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700834
Scott Baker1e7e3482015-10-15 15:59:19 -0700835class TenantAttribute(PlCoreBase):
Scott Baker3ab4db82015-10-20 17:12:36 -0700836 name = models.CharField(help_text="Attribute Name", max_length=128)
Scott Baker1e7e3482015-10-15 15:59:19 -0700837 value = models.TextField(help_text="Attribute Value")
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700838 tenant = models.ForeignKey(Tenant, related_name='tenantattributes',
839 help_text="The Tenant this attribute is associated with")
Scott Baker1e7e3482015-10-15 15:59:19 -0700840
Scott Bakerb0955d92016-04-06 14:34:49 -0700841 def __unicode__(self): return u'%s-%s' % (self.name, self.id)
842
Scott Bakera86489f2015-07-01 18:29:08 -0700843
844class TenantRootRole(PlCoreBase):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700845 ROLE_CHOICES = (('admin', 'Admin'), ('access', 'Access'))
Scott Bakera86489f2015-07-01 18:29:08 -0700846
847 role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
848
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700849 def __unicode__(self): return u'%s' % (self.role)
850
Scott Bakera86489f2015-07-01 18:29:08 -0700851
852class TenantRootPrivilege(PlCoreBase):
853 user = models.ForeignKey('User', related_name="tenant_root_privileges")
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700854 tenant_root = models.ForeignKey(
855 'TenantRoot', related_name="tenant_root_privileges")
856 role = models.ForeignKey(
857 'TenantRootRole', related_name="tenant_root_privileges")
Scott Bakera86489f2015-07-01 18:29:08 -0700858
859 class Meta:
860 unique_together = ('user', 'tenant_root', 'role')
861
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700862 def __unicode__(self): return u'%s %s %s' % (
863 self.tenant_root, self.user, self.role)
Scott Bakera86489f2015-07-01 18:29:08 -0700864
865 def save(self, *args, **kwds):
866 if not self.user.is_active:
867 raise PermissionDenied, "Cannot modify role(s) of a disabled user"
Scott Bakerc8e947a2015-07-24 10:15:31 -0700868 super(TenantRootPrivilege, self).save(*args, **kwds)
Scott Bakera86489f2015-07-01 18:29:08 -0700869
870 def can_update(self, user):
Scott Bakerc8e947a2015-07-24 10:15:31 -0700871 return user.can_update_tenant_root_privilege(self)
Scott Bakera86489f2015-07-01 18:29:08 -0700872
Scott Baker27de6012015-07-24 15:36:02 -0700873 @classmethod
874 def select_by_user(cls, user):
Scott Bakera86489f2015-07-01 18:29:08 -0700875 if user.is_admin:
Scott Baker1729e342015-07-24 15:48:03 -0700876 return cls.objects.all()
Scott Bakera86489f2015-07-01 18:29:08 -0700877 else:
Scott Baker1729e342015-07-24 15:48:03 -0700878 # User can see his own privilege
879 trp_ids = [trp.id for trp in cls.objects.filter(user=user)]
880
881 # A slice admin can see the SlicePrivileges for his Slice
882 for priv in cls.objects.filter(user=user, role__role="admin"):
Jeremy Mowery46d96d12016-04-17 20:56:54 -0700883 trp_ids.extend(
884 [trp.id for trp in cls.objects.filter(tenant_root=priv.tenant_root)])
Scott Baker1729e342015-07-24 15:48:03 -0700885
886 return cls.objects.filter(id__in=trp_ids)
887
Jeremy Moweryda57d402016-04-15 17:39:49 -0700888
Jeremy Mowery752dc502016-03-14 23:59:11 -0700889class TenantRole(PlCoreBase):
Jeremy Mowery308e8f02016-04-15 00:05:27 -0700890 """A TenantRole option."""
Jeremy Moweryda57d402016-04-15 17:39:49 -0700891 ROLE_CHOICES = (('admin', 'Admin'), ('access', 'Access'))
Jeremy Mowery752dc502016-03-14 23:59:11 -0700892 role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
Jeremy Moweryda57d402016-04-15 17:39:49 -0700893
894 def __unicode__(self): return u'%s' % (self.role)
895
Jeremy Mowery752dc502016-03-14 23:59:11 -0700896
897class TenantPrivilege(PlCoreBase):
Jeremy Mowery308e8f02016-04-15 00:05:27 -0700898 """"A TenantPrivilege which defines how users can access a particular Tenant.
899
900 Attributes:
901 id (models.AutoField): The ID of the privilege.
902 user (models.ForeignKey): A Foreign Key to the a User.
903 tenant (models.ForeignKey): A ForeignKey to the Tenant.
904 role (models.ForeignKey): A ForeignKey to the TenantRole.
905 """
Jeremy Mowery05d16d42016-04-10 23:00:54 -0700906 id = models.AutoField(primary_key=True)
Jeremy Mowerya74c31d2016-04-04 22:30:44 -0700907 user = models.ForeignKey('User', related_name="tenantprivileges")
908 tenant = models.ForeignKey('Tenant', related_name="tenantprivileges")
909 role = models.ForeignKey('TenantRole', related_name="tenantprivileges")
Jeremy Mowery752dc502016-03-14 23:59:11 -0700910
Jeremy Moweryda57d402016-04-15 17:39:49 -0700911 def __unicode__(self): return u'%s %s %s' % (
912 self.tenant, self.user, self.role)
Jeremy Mowery752dc502016-03-14 23:59:11 -0700913
914 def save(self, *args, **kwds):
915 if not self.user.is_active:
916 raise PermissionDenied, "Cannot modify role(s) of a disabled user"
917 super(TenantPrivilege, self).save(*args, **kwds)
918
919 def can_update(self, user):
920 return user.can_update_tenant_privilege(self)
921
922 @classmethod
923 def select_by_user(cls, user):
924 if user.is_admin:
925 return cls.objects.all()
926 else:
927 # User can see his own privilege
928 trp_ids = [trp.id for trp in cls.objects.filter(user=user)]
929
930 # A tenant admin can see the TenantPrivileges for their Tenants
931 for priv in cls.objects.filter(user=user, role__role="admin"):
Jeremy Moweryda57d402016-04-15 17:39:49 -0700932 trp_ids.extend(
933 [trp.id for trp in cls.objects.filter(tenant=priv.tenant)])
Jeremy Mowery752dc502016-03-14 23:59:11 -0700934
935 return cls.objects.filter(id__in=trp_ids)