Commit 5bb624cc authored by Christos Stavrakakis's avatar Christos Stavrakakis
Browse files

cyclades: Implement server actions for volumes

Implement 'attach_volume' and 'detach_volume' server actions. Also, add
'GANETI_MAX_DISKS_PER_INSTANCE' setting, to force a hard limit on the
maximum number of disks a server can have.
parent 0d0fcc39
......@@ -46,6 +46,10 @@
## than 'max:nic-count' option of Ganeti's ipolicy.
#GANETI_MAX_NICS_PER_INSTANCE = 8
#
## Maximum number of disks per Ganeti instance. This value must be less or
## equal than 'max:disk-count' option of Ganeti's ipolicy.
#GANETI_MAX_DISKS_PER_INSTANCE = 8
#
## The following setting defines a dictionary with key-value parameters to be
## passed to each Ganeti ExtStorage provider. The setting defines a mapping
## from the provider name, e.g. 'archipelago' to a dictionary with the actual
......
......@@ -46,6 +46,10 @@ BACKEND_REFRESH_MIN = 15
# than 'max:nic-count' option of Ganeti's ipolicy.
GANETI_MAX_NICS_PER_INSTANCE = 8
# Maximum number of disks per Ganeti instance. This value must be less or equal
# than 'max:disk-count' option of Ganeti's ipolicy.
GANETI_MAX_DISKS_PER_INSTANCE = 8
# The following setting defines a dictionary with key-value parameters to be
# passed to each Ganeti ExtStorage provider. The setting defines a mapping from
# the provider name, e.g. 'archipelago' to a dictionary with the actual
......
......@@ -80,7 +80,11 @@ def validate_server_action(vm, action):
elif (action == "START" and operstate != "STOPPED") or\
(action == "STOP" and operstate != "STARTED") or\
(action == "RESIZE" and operstate != "STOPPED") or\
(action in ["CONNECT", "DISCONNECT"] and operstate != "STOPPED"
(action in ["CONNECT", "DISCONNECT"]
and operstate != "STOPPED"
and not settings.GANETI_USE_HOTPLUG) or \
(action in ["ATTACH_VOLUME", "DETACH_VOLUME"]
and operstate != "STOPPED"
and not settings.GANETI_USE_HOTPLUG):
raise faults.BadRequest("Cannot perform '%s' action while server is"
" in '%s' state." % (action, operstate))
......@@ -798,3 +802,76 @@ def _port_for_request(user_id, network_dict):
else:
raise faults.BadRequest("Network 'uuid' or 'port' attribute"
" is required.")
@server_command("ATTACH_VOLUME")
def attach_volume(vm, volume):
"""Attach a volume to a server.
The volume must be in 'AVAILABLE' status in order to be attached. Also,
number of the volumes that are attached to the server must remain less
than 'GANETI_MAX_DISKS_PER_INSTANCE' setting. This function will send
the corresponding job to Ganeti backend and update the status of the
volume to 'ATTACHING'.
"""
# Check volume state
if volume.status not in ["AVAILABLE", "CREATING"]:
raise faults.BadRequest("Cannot attach volume while volume is in"
" '%s' status." % volume.status)
# Check that disk templates are the same
if volume.disk_template != vm.flavor.disk_template:
msg = ("Volume and server must have the same disk template. Volume has"
" disk template '%s' while server has '%s'"
% (volume.disk_template, vm.flavor.disk_template))
raise faults.BadRequest(msg)
# Check maximum disk per instance hard limit
if vm.volumes.count() == settings.GANETI_MAX_DISKS_PER_INSTANCE:
raise faults.BadRequest("Maximum volumes per server limit reached")
jobid = backend.attach_volume(vm, volume)
log.info("Attached volume '%s' to server '%s'. JobID: '%s'", volume.id,
volume.machine_id, jobid)
volume.backendjobid = jobid
volume.machine = vm
volume.status = "ATTACHING"
volume.save()
return jobid
@server_command("DETACH_VOLUME")
def detach_volume(vm, volume):
"""Detach a volume to a server.
The volume must be in 'IN_USE' status in order to be detached. Also,
the root volume of the instance (index=0) can not be detached. This
function will send the corresponding job to Ganeti backend and update the
status of the volume to 'DETACHING'.
"""
_check_attachment(vm, volume)
if volume.status != "IN_USE":
#TODO: Maybe allow other statuses as well ?
raise faults.BadRequest("Cannot detach volume while volume is in"
" '%s' status." % volume.status)
if volume.index == 0:
raise faults.BadRequest("Cannot detach the root volume of a server")
jobid = backend.detach_volume(vm, volume)
log.info("Detached volume '%s' from server '%s'. JobID: '%s'", volume.id,
volume.machine_id, jobid)
volume.backendjobid = jobid
volume.status = "DETACHING"
volume.save()
return jobid
def _check_attachment(vm, volume):
"""Check that volume is attached to vm."""
if volume.machine_id != vm.id:
raise faults.BadRequest("Volume '%s' is not attached to server '%s'"
% volume.id, vm.id)
......@@ -4,7 +4,7 @@ from django.db import transaction
from synnefo.db.models import Volume
from snf_django.lib.api import faults
from synnefo.volume import util
from synnefo.logic import backend
from synnefo.logic import backend, servers
log = logging.getLogger(__name__)
......@@ -43,19 +43,20 @@ def create(user_id, size, server_id, name=None, description=None,
msg = ("Cannot take a snapshot while snapshot is in '%s' state"
% source_volume.status)
raise faults.BadRequest(msg)
source = Volume.SOURCE_VOLUME_PREFIX + str(source_volume_id)
source = Volume.prefix_source(source_volume_id, source_type="volume")
origin = source_volume.backend_volume_uuid
elif source_snapshot_id is not None:
source_snapshot = util.get_snapshot(user_id, source_snapshot_id,
exception=faults.BadRequest)
# TODO: Check the state of the snapshot!!
source = Volume.prefix_source(source_snapshot_id,
source_type="snapshot")
origin = source_snapshot["checksum"]
source = Volume.SOURCE_SNAPSHOT_PREFIX + str(source_snapshot_id)
elif source_image_id is not None:
source_image = util.get_image(user_id, source_image_id,
exception=faults.BadRequest)
source = Volume.prefix_source(source_image_id, source_type="image")
origin = source_image["checksum"]
source = Volume.SOURCE_IMAGE_PREFIX + str(source_image_id)
volume = Volume.objects.create(userid=user_id,
size=size,
......@@ -71,9 +72,7 @@ def create(user_id, size, server_id, name=None, description=None,
for meta_key, meta_val in metadata.items():
volume.metadata.create(key=meta_key, value=meta_val)
# Create the disk in the backend
volume.backendjobid = backend.attach_volume(server, volume)
volume.save()
servers.attach_volume(server, volume)
return volume
......@@ -83,11 +82,8 @@ def delete(volume):
"""Delete a Volume"""
# A volume is deleted by detaching it from the server that is attached.
# Deleting a detached volume is not implemented.
if volume.index == 0:
raise faults.BadRequest("Cannot detach the root volume of a server")
if volume.machine_id is not None:
volume.backendjobid = backend.detach_volume(volume.machine, volume)
servers.detach_volume(volume.machine, volume)
log.info("Detach volume '%s' from server '%s', job: %s",
volume.id, volume.machine_id, volume.backendjobid)
else:
......
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