cc1  v2.1
CC1 source code docs
 All Classes Namespaces Files Functions Variables Pages
vm.py
Go to the documentation of this file.
1 # -*- coding: utf-8 -*-
2 # @COPYRIGHT_begin
3 #
4 # Copyright [2010-2014] Institute of Nuclear Physics PAN, Krakow, Poland
5 #
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
9 #
10 # http://www.apache.org/licenses/LICENSE-2.0
11 #
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 #
18 # @COPYRIGHT_end
19 ##
20 # @package src.cm.models.vm
21 #
22 from cm.settings import VNC_PORTS, NOVNC_PORTS
23 
24 from cm.models.node import Node
25 from cm.models.public_ip import PublicIP
26 from cm.models.template import Template
27 from cm.models.user import User
28 from cm.models.farm import Farm
29 from cm.models.system_image import SystemImage
30 from cm.models.iso_image import IsoImage
31 from cm.models.lease import Lease
32 from cm.models.available_network import AvailableNetwork
33 from cm.utils.exception import CMException
34 from cm.utils import log
35 from cm.utils.monia import RrdHandler
36 
37 from django.db import models, transaction
38 from django.conf import settings
39 from django.template import loader, Context
40 from django.conf import settings as django_settings
41 
42 from common.states import vm_states, image_states, farm_states, lease_states, vnc_states
43 from common.utils import password_gen
44 
45 from datetime import datetime
46 import os
47 import uuid
48 import libvirt
49 import subprocess
50 
51 from netaddr import IPNetwork
52 from cm.utils import message
53 
54 
55 ##
56 #
57 # @model{vm} Virtual machine class
58 #
59 # Virtual Machines creation and management are the main features of the CC1
60 # system. They make use of the node's CPU (Node) to emulate physical
61 # hardware. They should be considered as remote workstations with operating
62 # system (VMImage) running on them.
63 #
64 # VM may be created and further saved to VMImage or destroyed irrevocably.
65 #
66 # VM may have plugged resources of several types, so that it's functionality
67 # and access to it are extended.
68 #
69 class VM(models.Model):
70  name = models.CharField(max_length=128)
71  node = models.ForeignKey(Node)
72  user = models.ForeignKey(User)
73  template = models.ForeignKey(Template)
74  system_image = models.ForeignKey(SystemImage)
75  iso_image = models.ForeignKey(IsoImage, null=True, blank=True)
76  libvirt_id = models.IntegerField()
77  state = models.IntegerField()
78  start_time = models.DateTimeField()
79  stop_time = models.DateTimeField(null=True, blank=True)
80  ctx_key = models.CharField(max_length=128, null=True, blank=True)
81  ctx_api_version = models.CharField(max_length=10, null=True, blank=True)
82  vnc_passwd = models.CharField(max_length=45)
83 
84  ssh_key = models.TextField(null=True, blank=True)
85  ssh_username = models.CharField(max_length=45, null=True, blank=True)
86  description = models.TextField(null=True, blank=True)
87  save_vm = models.IntegerField()
88  farm = models.ForeignKey(Farm, related_name='vms', null=True)
89  hostname = models.CharField(max_length=256, null=True, blank=True)
90  vnc_port = models.IntegerField()
91  novnc_port = models.IntegerField(default=0)
92  vnc_enabled = models.IntegerField(default=0)
93  reservation_id = models.IntegerField(default=0)
94  user_data = models.CharField(max_length=32768, null=True, blank=True)
95 
96  class Meta:
97  app_label = 'cm'
98 
99  # method for printing object instance
100  def __unicode__(self):
101  return self.name
102 
103  @property
104  ##
105  #
106  # @returns{dict} this VM's data
107  # \n fields:
108  # @dictkey{id,int}
109  # @dictkey{user_id,int}
110  # @dictkey{name,string}
111  # @dictkey{state,int} @seealso{src.common.states.vm_states}
112  # @dictkey{leases,list(dict)}
113  # @dictkey{image_name,string}
114  # @dictkey{template_name,string}
115  # @dictkey{platform,string}
116  # @dictkey{description,string}
117  # @dictkey{vnc_endpoint,int}
118  # @dictkey{vnc_enabled,bool}
119  # @dictkey{vnc_passwd,string}
120  # @dictkey{iso_images,list(dict)}
121  # @dictkey{storage_images,list(dict)}
122  # @dictkey{cpu_load,int}
123  #
124  def dict(self):
125  d = {}
126  d['vm_id'] = self.id
127  d['user_id'] = self.user.id
128  d['name'] = self.name
129  d['state'] = self.state
130  d['leases'] = [l.dict for l in self.lease_set.all()]
131  d['image_name'] = self.system_image.name
132  d['image_id'] = self.system_image.id
133  d['template_name'] = self.template.name
134  d['platform'] = 0
135  d['description'] = self.description or ''
136  d['vnc_endpoint'] = '%s:%d' % (settings.VNC_ADDRESS, self.vnc_port)
137  d['novnc_endpoint'] = '%s:%d' % (settings.VNC_ADDRESS, self.novnc_port)
138  d['vnc_enabled'] = self.vnc_enabled
139  d['vnc_passwd'] = self.vnc_passwd or ''
140 
141  if self.iso_image:
142  d['iso_images'] = [{'id': self.iso_image.id, 'name': self.iso_image.name}]
143  else:
144  d['iso_images'] = []
145  d['storage_images'] = [{'storage_image_id': img.id, 'name': img.name} for img in self.storage_images]
146  d['cpu_load'] = self.cpu_load
147 
148  return d
149 
150  @property
151  ##
152  #
153  # @returns{dict} this VM's extended data
154  # \n fields:
155  # @dictkey{id,int}
156  # @dictkey{user_id,int}
157  # @dictkey{name,string}
158  # @dictkey{state,int} @seealso{src.common.states.command_states}
159  # @dictkey{leases,list(dict)}
160  # @dictkey{image_name,string}
161  # @dictkey{template_name,string}
162  # @dictkey{platform,string}
163  # @dictkey{description,string}
164  # @dictkey{vnc_endpoint,int}
165  # @dictkey{vnc_enabled,bool}
166  # @dictkey{vnc_passwd,string}
167  # @dictkey{start_time,datetime.datetime}
168  # @dictkey{node,string} node's address
169  # @dictkey{libvirt_id,int}
170  # @dictkey{ssh_username,string}
171  # @dictkey{ssh_key,string}
172  # @dictkey{iso_images,list(dict)}
173  # @dictkey{storage_images,list(dict)}
174  # @dictkey{cpu_load,int}
175  # @dictkey{reservation_id,int}
176  # @dictkey{user_data,string}
177  #
178  def long_dict(self):
179  d = {}
180  d['vm_id'] = self.id
181  d['user_id'] = self.user.id
182  d['name'] = self.name
183  d['state'] = self.state
184  d['leases'] = [l.dict for l in self.lease_set.all()]
185  d['image_name'] = self.system_image.name
186  d['image_id'] = self.system_image.id
187  d['template_name'] = self.template.name
188  d['platform'] = 0
189  d['description'] = self.description or ''
190  d['vnc_endpoint'] = '%s:%d' % (settings.VNC_ADDRESS, self.vnc_port)
191  d['novnc_endpoint'] = '%s:%d' % (settings.VNC_ADDRESS, self.novnc_port)
192  d['vnc_enabled'] = self.vnc_enabled
193  d['vnc_passwd'] = self.vnc_passwd or ''
194  d['start_time'] = self.start_time
195  delta = datetime.now() - self.start_time
196  d['uptime'] = delta.seconds + 24 * 3600 * delta.days
197  d['node'] = self.node.address
198  d['libvirt_id'] = self.libvirt_id
199  d['ssh_username'] = self.ssh_username or ''
200  d['ssh_key'] = self.ssh_key or ''
201  d['reservation_id'] = self.reservation_id
202  d['user_data'] = self.user_data
203 
204  if self.iso_image:
205  d['iso_images'] = [{'id': self.iso_image.id, 'name': self.iso_image.name}]
206  else:
207  d['iso_images'] = []
208  d['storage_images'] = [{'storage_image_id': img.id, 'name': img.name, 'disk_controller': img.disk_controller}
209  for img in self.storage_images]
210  d['cpu_load'] = self.cpu_load
211  return d
212 
213  @staticmethod
214  def create(user, name, description, image_id, template_id, public_ip_id, iso_list, disk_list, vnc, groups,
215  ssh_key=None,
216  ssh_username=None, count=1, farm=None, head_template_id=None, node_id=False, lease_id=None,
217  user_data=None):
218  from cm.models.storage_image import StorageImage
219 
220  template = Template.get(template_id)
221  image = SystemImage.get(user.id, image_id, groups)
222 
223  if image.state != image_states['ok']:
224  raise CMException('image_unavailable')
225 
226  if farm:
227  head_template = Template.get(head_template_id)
228  wn_template = template
229  user.check_quota([(head_template, 1), (wn_template, count)])
230  count += 1
231  else:
232  user.check_quota([(template, count)])
233 
234  vms = []
235 
236  reservation_id = None
237 
238  for i in range(count):
239  log.debug(user.id, "Looking for node")
240  node = Node.get_free_node(template, image, node_id)
241  log.info(user.id, 'Selected node: %d' % node.id)
242  # create VM instance
243  vm = VM()
244  vm.libvirt_id = -1
245  if farm:
246  if i == 0:
247  vm.name = '%s-head' % name
248  vm.description = 'Farm head'
249  vm.template = head_template
250  else:
251  vm.name = '%s-wn%d' % (name, i)
252  vm.description = 'Worker Node'
253  vm.template = wn_template
254  else:
255  vm.template = template
256  vm.description = description
257  if count > 1:
258  vm.name = '%s_%d' % (name, i + 1)
259  else:
260  vm.name = name
261  vm.user = user
262  vm.state = vm_states['init']
263  vm.start_time = datetime.now()
264  vm.system_image = image
265  vm.node = node
266  vm.save_vm = True
267  if farm:
268  vm.farm = farm
269 
270  # Find first free vnc port
271  used_ports = VM.objects.exclude(state__in=[vm_states['closed'], vm_states['erased']]).values_list('vnc_port', flat=True)
272 
273  for new_vnc_port in xrange(VNC_PORTS['START'], VNC_PORTS['END'] + 1):
274  if new_vnc_port not in used_ports and new_vnc_port not in VNC_PORTS['EXCLUDE']:
275  break
276  else:
277  raise CMException('vm_vnc_not_found')
278 
279  log.debug(user.id, "Found vnc port: %d" % new_vnc_port)
280  vm.vnc_port = new_vnc_port
281 
282  # Find first free novnc port
283  used_ports = VM.objects.exclude(state__in=[vm_states['closed'], vm_states['erased']]).values_list('novnc_port', flat=True)
284  for new_novnc_port in xrange(NOVNC_PORTS['START'], NOVNC_PORTS['END'] + 1):
285  if new_novnc_port not in used_ports and new_novnc_port not in NOVNC_PORTS['EXCLUDE']:
286  break
287  else:
288  raise CMException('vm_novnc_not_found')
289 
290  log.debug(user.id, "Found novnc port: %d" % new_novnc_port)
291  vm.novnc_port = new_novnc_port
292 
293  if vnc:
294  vm.attach_vnc()
295  # vm.vnc_passwd = hashlib.md5(str(datetime.now()) + str(random())).hexdigest()
296  vm.vnc_passwd = password_gen(13, chars=['letters', 'digits'], extra_chars='!@#$%^&*()')
297  vm.ssh_key = ssh_key
298  vm.ssh_username = ssh_username
299  vm.user_data = user_data
300  vm.save()
301 
302  if not reservation_id:
303  reservation_id = vm.id
304 
305  vm.reservation_id = reservation_id
306  vm.save()
307 
308  if farm and i == 0:
309  farm.head = vm
310  vms.append(vm)
311 
312  log.debug(user.id, "Attaching disks")
313  disk_devs = []
314  if i == 0 and disk_list:
315  for disk_id in disk_list:
316  log.debug(user.id, 'Attaching disks to first VM')
317  disk = StorageImage.get(user.id, disk_id)
318  if disk.vm != None:
319  raise CMException('image_attached')
320  while disk.disk_dev in disk_devs:
321  disk.disk_dev += 1
322  disk_devs.append(disk.disk_dev)
323  disk.vm = vm
324  disk.save()
325 
326  log.debug(user.id, "Attaching CD")
327  if i == 0 and iso_list:
328  for iso_id in iso_list:
329  log.debug(user.id, 'Attaching iso to first VM')
330  # cd image have not be attached to any other vm
331  iso = IsoImage.get(user.id, iso_id)
332  iso.check_attached()
333  vm.iso_image = iso
334  vm.save()
335 
336  for i, vm in enumerate(vms):
337  if lease_id != None:
338  lease = Lease.objects.get(id=lease_id)
339 
340  if lease.user_network.user != user:
341  raise CMException('lease_permission')
342 
343  if lease.vm != None:
344  raise CMException('lease_attached')
345  lease.vm = vm
346  log.debug(user.id, "Attached ip: %s" % lease.address)
347  else:
348  lease = AvailableNetwork.get_lease(user)
349  lease.vm = vm
350  lease.save()
351  log.debug(user.id, "Attached ip: %s" % lease.address)
352 
353  if i == 0 and public_ip_id > 0:
354  log.debug(user.id, "Attaching PublicIP")
355  try:
356  publicip = PublicIP.objects.filter(user=user).get(id=public_ip_id)
357  publicip.assign(lease)
358  publicip.save()
359  except Exception, e:
360  log.exception(user.id, str(e))
361  raise CMException("lease_not_found")
362 
363  return vms
364 
365  # make connection to the node of the VM, refresh the storagePool 'Images',
366  # then get the path of StorageVol 'info' and initialize the path attribute
367  # with the correct path making join with os path
368  @property
369  ##
370  #
371  # @returns{string} path to this VM on the Storage
372  #
373  def path(self):
374  conn = libvirt.open(self.node.conn_string)
375  storage = conn.storagePoolLookupByName('images')
376  storage.refresh(0)
377  path = storage.storageVolLookupByName('info').path()
378  conn.close()
379  return os.path.join(os.path.dirname(path), str(self.id))
380 
381  def is_head(self):
382  return bool(self.farm) and self == self.farm.head
383 
384  def is_farm(self):
385  return bool(self.farm)
386 
387  # create a django template and render it with VM context
388  ##
389  #
390  # @returns{string} Libvirt XML template
391  #
392  def libvirt_template(self):
393  try:
394  lv_template = loader.get_template("%s.xml" % self.node.driver)
395  c = Context({'vm': self,
396  'uuid': uuid.uuid1(),
397  'memory': self.template.memory * 1024,
398  'cpu': self.template.cpu,
399  'image_path': self.path
400  })
401  # and render it
402  domain_template = lv_template.render(c)
403  except Exception, e:
404  log.debug(self.user.id, str(e))
405  return domain_template
406 
407  # create a django template for network
408  ##
409  #
410  # @returns{string} Libvirt network XML template
411  #
412  def network_template(self):
413  # Try to configure django
414  try:
415  django_settings.configure()
416  except:
417  pass
418  try:
419  # Open template file
420  template = open("%s/%s-network.xml" % (settings.TEMPLATE_DIR, settings.NETWORK_TYPE)).read()
421 
422  # Create django template
423  lv_template = loader.get_template_from_string(template)
424  c = Context({'vm': self})
425  lv_template = lv_template.render(c)
426  except Exception, e:
427  log.debug(self.user.id, str(e))
428  return lv_template
429 
430  # returns{VM} requested VM, if it belongs to user \c user_id
431  @staticmethod
432  ##
433  #
434  # @parameter{user_id,int} declared owner of the VM
435  # @parameter{vm_id,int} id of the requested VM
436  #
437  # @returns{VM} requested VM instance, if it belongs to declared owner
438  #
439  # @raises{vm_get,CMException} no such VM
440  # @raises{user_permission,CMException} requested VM doesn't belong to
441  # declared User.
442  #
443  def get(user_id, vm_id):
444  try:
445  vm = VM.objects.get(pk=vm_id)
446  except:
447  raise CMException('vm_get')
448 
449  if vm.user.id != user_id:
450  raise CMException('user_permission')
451 
452  return vm
453 
454  @staticmethod
455  ##
456  #
457  # @parameter{vm_id,int} id of the requested VM
458  #
459  # @returns{VM} requested VM instance
460  #
461  # @raises{vm_get,CMException} no such VM
462  #
463  def admin_get(vm_id):
464 
465  try:
466  vm = VM.objects.get(pk=vm_id)
467  except:
468  raise CMException('vm_get')
469 
470  return vm
471 
472  # save the VM into a system image
473  ##
474  #
475  # Method saves VM to image with VM's name, description and parameters.
476  #
477  def save_image(self):
478  self.set_state('saving')
479  # TODO:
480  # if self.is_head():
481  # self.farm.state = farm_states['saving head']
482  try:
483  self.save(update_fields=['state'])
484  transaction.commit()
485  except Exception, e:
486  log.exception(self.user.id, 'save img')
487  return
488 
489  # Insert new image to database
490  img = SystemImage.create(name=(self.name + "_autosave" if self.save_vm == 1 else self.name),
491  description=self.description, user=self.user, platform=self.system_image.platform,
492  disk_controller=self.system_image.disk_controller,
493  network_device=self.system_image.network_device,
494  video_device=self.system_image.video_device)
495  img.size = self.system_image.size
496  img.save()
497 
498  try:
499  img.copy_to_storage(self, img)
500  except Exception, e:
501  self.set_state('saving failed')
502  self.save()
503  self.node.lock()
504  message.error(self.user.id, 'vm_save', {'id': self.id, 'name': self.name})
505  try:
506  img.delete()
507  transaction.commit()
508  except Exception, e:
509  log.exception(self.user.id, "Cannot commit changes: %s" % e)
510  log.exception(self.user.id, "Cannot move image - error code: %s" % e)
511  return
512 
513  img.state = image_states['ok']
514  try:
515  img.save()
516  except Exception, e:
517  log.error(self.user.id, "Cannot commit changes: %s" % e)
518  message.error(self.user.id, 'vm_save', {'id': self.id, 'name': self.name})
519 
520  if self.is_head():
521  message.info(self.user_id, 'farm_saved', {'farm_name': self.vm.farm.name})
522  else:
523  message.info(self.user_id, 'vm_saved', {'vm_name': self.name})
524 
525  ##
526  #
527  #
528  def remove(self):
529  # if self.save != 0:
530  # self.set_state('closing')
531  if not self.state in (vm_states['closing'], vm_states['saving']):
532  self.set_state('closing')
533  try:
534  self.save()
535  except Exception, e:
536  log.exception(self.user.id, 'closing img')
537  return
538 
539  # Remove image
540  try:
541  conn = libvirt.open(self.node.conn_string)
542  pool = conn.storagePoolLookupByName('images')
543  pool.refresh(0)
544  vol = pool.storageVolLookupByName(str(self.id))
545  vol.delete(0)
546  conn.close()
547  except Exception, e:
548  log.debug(self.user.id, "Cannot remove image: %s" % str(e))
549 
550  self.node.lock()
551  self.set_state('failed')
552 
553  message.error(self.user.id, 'vm_destroy', {'id': self.id, 'name': self.name})
554  try:
555  self.save()
556  except Exception, e:
557  log.exception(self.user.id, "Cannot commit changes: %s" % e)
558  return
559 
560  # TODO:
561  # releases node's resources: vnc, public lease, network lease
562  ##
563  #
564  # Method releases node's resources.
565  #
566  def release_resources(self):
567  log.debug(self.user.id, "Detaching vnc for vm %d" % self.id)
568  if self.vnc_enabled == vnc_states['attached']:
569  try:
570  self.detach_vnc()
571  except Exception, e:
572  log.debug(self.user.id, str(e))
573  self.set_state('failed')
574  self.node.lock()
575 
576  log.debug(self.user.id, "Detaching leases for vm %d" % self.id)
577  for lease in self.lease_set.all():
578  log.debug(self.user.id, "\t...detaching lease %s" % lease.address)
579  lease.detach_node()
580 
581  # detach volume disks
582  # TODO:test
583  log.debug(self.user.id, "Detaching disks for vm %d" % self.id)
584  for img in self.storage_images:
585  img.vm = None
586  img.save()
587 
588  # detach cd image
589  self.iso_image = None
590 
591  try:
592  self.save()
593  except Exception, e:
594  log.exception(self.user_id, "Cannot update resurce information: %s", str(e))
595  self.node.lock()
596  return
597 
598  ##
599  #
600  # Method destroyes VM by libvirt.
601  #
602  def lv_destroy(self):
603  domain = self.lv_domain()
604  domain.destroy()
605 
606  # delete VM AND release resources
607  ##
608  #
609  # Method releases resources taken by deleted ex VM.
610  #
611  def delete(self):
612 
613  VM.objects.update()
614  self = VM.objects.get(pk=self.id)
615  if self.save_vm > 0:
616  log.debug(self.user.id, 'Saving image')
617  self.save_image()
618  log.debug(self.user.id, 'Removing image')
619  self.remove()
620 
621  log.debug(self.user.id, 'Releasing resources')
622  self.release_resources()
623 
624  # Update vm state
625  self.set_state('closed')
626  """
627  #TODO:
628  if self.is_head():
629  self.farm.state = farm_states['closed']
630  """
631  self.stop_time = datetime.now()
632  try:
633  self.save()
634  except Exception, e:
635  log.exception(self.user.id, "Cannot commit changes: %s" % e)
636 
637  ##
638  #
639  # Connects to Libvirt and returns its domain.
640  #
641  # @returns Libvirt domain
642  #
643  # @raises{vm_get_lv_domain,CMException}
644  #
645  def lv_domain(self):
646  try:
647  conn = libvirt.open(self.node.conn_string)
648  domain = conn.lookupByID(self.libvirt_id)
649  # TODO: could we close connection here?
650  # conn.close()
651  except Exception, e:
652  self.node.lock()
653  self.set_state('failed')
654  if self.is_farm() and self.is_head():
655  self.farm.state = farm_states['failed']
656  self.save()
657  log.exception(self.user.id, str(e))
658  raise CMException('vm_get_lv_domain')
659  return domain
660 
661  @property
662  ##
663  #
664  # @returns{StorageImage} vm's storage images
665  #
666  # @raises{storage_image_attach,CMException}
667  #
668  def storage_images(self):
669  return self.storageimage_set.all()
670 
671  @property
672  ##
673  #
674  # @returns{CDImage} vm's iso images
675  #
676  # @raises{iso_image_attach,CMException}
677  #
678  def iso_images(self):
679  # TODO: cd Image can be only one for each VM. sure?
680  """
681  letter = ord('a')
682  for img in images:
683  img.disk_dev = 'sr%s' % chr(letter)
684  letter += 1
685  """
686  image = self.iso_image
687  image.disk_dev = 1
688 
689  try:
690  image.save()
691  except Exception:
692  raise CMException('iso_image_attach')
693  return image
694 
695  ##
696  #
697  # @parameter{state,string} new state for entity, value in 'turned off',
698  # 'restart', 'running', 'running ctx', 'turned off', 'saving',
699  # 'closing', 'init', 'closed', 'saving failed', 'failed', 'suspend',
700  # 'ereased'
701  #
702  # @raises{vm_wrong_state,CMException}
703  #
704  def set_state(self, state):
705 
706  # Key - destination state
707  # Values - actual available states
708  states = {'init': (),
709  'running': ('init', 'turned off', 'restart',),
710  'running ctx': ('running', 'running ctx',),
711  'closing': ('turned off', 'running', 'running ctx', 'saving', 'turned off',),
712  'closed': ('saving', 'closing', 'erased'),
713  'saving': ('running', 'running ctx',),
714  'saving failed': ('saving',),
715  'failed': ('init', 'running', 'running ctx', 'closing', 'closed', 'saving', 'saving failed', 'failed',
716  'turned off', 'suspend', 'restart', 'erased'),
717  'turned off': ('running', 'init',),
718  'suspend': ('running', 'running ctx',),
719  'restart': ('running', 'running ctx',),
720  'erasing': (
721  'init', 'running', 'running ctx', 'closing', 'closed', 'saving', 'saving failed', 'failed',
722  'turned off', 'suspend', 'restart', 'erased', 'erasing'),
723  'erased': ('erasing', 'erased')
724  }
725 
726  # Find my state:
727  my_state = False
728  for s in vm_states.keys():
729  if self.state == vm_states[s]:
730  my_state = s
731 
732  log.info(self.user.id, "Changing state from %s to %s for %d" % (my_state, state, self.id))
733 
734  # Check if VM could go from actual state to given
735  if (my_state not in states[state] or my_state == False) and my_state != 'erasing':
736  raise CMException('vm_wrong_state', '%s -> %s for %d' % (my_state, state, self.id))
737 
738  self.state = vm_states[state]
739 
740  # Lock node on fail
741  if state in ('failed', 'saving failed') and my_state != 'erasing':
742  self.node.lock()
743  # self.node.state = node_states['locked']
744 
745  # TODO: farm state
746 
747  # Update farm states
748  # if self.is_head() and self.is_farm() and state == 'failed' and my_state != 'erasing':
749  # # TODO: farm.set_state
750  # log.exception(self.user_id, "Failing farm")
751  # self.farm.state = farm_states['failed']
752 
753  @staticmethod
754  ##
755  #
756  # Remove all after-effects of the failed vm and free the resources.
757  #
758  def erase(vm):
759  vm.save_vm = 0
760  vm.set_state('erasing')
761  try:
762  vm.save()
763  transaction.commit()
764  except:
765  log.error(vm.user.id, 'Cannot set save=0')
766 
767  conn = libvirt.open(vm.node.conn_string)
768  try:
769  domain = conn.lookupByID(vm.libvirt_id)
770  domain.destroy()
771  except Exception, e:
772  log.error(vm.user.id, "Cannot find libvirt domain (by ID): %d (%s)" % (vm.libvirt_id, str(e)))
773  try:
774  domain = conn.lookupByName("vm-%d-%d" % (vm.id, vm.user.id))
775  domain.destroy()
776  except Exception, e:
777  log.error(vm.user.id, "Cannot find libvirt domain (by name): %d (%s)" % (vm.libvirt_id, str(e)))
778 
779  try:
780  pool = conn.storagePoolLookupByName('images')
781  pool.refresh(0)
782  vol = pool.storageVolLookupByName(str(vm.id))
783  vol.delete(0)
784  except:
785  log.error(vm.user.id, "Cannot remove vm image (or other SSH error)")
786 
787  try:
788  vm.release_resources()
789  except Exception, e:
790  log.error(vm.user.id, "Cannot release resources: %s" % str(e))
791 
792  for l in vm.lease_set.all():
793  try:
794  l.detach_node()
795  except Exception, e:
796  log.error(vm.user.id, "Cannot detach lease: %s" % str(e))
797 
798  l.state = lease_states['free']
799  vm.state = vm_states['erased']
800  vm.stop_time = datetime.now()
801 
802  try:
803  vm.save()
804  except:
805  log.error(vm.user.id, "Cannot commit changes.")
806  conn.close()
807 
808  @property
809  def cpu_load(self):
810  res = ['60', '300', '900']
811  try:
812  return RrdHandler().cpu_load('vm-%d-%d' % (self.id, self.user_id), res)
813  except CMException, e:
814  log.error(self.user_id, 'cpu_load: %s' % str(e))
815  return dict([(x, '') for x in res])
816 
817  @staticmethod
818  ##
819  #
820  # @parameter{vms}
821  # @response result
822  #
823  def destroy(vms):
824  results = []
825  for vm in vms:
826  vm = VM.objects.get(pk=vm.id)
827  log.debug(vm.user.id, "Killing VM id: %s, state: %s" % (vm.id, vm.state))
828  # Check for VM state
829  if vm.state in (vm_states['closing'], vm_states['saving']):
830  # raise CMException('vm_already_closing')
831  results.append({'status': 'vm_already_closing', 'data': ''})
832  continue
833 
834  if vm.state in (vm_states['erased'], vm_states['closed']):
835  # raise CMException('vm_wrong_state')
836  results.append({'status': 'vm_wrong_state', 'data': ''})
837  continue
838 
839  # TODO: new implementation of destroy with reference to global threads
840  # global threads
841  # if vm.state in (vm_states['init'], vm_states['failed']):
842  # try:
843  # log.debug(vm.user_id, "Global threads: %s" % threads)
844  # if vm.id in threads:
845  # threads[vm.id].terminate()
846  # del threads[vm.id]
847  # erase(vm)
848  # # vm.set_state('closed')
849  #
850  # Session.commit()
851  # results.append({'status': 'ok', 'data': ''})
852  # except Exception:
853  # log.exception(vm.user_id, 'error destroying VM')
854  # results.append({'status': 'vm_destroy', 'data': ''})
855  # message.error(vm.user_id, 'vm_destroy', {'id': vm.id, 'name': vm.name})
856  # continue
857  # else:
858  # vm.save = 0
859  # vm.set_state('closing')
860 
861  vm.save_vm = 0
862 
863  try:
864  vm.save()
865  transaction.commit()
866  vm.lv_destroy()
867  except Exception, e:
868  log.exception(vm.user.id, 'error destroying VM: %s' % str(e))
869  results.append({'status': 'vm_destroy', 'data': ''})
870  message.error(vm.user_id, 'vm_destroy', {'id': vm.id, 'name': vm.name})
871  continue
872  # raise CMException('vm_destroy')
873 
874  results.append({'status': 'ok', 'data': ''})
875 
876  return results
877  # return response('ok', results)
878 
879  @staticmethod
880  def get_by_ip(ip):
881  ip = str(IPNetwork('%s/30' % ip).network)
882  try:
883  vm = VM.objects.filter(lease__address=ip)[0]
884  except:
885  raise CMException('vm_get')
886 
887  return vm
888 
889  @staticmethod
890  def reset(vms):
891  from cm.utils.threads.vm import VMThread
892 
893  results = []
894  for vm in vms:
895  if vm.state in (vm_states['running'], vm_states['running ctx']):
896  thread = VMThread(vm, 'reset')
897  thread.start()
898  results.append({'status': 'ok', 'data': ''})
899  else:
900  results.append({'status': 'vm_wrong_state', 'data': ''})
901 
902  return results
903 
904  def attach_vnc(self, reattach=False):
905  if not self.state in (vm_states['init'], vm_states['running'], vm_states['running ctx']):
906  raise CMException('vm_wrong_state')
907 
908  if self.vnc_enabled == vnc_states['attached'] and reattach == False:
909  raise CMException('vm_vnc_attached')
910 
911  subprocess.call(
912  ["sudo", "/sbin/iptables", "-t", "nat", "-A", "CC1_VNC_REDIRECT", "-d", settings.VNC_ADDRESS, "-p", "tcp",
913  "--dport", str(self.vnc_port), "-j", "DNAT", "--to-destination",
914  "%s:%s" % (self.node.address, str(self.vnc_port))])
915  subprocess.call(
916  ["sudo", "/sbin/iptables", "-t", "nat", "-A", "CC1_VNC_MASQUERADE", "-d", self.node.address, "-p", "tcp",
917  "--dport", str(self.vnc_port), "-j", "MASQUERADE"])
918  subprocess.call(
919  ["sudo", "/sbin/iptables", "-t", "nat", "-A", "CC1_VNC_REDIRECT", "-d", settings.VNC_ADDRESS, "-p", "tcp",
920  "--dport", str(self.novnc_port), "-j", "DNAT", "--to-destination",
921  "%s:%s" % (self.node.address, str(self.novnc_port))])
922  subprocess.call(
923  ["sudo", "/sbin/iptables", "-t", "nat", "-A", "CC1_VNC_MASQUERADE", "-d", self.node.address, "-p", "tcp",
924  "--dport", str(self.novnc_port), "-j", "MASQUERADE"])
925 
926  self.vnc_enabled = vnc_states['attached']
927 
928  def detach_vnc(self):
929  subprocess.call(
930  ["sudo", "/sbin/iptables", "-t", "nat", "-D", "CC1_VNC_REDIRECT", "-d", settings.VNC_ADDRESS, "-p", "tcp",
931  "--dport", str(self.vnc_port), "-j", "DNAT", "--to-destination",
932  "%s:%s" % (self.node.address, str(self.vnc_port))])
933  subprocess.call(
934  ["sudo", "/sbin/iptables", "-t", "nat", "-D", "CC1_VNC_MASQUERADE", "-d", self.node.address, "-p", "tcp",
935  "--dport", str(self.vnc_port), "-j", "MASQUERADE"])
936 
937  subprocess.call(
938  ["sudo", "/sbin/iptables", "-t", "nat", "-D", "CC1_VNC_REDIRECT", "-d", settings.VNC_ADDRESS, "-p", "tcp",
939  "--dport", str(self.novnc_port), "-j", "DNAT", "--to-destination",
940  "%s:%s" % (self.node.address, str(self.novnc_port))])
941  subprocess.call(
942  ["sudo", "/sbin/iptables", "-t", "nat", "-D", "CC1_VNC_MASQUERADE", "-d", self.node.address, "-p", "tcp",
943  "--dport", str(self.novnc_port), "-j", "MASQUERADE"])
944 
945  self.vnc_enabled = vnc_states['detached']
946 
947  @staticmethod
948  def save_and_shutdown(user_id, vm, name, description):
949  vm.description = description
950  vm.name = name
951  vm.save_vm = 2
952  try:
953  vm.save()
954  except:
955  raise CMException('vm_save')
956 
957  if vm.state != vm_states['running ctx']:
958  raise CMException('vm_cannot_shutdown')
959 
960  try:
961  from cm.models.command import Command
962 
963  Command.execute('shutdown', user_id, vm.id)
964  except:
965  raise CMException('vm_ctx_connect')
966