models.py 7.25 KB
Newer Older
1 2 3
# coding=utf-8

import logging
4 5
import uuid

6 7 8
from django.contrib.auth.models import User
from django.db.models import (Model, BooleanField, CharField, DateTimeField,
                              ForeignKey, TextField)
9
from django.utils import timezone
10
from django.utils.translation import ugettext_lazy as _
11
from model_utils.models import TimeStampedModel
12
from sizefield.models import FileSizeField
13

14
from .tasks import local_tasks, remote_tasks
15 16 17 18

logger = logging.getLogger(__name__)


19
class DataStore(Model):
Guba Sándor committed
20

21 22
    """Collection of virtual disks.
    """
23 24 25 26
    name = CharField(max_length=100, unique=True, verbose_name=_('name'))
    path = CharField(max_length=200, unique=True, verbose_name=_('path'))
    hostname = CharField(max_length=40, unique=True,
                         verbose_name=_('hostname'))
Guba Sándor committed
27

28 29 30 31 32 33 34 35 36 37
    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):
Guba Sándor committed
38

39 40 41 42
    """A virtual disk.
    """
    TYPES = [('qcow2-norm', 'qcow2 normal'), ('qcow2-snap', 'qcow2 snapshot'),
             ('iso', 'iso'), ('raw-ro', 'raw read-only'), ('raw-rw', 'raw')]
43 44 45 46 47
    name = CharField(blank=True, max_length=100, verbose_name=_("name"))
    filename = CharField(max_length=256, verbose_name=_("filename"))
    datastore = ForeignKey(DataStore, verbose_name=_("datastore"),
                           help_text=_("The datastore that holds the disk."))
    type = CharField(max_length=10, choices=TYPES)
48
    size = FileSizeField()
49 50 51 52
    base = ForeignKey('self', blank=True, null=True,
                      related_name='derivatives')
    ready = BooleanField(default=False)
    dev_num = CharField(default='a', max_length=1,
53
                        verbose_name=_("device number"))
54
    removed = DateTimeField(blank=True, default=None, null=True)
55 56 57 58 59 60

    class Meta:
        ordering = ['name']
        verbose_name = _('disk')
        verbose_name_plural = _('disks')

61 62 63 64 65 66 67 68
    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)

69 70 71 72 73 74 75 76 77 78 79 80 81 82
    @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]

83 84 85 86 87 88 89
    @property
    def device_type(self):
        return {
            'qcow2': 'vd',
            'raw': 'vd',
            'iso': 'hd',
        }[self.format]
90

91 92
    def get_exclusive(self):
        """Get an instance of the disk for exclusive usage.
93

94 95 96
        This method manipulates the database only.
        """
        type_mapping = {
97 98 99
            'qcow2-norm': 'qcow2-snap',
            'iso': 'iso',
            'raw-ro': 'raw-rw',
100 101 102 103 104 105 106
        }

        if self.type not in type_mapping.keys():
            raise self.WrongDiskTypeError(self.type)

        filename = self.filename if self.type == 'iso' else str(uuid.uuid4())
        new_type = type_mapping[self.type]
107

108 109 110
        return Disk.objects.create(base=self, datastore=self.datastore,
                                   filename=filename, name=self.name,
                                   size=self.size, type=new_type)
111 112 113

    def get_vmdisk_desc(self):
        return {
114
            'source': self.path,
115 116 117 118 119
            'driver_type': self.format,
            'driver_cache': 'default',
            'target_device': self.device_type + self.dev_num
        }

120 121 122 123 124 125 126 127 128 129
    def get_disk_desc(self):
        return {
            'name': self.filename,
            'dir': self.datastore.path,
            'format': self.format,
            'size': self.size,
            'base_name': self.base.filename if self.base else None,
            'type': 'snapshot' if self.type == 'qcow2-snap' else 'normal'
        }

130 131 132
    def __unicode__(self):
        return u"%s (#%d)" % (self.name, self.id)

133 134 135 136 137
    def clean(self, *args, **kwargs):
        if self.size == "" and self.base:
            self.size = self.base.size
        super(Disk, self).clean(*args, **kwargs)

138
    def deploy(self, user=None, task_uuid=None):
139 140 141 142 143 144 145 146 147
        """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
        """
148
        if self.ready:
149
            return False
150

151 152 153 154 155 156 157 158
        act = DiskActivity(activity_code='storage.Disk.deploy')
        act.disk = self
        act.started = timezone.now()
        act.state = 'PENDING'
        act.task_uuid = task_uuid
        act.user = user
        act.save()

159
        # Delegate create / snapshot jobs
160
        queue_name = self.datastore.hostname + ".storage"
161
        disk_desc = self.get_disk_desc()
162
        if self.type == 'qcow2-snap':
163
            act.update_state('CREATING SNAPSHOT')
164 165
            remote_tasks.snapshot.apply_async(args=[disk_desc],
                                              queue=queue_name).get()
166
        else:
167
            act.update_state('CREATING DISK')
168 169
            remote_tasks.create.apply_async(args=[disk_desc],
                                            queue=queue_name).get()
170

171 172
        self.ready = True
        self.save()
173 174

        act.finish('SUCCESS')
175
        return True
176

177
    def deploy_async(self, user=None):
178 179
        """Execute deploy asynchronously.
        """
180 181
        local_tasks.deploy.apply_async(args=[self, user],
                                       queue="localhost.man")
182

183 184 185 186 187 188 189 190
    def remove(self, user=None, task_uuid=None):
        # TODO add activity logging
        self.removed = timezone.now()
        self.save()

    def remove_async(self, user=None):
        local_tasks.remove.apply_async(args=[self, user],
                                       queue='localhost.man')
191

192 193 194 195 196 197 198 199 200 201
    def restore(self, user=None, task_uuid=None):
        """Restore removed disk.
        """
        # TODO
        pass

    def restore_async(self, user=None):
        local_tasks.restore.apply_async(args=[self, user],
                                        queue='localhost.man')

202 203 204 205 206 207 208 209 210 211 212 213 214

class DiskActivity(TimeStampedModel):
    activity_code = CharField(verbose_name=_('activity_code'), max_length=100)
    task_uuid = CharField(verbose_name=_('task_uuid'), blank=True,
                          max_length=50, null=True, unique=True)
    disk = ForeignKey(Disk, verbose_name=_('disk'),
                      related_name='activity_log')
    user = ForeignKey(User, verbose_name=_('user'), blank=True, null=True)
    started = DateTimeField(verbose_name=_('started'), blank=True, null=True)
    finished = DateTimeField(verbose_name=_('finished'), blank=True, null=True)
    result = TextField(verbose_name=_('result'), blank=True, null=True)
    state = CharField(verbose_name=_('state'), default='PENDING',
                      max_length=50)
215 216 217 218 219 220 221 222 223 224 225

    def update_state(self, new_state):
        self.state = new_state
        self.save()

    def finish(self, result=None):
        if not self.finished:
            self.finished = timezone.now()
            self.result = result
            self.state = 'COMPLETED'
            self.save()