Commit cce60631 authored by Christos Stavrakakis's avatar Christos Stavrakakis
Browse files

cyclades: Various fixes for snapshots

* Rename 'name' and 'description' to 'display_name' and
  'display_description' in the volume API.
* Small refactoring in snapshots methods
parent 6487b4f5
import datetime
import logging
from django.utils import simplejson as json
from django.db import transaction
from snf_django.lib.api import faults
from snf_django.lib.api.utils import isoformat
from synnefo.plankton.utils import image_backend
from synnefo.logic import backend
from synnefo.volume import util
#import datetime
#from snf_django.lib.api.utils import isoformat
log = logging.getLogger(__name__)
SNAPSHOTS_PREFIX = "plankton:"
......@@ -16,9 +20,21 @@ SNAPSHOTS_MAPFILE_PREFIX = "archip:"
def create(user_id, volume, name, description, metadata, force=False):
"""Create a snapshot from a given volume
Create a snapshot from a given volume. The snapshot is first created as
a file in Pithos, with specified metadata to indicate that it is a
snapshot. Then a job is sent to Ganeti backend to create the actual
snapshot of the volume.
Snapshots are only supported for volumes of ext_ disk template. Also,
the volume must be attached to some server.
# Check that taking a snapshot is feasible
if volume.machine is None:
raise faults.BadRequest("Can not snapshot detached volume!")
raise faults.BadRequest("Cannot snapshot a detached volume!")
flavor = volume.machine.flavor
if not flavor.disk_template.startswith("ext_"):
......@@ -26,43 +42,61 @@ def create(user_id, volume, name, description, metadata, force=False):
" disk template")
raise faults.BadRequest(msg)
# Increase the snapshot counter of the volume that is used in order to
# generate unique snapshot names
volume.snapshot_counter += 1
snapshot_metadata = {}
snapshot_metadata[SNAPSHOTS_PREFIX + "name"] = name
snapshot_metadata[SNAPSHOTS_PREFIX + "description"] = description
snapshot_metadata[SNAPSHOTS_PREFIX + "metadata"] = json.dumps(metadata)
snapshot_metadata[SNAPSHOTS_PREFIX + "volume_id"] =
snapshot_metadata[SNAPSHOTS_PREFIX + "status"] = "CREATING"
#XXX: just to work
# Snapshot information are stored as metadata on the Pithos file
snapshot_metadata = {
SNAPSHOTS_PREFIX + "name": name,
SNAPSHOTS_PREFIX + "description": description,
SNAPSHOTS_PREFIX + "volume_id":,
# TODO: The following are used in order plankton to work with snapshots
# exactly as with iamges
SNAPSHOTS_PREFIX + "store": "pithos",
SNAPSHOTS_PREFIX + "disk_format": "diskdump",
SNAPSHOTS_PREFIX + "default_container_format": "bare",
SNAPSHOTS_PREFIX + "metadata": json.dumps(metadata)})
# Set a special attribute to distinquish snapshots from the images
snapshot_metadata[SNAPSHOTS_PREFIX + "is_snapshot"] = True
#XXX: for images
snapshot_metadata[SNAPSHOTS_PREFIX + "store"] = "pithos"
snapshot_metadata[SNAPSHOTS_PREFIX + "disk_format"] = "diskdump"
snapshot_metadata[SNAPSHOTS_PREFIX + "default_container_format"] = "bare"
# XXX: Hack-ish way to clone the metadata
# Snapshots are used as images. We set the most important properties
# that are being used for images. We set 'EXCLUDE_ALL_TASKS' to bypass
# image customization. Also, we get some basic metadata for the volume from
# the server that the volume is attached
image_properties = {"EXCLUDE_ALL_TASKS": "yes",
"description": description}
vm_metadata = dict(volume.machine.metadata.values_list("meta_key", "meta_value"))
vm_metadata = dict(volume.machine.metadata
.values_list("meta_key", "meta_value"))
for key in ["OS", "users"]:
val = vm_metadata.get(key)
if val is not None:
image_properties[key] = val
snapshot_metadata[SNAPSHOTS_PREFIX + "properties"] = json.dumps(image_properties)
snapshot_metadata[SNAPSHOTS_PREFIX + "properties"] = \
snapshot_name = generate_snapshot_name(volume)
mapfile = SNAPSHOTS_MAPFILE_PREFIX + snapshot_name
# Generate a name for the Pithos file. Also, generate a name for the
# Archipelago mapfile.
snapshot_pithos_name = generate_snapshot_pithos_name(volume)
mapfile = SNAPSHOTS_MAPFILE_PREFIX + snapshot_pithos_name
# Convert size from Gbytes to bytes
size = volume.size << 30
with image_backend(user_id) as pithos_backend:
# move this to plankton backend
snapshot_uuid = pithos_backend.backend.register_object_map(
......@@ -72,22 +106,40 @@ def create(user_id, volume, name, description, metadata, force=False):
backend.snapshot_instance(volume.machine, snapshot_name=snapshot_name)
snapshot = util.get_snapshot(user_id, snapshot_uuid)
return snapshot
def generate_snapshot_name(volume):
time = isoformat(
return "snf-snapshot-of-volume-%s-%s" % (,
def generate_snapshot_pithos_name(volume):
"""Helper function to generate a name for the Pithos file."""
# time = isoformat(
return "snapshot-of-volume-%s-%s" % (,
def delete(snapshot):
"""Delete a snapshot.
Delete a snapshot by deleting the corresponding file from Pithos.
user_id = snapshot["owner"]"Deleting snapshot '%s'", snapshot["location"])
with image_backend(user_id) as pithos_backend:
return snapshot
def rename(snapshot, new_name):
# user_id = snapshot["owner"]
raise NotImplemented("Renaming a snapshot is not implemented!")
def update_description(snapshot, new_description):
# user_id = snapshot["owner"]
raise NotImplemented("Updating snapshot's description is not implemented!")
......@@ -222,8 +222,8 @@ def snapshot_to_dict(snapshot, detail=True):
data = {
"id": snapshot["uuid"],
"size": int(snapshot["size"]) >> 30, # gigabytes
"name": snapshot["name"],
"description": snapshot["description"],
"display_name": snapshot["name"],
"display_description": snapshot["description"],
"status": status,
"user_id": owner,
"tenant_id": owner,
......@@ -232,7 +232,7 @@ def snapshot_to_dict(snapshot, detail=True):
"created_at": utils.isoformat(date_parse(snapshot["created_at"])),
"metadata": snapshot.get("metadata", {}),
"volume_id": snapshot.get("volume_id"),
"links": "", # TODO fix links
"links": util.snapshot_to_links(snapshot["uuid"])
return data
......@@ -263,11 +263,11 @@ def create_snapshot(request):
volume = util.get_volume(user_id, volume_id, for_update=True,
name = new_snapshot.get("name", None)
name = new_snapshot.get("display_name", None)
if name is None:
name = "snapshot_volume_%s_%s" %\
(, str(
description = new_snapshot.get("description", "")
description = new_snapshot.get("display_description", "")
# TODO: What to do with force ?
force = new_snapshot.get("force", False)
......@@ -329,9 +329,16 @@ def update_snapshot(request, snapshot_id):
req = utils.get_request_dict(request)
log.debug('update_snapshot snapshot_id: %s, request: %s', snapshot_id, req)
snapshot = util.get_snapshot(request.user_uniq, snapshot_id)
# TODO = req.get("name",
#snapshot.description = req.get("description", snapshot.description)
new_name = req.get("display_name")
new_description = req.get("display_description")
if new_name is None and new_description is None:
raise faults.BadRequest("Nothing to update.")
if new_name is not None:
snapshot = snapshots.rename(snapshot, new_name)
if new_description is not None:
snapshot = snapshots.update_description(snapshot, new_description)
data = json.dumps({'snapshot': snapshot_to_dict(snapshot, detail=True)})
return HttpResponse(data, content_type="application/json", status=200)
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment