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

cyclades: Various fixes for volumes and snapshots

parent b355876d
......@@ -117,6 +117,7 @@ def image_to_dict(image, detail=True):
d['metadata'] = image['properties']
else:
d['metadata'] = {}
d["is_snapshot"] = image["is_snapshot"]
return d
......
......@@ -194,6 +194,8 @@ def vm_to_dict(vm, detail=False):
d['attachments'] = attachments
d['addresses'] = attachments_to_addresses(attachments)
d['volumes'] = [v.id for v in vm.volumes.order_by('id')]
# include the latest vm diagnostic, if set
diagnostic = vm.get_last_diagnostic()
if diagnostic:
......
......@@ -176,12 +176,20 @@ def get_image_dict(image_id, user_id):
image["id"] = img["id"]
image["name"] = img["name"]
image["format"] = img["disk_format"]
image["checksum"] = img["checksum"]
image["location"] = img["location"]
checksum = image["checksum"] = img["checksum"]
size = image["size"] = img["size"]
image["backend_id"] = PITHOSMAP_PREFIX + "/".join([checksum, str(size)])
checksum = img["checksum"]
if checksum.startswith("archip:"):
unprefixed_checksum, _ = checksum.split("archip:")
checksum = unprefixed_checksum
else:
unprefixed_checksum = checksum
checksum = "pithos:" + checksum
image["backend_id"] = PITHOSMAP_PREFIX + "/".join([unprefixed_checksum,
str(size)])
image["checksum"] = checksum
properties = img.get("properties", {})
image["metadata"] = dict((key.upper(), val)
......
......@@ -609,7 +609,7 @@ def create_instance_diagnostic(vm, message, source, level="DEBUG", etime=None,
details=details)
def create_instance(vm, nics, flavor, image):
def create_instance(vm, nics, volumes, flavor, image):
"""`image` is a dictionary which should contain the keys:
'backend_id', 'format' and 'metadata'
......@@ -628,14 +628,21 @@ def create_instance(vm, nics, flavor, image):
# Defined in settings.GANETI_CREATEINSTANCE_KWARGS
kw['disk_template'] = flavor.disk_template
kw['disks'] = [{"size": flavor.disk * 1024}]
provider = flavor.disk_provider
if provider:
kw['disks'][0]['provider'] = provider
kw['disks'][0]['origin'] = flavor.disk_origin
extra_disk_params = settings.GANETI_DISK_PROVIDER_KWARGS.get(provider)
if extra_disk_params is not None:
kw["disks"][0].update(extra_disk_params)
disks = []
for volume in volumes:
disk = {}
disk["size"] = volume.size * 1024
provider = flavor.disk_provider
if provider is not None:
disk["provider"] = provider
disk["origin"] = volume.source_image["checksum"]
extra_disk_params = settings.GANETI_DISK_PROVIDER_KWARGS\
.get(provider)
if extra_disk_params is not None:
disk.update(extra_disk_params)
disks.append(disk)
kw["disks"] = disks
kw['nics'] = [{"name": nic.backend_uuid,
"network": nic.network.backend_id,
......
......@@ -44,7 +44,7 @@ from synnefo.logic import backend, ips, utils
from synnefo.logic.backend_allocator import BackendAllocator
from synnefo.db.models import (NetworkInterface, VirtualMachine,
VirtualMachineMetadata, IPAddressLog, Network,
pooled_rapi_client)
Volume, pooled_rapi_client)
from vncauthproxy.client import request_forwarding as request_vnc_forwarding
from synnefo.logic import rapi
......@@ -210,6 +210,8 @@ def create(userid, name, password, flavor, image, metadata={},
port.index = index
port.save()
volumes = create_instance_volumes(vm, flavor, image)
for key, val in metadata.items():
VirtualMachineMetadata.objects.create(
meta_key=key,
......@@ -217,7 +219,8 @@ def create(userid, name, password, flavor, image, metadata={},
vm=vm)
# Create the server in Ganeti.
vm = create_server(vm, ports, flavor, image, personality, password)
vm = create_server(vm, ports, volumes, flavor, image, personality,
password)
return vm
......@@ -243,7 +246,7 @@ def allocate_new_server(userid, flavor):
@server_command("BUILD")
def create_server(vm, nics, flavor, image, personality, password):
def create_server(vm, nics, volumes, flavor, image, personality, password):
# dispatch server created signal needed to trigger the 'vmapi', which
# enriches the vm object with the 'config_url' attribute which must be
# passed to the Ganeti job.
......@@ -256,7 +259,7 @@ def create_server(vm, nics, flavor, image, personality, password):
})
# send job to Ganeti
try:
jobID = backend.create_instance(vm, nics, flavor, image)
jobID = backend.create_instance(vm, nics, volumes, flavor, image)
except:
log.exception("Failed create instance '%s'", vm)
jobID = None
......@@ -275,6 +278,21 @@ def create_server(vm, nics, flavor, image, personality, password):
return jobID
def create_instance_volumes(vm, flavor, image):
name = "Root volume of server: %s" % vm.id
volume = Volume.objects.create(userid=vm.userid,
machine=vm,
name=name,
size=flavor.disk,
source_image_id=image["id"],
status="CREATING")
volume.source_image = image
volume.save()
return [volume]
@server_command("DESTROY")
def destroy(vm, shutdown_timeout=None):
# XXX: Workaround for race where OP_INSTANCE_REMOVE starts executing on
......
......@@ -563,7 +563,7 @@ def snapshot_to_dict(snapshot_url, meta, permissions):
snapshot["location"] = snapshot_url
snapshot["file_name"] = name
created = meta.get("created_at", meta["modified"])
created = meta["version_timestamp"]
snapshot["created_at"] = format_timestamp(created)
for key, val in meta.items():
......
......@@ -62,7 +62,7 @@ LIST_FIELDS = ('status', 'name', 'disk_format', 'container_format', 'size',
DETAIL_FIELDS = ('name', 'disk_format', 'container_format', 'size', 'checksum',
'location', 'created_at', 'updated_at', 'deleted_at',
'status', 'is_public', 'owner', 'properties', 'id')
'status', 'is_public', 'owner', 'properties', 'id', "is_snapshot")
ADD_FIELDS = ('name', 'id', 'store', 'disk_format', 'container_format', 'size',
'checksum', 'is_public', 'owner', 'properties', 'location')
......
......@@ -7,7 +7,6 @@ from synnefo.plankton.utils import image_backend
from synnefo.logic import backend
from synnefo.volume import util
SNAPSHOTS_CONTAINER = "snapshots"
SNAPSHOTS_DOMAIN = "plankton"
SNAPSHOTS_PREFIX = "plankton:"
......@@ -23,35 +22,51 @@ def create(user_id, volume, name, description, metadata, force=False):
volume.snapshot_counter += 1
volume.save()
transaction.commit()
snapshot_metadata = {}
snapshot_metadata[SNAPSHOTS_PREFIX + "name"] = description
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"] = volume.id
snapshot_metadata[SNAPSHOTS_PREFIX + "status"] = "CREATING"
#XXX: just to work
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
image_properties = {"EXCLUDE_ALL_TASKS": "yes",
"description": description}
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_name = generate_snapshot_name(volume)
mapfile = SNAPSHOTS_MAPFILE_PREFIX + snapshot_name
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(
user=user_id,
account=user_id,
container=SNAPSHOTS_CONTAINER,
name=name,
size=volume.size,
name=snapshot_name,
size=size,
domain=SNAPSHOTS_DOMAIN,
type=SNAPSHOTS_TYPE,
mapfile=mapfile,
meta=snapshot_metadata,
replace_meta=False,
replace_meta=True,
permissions=None)
#checksum=None,
backend.snapshot_instance(volume.machine, snapshot_name=snapshot_uuid)
backend.snapshot_instance(volume.machine, snapshot_name=snapshot_name)
snapshot = util.get_snapshot(user_id, snapshot_uuid)
......@@ -60,8 +75,8 @@ def create(user_id, volume, name, description, metadata, force=False):
def generate_snapshot_name(volume):
time = isoformat(datetime.datetime.now())
return "snf-snapshot-of-volume-%s-%s-%s" % (volume.id,
volume.snapshot_counter, time)
return "snf-snapshot-of-volume-%s-%s" % (volume.id,
volume.snapshot_counter)
@transaction.commit_on_success
......
from synnefo.db import models
from snf_django.lib.api import faults
from synnefo.api.util import get_image_dict, get_vm
from synnefo.api.util import get_image_dict, get_vm, image_backend
def get_volume(user_id, volume_id, for_update=False,
......@@ -15,10 +15,8 @@ def get_volume(user_id, volume_id, for_update=False,
def get_snapshot(user_id, snapshot_id, exception=faults.ItemNotFound):
try:
return get_image_dict(snapshot_id, user_id)
except faults.ItemNotFound:
raise exception("Snapshot %s not found" % snapshot_id)
with image_backend(user_id) as b:
return b.get_snapshot(user_id, snapshot_id)
def get_image(user_id, image_id, exception=faults.ItemNotFound):
......
......@@ -101,15 +101,19 @@ def create_volume(request):
req = utils.get_request_dict(request)
log.debug("create_volume %s", req)
user_id = request.user_uniq
new_volume = req.get("volume")
if new_volume is None:
raise faults.BadRequest("Missing 'volume' attribute.")
# Get and validate 'name' parameter
# TODO: auto generate name
name = req.get("name", None)
name = new_volume.get("name", None)
if name is None:
raise faults.BadRequest("Volume 'name' is needed.")
# Get and validate 'size' parameter
size = req.get("size")
size = new_volume.get("size")
if size is None:
raise faults.BadRequest("Volume 'size' is needed.")
try:
......@@ -121,25 +125,25 @@ def create_volume(request):
" value. '%s' cannot be accepted." % size)
# TODO: Fix volume type, validate, etc..
volume_type = req.get("volume_type", None)
volume_type = new_volume.get("volume_type", None)
# Optional parameters
description = req.get("description", "")
metadata = req.get("metadata", {})
description = new_volume.get("description", "")
metadata = new_volume.get("metadata", {})
if not isinstance(metadata, dict):
msg = "Volume 'metadata' needs to be a dictionary of key-value pairs."\
" '%s' can not be accepted." % metadata
raise faults.BadRequest(msg)
# Id of the volume to clone from
source_volume_id = req.get("source_volid")
source_volume_id = new_volume.get("source_volid")
# Id of the snapshot to create the volume from
source_snapshot_id = req.get("snapshot_id")
source_snapshot_id = new_volume.get("snapshot_id")
# Reference to an Image stored in Glance
source_image_id = req.get("imageRef")
source_image_id = new_volume.get("imageRef")
# TODO: Check that not all of them are used
server_id = req.get("server_id")
server_id = new_volume.get("server_id")
if server_id is None:
raise faults.BadRequest("Attribute 'server_id' is mandatory")
......@@ -217,9 +221,10 @@ def update_volume(request, volume_id):
def snapshot_to_dict(snapshot, detail=True):
owner = snapshot["owner"]
status = snapshot["status"]
progress = snapshot["progress"]
owner = snapshot['owner']
status = snapshot['status']
progress = "%s%%" % 100 if status == "ACTIVE" else 0
data = {
"id": snapshot["uuid"],
"size": int(snapshot["size"]) >> 30, # gigabytes
......@@ -244,30 +249,34 @@ def create_snapshot(request):
req = utils.get_request_dict(request)
log.debug("create_snapshot %s", req)
user_id = request.user_uniq
new_snapshot = req.get("snapshot")
if new_snapshot is None:
raise faults.BadRequest("Missing 'snapshot' attribute.")
# Get and validate 'name' parameter
# TODO: auto generate name
metadata = req.get("metadata", {})
metadata = new_snapshot.get("metadata", {})
if not isinstance(metadata, dict):
msg = "Snapshot 'metadata' needs to be a dictionary of key-value"\
" pairs. '%s' can not be accepted." % metadata
raise faults.BadRequest(msg)
volume_id = req.get("volume_id", None)
volume_id = new_snapshot.get("volume_id", None)
if volume_id is None:
raise faults.BadRequest("'volume_id' attribute is missing.")
volume = util.get_volume(user_id, volume_id, for_update=True,
exception=faults.BadRequest)
name = req.get("name", None)
name = new_snapshot.get("name", None)
if name is None:
name = "snapshot_volume_%s_%s" %\
(volume.id, str(datetime.datetime.now()))
description = req.get("description", "")
description = new_snapshot.get("description", "")
# TODO: What to do with force ?
force = req.get("force", False)
force = new_snapshot.get("force", False)
if not isinstance(force, bool):
raise faults.BadRequest("Invalid value for 'force' attribute.")
......@@ -277,7 +286,7 @@ def create_snapshot(request):
# Render response
data = json.dumps(dict(snapshot=snapshot_to_dict(snapshot, detail=False)))
return HttpResponse(data, status=200) # TOO: Maybe 202 ?
return HttpResponse(data, status=202)
@api.api_method(http_method="GET", user_required=True, logger=log)
......
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