blob: a0791f76bc3bc0da4b65e2a15e9410eefea50a2c [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 *
Scott Bakerb0eb23e2016-06-01 16:08:04 -07008import urlparse
Jeremy Mowery11ed8992016-04-17 20:56:54 -07009
10COARSE_KIND = "coarse"
11
Scott Baker80d65b12016-05-26 20:20:47 -070012def get_xos():
13 xos = XOS.objects.all()
14
15 if xos:
16 return xos[0]
17 else:
18 return None
19
Scott Baker2461bec2015-08-14 09:10:11 -070020
Scott Baker82498c52015-07-13 13:07:27 -070021class AttributeMixin(object):
Jeremy Mowery11ed8992016-04-17 20:56:54 -070022 # helper for extracting things from a json-encoded
23 # service_specific_attribute
24
Scott Baker82498c52015-07-13 13:07:27 -070025 def get_attribute(self, name, default=None):
26 if self.service_specific_attribute:
27 attributes = json.loads(self.service_specific_attribute)
28 else:
29 attributes = {}
30 return attributes.get(name, default)
31
32 def set_attribute(self, name, value):
33 if self.service_specific_attribute:
34 attributes = json.loads(self.service_specific_attribute)
35 else:
36 attributes = {}
Jeremy Mowery11ed8992016-04-17 20:56:54 -070037 attributes[name] = value
Scott Baker82498c52015-07-13 13:07:27 -070038 self.service_specific_attribute = json.dumps(attributes)
39
40 def get_initial_attribute(self, name, default=None):
41 if self._initial["service_specific_attribute"]:
Jeremy Mowery11ed8992016-04-17 20:56:54 -070042 attributes = json.loads(
43 self._initial["service_specific_attribute"])
Scott Baker82498c52015-07-13 13:07:27 -070044 else:
45 attributes = {}
46 return attributes.get(name, default)
47
Scott Baker74404fe2015-07-13 13:54:06 -070048 @classmethod
Scott Baker6dfde212016-03-01 20:10:24 -080049 def get_default_attribute(cls, name):
50 for (attrname, default) in cls.simple_attributes:
Jeremy Mowery11ed8992016-04-17 20:56:54 -070051 if attrname == name:
Scott Baker6dfde212016-03-01 20:10:24 -080052 return default
Jeremy Mowery11ed8992016-04-17 20:56:54 -070053 if hasattr(cls, "default_attributes"):
Scott Bakerf180ff32016-03-31 11:46:02 -070054 if name in cls.default_attributes:
55 return cls.default_attributes[name]
56
57 return None
Scott Baker6dfde212016-03-01 20:10:24 -080058
59 @classmethod
Scott Baker74404fe2015-07-13 13:54:06 -070060 def setup_simple_attributes(cls):
61 for (attrname, default) in cls.simple_attributes:
Scott Baker096dce82015-07-13 14:27:51 -070062 setattr(cls, attrname, property(lambda self, attrname=attrname, default=default: self.get_attribute(attrname, default),
Jeremy Mowery11ed8992016-04-17 20:56:54 -070063 lambda self, value, attrname=attrname: self.set_attribute(
64 attrname, value),
Scott Baker096dce82015-07-13 14:27:51 -070065 None,
66 attrname))
Scott Baker74404fe2015-07-13 13:54:06 -070067
Scott Baker80d65b12016-05-26 20:20:47 -070068class ServiceController(PlCoreBase):
Scott Bakerb0eb23e2016-06-01 16:08:04 -070069 xos = models.ForeignKey(XOS, related_name='service_controllers', help_text="Pointer to XOS", default=get_xos)
Scott Baker80d65b12016-05-26 20:20:47 -070070 name = StrippedCharField(max_length=30, help_text="Service Name")
Scott Baker1e559822016-05-26 20:57:10 -070071 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 -070072
Scott Baker147cace2016-05-26 21:11:37 -070073 def __unicode__(self): return u'%s' % (self.name)
74
Scott Baker80d65b12016-05-26 20:20:47 -070075class ServiceControllerResource(PlCoreBase):
76 KIND_CHOICES = (('models', 'Models'),
77 ('admin', 'Admin'),
78 ('django_library', 'Django Library'),
Scott Baker1ea8cb52016-05-26 22:19:35 -070079 ('synchronizer', 'Synchronizer'),
Scott Baker16cfb9c2016-06-07 15:37:03 -070080 ('rest_service', 'REST API (service)'),
81 ('rest_tenant', 'REST API (tenant)'),
Scott Baker80d65b12016-05-26 20:20:47 -070082 ('tosca_custom_types', 'Tosca Custom Types'),
Scott Baker18c89172016-06-02 16:40:25 -070083 ('tosca_resource', 'Tosca Resource'),
84 ('private_key', 'Private Key'),
85 ('public_key', 'Public Key'))
Scott Baker80d65b12016-05-26 20:20:47 -070086
87 FORMAT_CHOICES = (('python', 'Python'),
88 ('manifest', 'Manifest'),
Scott Baker411c4e92016-05-26 22:50:42 -070089 ('docker', 'Docker Container'),
Scott Baker18c89172016-06-02 16:40:25 -070090 ('yaml', 'YAML'),
91 ('raw', 'raw'))
Scott Baker80d65b12016-05-26 20:20:47 -070092
Scott Bakerb0eb23e2016-06-01 16:08:04 -070093 service_controller = models.ForeignKey(ServiceController, related_name='service_controller_resources',
Scott Baker80d65b12016-05-26 20:20:47 -070094 help_text="The Service Controller this resource is associated with")
95
96 name = StrippedCharField(max_length=30, help_text="Object Name")
97 kind = StrippedCharField(choices=KIND_CHOICES, max_length=30)
98 format = StrippedCharField(choices=FORMAT_CHOICES, max_length=30)
Scott Baker1e559822016-05-26 20:57:10 -070099 url = StrippedCharField(max_length=1024, help_text="URL of resource", null=True, blank=True)
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700100
Scott Baker147cace2016-05-26 21:11:37 -0700101 def __unicode__(self): return u'%s' % (self.name)
102
Scott Bakerb0eb23e2016-06-01 16:08:04 -0700103 @property
104 def full_url(self):
105 if self.service_controller and self.service_controller.base_url:
106 return urlparse.urljoin(self.service_controller.base_url, self.url)
107 else:
108 return self.url
109
Scott Baker82498c52015-07-13 13:07:27 -0700110class Service(PlCoreBase, AttributeMixin):
Scott Baker008a9962015-04-15 20:58:20 -0700111 # when subclassing a service, redefine KIND to describe the new service
112 KIND = "generic"
113
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700114 description = models.TextField(
115 max_length=254, null=True, blank=True, help_text="Description of Service")
Siobhan Tully00353f72013-10-08 21:53:27 -0400116 enabled = models.BooleanField(default=True)
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700117 kind = StrippedCharField(
118 max_length=30, help_text="Kind of service", default=KIND)
Tony Mackd84b1ff2015-03-09 13:03:56 -0400119 name = StrippedCharField(max_length=30, help_text="Service Name")
Scott Bakerd3e30292016-05-02 09:38:24 -0700120 versionNumber = StrippedCharField(blank=True, null=True,
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700121 max_length=30, help_text="Version of Service Definition")
Siobhan Tullycf04fb62014-01-11 11:25:57 -0500122 published = models.BooleanField(default=True)
Tony Mackd84b1ff2015-03-09 13:03:56 -0400123 view_url = StrippedCharField(blank=True, null=True, max_length=1024)
124 icon_url = StrippedCharField(blank=True, null=True, max_length=1024)
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700125 public_key = models.TextField(
126 null=True, blank=True, max_length=1024, help_text="Public key string")
Scott Bakerf60c0102015-11-12 16:22:52 -0800127 private_key_fn = StrippedCharField(blank=True, null=True, max_length=1024)
Siobhan Tully00353f72013-10-08 21:53:27 -0400128
Scott Baker2f0828e2015-07-13 12:33:28 -0700129 # Service_specific_attribute and service_specific_id are opaque to XOS
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700130 service_specific_id = StrippedCharField(
131 max_length=30, blank=True, null=True)
Scott Baker2f0828e2015-07-13 12:33:28 -0700132 service_specific_attribute = models.TextField(blank=True, null=True)
133
Scott Baker80d65b12016-05-26 20:20:47 -0700134 controller = models.ForeignKey(ServiceController, related_name='services',
Scott Baker1febf622016-06-07 17:54:08 -0700135 help_text="The Service Controller this Service uses",
136 null=True, blank=True)
Scott Baker80d65b12016-05-26 20:20:47 -0700137
Scott Baker008a9962015-04-15 20:58:20 -0700138 def __init__(self, *args, **kwargs):
139 # for subclasses, set the default kind appropriately
140 self._meta.get_field("kind").default = self.KIND
141 super(Service, self).__init__(*args, **kwargs)
142
143 @classmethod
144 def get_service_objects(cls):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700145 return cls.objects.filter(kind=cls.KIND)
Scott Baker008a9962015-04-15 20:58:20 -0700146
Scott Baker16573d32015-07-24 15:36:02 -0700147 @classmethod
Scott Baker542cd6f2015-10-19 21:18:53 -0700148 def get_deleted_service_objects(cls):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700149 return cls.deleted_objects.filter(kind=cls.KIND)
Scott Baker542cd6f2015-10-19 21:18:53 -0700150
151 @classmethod
Scott Baker16573d32015-07-24 15:36:02 -0700152 def get_service_objects_by_user(cls, user):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700153 return cls.select_by_user(user).filter(kind=cls.KIND)
Scott Baker16573d32015-07-24 15:36:02 -0700154
155 @classmethod
156 def select_by_user(cls, user):
157 if user.is_admin:
158 return cls.objects.all()
159 else:
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700160 service_ids = [
161 sp.slice.id for sp in ServicePrivilege.objects.filter(user=user)]
Scott Baker16573d32015-07-24 15:36:02 -0700162 return cls.objects.filter(id__in=service_ids)
163
Scott Bakercce158d2015-12-07 22:20:40 -0800164 @property
165 def serviceattribute_dict(self):
166 attrs = {}
167 for attr in self.serviceattributes.all():
168 attrs[attr.name] = attr.value
169 return attrs
170
Siobhan Tully00353f72013-10-08 21:53:27 -0400171 def __unicode__(self): return u'%s' % (self.name)
172
Tony Mack9d2ea092015-04-29 12:23:10 -0400173 def can_update(self, user):
174 return user.can_update_service(self, allow=['admin'])
Scott Baker618a4892015-07-06 14:27:31 -0700175
Scott Baker98436732015-05-11 16:36:41 -0700176 def get_scalable_nodes(self, slice, max_per_node=None, exclusive_slices=[]):
177 """
178 Get a list of nodes that can be used to scale up a slice.
179
180 slice - slice to scale up
Tony Mack3de59e32015-08-19 11:58:18 -0400181 max_per_node - maximum numbers of instances that 'slice' can have on a single node
Scott Baker98436732015-05-11 16:36:41 -0700182 exclusive_slices - list of slices that must have no nodes in common with 'slice'.
183 """
184
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700185 # late import to get around order-of-imports constraint in __init__.py
186 from core.models import Node, Instance
Scott Baker98436732015-05-11 16:36:41 -0700187
188 nodes = list(Node.objects.all())
189
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700190 conflicting_instances = Instance.objects.filter(
191 slice__in=exclusive_slices)
192 conflicting_nodes = Node.objects.filter(
193 instances__in=conflicting_instances)
Scott Baker98436732015-05-11 16:36:41 -0700194
195 nodes = [x for x in nodes if x not in conflicting_nodes]
196
Tony Mack3de59e32015-08-19 11:58:18 -0400197 # If max_per_node is set, then limit the number of instances this slice
Scott Baker98436732015-05-11 16:36:41 -0700198 # can have on a single node.
199 if max_per_node:
200 acceptable_nodes = []
201 for node in nodes:
Tony Mack3de59e32015-08-19 11:58:18 -0400202 existing_count = node.instances.filter(slice=slice).count()
Scott Baker98436732015-05-11 16:36:41 -0700203 if existing_count < max_per_node:
204 acceptable_nodes.append(node)
205 nodes = acceptable_nodes
206
207 return nodes
208
209 def pick_node(self, slice, max_per_node=None, exclusive_slices=[]):
210 # Pick the best node to scale up a slice.
211
212 nodes = self.get_scalable_nodes(slice, max_per_node, exclusive_slices)
Tony Mack3de59e32015-08-19 11:58:18 -0400213 nodes = sorted(nodes, key=lambda node: node.instances.all().count())
Scott Baker98436732015-05-11 16:36:41 -0700214 if not nodes:
215 return None
216 return nodes[0]
217
218 def adjust_scale(self, slice_hint, scale, max_per_node=None, exclusive_slices=[]):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700219 # late import to get around order-of-imports constraint in __init__.py
220 from core.models import Instance
Scott Baker98436732015-05-11 16:36:41 -0700221
222 slices = [x for x in self.slices.all() if slice_hint in x.name]
223 for slice in slices:
Tony Mack3de59e32015-08-19 11:58:18 -0400224 while slice.instances.all().count() > scale:
225 s = slice.instances.all()[0]
226 # print "drop instance", s
Scott Baker98436732015-05-11 16:36:41 -0700227 s.delete()
228
Tony Mack3de59e32015-08-19 11:58:18 -0400229 while slice.instances.all().count() < scale:
Scott Baker98436732015-05-11 16:36:41 -0700230 node = self.pick_node(slice, max_per_node, exclusive_slices)
231 if not node:
232 # no more available nodes
233 break
234
235 image = slice.default_image
236 if not image:
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700237 raise XOSConfigurationError(
238 "No default_image for slice %s" % slice.name)
Scott Baker98436732015-05-11 16:36:41 -0700239
240 flavor = slice.default_flavor
241 if not flavor:
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700242 raise XOSConfigurationError(
243 "No default_flavor for slice %s" % slice.name)
Scott Baker98436732015-05-11 16:36:41 -0700244
Tony Mack3de59e32015-08-19 11:58:18 -0400245 s = Instance(slice=slice,
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700246 node=node,
247 creator=slice.creator,
248 image=image,
249 flavor=flavor,
250 deployment=node.site_deployment.deployment)
Scott Baker98436732015-05-11 16:36:41 -0700251 s.save()
252
Tony Mack3de59e32015-08-19 11:58:18 -0400253 # print "add instance", s
Tony Mack9d2ea092015-04-29 12:23:10 -0400254
Scott Baker8a67ae72015-12-09 22:54:52 -0800255 def get_vtn_src_nets(self):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700256 nets = []
Scott Baker8a67ae72015-12-09 22:54:52 -0800257 for slice in self.slices.all():
258 for ns in slice.networkslices.all():
259 if not ns.network:
260 continue
Scott Bakerf63cf1c2016-01-12 19:59:12 -0800261# if ns.network.template.access in ["direct", "indirect"]:
262# # skip access networks; we want to use the private network
263# continue
Scott Bakere6e78552016-04-11 21:26:39 -0700264 if "management" in ns.network.name:
265 # don't try to connect the management network to anything
266 continue
Scott Baker8a67ae72015-12-09 22:54:52 -0800267 if ns.network.name in ["wan_network", "lan_network"]:
268 # we don't want to attach to the vCPE's lan or wan network
269 # we only want to attach to its private network
270 # TODO: fix hard-coding of network name
271 continue
272 for cn in ns.network.controllernetworks.all():
273 if cn.net_id:
274 net = {"name": ns.network.name, "net_id": cn.net_id}
275 nets.append(net)
276 return nets
277
Scott Bakercbd718e2015-12-08 21:31:18 -0800278 def get_vtn_nets(self):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700279 nets = []
Scott Bakercbd718e2015-12-08 21:31:18 -0800280 for slice in self.slices.all():
281 for ns in slice.networkslices.all():
282 if not ns.network:
283 continue
Scott Baker8a67ae72015-12-09 22:54:52 -0800284 if ns.network.template.access not in ["direct", "indirect"]:
285 # skip anything that's not an access network
286 continue
Scott Bakercbd718e2015-12-08 21:31:18 -0800287 for cn in ns.network.controllernetworks.all():
288 if cn.net_id:
289 net = {"name": ns.network.name, "net_id": cn.net_id}
290 nets.append(net)
291 return nets
292
293 def get_vtn_dependencies_nets(self):
294 provider_nets = []
Scott Baker5380a442015-12-08 19:27:50 -0800295 for tenant in self.subscribed_tenants.all():
296 if tenant.provider_service:
Scott Bakercbd718e2015-12-08 21:31:18 -0800297 for net in tenant.provider_service.get_vtn_nets():
298 if not net in provider_nets:
299 provider_nets.append(net)
300 return provider_nets
301
302 def get_vtn_dependencies_ids(self):
303 return [x["net_id"] for x in self.get_vtn_dependencies_nets()]
304
305 def get_vtn_dependencies_names(self):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700306 return [x["name"] + "_" + x["net_id"] for x in self.get_vtn_dependencies_nets()]
Scott Bakercbd718e2015-12-08 21:31:18 -0800307
Scott Baker8a67ae72015-12-09 22:54:52 -0800308 def get_vtn_src_ids(self):
309 return [x["net_id"] for x in self.get_vtn_src_nets()]
Scott Bakercbd718e2015-12-08 21:31:18 -0800310
Scott Baker8a67ae72015-12-09 22:54:52 -0800311 def get_vtn_src_names(self):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700312 return [x["name"] + "_" + x["net_id"] for x in self.get_vtn_src_nets()]
Scott Baker5380a442015-12-08 19:27:50 -0800313
314
Siobhan Tully00353f72013-10-08 21:53:27 -0400315class ServiceAttribute(PlCoreBase):
Scott Bakercce158d2015-12-07 22:20:40 -0800316 name = models.CharField(help_text="Attribute Name", max_length=128)
Tony Mackd84b1ff2015-03-09 13:03:56 -0400317 value = StrippedCharField(help_text="Attribute Value", max_length=1024)
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700318 service = models.ForeignKey(Service, related_name='serviceattributes',
319 help_text="The Service this attribute is associated with")
320
Siobhan Tully00353f72013-10-08 21:53:27 -0400321
Tony Mack9d2ea092015-04-29 12:23:10 -0400322class ServiceRole(PlCoreBase):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700323 ROLE_CHOICES = (('admin', 'Admin'),)
Tony Mack9d2ea092015-04-29 12:23:10 -0400324 role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
325
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700326 def __unicode__(self): return u'%s' % (self.role)
327
Tony Mack9d2ea092015-04-29 12:23:10 -0400328
329class ServicePrivilege(PlCoreBase):
330 user = models.ForeignKey('User', related_name='serviceprivileges')
331 service = models.ForeignKey('Service', related_name='serviceprivileges')
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700332 role = models.ForeignKey('ServiceRole', related_name='serviceprivileges')
Tony Mack9d2ea092015-04-29 12:23:10 -0400333
334 class Meta:
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700335 unique_together = ('user', 'service', 'role')
Tony Mack9d2ea092015-04-29 12:23:10 -0400336
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700337 def __unicode__(self): return u'%s %s %s' % (
338 self.service, self.user, self.role)
Tony Mack9d2ea092015-04-29 12:23:10 -0400339
340 def can_update(self, user):
341 if not self.service.enabled:
342 raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
343 return self.service.can_update(user)
344
345 def save(self, *args, **kwds):
346 if not self.service.enabled:
347 raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
348 super(ServicePrivilege, self).save(*args, **kwds)
349
350 def delete(self, *args, **kwds):
351 if not self.service.enabled:
352 raise PermissionDenied, "Cannot modify permission(s) of a disabled service"
Scott Baker4587b822015-07-01 18:29:08 -0700353 super(ServicePrivilege, self).delete(*args, **kwds)
354
Scott Baker16573d32015-07-24 15:36:02 -0700355 @classmethod
356 def select_by_user(cls, user):
Tony Mack9d2ea092015-04-29 12:23:10 -0400357 if user.is_admin:
Scott Baker16573d32015-07-24 15:36:02 -0700358 qs = cls.objects.all()
Tony Mack9d2ea092015-04-29 12:23:10 -0400359 else:
Scott Baker16573d32015-07-24 15:36:02 -0700360 qs = cls.objects.filter(user=user)
Scott Baker4587b822015-07-01 18:29:08 -0700361 return qs
362
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700363
Scott Baker82498c52015-07-13 13:07:27 -0700364class TenantRoot(PlCoreBase, AttributeMixin):
Scott Baker4587b822015-07-01 18:29:08 -0700365 """ A tenantRoot is one of the things that can sit at the root of a chain
366 of tenancy. This object represents a node.
367 """
368
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700369 KIND = "generic"
Scott Baker4587b822015-07-01 18:29:08 -0700370 kind = StrippedCharField(max_length=30, default=KIND)
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700371 name = StrippedCharField(
372 max_length=255, help_text="name", blank=True, null=True)
Scott Baker4587b822015-07-01 18:29:08 -0700373
Scott Bakerefcec632015-07-07 12:12:42 -0700374 service_specific_attribute = models.TextField(blank=True, null=True)
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700375 service_specific_id = StrippedCharField(
376 max_length=30, blank=True, null=True)
Scott Baker4587b822015-07-01 18:29:08 -0700377
Scott Bakerdb66fd32015-07-07 17:59:44 -0700378 def __init__(self, *args, **kwargs):
379 # for subclasses, set the default kind appropriately
380 self._meta.get_field("kind").default = self.KIND
381 super(TenantRoot, self).__init__(*args, **kwargs)
382
Scott Baker618a4892015-07-06 14:27:31 -0700383 def __unicode__(self):
384 if not self.name:
385 return u"%s-tenant_root-#%s" % (str(self.kind), str(self.id))
386 else:
387 return self.name
388
389 def can_update(self, user):
390 return user.can_update_tenant_root(self, allow=['admin'])
391
Scott Bakerefcec632015-07-07 12:12:42 -0700392 def get_subscribed_tenants(self, tenant_class):
393 ids = self.subscribed_tenants.filter(kind=tenant_class.KIND)
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700394 return tenant_class.objects.filter(id__in=ids)
Scott Bakerefcec632015-07-07 12:12:42 -0700395
396 def get_newest_subscribed_tenant(self, kind):
397 st = list(self.get_subscribed_tenants(kind))
398 if not st:
399 return None
400 return sorted(st, key=attrgetter('id'))[0]
401
402 @classmethod
403 def get_tenant_objects(cls):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700404 return cls.objects.filter(kind=cls.KIND)
Scott Bakerefcec632015-07-07 12:12:42 -0700405
Scott Baker16573d32015-07-24 15:36:02 -0700406 @classmethod
407 def get_tenant_objects_by_user(cls, user):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700408 return cls.select_by_user(user).filter(kind=cls.KIND)
Scott Baker16573d32015-07-24 15:36:02 -0700409
410 @classmethod
411 def select_by_user(cls, user):
412 if user.is_admin:
413 return cls.objects.all()
414 else:
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700415 tr_ids = [
416 trp.tenant_root.id for trp in TenantRootPrivilege.objects.filter(user=user)]
Scott Baker16573d32015-07-24 15:36:02 -0700417 return cls.objects.filter(id__in=tr_ids)
418
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700419 # helper function to be used in subclasses that want to ensure
420 # service_specific_id is unique
Scott Bakerdc880f32016-03-31 14:45:31 -0700421 def validate_unique_service_specific_id(self, none_okay=False):
422 if not none_okay and (self.service_specific_id is None):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700423 raise XOSMissingField("subscriber_specific_id is None, and it's a required field", fields={
424 "service_specific_id": "cannot be none"})
Scott Bakerdc880f32016-03-31 14:45:31 -0700425
426 if self.service_specific_id:
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700427 conflicts = self.get_tenant_objects().filter(
428 service_specific_id=self.service_specific_id)
Scott Bakerdc880f32016-03-31 14:45:31 -0700429 if self.pk:
Scott Bakere4c9d872016-04-01 16:28:41 -0700430 conflicts = conflicts.exclude(pk=self.pk)
Scott Bakerdc880f32016-03-31 14:45:31 -0700431 if conflicts:
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700432 raise XOSDuplicateKey("service_specific_id %s already exists" % self.service_specific_id, fields={
433 "service_specific_id": "duplicate key"})
434
Scott Bakerdc880f32016-03-31 14:45:31 -0700435
Scott Baker82498c52015-07-13 13:07:27 -0700436class Tenant(PlCoreBase, AttributeMixin):
Scott Baker8103d0f2015-04-10 16:42:26 -0700437 """ A tenant is a relationship between two entities, a subscriber and a
Scott Baker4587b822015-07-01 18:29:08 -0700438 provider. This object represents an edge.
Scott Baker8103d0f2015-04-10 16:42:26 -0700439
440 The subscriber can be a User, a Service, or a Tenant.
441
442 The provider is always a Service.
Scott Baker4587b822015-07-01 18:29:08 -0700443
444 TODO: rename "Tenant" to "Tenancy"
Scott Baker8103d0f2015-04-10 16:42:26 -0700445 """
Scott Baker008a9962015-04-15 20:58:20 -0700446
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700447 CONNECTIVITY_CHOICES = (('public', 'Public'),
448 ('private', 'Private'), ('na', 'Not Applicable'))
Scott Baker925a8fa2015-04-26 20:30:40 -0700449
Scott Baker008a9962015-04-15 20:58:20 -0700450 # when subclassing a service, redefine KIND to describe the new service
451 KIND = "generic"
452
453 kind = StrippedCharField(max_length=30, default=KIND)
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700454 provider_service = models.ForeignKey(
455 Service, related_name='provided_tenants')
Scott Baker4587b822015-07-01 18:29:08 -0700456
457 # The next four things are the various type of objects that can be subscribers of this Tenancy
458 # relationship. One and only one can be used at a time.
Scott Baker97ca9102016-04-13 16:56:39 -0700459 # XXX these should really be changed to GenericForeignKey
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700460 subscriber_service = models.ForeignKey(
461 Service, related_name='subscribed_tenants', blank=True, null=True)
462 subscriber_tenant = models.ForeignKey(
463 "Tenant", related_name='subscribed_tenants', blank=True, null=True)
464 subscriber_user = models.ForeignKey(
465 "User", related_name='subscribed_tenants', blank=True, null=True)
466 subscriber_root = models.ForeignKey(
467 "TenantRoot", related_name="subscribed_tenants", blank=True, null=True)
468 subscriber_network = models.ForeignKey(
469 "Network", related_name="subscribed_tenants", blank=True, null=True)
Scott Baker4587b822015-07-01 18:29:08 -0700470
471 # Service_specific_attribute and service_specific_id are opaque to XOS
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700472 service_specific_id = StrippedCharField(
473 max_length=30, blank=True, null=True)
Scott Baker76934d82015-05-06 19:49:31 -0700474 service_specific_attribute = models.TextField(blank=True, null=True)
Scott Baker4587b822015-07-01 18:29:08 -0700475
476 # Connect_method is only used by Coarse tenants
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700477 connect_method = models.CharField(
478 null=False, blank=False, max_length=30, choices=CONNECTIVITY_CHOICES, default="na")
Scott Baker8103d0f2015-04-10 16:42:26 -0700479
Scott Baker008a9962015-04-15 20:58:20 -0700480 def __init__(self, *args, **kwargs):
481 # for subclasses, set the default kind appropriately
482 self._meta.get_field("kind").default = self.KIND
483 super(Tenant, self).__init__(*args, **kwargs)
484
Scott Baker8103d0f2015-04-10 16:42:26 -0700485 def __unicode__(self):
Scott Bakerf996b762015-05-20 20:42:04 -0700486 return u"%s-tenant-%s" % (str(self.kind), str(self.id))
Scott Baker8103d0f2015-04-10 16:42:26 -0700487
Scott Baker008a9962015-04-15 20:58:20 -0700488 @classmethod
489 def get_tenant_objects(cls):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700490 return cls.objects.filter(kind=cls.KIND)
Scott Baker008a9962015-04-15 20:58:20 -0700491
Scott Bakere7fc9f52015-05-05 17:52:03 -0700492 @classmethod
Scott Baker16573d32015-07-24 15:36:02 -0700493 def get_tenant_objects_by_user(cls, user):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700494 return cls.select_by_user(user).filter(kind=cls.KIND)
Scott Baker16573d32015-07-24 15:36:02 -0700495
496 @classmethod
Scott Bakere7fc9f52015-05-05 17:52:03 -0700497 def get_deleted_tenant_objects(cls):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700498 return cls.deleted_objects.filter(kind=cls.KIND)
Scott Bakere7fc9f52015-05-05 17:52:03 -0700499
Scott Bakercce158d2015-12-07 22:20:40 -0800500 @property
501 def tenantattribute_dict(self):
502 attrs = {}
503 for attr in self.tenantattributes.all():
504 attrs[attr.name] = attr.value
505 return attrs
506
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700507 # helper function to be used in subclasses that want to ensure
508 # service_specific_id is unique
Scott Bakerd921e1c2015-04-20 14:24:29 -0700509 def validate_unique_service_specific_id(self):
510 if self.pk is None:
511 if self.service_specific_id is None:
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700512 raise XOSMissingField("subscriber_specific_id is None, and it's a required field", fields={
513 "service_specific_id": "cannot be none"})
Scott Bakerd921e1c2015-04-20 14:24:29 -0700514
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700515 conflicts = self.get_tenant_objects().filter(
516 service_specific_id=self.service_specific_id)
Scott Bakerd921e1c2015-04-20 14:24:29 -0700517 if conflicts:
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700518 raise XOSDuplicateKey("service_specific_id %s already exists" % self.service_specific_id, fields={
519 "service_specific_id": "duplicate key"})
Scott Bakerd921e1c2015-04-20 14:24:29 -0700520
Scott Baker618a4892015-07-06 14:27:31 -0700521 def save(self, *args, **kwargs):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700522 subCount = sum([1 for e in [self.subscriber_service, self.subscriber_tenant,
523 self.subscriber_user, self.subscriber_root] if e is not None])
Scott Baker618a4892015-07-06 14:27:31 -0700524 if (subCount > 1):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700525 raise XOSConflictingField(
526 "Only one of subscriber_service, subscriber_tenant, subscriber_user, subscriber_root should be set")
Scott Baker618a4892015-07-06 14:27:31 -0700527
528 super(Tenant, self).save(*args, **kwargs)
529
530 def get_subscribed_tenants(self, tenant_class):
531 ids = self.subscribed_tenants.filter(kind=tenant_class.KIND)
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700532 return tenant_class.objects.filter(id__in=ids)
Scott Baker618a4892015-07-06 14:27:31 -0700533
534 def get_newest_subscribed_tenant(self, kind):
535 st = list(self.get_subscribed_tenants(kind))
536 if not st:
537 return None
538 return sorted(st, key=attrgetter('id'))[0]
539
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700540
Scott Baker534dd2a2015-11-18 20:58:08 -0800541class Scheduler(object):
542 # XOS Scheduler Abstract Base Class
543 # Used to implement schedulers that pick which node to put instances on
544
545 def __init__(self, slice):
546 self.slice = slice
547
548 def pick(self):
549 # this method should return a tuple (node, parent)
550 # node is the node to instantiate on
551 # parent is for container_vm instances only, and is the VM that will
552 # hold the container
553
554 raise Exception("Abstract Base")
555
Scott Baker534dd2a2015-11-18 20:58:08 -0800556
Scott Baker534dd2a2015-11-18 20:58:08 -0800557class LeastLoadedNodeScheduler(Scheduler):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700558 # This scheduler always return the node with the fewest number of
559 # instances.
Scott Baker534dd2a2015-11-18 20:58:08 -0800560
Scott Baker6526f552016-03-04 11:29:02 -0800561 def __init__(self, slice, label=None):
Scott Baker534dd2a2015-11-18 20:58:08 -0800562 super(LeastLoadedNodeScheduler, self).__init__(slice)
Scott Baker6526f552016-03-04 11:29:02 -0800563 self.label = label
Scott Baker534dd2a2015-11-18 20:58:08 -0800564
565 def pick(self):
566 from core.models import Node
Pingping Linfa30bae2016-03-03 09:52:24 -0800567 if not self.slice.default_node:
568 nodes = list(Node.objects.all())
569 nodes = sorted(nodes, key=lambda node: node.instances.all().count())
570 else:
571 nodes = list(Node.objects.filter(name = self.slice.default_node))
Matteo Scandolo1d7ff812016-04-21 09:02:08 -0700572
Scott Baker6526f552016-03-04 11:29:02 -0800573 if self.label:
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700574 nodes = nodes.filter(nodelabels__name=self.label)
Scott Baker6526f552016-03-04 11:29:02 -0800575
576 nodes = list(nodes)
577
578 if not nodes:
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700579 raise Exception(
580 "LeastLoadedNodeScheduler: No suitable nodes to pick from")
Scott Bakercce158d2015-12-07 22:20:40 -0800581
Scott Baker534dd2a2015-11-18 20:58:08 -0800582 # TODO: logic to filter nodes by which nodes are up, and which
583 # nodes the slice can instantiate on.
Pingping Linfa30bae2016-03-03 09:52:24 -0800584# nodes = sorted(nodes, key=lambda node: node.instances.all().count())
Scott Baker534dd2a2015-11-18 20:58:08 -0800585 return [nodes[0], None]
586
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700587
Scott Baker534dd2a2015-11-18 20:58:08 -0800588class ContainerVmScheduler(Scheduler):
589 # This scheduler picks a VM in the slice with the fewest containers inside
590 # of it. If no VMs are suitable, then it creates a VM.
591
Scott Baker534dd2a2015-11-18 20:58:08 -0800592 MAX_VM_PER_CONTAINER = 10
593
594 def __init__(self, slice):
595 super(ContainerVmScheduler, self).__init__(slice)
596
597 @property
598 def image(self):
599 from core.models import Image
600
Scott Bakera8188a72016-05-19 17:54:52 -0700601 # If slice has default_image set then use it
602 if self.slice.default_image:
603 return self.slice.default_image
Scott Baker534dd2a2015-11-18 20:58:08 -0800604
Scott Bakera8188a72016-05-19 17:54:52 -0700605 raise XOPSProgrammingError("Please set a default image for %s" % self.slice.name)
Scott Baker534dd2a2015-11-18 20:58:08 -0800606
607 def make_new_instance(self):
608 from core.models import Instance, Flavor
609
610 flavors = Flavor.objects.filter(name="m1.small")
611 if not flavors:
612 raise XOSConfigurationError("No m1.small flavor")
613
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700614 (node, parent) = LeastLoadedNodeScheduler(self.slice).pick()
Scott Baker534dd2a2015-11-18 20:58:08 -0800615
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700616 instance = Instance(slice=self.slice,
617 node=node,
618 image=self.image,
619 creator=self.slice.creator,
620 deployment=node.site_deployment.deployment,
621 flavor=flavors[0],
622 isolation="vm",
623 parent=parent)
Scott Baker534dd2a2015-11-18 20:58:08 -0800624 instance.save()
625 # We rely on a special naming convention to identify the VMs that will
626 # hole containers.
627 instance.name = "%s-outer-%s" % (instance.slice.name, instance.id)
628 instance.save()
629 return instance
630
631 def pick(self):
632 from core.models import Instance, Flavor
633
634 for vm in self.slice.instances.filter(isolation="vm"):
635 avail_vms = []
636 if (vm.name.startswith("%s-outer-" % self.slice.name)):
637 container_count = Instance.objects.filter(parent=vm).count()
638 if (container_count < self.MAX_VM_PER_CONTAINER):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700639 avail_vms.append((vm, container_count))
Scott Baker534dd2a2015-11-18 20:58:08 -0800640 # sort by least containers-per-vm
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700641 avail_vms = sorted(avail_vms, key=lambda x: x[1])
Scott Baker534dd2a2015-11-18 20:58:08 -0800642 print "XXX", avail_vms
643 if avail_vms:
644 instance = avail_vms[0][0]
645 return (instance.node, instance)
646
647 instance = self.make_new_instance()
648 return (instance.node, instance)
649
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700650
Scott Bakere458afd2015-09-09 16:36:06 -0700651class TenantWithContainer(Tenant):
652 """ A tenant that manages a container """
653
Scott Bakere458afd2015-09-09 16:36:06 -0700654 class Meta:
655 proxy = True
656
657 def __init__(self, *args, **kwargs):
658 super(TenantWithContainer, self).__init__(*args, **kwargs)
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700659 self.cached_instance = None
Tony Mack6a782f92015-09-13 22:50:39 +0000660 self.orig_instance_id = self.get_initial_attribute("instance_id")
Scott Bakere274f6b2015-11-02 20:54:28 -0800661
Scott Bakere458afd2015-09-09 16:36:06 -0700662 @property
Tony Mack6a782f92015-09-13 22:50:39 +0000663 def instance(self):
664 from core.models import Instance
665 if getattr(self, "cached_instance", None):
666 return self.cached_instance
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700667 instance_id = self.get_attribute("instance_id")
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600668 if not instance_id:
669 return None
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700670 instances = Instance.objects.filter(id=instance_id)
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600671 if not instances:
672 return None
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700673 instance = instances[0]
Tony Mack6a782f92015-09-13 22:50:39 +0000674 instance.caller = self.creator
675 self.cached_instance = instance
676 return instance
Scott Bakere458afd2015-09-09 16:36:06 -0700677
Tony Mack6a782f92015-09-13 22:50:39 +0000678 @instance.setter
679 def instance(self, value):
Scott Bakere458afd2015-09-09 16:36:06 -0700680 if value:
681 value = value.id
Tony Mack6a782f92015-09-13 22:50:39 +0000682 if (value != self.get_attribute("instance_id", None)):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700683 self.cached_instance = None
Tony Mack6a782f92015-09-13 22:50:39 +0000684 self.set_attribute("instance_id", value)
Scott Bakere458afd2015-09-09 16:36:06 -0700685
Scott Bakere274f6b2015-11-02 20:54:28 -0800686 @property
Scott Bakerbdda0be2016-02-10 12:23:53 -0800687 def external_hostname(self):
688 return self.get_attribute("external_hostname", "")
689
690 @external_hostname.setter
691 def external_hostname(self, value):
692 self.set_attribute("external_hostname", value)
693
694 @property
695 def external_container(self):
696 return self.get_attribute("external_container", "")
697
698 @external_container.setter
699 def external_container(self, value):
700 self.set_attribute("external_container", value)
701
702 @property
Scott Bakere458afd2015-09-09 16:36:06 -0700703 def creator(self):
704 from core.models import User
705 if getattr(self, "cached_creator", None):
706 return self.cached_creator
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700707 creator_id = self.get_attribute("creator_id")
Scott Bakere458afd2015-09-09 16:36:06 -0700708 if not creator_id:
709 return None
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700710 users = User.objects.filter(id=creator_id)
Scott Bakere458afd2015-09-09 16:36:06 -0700711 if not users:
712 return None
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700713 user = users[0]
Scott Bakere458afd2015-09-09 16:36:06 -0700714 self.cached_creator = users[0]
715 return user
716
717 @creator.setter
718 def creator(self, value):
719 if value:
720 value = value.id
721 if (value != self.get_attribute("creator_id", None)):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700722 self.cached_creator = None
Scott Bakere458afd2015-09-09 16:36:06 -0700723 self.set_attribute("creator_id", value)
724
725 @property
726 def image(self):
727 from core.models import Image
728 # Implement the logic here to pick the image that should be used when
729 # instantiating the VM that will hold the container.
Scott Bakerabad4c92016-05-25 10:02:29 -0700730
Scott Bakercfaf0d32015-11-16 22:51:02 -0800731 slice = self.provider_service.slices.all()
732 if not slice:
733 raise XOSProgrammingError("provider service has no slice")
734 slice = slice[0]
735
Scott Bakerabad4c92016-05-25 10:02:29 -0700736 # If slice has default_image set then use it
737 if slice.default_image:
738 return slice.default_image
Scott Bakere458afd2015-09-09 16:36:06 -0700739
Scott Bakera8188a72016-05-19 17:54:52 -0700740 raise XOPSProgrammingError("Please set a default image for %s" % self.slice.name)
Scott Bakere274f6b2015-11-02 20:54:28 -0800741
Scott Bakercfaf0d32015-11-16 22:51:02 -0800742 def save_instance(self, instance):
743 # Override this function to do custom pre-save or post-save processing,
744 # such as creating ports for containers.
745 instance.save()
Scott Bakere274f6b2015-11-02 20:54:28 -0800746
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600747 def pick_least_loaded_instance_in_slice(self, slices):
748 for slice in slices:
749 if slice.instances.all().count() > 0:
750 for instance in slice.instances.all():
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700751 # Pick the first instance that has lesser than 5 tenants
752 if self.count_of_tenants_of_an_instance(instance) < 5:
753 return instance
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600754 return None
755
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700756 # TODO: Ideally the tenant count for an instance should be maintained using a
757 # many-to-one relationship attribute, however this model being proxy, it does
758 # not permit any new attributes to be defined. Find if any better solutions
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600759 def count_of_tenants_of_an_instance(self, instance):
760 tenant_count = 0
761 for tenant in self.get_tenant_objects().all():
762 if tenant.get_attribute("instance_id", None) == instance.id:
763 tenant_count += 1
764 return tenant_count
765
Scott Bakercfaf0d32015-11-16 22:51:02 -0800766 def manage_container(self):
Tony Mack6a782f92015-09-13 22:50:39 +0000767 from core.models import Instance, Flavor
Scott Bakere458afd2015-09-09 16:36:06 -0700768
769 if self.deleted:
770 return
771
Tony Mack6a782f92015-09-13 22:50:39 +0000772 if (self.instance is not None) and (self.instance.image != self.image):
773 self.instance.delete()
774 self.instance = None
Scott Bakere458afd2015-09-09 16:36:06 -0700775
Tony Mack6a782f92015-09-13 22:50:39 +0000776 if self.instance is None:
Scott Bakere458afd2015-09-09 16:36:06 -0700777 if not self.provider_service.slices.count():
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600778 raise XOSConfigurationError("The service has no slices")
Scott Bakere458afd2015-09-09 16:36:06 -0700779
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600780 new_instance_created = False
781 instance = None
782 if self.get_attribute("use_same_instance_for_multiple_tenants", default=False):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700783 # Find if any existing instances can be used for this tenant
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600784 slices = self.provider_service.slices.all()
785 instance = self.pick_least_loaded_instance_in_slice(slices)
Scott Bakere458afd2015-09-09 16:36:06 -0700786
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600787 if not instance:
Srikanth Vavilapalli71120a92015-11-17 13:41:38 -0600788 slice = self.provider_service.slices.all()[0]
Scott Bakercfaf0d32015-11-16 22:51:02 -0800789
Srikanth Vavilapalli2ab2d072016-02-15 01:18:09 -0500790 flavor = slice.default_flavor
791 if not flavor:
792 flavors = Flavor.objects.filter(name="m1.small")
793 if not flavors:
794 raise XOSConfigurationError("No m1.small flavor")
795 flavor = flavors[0]
796
Srikanth Vavilapalli71120a92015-11-17 13:41:38 -0600797 if slice.default_isolation == "container_vm":
Scott Baker534dd2a2015-11-18 20:58:08 -0800798 (node, parent) = ContainerVmScheduler(slice).pick()
Srikanth Vavilapalli71120a92015-11-17 13:41:38 -0600799 else:
Scott Baker534dd2a2015-11-18 20:58:08 -0800800 (node, parent) = LeastLoadedNodeScheduler(slice).pick()
Scott Bakercfaf0d32015-11-16 22:51:02 -0800801
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700802 instance = Instance(slice=slice,
803 node=node,
804 image=self.image,
805 creator=self.creator,
806 deployment=node.site_deployment.deployment,
807 flavor=flavor,
808 isolation=slice.default_isolation,
809 parent=parent)
Srikanth Vavilapalli71120a92015-11-17 13:41:38 -0600810 self.save_instance(instance)
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600811 new_instance_created = True
Scott Bakere458afd2015-09-09 16:36:06 -0700812
813 try:
Tony Mack6a782f92015-09-13 22:50:39 +0000814 self.instance = instance
Scott Bakere458afd2015-09-09 16:36:06 -0700815 super(TenantWithContainer, self).save()
816 except:
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600817 if new_instance_created:
818 instance.delete()
Scott Bakere458afd2015-09-09 16:36:06 -0700819 raise
820
821 def cleanup_container(self):
Tony Mack6a782f92015-09-13 22:50:39 +0000822 if self.instance:
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600823 if self.get_attribute("use_same_instance_for_multiple_tenants", default=False):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700824 # Delete the instance only if this is last tenant in that
825 # instance
826 tenant_count = self.count_of_tenants_of_an_instance(
827 self.instance)
Srikanth Vavilapalli1d14ff02015-11-17 12:21:02 -0600828 if tenant_count == 0:
829 self.instance.delete()
830 else:
831 self.instance.delete()
Tony Mack6a782f92015-09-13 22:50:39 +0000832 self.instance = None
Scott Baker618a4892015-07-06 14:27:31 -0700833
Scott Bakeree83f2a2015-12-10 23:23:07 -0800834 def save(self, *args, **kwargs):
835 if (not self.creator) and (hasattr(self, "caller")) and (self.caller):
836 self.creator = self.caller
837 super(TenantWithContainer, self).save(*args, **kwargs)
838
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700839
Scott Baker925a8fa2015-04-26 20:30:40 -0700840class CoarseTenant(Tenant):
Scott Baker4587b822015-07-01 18:29:08 -0700841 """ TODO: rename "CoarseTenant" --> "StaticTenant" """
Scott Baker925a8fa2015-04-26 20:30:40 -0700842 class Meta:
843 proxy = True
Siobhan Tully00353f72013-10-08 21:53:27 -0400844
Scott Baker2461bec2015-08-14 09:10:11 -0700845 KIND = COARSE_KIND
Scott Baker925a8fa2015-04-26 20:30:40 -0700846
847 def save(self, *args, **kwargs):
848 if (not self.subscriber_service):
849 raise XOSValidationError("subscriber_service cannot be null")
850 if (self.subscriber_tenant or self.subscriber_user):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700851 raise XOSValidationError(
852 "subscriber_tenant and subscriber_user must be null")
Scott Baker925a8fa2015-04-26 20:30:40 -0700853
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700854 super(CoarseTenant, self).save()
855
Scott Baker4587b822015-07-01 18:29:08 -0700856
857class Subscriber(TenantRoot):
858 """ Intermediate class for TenantRoots that are to be Subscribers """
859
860 class Meta:
861 proxy = True
862
863 KIND = "Subscriber"
864
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700865
Scott Baker4587b822015-07-01 18:29:08 -0700866class Provider(TenantRoot):
867 """ Intermediate class for TenantRoots that are to be Providers """
868
869 class Meta:
870 proxy = True
871
872 KIND = "Provider"
873
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700874
Scott Baker462a1d92015-10-15 15:59:19 -0700875class TenantAttribute(PlCoreBase):
Scott Baker907f9912015-10-20 17:12:36 -0700876 name = models.CharField(help_text="Attribute Name", max_length=128)
Scott Baker462a1d92015-10-15 15:59:19 -0700877 value = models.TextField(help_text="Attribute Value")
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700878 tenant = models.ForeignKey(Tenant, related_name='tenantattributes',
879 help_text="The Tenant this attribute is associated with")
Scott Baker462a1d92015-10-15 15:59:19 -0700880
Scott Baker1271a4d2016-04-06 14:34:49 -0700881 def __unicode__(self): return u'%s-%s' % (self.name, self.id)
882
Scott Baker4587b822015-07-01 18:29:08 -0700883
884class TenantRootRole(PlCoreBase):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700885 ROLE_CHOICES = (('admin', 'Admin'), ('access', 'Access'))
Scott Baker4587b822015-07-01 18:29:08 -0700886
887 role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
888
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700889 def __unicode__(self): return u'%s' % (self.role)
890
Scott Baker4587b822015-07-01 18:29:08 -0700891
892class TenantRootPrivilege(PlCoreBase):
893 user = models.ForeignKey('User', related_name="tenant_root_privileges")
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700894 tenant_root = models.ForeignKey(
895 'TenantRoot', related_name="tenant_root_privileges")
896 role = models.ForeignKey(
897 'TenantRootRole', related_name="tenant_root_privileges")
Scott Baker4587b822015-07-01 18:29:08 -0700898
899 class Meta:
900 unique_together = ('user', 'tenant_root', 'role')
901
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700902 def __unicode__(self): return u'%s %s %s' % (
903 self.tenant_root, self.user, self.role)
Scott Baker4587b822015-07-01 18:29:08 -0700904
905 def save(self, *args, **kwds):
906 if not self.user.is_active:
907 raise PermissionDenied, "Cannot modify role(s) of a disabled user"
Scott Baker335882a2015-07-24 10:15:31 -0700908 super(TenantRootPrivilege, self).save(*args, **kwds)
Scott Baker4587b822015-07-01 18:29:08 -0700909
910 def can_update(self, user):
Scott Baker335882a2015-07-24 10:15:31 -0700911 return user.can_update_tenant_root_privilege(self)
Scott Baker4587b822015-07-01 18:29:08 -0700912
Scott Baker16573d32015-07-24 15:36:02 -0700913 @classmethod
914 def select_by_user(cls, user):
Scott Baker4587b822015-07-01 18:29:08 -0700915 if user.is_admin:
Scott Baker5116b302015-07-24 15:48:03 -0700916 return cls.objects.all()
Scott Baker4587b822015-07-01 18:29:08 -0700917 else:
Scott Baker5116b302015-07-24 15:48:03 -0700918 # User can see his own privilege
919 trp_ids = [trp.id for trp in cls.objects.filter(user=user)]
920
921 # A slice admin can see the SlicePrivileges for his Slice
922 for priv in cls.objects.filter(user=user, role__role="admin"):
Jeremy Mowery11ed8992016-04-17 20:56:54 -0700923 trp_ids.extend(
924 [trp.id for trp in cls.objects.filter(tenant_root=priv.tenant_root)])
Scott Baker5116b302015-07-24 15:48:03 -0700925
926 return cls.objects.filter(id__in=trp_ids)
927
Jeremy Mowery95635132016-04-15 17:39:49 -0700928
Jeremy Moweryb31bd9e2016-03-14 23:59:11 -0700929class TenantRole(PlCoreBase):
Jeremy Mowery98e97d72016-04-15 00:05:27 -0700930 """A TenantRole option."""
Jeremy Mowery95635132016-04-15 17:39:49 -0700931 ROLE_CHOICES = (('admin', 'Admin'), ('access', 'Access'))
Jeremy Moweryb31bd9e2016-03-14 23:59:11 -0700932 role = StrippedCharField(choices=ROLE_CHOICES, unique=True, max_length=30)
Jeremy Mowery95635132016-04-15 17:39:49 -0700933
934 def __unicode__(self): return u'%s' % (self.role)
935
Jeremy Moweryb31bd9e2016-03-14 23:59:11 -0700936
937class TenantPrivilege(PlCoreBase):
Jeremy Mowery98e97d72016-04-15 00:05:27 -0700938 """"A TenantPrivilege which defines how users can access a particular Tenant.
939
940 Attributes:
941 id (models.AutoField): The ID of the privilege.
942 user (models.ForeignKey): A Foreign Key to the a User.
943 tenant (models.ForeignKey): A ForeignKey to the Tenant.
944 role (models.ForeignKey): A ForeignKey to the TenantRole.
945 """
Jeremy Moweryc86352a2016-04-10 23:00:54 -0700946 id = models.AutoField(primary_key=True)
Jeremy Mowery5d06a232016-04-04 22:30:44 -0700947 user = models.ForeignKey('User', related_name="tenantprivileges")
948 tenant = models.ForeignKey('Tenant', related_name="tenantprivileges")
949 role = models.ForeignKey('TenantRole', related_name="tenantprivileges")
Jeremy Moweryb31bd9e2016-03-14 23:59:11 -0700950
Jeremy Mowery95635132016-04-15 17:39:49 -0700951 def __unicode__(self): return u'%s %s %s' % (
952 self.tenant, self.user, self.role)
Jeremy Moweryb31bd9e2016-03-14 23:59:11 -0700953
954 def save(self, *args, **kwds):
955 if not self.user.is_active:
956 raise PermissionDenied, "Cannot modify role(s) of a disabled user"
957 super(TenantPrivilege, self).save(*args, **kwds)
958
959 def can_update(self, user):
960 return user.can_update_tenant_privilege(self)
961
962 @classmethod
963 def select_by_user(cls, user):
964 if user.is_admin:
965 return cls.objects.all()
966 else:
967 # User can see his own privilege
968 trp_ids = [trp.id for trp in cls.objects.filter(user=user)]
969
970 # A tenant admin can see the TenantPrivileges for their Tenants
971 for priv in cls.objects.filter(user=user, role__role="admin"):
Jeremy Mowery95635132016-04-15 17:39:49 -0700972 trp_ids.extend(
973 [trp.id for trp in cls.objects.filter(tenant=priv.tenant)])
Jeremy Moweryb31bd9e2016-03-14 23:59:11 -0700974
975 return cls.objects.filter(id__in=trp_ids)