def test_assign_to_server(self): """Test if volume assignment to server works properly.""" vm = mf.VirtualMachineFactory() # Assign a volume to a server with no volumes. vol1 = mf.VolumeFactory() util.assign_volume_to_server(vm, vol1) # Assert that the volume is associated with the server and that its # index is 0. self.assertEqual(vol1.machine, vm) self.assertItemsEqual(vm.volumes.all(), [vol1]) self.assertEqual(vol1.index, 0) # Assign a volume to a server with a volume. vol2 = mf.VolumeFactory() util.assign_volume_to_server(vm, vol2) # Assert that the volume is associated with the server and that its # index is 1. self.assertEqual(vol2.machine, vm) self.assertItemsEqual(vm.volumes.all(), [vol1, vol2]) self.assertEqual(vol2.index, 1) # Assign a volume to a server with more than one volume and set its # index to a custom value (e.g. 9) vol3 = mf.VolumeFactory() util.assign_volume_to_server(vm, vol3, index=9) # Assert that the volume is associated with the server and that its # index is set to 9. self.assertEqual(vol3.machine, vm) self.assertItemsEqual(vm.volumes.all(), [vol1, vol2, vol3]) self.assertEqual(vol3.index, 9) # Assign a volume to a server with a volume whose index is 1 and a # deleted volume whose index is 9. vol1.machine = None vol1.save() vol3.deleted = True vol3.save() vol4 = mf.VolumeFactory() util.assign_volume_to_server(vm, vol4) # Assert that the volume is associated with the server and that its # index is 2. self.assertEqual(vol4.machine, vm) self.assertItemsEqual(vm.volumes.filter(deleted=False), [vol2, vol4]) self.assertEqual(vol4.index, 2) # Assert that the same index cannot be assigned to a different volume. vol5 = mf.VolumeFactory() with self.assertRaisesMessage(faults.BadRequest, self.wrong_msg.format(vol5, 2, vm)): util.assign_volume_to_server(vm, vol5, index=2)
def _attach_volume(vm, volume): """Attach a Volume to a VM and update the Volume's status.""" util.assign_volume_to_server(vm, volume) 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 if volume.status == "AVAILABLE": volume.status = "ATTACHING" else: volume.status = "CREATING" volume.save() return jobid
def attach_volume(vm, volume, atomic_context): """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) elif volume.status == "AVAILABLE": util.assert_detachable_volume_type(volume.volume_type) # Check that disk templates are the same if volume.volume_type.template != vm.flavor.volume_type.template: msg = ("Volume and server must have the same volume template. Volume" " has volume template'%s' while server has '%s'" % (volume.volume_type.template, vm.flavor.volume_type.template)) raise faults.BadRequest(msg) # Check maximum disk per instance hard limit vm_volumes_num = vm.volumes.filter(deleted=False).count() if vm_volumes_num == settings.GANETI_MAX_DISKS_PER_INSTANCE: raise faults.BadRequest("Maximum volumes per server limit reached") if volume.status == "CREATING": action_fields = {"disks": [("add", volume, {})]} else: action_fields = None with commands.ServerCommand("ATTACH_VOLUME", vm, atomic_context=atomic_context, action_fields=action_fields): util.assign_volume_to_server(vm, volume) jobid = backend.attach_volume(vm, volume) vm.record_job(jobid) log.info("Attached volume '%s' to server '%s'. JobID: '%s'", volume.id, volume.machine_id, jobid) volume.backendjobid = jobid volume.machine = vm if volume.status == "AVAILABLE": volume.status = "ATTACHING" else: volume.status = "CREATING" volume.save()
def _db_create_server(credentials, name, flavor, image, metadata, networks, use_backend, project, volumes, helper, shared_to_project, key_names, atomic_context=None): rescue_properties = RescueProperties() try: rescue_properties.os = image["metadata"].get("OSFAMILY", '') rescue_properties.os_family = image["metadata"].get("OS", '') except KeyError as e: log.error("Failed to parse iamge info: %s", e) rescue_properties.save() # Create the ports for the server ports = create_instance_ports(credentials, networks) # We must save the VM instance now, so that it gets a valid # vm.backend_vm_id. vm = VirtualMachine.objects.create(name=name, backend=use_backend, userid=credentials.userid, project=project, shared_to_project=shared_to_project, imageid=image["id"], image_version=image["version"], key_names=json.dumps(key_names), flavor=flavor, operstate="BUILD", rescue_properties=rescue_properties, helper=helper) log.info("Created entry in DB for VM '%s'", vm) # Associate the ports with the server for index, port in enumerate(ports): associate_port_with_machine(port, vm) port.index = index port.save() # Create instance volumes server_vtype = flavor.volume_type server_volumes = [] for index, vol_info in enumerate(volumes): if vol_info["source_type"] == "volume": uuid = vol_info["source_uuid"] v = get_volume(credentials, uuid, for_update=True, non_deleted=True, exception=faults.BadRequest) if v.volume_type_id != server_vtype.id: msg = ("Volume '%s' has type '%s' while flavor's volume type" " is '%s'" % (v.id, v.volume_type_id, server_vtype.id)) raise faults.BadRequest(msg) if v.status != "AVAILABLE": raise faults.BadRequest("Cannot use volume while it is in %s" " status" % v.status) v.delete_on_termination = vol_info["delete_on_termination"] else: v = _create_volume(user_id=credentials.userid, volume_type=server_vtype, project=project, index=index, shared_to_project=shared_to_project, **vol_info) assign_volume_to_server(vm, v, index=index) 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") utils.check_name_length(val, VirtualMachineMetadata.VALUE_LENGTH, "Metadata value is too long") VirtualMachineMetadata.objects.create(meta_key=key, meta_value=val, vm=vm) quotas.issue_and_accept_commission(vm, action="BUILD", atomic_context=atomic_context) return (vm.id, [port.id for port in ports ], [volume.id for volume in server_volumes], {v.id: v.origin_size for v in server_volumes})
def create(userid, name, password, flavor, image_id, metadata={}, personality=[], networks=None, use_backend=None, project=None, volumes=None, helper=False, user_projects=None, shared_to_project=False, key_name=None): 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.") if len(metadata) > settings.CYCLADES_VM_MAX_METADATA: raise faults.BadRequest("Virtual Machines cannot have more than %s " "metadata items" % settings.CYCLADES_VM_MAX_METADATA) # Get image info image = util.get_image_dict(image_id, userid) 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") try: is_system = (image["owner"] == settings.SYSTEM_IMAGES_OWNER) img, created = Image.objects.get_or_create(uuid=image["id"], version=image["version"]) if created: img.owner = image["owner"] img.name = image["name"] img.location = image["location"] img.mapfile = image["mapfile"] img.is_public = image["is_public"] img.is_snapshot = image["is_snapshot"] img.is_system = is_system img.os = image["metadata"].get("OS", "unknown") img.osfamily = image["metadata"].get("OSFAMILY", "unknown") img.save() except Exception as e: # Image info is not critical. Continue if it fails for any reason log.warning("Failed to store image info: %s", e) if project is None: project = userid if use_backend is None: # Allocate server to a Ganeti backend use_backend = allocate_new_server(userid, project, flavor) # Create the ports for the server ports = create_instance_ports(userid, user_projects, networks) # We must save the VM instance now, so that it gets a valid # vm.backend_vm_id. vm = VirtualMachine.objects.create(name=name, backend=use_backend, userid=userid, project=project, shared_to_project=shared_to_project, imageid=image["id"], image_version=image["version"], key_name=key_name, flavor=flavor, operstate="BUILD", helper=helper) log.info("Created entry in DB for VM '%s'", vm) # Associate the ports with the server for index, port in enumerate(ports): associate_port_with_machine(port, vm) port.index = index port.save() # Create instance volumes server_vtype = flavor.volume_type server_volumes = [] for index, vol_info in enumerate(volumes): if vol_info["source_type"] == "volume": uuid = vol_info["source_uuid"] v = get_volume(userid, user_projects, uuid, for_update=True, non_deleted=True, exception=faults.BadRequest) if v.volume_type_id != server_vtype.id: msg = ("Volume '%s' has type '%s' while flavor's volume type" " is '%s'" % (v.id, v.volume_type_id, server_vtype.id)) raise faults.BadRequest(msg) if v.status != "AVAILABLE": raise faults.BadRequest("Cannot use volume while it is in %s" " status" % v.status) v.delete_on_termination = vol_info["delete_on_termination"] else: v = _create_volume(user_id=userid, volume_type=server_vtype, project=project, index=index, shared_to_project=shared_to_project, **vol_info) assign_volume_to_server(vm, v, index=index) 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") utils.check_name_length(val, VirtualMachineMetadata.VALUE_LENGTH, "Metadata value is too long") VirtualMachineMetadata.objects.create(meta_key=key, meta_value=val, vm=vm) public_key = None if key_name is not None: keypair = util.get_keypair(key_name, userid) public_key = keypair.content # Create the server in Ganeti. vm = create_server(vm, ports, server_volumes, flavor, image, personality, password, public_key) return vm
def create(userid, name, password, flavor, image_id, metadata={}, personality=[], networks=None, use_backend=None, project=None, volumes=None, helper=False, user_projects=None, shared_to_project=False): 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.") if len(metadata) > settings.CYCLADES_VM_MAX_METADATA: raise faults.BadRequest("Virtual Machines cannot have more than %s " "metadata items" % settings.CYCLADES_VM_MAX_METADATA) # Get image info image = util.get_image_dict(image_id, userid) 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") try: is_system = (image["owner"] == settings.SYSTEM_IMAGES_OWNER) img, created = Image.objects.get_or_create(uuid=image["id"], version=image["version"]) if created: img.owner = image["owner"] img.name = image["name"] img.location = image["location"] img.mapfile = image["mapfile"] img.is_public = image["is_public"] img.is_snapshot = image["is_snapshot"] img.is_system = is_system img.os = image["metadata"].get("OS", "unknown") img.osfamily = image["metadata"].get("OSFAMILY", "unknown") img.save() except Exception as e: # Image info is not critical. Continue if it fails for any reason log.warning("Failed to store image info: %s", e) if use_backend is None: # Allocate server to a Ganeti backend use_backend = allocate_new_server(userid, flavor) # Create the ports for the server ports = create_instance_ports(userid, user_projects, networks) if project is None: project = userid # We must save the VM instance now, so that it gets a valid # vm.backend_vm_id. vm = VirtualMachine.objects.create(name=name, backend=use_backend, userid=userid, project=project, shared_to_project=shared_to_project, imageid=image["id"], image_version=image["version"], flavor=flavor, operstate="BUILD", helper=helper) log.info("Created entry in DB for VM '%s'", vm) # Associate the ports with the server for index, port in enumerate(ports): associate_port_with_machine(port, vm) port.index = index port.save() # Create instance volumes server_vtype = flavor.volume_type server_volumes = [] for index, vol_info in enumerate(volumes): if vol_info["source_type"] == "volume": uuid = vol_info["source_uuid"] v = get_volume(userid, user_projects, uuid, for_update=True, non_deleted=True, exception=faults.BadRequest) if v.volume_type_id != server_vtype.id: msg = ("Volume '%s' has type '%s' while flavor's volume type" " is '%s'" % (v.id, v.volume_type_id, server_vtype.id)) raise faults.BadRequest(msg) if v.status != "AVAILABLE": raise faults.BadRequest("Cannot use volume while it is in %s" " status" % v.status) v.delete_on_termination = vol_info["delete_on_termination"] else: v = _create_volume(user_id=userid, volume_type=server_vtype, project=project, index=index, shared_to_project=shared_to_project, **vol_info) assign_volume_to_server(vm, v, index=index) 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") utils.check_name_length(val, VirtualMachineMetadata.VALUE_LENGTH, "Metadata value is too long") VirtualMachineMetadata.objects.create( meta_key=key, meta_value=val, vm=vm) # Create the server in Ganeti. vm = create_server(vm, ports, server_volumes, flavor, image, personality, password) return vm
def _db_create_server( credentials, name, flavor, image, metadata, networks, use_backend, project, volumes, helper, shared_to_project, key_names, atomic_context=None): rescue_properties = RescueProperties() try: rescue_properties.os = image["metadata"].get("OSFAMILY", '') rescue_properties.os_family = image["metadata"].get("OS", '') except KeyError as e: log.error("Failed to parse iamge info: %s", e) rescue_properties.save() # Create the ports for the server ports = create_instance_ports(credentials, networks) # We must save the VM instance now, so that it gets a valid # vm.backend_vm_id. vm = VirtualMachine.objects.create(name=name, backend=use_backend, userid=credentials.userid, project=project, shared_to_project=shared_to_project, imageid=image["id"], image_version=image["version"], key_names=json.dumps(key_names), flavor=flavor, operstate="BUILD", rescue_properties=rescue_properties, helper=helper) log.info("Created entry in DB for VM '%s'", vm) # Associate the ports with the server for index, port in enumerate(ports): associate_port_with_machine(port, vm) port.index = index port.save() # Create instance volumes server_vtype = flavor.volume_type server_volumes = [] for index, vol_info in enumerate(volumes): if vol_info["source_type"] == "volume": uuid = vol_info["source_uuid"] v = get_volume(credentials, uuid, for_update=True, non_deleted=True, exception=faults.BadRequest) if v.volume_type_id != server_vtype.id: msg = ("Volume '%s' has type '%s' while flavor's volume type" " is '%s'" % (v.id, v.volume_type_id, server_vtype.id)) raise faults.BadRequest(msg) if v.status != "AVAILABLE": raise faults.BadRequest("Cannot use volume while it is in %s" " status" % v.status) v.delete_on_termination = vol_info["delete_on_termination"] else: v = _create_volume(user_id=credentials.userid, volume_type=server_vtype, project=project, index=index, shared_to_project=shared_to_project, **vol_info) assign_volume_to_server(vm, v, index=index) 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") utils.check_name_length(val, VirtualMachineMetadata.VALUE_LENGTH, "Metadata value is too long") VirtualMachineMetadata.objects.create( meta_key=key, meta_value=val, vm=vm) quotas.issue_and_accept_commission(vm, action="BUILD", atomic_context=atomic_context) return (vm.id, [port.id for port in ports], [volume.id for volume in server_volumes], {v.id: v.origin_size for v in server_volumes})