diff --git a/circle/storage/migrations/0015_set_is_ready_to_base_images.py b/circle/storage/migrations/0015_set_is_ready_to_base_images.py new file mode 100644 index 0000000..2c4af52 --- /dev/null +++ b/circle/storage/migrations/0015_set_is_ready_to_base_images.py @@ -0,0 +1,57 @@ +# -*- coding: utf-8 -*- +from south.utils import datetime_utils as datetime +from south.db import db +from south.v2 import DataMigration +from django.db import models + +import logging + +logging.basicConfig(filename='/var/tmp/0015_disk_migration.log',level=logging.INFO) +logger = logging.getLogger() + +class Migration(DataMigration): + + def forwards(self, orm): + "Write your forwards methods here." + # Note: Don't use "from appname.models import ModelName". + # Use orm.ModelName to refer to models in this application, + # and orm['appname.ModelName'] for models in other applications. + disks = orm.Disk + for disk in disks.objects.all(): + if disk.base is None and disk.destroyed is None: + logger.info("Set disk %s (%s) with filename: %s to ready.", + disk.name, disk.pk, disk.filename) + disk.is_ready = True + disk.save() + + + def backwards(self, orm): + "Write your backwards methods here." + + models = { + u'storage.datastore': { + 'Meta': {'ordering': "[u'name']", 'object_name': 'DataStore'}, + 'hostname': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '40'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '100'}), + 'path': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '200'}) + }, + u'storage.disk': { + 'Meta': {'ordering': "[u'name']", 'object_name': 'Disk'}, + 'base': ('django.db.models.fields.related.ForeignKey', [], {'blank': 'True', 'related_name': "u'derivatives'", 'null': 'True', 'to': u"orm['storage.Disk']"}), + 'created': ('model_utils.fields.AutoCreatedField', [], {'default': 'datetime.datetime.now'}), + 'datastore': ('django.db.models.fields.related.ForeignKey', [], {'to': u"orm['storage.DataStore']"}), + 'destroyed': ('django.db.models.fields.DateTimeField', [], {'default': 'None', 'null': 'True', 'blank': 'True'}), + 'dev_num': ('django.db.models.fields.CharField', [], {'default': "u'a'", 'max_length': '1'}), + 'filename': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '256'}), + u'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), + 'is_ready': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), + 'modified': ('model_utils.fields.AutoLastModifiedField', [], {'default': 'datetime.datetime.now'}), + 'name': ('django.db.models.fields.CharField', [], {'max_length': '100', 'blank': 'True'}), + 'size': ('sizefield.models.FileSizeField', [], {'default': 'None', 'null': 'True'}), + 'type': ('django.db.models.fields.CharField', [], {'max_length': '10'}) + } + } + + complete_apps = ['storage'] + symmetrical = True diff --git a/circle/storage/models.py b/circle/storage/models.py index a4d7fe6..3600b7a 100644 --- a/circle/storage/models.py +++ b/circle/storage/models.py @@ -27,13 +27,13 @@ from celery.contrib.abortable import AbortableAsyncResult from django.db.models import (Model, BooleanField, CharField, DateTimeField, ForeignKey) from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext_lazy as _, ugettext_noop from model_utils.models import TimeStampedModel from sizefield.models import FileSizeField from .tasks import local_tasks, storage_tasks from celery.exceptions import TimeoutError -from common.models import WorkerNotFound +from common.models import WorkerNotFound, HumanReadableException logger = logging.getLogger(__name__) @@ -104,43 +104,73 @@ class Disk(TimeStampedModel): ('create_empty_disk', _('Can create an empty disk.')), ('download_disk', _('Can download a disk.'))) - class WrongDiskTypeError(Exception): - - def __init__(self, type, message=None): - if message is None: - message = ("Operation can't be invoked on a disk of type '%s'." - % type) - - Exception.__init__(self, message) - - self.type = type - - class DiskInUseError(Exception): - - def __init__(self, disk, message=None): - if message is None: - message = ("The requested operation can't be performed on " - "disk '%s (%s)' because it is in use." % - (disk.name, disk.filename)) - - Exception.__init__(self, message) - - self.disk = disk - - class DiskIsNotReady(Exception): - - """ Exception for operations that need a deployed disk. - """ - - def __init__(self, disk, message=None): - if message is None: - message = ("The requested operation can't be performed on " - "disk '%s (%s)' because it has never been" - "deployed." % (disk.name, disk.filename)) - - Exception.__init__(self, message) - - self.disk = disk + class DiskError(HumanReadableException): + admin_message = None + + def __init__(self, disk, params=None, level=None, **kwargs): + kwargs.update(params or {}) + self.disc = kwargs["disk"] = disk + super(Disk.DiskError, self).__init__( + level, self.message, self.admin_message or self.message, + kwargs) + + class WrongDiskTypeError(DiskError): + message = ugettext_noop("Operation can't be invoked on disk " + "'%(name)s' of type '%(type)s'.") + + admin_message = ugettext_noop( + "Operation can't be invoked on disk " + "'%(name)s' (%(pk)s) of type '%(type)s'.") + + def __init__(self, disk, params=None, **kwargs): + super(Disk.WrongDiskTypeError, self).__init__( + disk, params, type=disk.type, name=disk.name, pk=disk.pk) + + class DiskInUseError(DiskError): + message = ugettext_noop( + "The requested operation can't be performed on " + "disk '%(name)s' because it is in use.") + + admin_message = ugettext_noop( + "The requested operation can't be performed on " + "disk '%(name)s' (%(pk)s) because it is in use.") + + def __init__(self, disk, params=None, **kwargs): + super(Disk.DiskInUseError, self).__init__( + disk, params, name=disk.name, pk=disk.pk) + + class DiskIsNotReady(DiskError): + message = ugettext_noop( + "The requested operation can't be performed on " + "disk '%(name)s' because it has never been deployed.") + + admin_message = ugettext_noop( + "The requested operation can't be performed on " + "disk '%(name)s' (%(pk)s) [%(filename)s] because it has never been" + "deployed.") + + def __init__(self, disk, params=None, **kwargs): + super(Disk.DiskIsNotReady, self).__init__( + disk, params, name=disk.name, pk=disk.pk, + filename=disk.filename) + + class DiskBaseIsNotReady(DiskError): + message = ugettext_noop( + "The requested operation can't be performed on " + "disk '%(name)s' because its base has never been deployed.") + + admin_message = ugettext_noop( + "The requested operation can't be performed on " + "disk '%(name)s' (%(pk)s) [%(filename)s] because its base " + "'%(b_name)s' (%(b_pk)s) [%(b_filename)s] has never been" + "deployed.") + + def __init__(self, disk, params=None, **kwargs): + base = kwargs.get('base') + super(Disk.DiskBaseIsNotReady, self).__init__( + disk, params, name=disk.name, pk=disk.pk, + filename=disk.filename, b_name=base.name, + b_pk=base.pk, b_filename=base.filename) @property def path(self): @@ -240,7 +270,7 @@ class Disk(TimeStampedModel): } if self.type not in type_mapping.keys(): - raise self.WrongDiskTypeError(self.type) + raise self.WrongDiskTypeError(self) new_type = type_mapping[self.type] @@ -313,6 +343,8 @@ class Disk(TimeStampedModel): if self.is_ready: return True + if self.base and not self.base.is_ready: + raise self.DiskBaseIsNotReady(self, base=self.base) queue_name = self.get_remote_queue_name('storage', priority="fast") disk_desc = self.get_disk_desc() if self.base is not None: @@ -417,7 +449,7 @@ class Disk(TimeStampedModel): 'iso': ("iso", self), } if self.type not in mapping.keys(): - raise self.WrongDiskTypeError(self.type) + raise self.WrongDiskTypeError(self) if self.is_in_use: raise self.DiskInUseError(self) @@ -433,7 +465,7 @@ class Disk(TimeStampedModel): disk = Disk.create(datastore=self.datastore, base=new_base, name=self.name, size=self.size, - type=new_type) + type=new_type, dev_num=self.dev_num) queue_name = self.get_remote_queue_name("storage", priority="slow") remote = storage_tasks.merge.apply_async(kwargs={ @@ -450,4 +482,6 @@ class Disk(TimeStampedModel): AbortableAsyncResult(remote.id).abort() disk.destroy() raise Exception("Save as aborted by use.") + disk.is_ready = True + disk.save() return disk