blob: 6657c6981384cecf41369a1a3272144e84e6cfba [file] [log] [blame]
Siobhan Tully4bc09f22013-04-10 21:15:21 -04001import os
2from django.db import models
Sapan Bhatia14356b72014-11-05 10:32:41 -05003from django.db.models import Q
Tony Mack2b241cf2013-04-16 21:57:55 -04004from django.core import exceptions
Sapan Bhatia14356b72014-11-05 10:32:41 -05005from core.models import PlCoreBase,PlCoreBaseManager,PlCoreBaseDeletionManager
Tony Mack50e12212015-03-09 13:03:56 -04006from core.models.plcorebase import StrippedCharField
Siobhan Tully30fd4292013-05-10 08:59:56 -04007from core.models import Image
Scott Bakera115c1e2015-05-07 14:58:46 -07008from core.models import Slice, SlicePrivilege
Siobhan Tully30fd4292013-05-10 08:59:56 -04009from core.models import Node
10from core.models import Site
Siobhan Tullybf1153a2013-05-27 20:53:48 -040011from core.models import Deployment
Tony Mack68a1e422014-12-08 16:43:02 -050012from core.models import Controller
Tony Mackb0d97422013-06-10 09:57:45 -040013from core.models import User
Siobhan Tullyde5450d2013-06-21 11:35:33 -040014from core.models import Tag
Scott Baker7a61dc42014-09-02 17:08:20 -070015from core.models import Flavor
Siobhan Tullyde5450d2013-06-21 11:35:33 -040016from django.contrib.contenttypes import generic
Scott Baker86e132c2015-02-11 21:38:09 -080017from xos.config import Config
Scott Bakerf41fe2c2015-07-09 19:06:08 -070018from django.core.exceptions import PermissionDenied, ValidationError
Sapan Bhatia14356b72014-11-05 10:32:41 -050019
20config = Config()
Siobhan Tully4bc09f22013-04-10 21:15:21 -040021
Tony Mack06c8e472014-11-30 15:53:08 -050022def get_default_flavor(controller = None):
Tony Mackd8515472015-08-19 11:58:18 -040023 # Find a default flavor that can be used for a instance. This is particularly
Scott Baker7a61dc42014-09-02 17:08:20 -070024 # useful in evolution. It's also intended this helper function can be used
25 # for admin.py when users
26
Tony Mack06c8e472014-11-30 15:53:08 -050027 if controller:
28 flavors = controller.flavors.all()
Scott Baker7a61dc42014-09-02 17:08:20 -070029 else:
30 flavors = Flavor.objects.all()
31
32 if not flavors:
Scott Baker7a61dc42014-09-02 17:08:20 -070033 return None
34
35 for flavor in flavors:
36 if flavor.default:
Scott Baker7a61dc42014-09-02 17:08:20 -070037 return flavor
38
Scott Baker7a61dc42014-09-02 17:08:20 -070039 return flavors[0]
40
Tony Mackd8515472015-08-19 11:58:18 -040041class InstanceDeletionManager(PlCoreBaseDeletionManager):
Sapan Bhatia14356b72014-11-05 10:32:41 -050042 def get_queryset(self):
Tony Mackd8515472015-08-19 11:58:18 -040043 parent=super(InstanceDeletionManager, self)
Sapan Bhatia14356b72014-11-05 10:32:41 -050044 try:
45 backend_type = config.observer_backend_type
46 except AttributeError:
47 backend_type = None
48
49 parent_queryset = parent.get_queryset() if hasattr(parent, "get_queryset") else parent.get_query_set()
50 if (backend_type):
Tony Mack51c4a7d2014-11-30 15:33:35 -050051 return parent_queryset.filter(Q(node__controller__backend_type=backend_type))
Sapan Bhatia14356b72014-11-05 10:32:41 -050052 else:
53 return parent_queryset
54
55 # deprecated in django 1.7 in favor of get_queryset().
56 def get_query_set(self):
57 return self.get_queryset()
58
59
Tony Mackd8515472015-08-19 11:58:18 -040060class InstanceManager(PlCoreBaseManager):
Sapan Bhatia14356b72014-11-05 10:32:41 -050061 def get_queryset(self):
Tony Mackd8515472015-08-19 11:58:18 -040062 parent=super(InstanceManager, self)
Sapan Bhatia14356b72014-11-05 10:32:41 -050063
64 try:
65 backend_type = config.observer_backend_type
66 except AttributeError:
67 backend_type = None
68
69 parent_queryset = parent.get_queryset() if hasattr(parent, "get_queryset") else parent.get_query_set()
70
71 if backend_type:
Tony Mack51c4a7d2014-11-30 15:33:35 -050072 return parent_queryset.filter(Q(node__controller__backend_type=backend_type))
Sapan Bhatia14356b72014-11-05 10:32:41 -050073 else:
74 return parent_queryset
75
76 # deprecated in django 1.7 in favor of get_queryset().
77 def get_query_set(self):
78 return self.get_queryset()
79
Siobhan Tully4bc09f22013-04-10 21:15:21 -040080# Create your models here.
Tony Mackd8515472015-08-19 11:58:18 -040081class Instance(PlCoreBase):
Scott Baker1f454e32015-11-12 17:25:53 -080082 ISOLATION_CHOICES = (('vm', 'Virtual Machine'), ('container', 'Container'), ('container_vm', 'Container In VM'))
Scott Bakerdcf9e0d2015-11-09 16:17:11 -080083
Tony Mackd8515472015-08-19 11:58:18 -040084 objects = InstanceManager()
85 deleted_objects = InstanceDeletionManager()
Tony Mack50e12212015-03-09 13:03:56 -040086 instance_id = StrippedCharField(null=True, blank=True, max_length=200, help_text="Nova instance id")
87 instance_uuid = StrippedCharField(null=True, blank=True, max_length=200, help_text="Nova instance uuid")
Tony Mackd8515472015-08-19 11:58:18 -040088 name = StrippedCharField(max_length=200, help_text="Instance name")
Tony Mack50e12212015-03-09 13:03:56 -040089 instance_name = StrippedCharField(blank=True, null=True, max_length=200, help_text="OpenStack generated name")
Tony Mackd8515472015-08-19 11:58:18 -040090 ip = models.GenericIPAddressField(help_text="Instance ip address", blank=True, null=True)
91 image = models.ForeignKey(Image, related_name='instances')
Tony Mackd8515472015-08-19 11:58:18 -040092 creator = models.ForeignKey(User, related_name='instances', blank=True, null=True)
93 slice = models.ForeignKey(Slice, related_name='instances')
94 deployment = models.ForeignKey(Deployment, verbose_name='deployment', related_name='instance_deployment')
95 node = models.ForeignKey(Node, related_name='instances')
96 numberCores = models.IntegerField(verbose_name="Number of Cores", help_text="Number of cores for instance", default=0)
Scott Baker7a61dc42014-09-02 17:08:20 -070097 flavor = models.ForeignKey(Flavor, help_text="Flavor of this instance", default=get_default_flavor)
Siobhan Tullyde5450d2013-06-21 11:35:33 -040098 tags = generic.GenericRelation(Tag)
Scott Baker9c7a1e12014-05-30 14:42:42 -070099 userData = models.TextField(blank=True, null=True, help_text="user_data passed to instance during creation")
Scott Bakerdcf9e0d2015-11-09 16:17:11 -0800100 isolation = models.CharField(null=False, blank=False, max_length=30, choices=ISOLATION_CHOICES, default="vm")
Scott Bakere317a992015-11-10 17:07:23 -0800101 volumes = models.TextField(null=True, blank=True, help_text="Comma-separated list of directories to expose to parent context")
Scott Baker1f454e32015-11-12 17:25:53 -0800102 parent = models.ForeignKey("Instance", null=True, blank=True, help_text="Parent Instance for containers nested inside of VMs")
Siobhan Tully4bc09f22013-04-10 21:15:21 -0400103
Sapan Bhatia5b2ec272016-02-10 17:51:26 +0100104 def get_controller (self):
Sapan Bhatiaddcac192016-02-10 18:11:30 +0100105 return self.node.site_deployment.controller
Sapan Bhatia5b2ec272016-02-10 17:51:26 +0100106
Scott Baker3015c762013-08-23 10:28:07 -0700107 def __unicode__(self):
Scott Bakerc1452212015-08-04 23:50:51 -0700108 if self.name and Slice.objects.filter(id=self.slice_id) and (self.name != self.slice.name):
109 # NOTE: The weird check on self.slice_id was due to a problem when
Tony Mackd8515472015-08-19 11:58:18 -0400110 # deleting the slice before the instance.
Scott Baker02f913f2015-08-04 16:05:18 -0700111 return u'%s' % self.name
112 elif self.instance_name:
Scott Baker3015c762013-08-23 10:28:07 -0700113 return u'%s' % (self.instance_name)
114 elif self.id:
115 return u'uninstantiated-%s' % str(self.id)
116 elif self.slice:
Tony Mackd8515472015-08-19 11:58:18 -0400117 return u'unsaved-instance on %s' % self.slice.name
Scott Baker3015c762013-08-23 10:28:07 -0700118 else:
Tony Mackd8515472015-08-19 11:58:18 -0400119 return u'unsaved-instance'
Scott Baker3015c762013-08-23 10:28:07 -0700120
Tony Mackcdec0902013-04-15 00:38:49 -0400121 def save(self, *args, **kwds):
Scott Baker02f913f2015-08-04 16:05:18 -0700122 if not self.name:
123 self.name = self.slice.name
Tony Mack2bd5b412013-06-11 21:05:06 -0400124 if not self.creator and hasattr(self, 'caller'):
125 self.creator = self.caller
Scott Baker2d599df2015-01-29 17:55:40 -0800126 if not self.creator:
Tony Mackd8515472015-08-19 11:58:18 -0400127 raise ValidationError('instance has no creator')
Scott Baker5380c522014-06-06 14:49:43 -0700128
Scott Baker1f454e32015-11-12 17:25:53 -0800129 if (self.isolation == "container") or (self.isolation == "container_vm"):
Scott Baker73b39ee2015-11-11 09:24:03 -0800130 if (self.image.kind != "container"):
Pingping Linfc9256a2016-03-03 09:52:24 -0800131 raise ValidationError("Container instance must use container image")
Scott Baker73b39ee2015-11-11 09:24:03 -0800132 elif (self.isolation == "vm"):
133 if (self.image.kind != "vm"):
Pingping Linfc9256a2016-03-03 09:52:24 -0800134 raise ValidationError("VM instance must use VM image")
Scott Baker73b39ee2015-11-11 09:24:03 -0800135
Scott Baker1f454e32015-11-12 17:25:53 -0800136 if (self.isolation == "container_vm") and (not self.parent):
137 raise ValidationError("Container-vm instance must have a parent")
138
139 if (self.parent) and (self.isolation != "container_vm"):
140 raise ValidationError("Parent field can only be set on Container-vm instances")
141
Scott Bakerf41fe2c2015-07-09 19:06:08 -0700142 if (self.slice.creator != self.creator):
143 # Check to make sure there's a slice_privilege for the user. If there
144 # isn't, then keystone will throw an exception inside the observer.
145 slice_privs = SlicePrivilege.objects.filter(slice=self.slice, user=self.creator)
146 if not slice_privs:
Tony Mackd8515472015-08-19 11:58:18 -0400147 raise ValidationError('instance creator has no privileges on slice')
Scott Bakerf41fe2c2015-07-09 19:06:08 -0700148
Scott Bakerec06eba2014-06-20 18:03:04 -0700149# XXX smbaker - disabled for now, was causing fault in tenant view create slice
Tony Mack06c8e472014-11-30 15:53:08 -0500150# if not self.controllerNetwork.test_acl(slice=self.slice):
151# raise exceptions.ValidationError("Deployment %s's ACL does not allow any of this slice %s's users" % (self.controllerNetwork.name, self.slice.name))
Scott Baker5380c522014-06-06 14:49:43 -0700152
Tony Mackd8515472015-08-19 11:58:18 -0400153 super(Instance, self).save(*args, **kwds)
Tony Mack5b061472014-02-04 07:57:10 -0500154
155 def can_update(self, user):
Tony Mack3428e6e2015-02-08 21:38:41 -0500156 return user.can_update_slice(self.slice)
Tony Mack5b061472014-02-04 07:57:10 -0500157
Scott Baker434ca7e2014-08-15 12:29:20 -0700158 def all_ips(self):
159 ips={}
Scott Baker9a2b7c02015-08-28 11:37:50 -0700160 for ns in self.ports.all():
Scott Baker01907e92015-08-18 23:02:11 -0700161 if ns.ip:
162 ips[ns.network.name] = ns.ip
Scott Baker434ca7e2014-08-15 12:29:20 -0700163 return ips
164
165 def all_ips_string(self):
166 result = []
167 ips = self.all_ips()
168 for key in sorted(ips.keys()):
169 #result.append("%s = %s" % (key, ips[key]))
170 result.append(ips[key])
171 return ", ".join(result)
172 all_ips_string.short_description = "addresses"
173
Scott Baker7d7d8cd2015-03-16 17:13:58 -0700174 def get_public_ip(self):
Scott Baker9a2b7c02015-08-28 11:37:50 -0700175 for ns in self.ports.all():
Scott Bakerf8ec83d2015-03-16 16:44:41 -0700176 if (ns.ip) and (ns.network.template.visibility=="public") and (ns.network.template.translation=="none"):
177 return ns.ip
178 return None
179
Scott Baker1eb90b72015-10-20 21:21:05 -0700180 # return an address on nat-net
181 def get_network_ip(self, pattern):
Scott Baker5bdada32015-10-19 21:18:29 -0700182 for ns in self.ports.all():
Scott Baker1eb90b72015-10-20 21:21:05 -0700183 if pattern in ns.network.name.lower():
Scott Baker5bdada32015-10-19 21:18:29 -0700184 return ns.ip
185 return None
186
Scott Baker1eb90b72015-10-20 21:21:05 -0700187 # return an address that the synchronizer can use to SSH to the instance
188 def get_ssh_ip(self):
Scott Bakerf5478bf2016-02-10 17:24:22 -0800189 management=self.get_network_ip("management")
190 if management:
191 return management
Scott Baker1eb90b72015-10-20 21:21:05 -0700192 return self.get_network_ip("nat")
193
Tony Mack5b061472014-02-04 07:57:10 -0500194 @staticmethod
195 def select_by_user(user):
196 if user.is_admin:
Tony Mackd8515472015-08-19 11:58:18 -0400197 qs = Instance.objects.all()
Tony Mack5b061472014-02-04 07:57:10 -0500198 else:
Tony Macke4be32f2014-03-11 20:45:25 -0400199 slices = Slice.select_by_user(user)
Tony Mackd8515472015-08-19 11:58:18 -0400200 qs = Instance.objects.filter(slice__in=slices)
Tony Mack5b061472014-02-04 07:57:10 -0500201 return qs
Sapan Bhatiaeec8acc2014-12-16 01:08:51 -0500202
203 def get_cpu_stats(self):
Tony Mackd8515472015-08-19 11:58:18 -0400204 filter = 'instance_id=%s'%self.instance_id
Sapan Bhatiaeec8acc2014-12-16 01:08:51 -0500205 return monitor.get_meter('cpu',filter,None)
206
207 def get_bw_stats(self):
Tony Mackd8515472015-08-19 11:58:18 -0400208 filter = 'instance_id=%s'%self.instance_id
Sapan Bhatiaeec8acc2014-12-16 01:08:51 -0500209 return monitor.get_meter('network.outgoing.bytes',filter,None)
210
211 def get_node_stats(self):
212 # Note sure what should go back here
213 return 1
Scott Baker7ccc6ad2015-01-25 22:16:13 -0800214
215 def get_ssh_command(self):
216 if (not self.instance_id) or (not self.node) or (not self.instance_name):
217 return None
218 else:
219 return 'ssh -o "ProxyCommand ssh -q %s@%s" ubuntu@%s' % (self.instance_id, self.node.name, self.instance_name)
Scott Bakera115c1e2015-05-07 14:58:46 -0700220
221 def get_public_keys(self):
222 slice_memberships = SlicePrivilege.objects.filter(slice=self.slice)
223 pubkeys = set([sm.user.public_key for sm in slice_memberships if sm.user.public_key])
224
225 if self.creator.public_key:
226 pubkeys.add(self.creator.public_key)
227
228 if self.slice.creator.public_key:
229 pubkeys.add(self.slice.creator.public_key)
230
231 if self.slice.service and self.slice.service.public_key:
232 pubkeys.add(self.slice.service.public_key)
233
234 return pubkeys
Sapan Bhatia52ba5422015-05-13 15:48:02 +0200235
236def controller_setter(instance, **kwargs):
237 try:
238 instance.controller = instance.node.site_deployment.controller
239 except:
240 instance.controller = None
241
Tony Mackd8515472015-08-19 11:58:18 -0400242models.signals.post_init.connect(controller_setter, Instance)