summaryrefslogtreecommitdiffstats
path: root/nova/compute
diff options
context:
space:
mode:
Diffstat (limited to 'nova/compute')
-rw-r--r--nova/compute/api.py47
-rw-r--r--nova/compute/manager.py75
2 files changed, 117 insertions, 5 deletions
diff --git a/nova/compute/api.py b/nova/compute/api.py
index 92b87e75c..e7d9f1bc3 100644
--- a/nova/compute/api.py
+++ b/nova/compute/api.py
@@ -725,19 +725,60 @@ class API(base.Base):
raise exception.Error(_("Unable to find host for Instance %s")
% instance_id)
+ def backup(self, context, instance_id, name, backup_type, rotation,
+ extra_properties=None):
+ """Backup the given instance
+
+ :param instance_id: nova.db.sqlalchemy.models.Instance.Id
+ :param name: name of the backup or snapshot
+ name = backup_type # daily backups are called 'daily'
+ :param rotation: int representing how many backups to keep around;
+ None if rotation shouldn't be used (as in the case of snapshots)
+ :param extra_properties: dict of extra image properties to include
+ """
+ recv_meta = self._create_image(context, instance_id, name, 'backup',
+ backup_type=backup_type, rotation=rotation,
+ extra_properties=extra_properties)
+ return recv_meta
+
def snapshot(self, context, instance_id, name, extra_properties=None):
"""Snapshot the given instance.
+ :param instance_id: nova.db.sqlalchemy.models.Instance.Id
+ :param name: name of the backup or snapshot
+ :param extra_properties: dict of extra image properties to include
+
:returns: A dict containing image metadata
"""
- properties = {'instance_id': str(instance_id),
+ return self._create_image(context, instance_id, name, 'snapshot',
+ extra_properties=extra_properties)
+
+ def _create_image(self, context, instance_id, name, image_type,
+ backup_type=None, rotation=None, extra_properties=None):
+ """Create snapshot or backup for an instance on this host.
+
+ :param context: security context
+ :param instance_id: nova.db.sqlalchemy.models.Instance.Id
+ :param name: string for name of the snapshot
+ :param image_type: snapshot | backup
+ :param backup_type: daily | weekly
+ :param rotation: int representing how many backups to keep around;
+ None if rotation shouldn't be used (as in the case of snapshots)
+ :param extra_properties: dict of extra image properties to include
+
+ """
+ instance = db.api.instance_get(context, instance_id)
+ properties = {'instance_uuid': instance['uuid'],
'user_id': str(context.user_id),
- 'image_state': 'creating'}
+ 'image_state': 'creating',
+ 'image_type': image_type,
+ 'backup_type': backup_type}
properties.update(extra_properties or {})
sent_meta = {'name': name, 'is_public': False,
'status': 'creating', 'properties': properties}
recv_meta = self.image_service.create(context, sent_meta)
- params = {'image_id': recv_meta['id']}
+ params = {'image_id': recv_meta['id'], 'image_type': image_type,
+ 'backup_type': backup_type, 'rotation': rotation}
self._cast_compute_message('snapshot_instance', context, instance_id,
params=params)
return recv_meta
diff --git a/nova/compute/manager.py b/nova/compute/manager.py
index 98e02f5b2..40a640083 100644
--- a/nova/compute/manager.py
+++ b/nova/compute/manager.py
@@ -46,6 +46,7 @@ from eventlet import greenthread
from nova import exception
from nova import flags
+import nova.image
from nova import log as logging
from nova import manager
from nova import network
@@ -506,8 +507,19 @@ class ComputeManager(manager.SchedulerDependentManager):
self._update_state(context, instance_id)
@exception.wrap_exception
- def snapshot_instance(self, context, instance_id, image_id):
- """Snapshot an instance on this host."""
+ def snapshot_instance(self, context, instance_id, image_id,
+ image_type='snapshot', backup_type=None,
+ rotation=None):
+ """Snapshot an instance on this host.
+
+ :param context: security context
+ :param instance_id: nova.db.sqlalchemy.models.Instance.Id
+ :param image_id: glance.db.sqlalchemy.models.Image.Id
+ :param image_type: snapshot | backup
+ :param backup_type: daily | weekly
+ :param rotation: int representing how many backups to keep around;
+ None if rotation shouldn't be used (as in the case of snapshots)
+ """
context = context.elevated()
instance_ref = self.db.instance_get(context, instance_id)
@@ -527,6 +539,65 @@ class ComputeManager(manager.SchedulerDependentManager):
self.driver.snapshot(instance_ref, image_id)
+ if image_type == 'snapshot':
+ if rotation:
+ raise exception.ImageRotationNotAllowed()
+ elif image_type == 'backup':
+ if rotation:
+ instance_uuid = instance_ref['uuid']
+ self.rotate_backups(context, instance_uuid, backup_type,
+ rotation)
+ else:
+ raise exception.RotationRequiredForBackup()
+ else:
+ raise Exception(_('Image type not recognized %s') % image_type)
+
+ def rotate_backups(self, context, instance_uuid, backup_type, rotation):
+ """Delete excess backups associated to an instance.
+
+ Instances are allowed a fixed number of backups (the rotation number);
+ this method deletes the oldest backups that exceed the rotation
+ threshold.
+
+ :param context: security context
+ :param instance_uuid: string representing uuid of instance
+ :param backup_type: daily | weekly
+ :param rotation: int representing how many backups to keep around;
+ None if rotation shouldn't be used (as in the case of snapshots)
+ """
+ # NOTE(jk0): Eventually extract this out to the ImageService?
+ def fetch_images():
+ images = []
+ marker = None
+ while True:
+ batch = image_service.detail(context, filters=filters,
+ marker=marker, sort_key='created_at', sort_dir='desc')
+ if not batch:
+ break
+ images += batch
+ marker = batch[-1]['id']
+ return images
+
+ image_service = nova.image.get_default_image_service()
+ filters = {'property-image_type': 'backup',
+ 'property-backup_type': backup_type,
+ 'property-instance_uuid': instance_uuid}
+
+ images = fetch_images()
+ num_images = len(images)
+ LOG.debug(_("Found %(num_images)d images (rotation: %(rotation)d)"
+ % locals()))
+ if num_images > rotation:
+ # NOTE(sirp): this deletes all backups that exceed the rotation
+ # limit
+ excess = len(images) - rotation
+ LOG.debug(_("Rotating out %d backups" % excess))
+ for i in xrange(excess):
+ image = images.pop()
+ image_id = image['id']
+ LOG.debug(_("Deleting image %d" % image_id))
+ image_service.delete(context, image_id)
+
@exception.wrap_exception
@checks_instance_lock
def set_admin_password(self, context, instance_id, new_pass=None):