# coding=utf-8 import logging import uuid from django.db import models from django.utils.translation import ugettext_lazy as _ from django.db.models.signals import post_delete from model_utils.models import TimeStampedModel from manager import storage_manager import tasks logger = logging.getLogger(__name__) class DataStore(models.Model): """Collection of virtual disks. """ name = models.CharField(max_length=100, unique=True, verbose_name=_('name')) path = models.CharField(max_length=200, unique=True, verbose_name=_('path')) hostname = models.CharField(max_length=40, unique=True, verbose_name=_('hostname')) class Meta: ordering = ['name'] verbose_name = _('datastore') verbose_name_plural = _('datastores') def __unicode__(self): return u'%s (%s)' % (self.name, self.path) class Disk(TimeStampedModel): """A virtual disk. """ TYPES = [('qcow2-norm', 'qcow2 normal'), ('qcow2-snap', 'qcow2 snapshot'), ('iso', 'iso'), ('raw-ro', 'raw read-only'), ('raw-rw', 'raw')] name = models.CharField(blank=True, max_length=100, verbose_name=_('name')) filename = models.CharField(max_length=256, verbose_name=_('filename')) datastore = models.ForeignKey(DataStore) type = models.CharField(max_length=10, choices=TYPES) size = models.IntegerField() base = models.ForeignKey('self', blank=True, null=True, related_name='derivatives') ready = models.BooleanField(default=False) dev_num = models.CharField(default='a', max_length=1, verbose_name="device number") class Meta: ordering = ['name'] verbose_name = _('disk') verbose_name_plural = _('disks') @property def path(self): return self.datastore.path + '/' + self.filename @property def format(self): return { 'qcow2-norm': 'qcow2', 'qcow2-snap': 'qcow2', 'iso': 'iso', 'raw-ro': 'raw', 'raw-rw': 'raw', }[self.type] class WrongDiskTypeError(Exception): def __init__(self, type): self.type = type def __str__(self): return ("Operation can't be invoked on a disk of type '%s'." % self.type) def get_exclusive(self): """Get an instance of the disk for exclusive usage. """ if self.type in ['qcow2-snap', 'raw-rw']: raise self.WrongDiskTypeError(self.type) filename = self.filename if self.type == 'iso' else str(uuid.uuid4()) new_type = { 'qcow2-norm': 'qcow2-snap', 'iso': 'iso', 'raw-ro': 'raw-rw', }[self.type] return Disk(base=self, datastore=self.datastore, filename=filename, name=self.name, size=self.size, type=new_type) @property def device_type(self): return { 'qcow2': 'vd', 'raw': 'vd', 'iso': 'hd', }[self.format] def get_vmdisk_desc(self): return { 'source': self.path, 'driver_type': self.format, 'driver_cache': 'default', 'target_device': self.device_type + self.dev_num } def __unicode__(self): return u"%s (#%d)" % (self.name, self.id) def deploy_async(self): storage_manager.deploy.apply_async(self) def deploy(self): """Reify the disk model on the associated data store. :param self: the disk model to reify :type self: storage.models.Disk :return: True if a new reification of the disk has been created; otherwise, False. :rtype: bool """ if self.ready: return False disk_desc = { 'name': self.filename, 'dir': self.datastore.path, 'format': self.format, 'size': self.size, 'base_name': self.base.name if self.base else None, 'type': 'snapshot' if self.type == 'qcow2-snap' else 'normal' } tasks.create_disk.apply_async( args=[disk_desc], queue=self.datastore.hostname + ".storage").get() self.ready = True self.save() return True @classmethod def delete_signal(cls, sender, instance, using, **kwargs): # TODO # StorageDriver.delete_disk.delay(instance.to_json()).get() pass post_delete.connect(Disk.delete_signal, sender=Disk)