Commit 91007bc4 authored by Christos Stavrakakis's avatar Christos Stavrakakis
Browse files

cyclades: Accept an empty image when using volumes

Accept an empty 'imageRef' attribute when creating a server with
volumes. The attribute must always be present even with an empty value.
In case the server's image is left blank, we use the source
(image/snapshot) of the first volume. Also, if an image is specified
both for the server and for the first volume, we check that the image is
the same.
parent 9ec0143c
......@@ -149,7 +149,10 @@ def get_image(image_id, user_id):
"""Return an Image instance or raise ItemNotFound."""
with PlanktonBackend(user_id) as backend:
return backend.get_image(image_id)
try:
return backend.get_image(image_id)
except faults.ItemNotFound:
raise faults.ItemNotFound("Image '%s' not found" % image_id)
def get_image_dict(image_id, user_id):
......
......@@ -43,7 +43,7 @@ class Command(SynnefoCommand):
help="An arbitrary string for naming the server"),
make_option("--user-id", dest="user_id",
help="Unique identifier of the owner of the server"),
make_option("--image-id", dest="image_id",
make_option("--image-id", dest="image_id", default=None,
help="Unique identifier of the image."
" Use snf-manage image-list to find out"
" available images."),
......@@ -85,6 +85,7 @@ class Command(SynnefoCommand):
image_id = options['image_id']
flavor_id = options['flavor_id']
password = options['password']
volumes = options['volumes']
if not name:
raise CommandError("name is mandatory")
......@@ -94,19 +95,21 @@ class Command(SynnefoCommand):
raise CommandError("password is mandatory")
if not flavor_id:
raise CommandError("flavor-id is mandatory")
if not image_id:
if not image_id and not volumes:
raise CommandError("image-id is mandatory")
flavor = common.get_resource("flavor", flavor_id)
image = common.get_image(image_id, user_id)
if image_id is not None:
common.get_image(image_id, user_id)
if backend_id:
backend = common.get_resource("backend", backend_id)
else:
backend = None
connection_list = parse_connections(options["connections"])
volumes_list = parse_volumes(options["volumes"])
server = servers.create(user_id, name, password, flavor, image["id"],
volumes_list = parse_volumes(volumes)
server = servers.create(user_id, name, password, flavor, image_id,
networks=connection_list,
volumes=volumes_list,
use_backend=backend)
......
......@@ -47,23 +47,45 @@ def create(userid, name, password, flavor, image_id, metadata={},
personality=[], networks=None, use_backend=None, project=None,
volumes=None):
# Get image information
# TODO: Image is not mandatory if disks are specified
utils.check_name_length(name, VirtualMachine.VIRTUAL_MACHINE_NAME_LENGTH,
"Server name is too long")
# Get the image, if any, that is used for the first volume
vol_image_id = None
if volumes:
vol = volumes[0]
if vol["source_type"] in ["image", "snapshot"]:
vol_image_id = vol["source_uuid"]
# Check conflict between server's and volume's image
if image_id and vol_image_id and image_id != vol_image_id:
raise faults.BadRequest("The specified server's image is different"
" from the the source of the first volume.")
elif vol_image_id and not image_id:
image_id = vol_image_id
elif not image_id:
raise faults.BadRequest("You need to specify either an image or a"
" block device mapping.")
# Get image info
image = util.get_image_dict(image_id, userid)
# Check that image fits into the disk
if int(image["size"]) > (flavor.disk << 30):
msg = ("Flavor's disk size '%s' is smaller than the image's"
"size '%s'" % (flavor.disk << 30, image["size"]))
raise faults.BadRequest(msg)
if not volumes:
# If no volumes are specified, we automatically create a volume with
# the size of the flavor and filled with the specified image.
volumes = [{"source_type": "image",
"source_uuid": image_id,
"size": flavor.disk,
"delete_on_termination": True}]
assert(len(volumes) > 0), "Cannot create server without volumes"
if volumes[0]["source_type"] == "blank":
raise faults.BadRequest("Root volume cannot be blank")
if use_backend is None:
# Allocate server to a Ganeti backend
use_backend = allocate_new_server(userid, flavor)
utils.check_name_length(name, VirtualMachine.VIRTUAL_MACHINE_NAME_LENGTH,
"Server name is too long")
# Create the ports for the server
ports = create_instance_ports(userid, networks)
......@@ -76,7 +98,7 @@ def create(userid, name, password, flavor, image_id, metadata={},
backend=use_backend,
userid=userid,
project=project,
imageid=image["id"],
imageid=image_id,
flavor=flavor,
operstate="BUILD")
log.info("Created entry in DB for VM '%s'", vm)
......@@ -87,19 +109,7 @@ def create(userid, name, password, flavor, image_id, metadata={},
port.index = index
port.save()
# If no volumes are specified, we automatically create a volume with the
# size of the flavor and filled with the specified image.
if not volumes:
volumes = [{"source_type": "image",
"source_uuid": image["id"],
"size": flavor.disk,
"delete_on_termination": True}]
assert(len(volumes) > 0), "Cannot create server without volumes"
if volumes[0]["source_type"] == "blank":
raise faults.BadRequest("Root volume cannot be blank")
# Create instance volumes
server_vtype = flavor.volume_type
server_volumes = []
for index, vol_info in enumerate(volumes):
......@@ -115,6 +125,7 @@ def create(userid, name, password, flavor, image_id, metadata={},
raise faults.BadRequest("Cannot use volume while it is in %s"
" status" % v.status)
v.delete_on_termination = vol_info["delete_on_termination"]
v.machine = vm
v.index = index
v.save()
else:
......@@ -123,6 +134,7 @@ def create(userid, name, password, flavor, image_id, metadata={},
index=index, **vol_info)
server_volumes.append(v)
# Create instance metadata
for key, val in metadata.items():
utils.check_name_length(key, VirtualMachineMetadata.KEY_LENGTH,
"Metadata key is too long")
......@@ -180,6 +192,7 @@ def create_server(vm, nics, volumes, flavor, image, personality, password):
'img_personality': json.dumps(personality),
'img_properties': json.dumps(image['metadata']),
})
# send job to Ganeti
try:
jobID = backend.create_instance(vm, nics, volumes, flavor, image)
......
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