def resize(server_id, flavor_id, credentials=None, atomic_context=None): vm = util.get_vm(server_id, credentials, for_update=True, non_deleted=True, non_suspended=True) flavor = util.get_flavor(flavor_id, credentials, include_deleted=False, for_project=vm.project) action_fields = {"beparams": {"vcpus": flavor.cpu, "maxmem": flavor.ram}} with commands.ServerCommand("RESIZE", server_id, credentials, atomic_context, action_fields=action_fields) as vm: old_flavor = vm.flavor # User requested the same flavor if old_flavor.id == flavor.id: raise faults.BadRequest("Server '%s' flavor is already '%s'." % (vm, flavor)) # Check that resize can be performed if old_flavor.disk != flavor.disk: raise faults.BadRequest("Cannot change instance's disk size.") if old_flavor.volume_type_id != flavor.volume_type_id: raise faults.BadRequest("Cannot change instance's volume type.") log.info("Resizing VM from flavor '%s' to '%s", old_flavor, flavor) job_id = backend.resize_instance(vm, vcpus=flavor.cpu, memory=flavor.ram) vm.record_job(job_id) return vm
def set_firewall_profile(request, server_id, args): # Normal Response Code: 200 # Error Response Codes: computeFault (400, 500), # serviceUnavailable (503), # unauthorized (401), # badRequest (400), # badMediaType(415), # itemNotFound (404), # buildInProgress (409), # overLimit (413) credentials = request.credentials profile = args.get("profile") if profile is None: raise faults.BadRequest("Missing 'profile' attribute") nic_id = args.get("nic") if nic_id is None: raise faults.BadRequest("Missing 'nic' attribute") servers.set_firewall_profile(server_id, profile=profile, nic_id=nic_id, credentials=credentials) log.info("User %s set firewall profile of VM %s, port %s", credentials.userid, server_id, nic_id) return HttpResponse(status=202)
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.volume_type_id != vm.flavor.volume_type_id: msg = ("Volume and server must have the same volume type. Volume has" " volume type '%s' while server has '%s'" % (volume.volume_type_id, vm.flavor.volume_type_id)) 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 = {} comm = commands.server_command("ATTACH_VOLUME", action_fields=action_fields) return comm(_attach_volume)(vm, volume)
def remove(request, net, args): # Normal Response Code: 202 # Error Response Codes: computeFault (400, 500), # serviceUnavailable (503), # unauthorized (401), # badRequest (400), # badMediaType(415), # itemNotFound (404), # overLimit (413) attachment = args.get("attachment") if attachment is None: raise faults.BadRequest("Missing 'attachment' attribute.") try: nic_id = int(attachment) except (ValueError, TypeError): raise faults.BadRequest("Invalid 'attachment' attribute.") nic = util.get_nic(nic_id=nic_id) server_id = nic.machine_id vm = util.get_vm(server_id, request.user_uniq, non_suspended=True, for_update=True, non_deleted=True) servers.disconnect(vm, nic) return HttpResponse(status=202)
def get_uuid_displayname_catalogs(request, user_call=True): # Normal Response Codes: 200 # Error Response Codes: BadRequest (400) try: input_data = json.loads(request.body) except: raise faults.BadRequest('Request body should be json formatted.') else: if not isinstance(input_data, dict): raise faults.BadRequest( 'Request body should be a json formatted dictionary') uuids = input_data.get('uuids', []) if uuids is None and user_call: uuids = [] displaynames = input_data.get('displaynames', []) if displaynames is None and user_call: displaynames = [] user_obj = AstakosUser.objects d = { 'uuid_catalog': user_obj.uuid_catalog(uuids), 'displayname_catalog': user_obj.displayname_catalog(displaynames) } response = HttpResponse() response.content = json.dumps(d) response['Content-Type'] = 'application/json; charset=UTF-8' response['Content-Length'] = len(response.content) return response
def detach_volume(vm, volume): """Detach a Volume from a VM 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'. """ util.assert_detachable_volume_type(volume.volume_type) _check_attachment(vm, volume) if volume.status not in ["IN_USE", "ERROR"]: 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 server %s." % vm) with commands.ServerCommand("DETACH_VOLUME", vm): jobid = backend.detach_volume(vm, volume) vm.record_job(jobid) log.info("Detached volume '%s' from server '%s'. JobID: '%s'", volume.id, volume.machine_id, jobid) volume.backendjobid = jobid volume.status = "DETACHING" volume.save()
def server_action(request, server_id): req = utils.get_request_dict(request) log.debug('server_action %s %s', server_id, req) if len(req) != 1: raise faults.BadRequest("Malformed request") # Do not allow any action on deleted or suspended VMs vm = util.get_vm(server_id, request.user_uniq, for_update=True, non_deleted=True, non_suspended=True) try: key = req.keys()[0] if key not in ARBITRARY_ACTIONS: start_action(vm, key_to_action(key)) val = req[key] assert isinstance(val, dict) return server_actions[key](request, vm, val) except KeyError: raise faults.BadRequest("Unknown action") except AssertionError: raise faults.BadRequest("Invalid argument")
def validate_server_action(vm, action): if vm.deleted: raise faults.BadRequest("Server '%s' has been deleted." % vm.id) # Destroyin a server should always be permitted if action == "DESTROY": return # Check that there is no pending action pending_action = vm.task if pending_action: if pending_action == "BUILD": raise faults.BuildInProgress("Server '%s' is being build." % vm.id) raise faults.BadRequest("Cannot perform '%s' action while there is a" " pending '%s'." % (action, pending_action)) # Check if action can be performed to VM's operstate operstate = vm.operstate if operstate == "ERROR": raise faults.BadRequest("Cannot perform '%s' action while server is" " in 'ERROR' state." % action) elif operstate == "BUILD" and action != "BUILD": raise faults.BuildInProgress("Server '%s' is being build." % vm.id) 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" and not settings.GANETI_USE_HOTPLUG): raise faults.BadRequest("Cannot perform '%s' action while server is" " in '%s' state." % (action, operstate)) return
def delete_volume(vm, volume, atomic_context): """Delete attached volume and update its status The volume must be in 'IN_USE' status in order to be deleted. This function will send the corresponding job to Ganeti backend and update the status of the volume to 'DELETING'. """ _check_attachment(vm, volume) if volume.status not in ["IN_USE", "ERROR"]: raise faults.BadRequest("Cannot delete volume while volume is in" " '%s' status." % volume.status) if volume.index == 0: raise faults.BadRequest("Cannot delete the root volume of server %s." % vm) action_fields = {"disks": [("remove", volume, {})]} with commands.ServerCommand("DELETE_VOLUME", vm, atomic_context=atomic_context, action_fields=action_fields, for_user=volume.userid): jobid = backend.delete_volume(vm, volume) vm.record_job(jobid) log.info("Deleted volume '%s' from server '%s'. JobID: '%s'", volume.id, volume.machine_id, jobid) volume.backendjobid = jobid util.mark_volume_as_deleted(volume)
def get_console(request, vm, args): # Normal Response Code: 200 # Error Response Codes: computeFault (400, 500), # serviceUnavailable (503), # unauthorized (401), # badRequest (400), # badMediaType(415), # itemNotFound (404), # buildInProgress (409), # overLimit (413) log.info("Get console VM %s: %s", vm, args) console_type = args.get("type") if console_type is None: raise faults.BadRequest("No console 'type' specified.") supported_types = ['vnc', 'vnc-ws', 'vnc-wss'] if console_type not in supported_types: raise faults.BadRequest('Supported types: %s' % ', '.join(supported_types)) console_info = servers.console(vm, console_type) if request.serialization == 'xml': mimetype = 'application/xml' data = render_to_string('console.xml', {'console': console_info}) else: mimetype = 'application/json' data = json.dumps({'console': console_info}) return HttpResponse(data, mimetype=mimetype, status=200)
def _port_for_request(user_id, network_dict): if not isinstance(network_dict, dict): raise faults.BadRequest("Malformed request. Invalid 'networks' field") port_id = network_dict.get("port") network_id = network_dict.get("uuid") if port_id is not None: return util.get_port(port_id, user_id, for_update=True) elif network_id is not None: address = network_dict.get("fixed_ip") network = util.get_network(network_id, user_id, non_deleted=True) if network.public: if network.subnet4 is not None: if not "fixed_ip" in network_dict: return create_public_ipv4_port(user_id, network) elif address is None: msg = "Cannot connect to public network" raise faults.BadRequest(msg % network.id) else: return create_public_ipv4_port(user_id, network, address) else: raise faults.Forbidden("Cannot connect to IPv6 only public" " network %" % network.id) else: return _create_port(user_id, network, address=address) else: raise faults.BadRequest("Network 'uuid' or 'port' attribute" " is required.")
def update_network_name(request, network_id): # Normal Response Code: 204 # Error Response Codes: computeFault (400, 500), # serviceUnavailable (503), # unauthorized (401), # badRequest (400), # forbidden (403) # badMediaType(415), # itemNotFound (404), # overLimit (413) req = utils.get_request_dict(request) log.info('update_network_name %s', network_id) try: name = req['network']['name'] except (TypeError, KeyError): raise faults.BadRequest('Malformed request.') net = util.get_network(network_id, request.user_uniq) if net.public: raise faults.Forbidden('Can not rename the public network.') if net.deleted: raise faults.BadRequest("Network has been deleted.") net.name = name net.save() return HttpResponse(status=204)
def get_vm(server_id, user_id, projects, for_update=False, non_deleted=False, non_suspended=False, prefetch_related=None): """Find a VirtualMachine instance based on ID and owner.""" try: server_id = int(server_id) servers = VirtualMachine.objects.for_user(userid=user_id, projects=projects) if for_update: servers = servers.select_for_update() if prefetch_related is not None: if isinstance(prefetch_related, list): servers = servers.prefetch_related(*prefetch_related) else: servers = servers.prefetch_related(prefetch_related) vm = servers.get(id=server_id) if non_deleted and vm.deleted: raise faults.BadRequest("Server has been deleted.") if non_suspended and vm.suspended: raise faults.Forbidden("Administratively Suspended VM") return vm except (ValueError, TypeError): raise faults.BadRequest('Invalid server ID.') except VirtualMachine.DoesNotExist: raise faults.ItemNotFound('Server not found.')
def remove(request, net, args): # Normal Response Code: 202 # Error Response Codes: computeFault (400, 500), # serviceUnavailable (503), # unauthorized (401), # badRequest (400), # badMediaType(415), # itemNotFound (404), # overLimit (413) try: # attachment string: nic-<vm-id>-<nic-index> server_id = args.get('attachment', None).split('-')[1] nic_index = args.get('attachment', None).split('-')[2] except AttributeError: raise faults.BadRequest("Malformed Request") except IndexError: raise faults.BadRequest('Malformed Network Interface Id') if not server_id or not nic_index: raise faults.BadRequest('Malformed Request.') vm = get_vm(server_id, request.user_uniq, non_suspended=True) nic = get_nic_from_index(vm, nic_index) log.info("Removing NIC %s from VM %s", str(nic.index), vm) if nic.dirty: raise faults.BuildInProgress('Machine is busy.') else: vm.nics.all().update(dirty=True) backend.disconnect_from_network(vm, nic) return HttpResponse(status=202)
def demux_server_action(request, server_id): req = utils.get_json_body(request) log.debug('server_action %s %s', server_id, req) if not isinstance(req, dict) and len(req) != 1: raise faults.BadRequest("Malformed request") # Do not allow any action on deleted or suspended VMs vm = util.get_vm(server_id, request.user_uniq, for_update=True, non_deleted=True, non_suspended=True) try: action = req.keys()[0] except IndexError: raise faults.BadRequest("Malformed Request.") if not isinstance(action, basestring): raise faults.BadRequest("Malformed Request. Invalid action.") if key_to_action(action) not in [x[0] for x in VirtualMachine.ACTIONS]: if action not in ARBITRARY_ACTIONS: raise faults.BadRequest("Action %s not supported" % action) action_args = utils.get_attribute(req, action, required=True, attr_type=dict) return server_actions[action](request, vm, action_args)
def wrapper(request, *args, **kwargs): # The args variable may contain up to (account, container, object). if len(args) > 1 and len(args[1]) > 256: raise faults.BadRequest("Container name too large") if len(args) > 2 and len(args[2]) > 1024: raise faults.BadRequest('Object name too large.') success_status = False try: # Add a PithosBackend as attribute of the request object request.backend = get_backend() request.backend.pre_exec(lock_container_path) # Many API method expect thet X-Auth-Token in request,token request.token = request.x_auth_token update_request_headers(request) response = func(request, *args, **kwargs) update_response_headers(request, response) success_status = True return response finally: # Always close PithosBackend connection if getattr(request, "backend", None) is not None: request.backend.post_exec(success_status) request.backend.close()
def demux_server_action(request, server_id): credentials = request.credentials req = utils.get_json_body(request) if not isinstance(req, dict) and len(req) != 1: raise faults.BadRequest("Malformed request") try: action = req.keys()[0] except IndexError: raise faults.BadRequest("Malformed Request.") log.debug("User: %s, VM: %s, Action: %s Request: %s", credentials.userid, server_id, action, req) if not isinstance(action, basestring): raise faults.BadRequest("Malformed Request. Invalid action.") if key_to_action(action) not in [x[0] for x in VirtualMachine.ACTIONS]: if action not in ARBITRARY_ACTIONS: raise faults.BadRequest("Action %s not supported" % action) action_args = utils.get_attribute(req, action, required=False, attr_type=dict) return server_actions[action](request, server_id, action_args)
def submit_modification(app_data, user, project_id): owner = app_data.get("owner") if owner is not None: try: owner = AstakosUser.objects.accepted().get(uuid=owner) except AstakosUser.DoesNotExist: raise faults.BadRequest("User does not exist.") name = app_data.get("name") join_policy = app_data.get("join_policy") if join_policy is not None: try: join_policy = MEMBERSHIP_POLICY[join_policy] except KeyError: raise faults.BadRequest("Invalid join policy") leave_policy = app_data.get("leave_policy") if leave_policy is not None: try: leave_policy = MEMBERSHIP_POLICY[leave_policy] except KeyError: raise faults.BadRequest("Invalid leave policy") start_date = app_data.get("start_date") end_date = app_data.get("end_date") max_members = app_data.get("max_members") if max_members is not None: max_members = _parse_max_members(max_members) private = _get_maybe_boolean(app_data, "private") homepage = _get_maybe_string(app_data, "homepage") description = _get_maybe_string(app_data, "description") comments = _get_maybe_string(app_data, "comments") resources = app_data.get("resources", {}) submit = functions.submit_application with ExceptionHandler(): application = submit( owner=owner, name=name, project_id=project_id, homepage=homepage, description=description, start_date=start_date, end_date=end_date, member_join_policy=join_policy, member_leave_policy=leave_policy, limit_on_members_number=max_members, private=private, comments=comments, resources=resources, request_user=user) result = {"application": application.id, "id": application.chain.uuid, } return json_response(result, status_code=201)
def check_meta_headers(meta): if len(meta) > 90: raise faults.BadRequest('Too many headers.') for k, v in meta.iteritems(): if len(k) > 128: raise faults.BadRequest('Header name too large.') if len(v) > 256: raise faults.BadRequest('Header value too large.')
def get_sharing(request): """Parse an X-Object-Sharing header from the request. Raises BadRequest on error. """ permissions = request.META.get('HTTP_X_OBJECT_SHARING') if permissions is None: return None # TODO: Document or remove '~' replacing. permissions = permissions.replace('~', '') ret = {} permissions = permissions.replace(' ', '') if permissions == '': return ret for perm in (x for x in permissions.split(';')): if perm.startswith('read='): ret['read'] = list( set([v.replace(' ', '').lower() for v in perm[5:].split(',')])) if '' in ret['read']: ret['read'].remove('') if '*' in ret['read']: ret['read'] = ['*'] if len(ret['read']) == 0: raise faults.BadRequest( 'Bad X-Object-Sharing header value: invalid length') elif perm.startswith('write='): ret['write'] = list( set([v.replace(' ', '').lower() for v in perm[6:].split(',')])) if '' in ret['write']: ret['write'].remove('') if '*' in ret['write']: ret['write'] = ['*'] if len(ret['write']) == 0: raise faults.BadRequest( 'Bad X-Object-Sharing header value: invalid length') else: raise faults.BadRequest( 'Bad X-Object-Sharing header value: missing prefix') # replace displayname with uuid if TRANSLATE_UUIDS: try: ret['read'] = [ replace_permissions_displayname( getattr(request, 'token', None), x) for x in ret.get('read', []) ] ret['write'] = [ replace_permissions_displayname( getattr(request, 'token', None), x) for x in ret.get('write', []) ] except ItemNotExists, e: raise faults.BadRequest( 'Bad X-Object-Sharing header value: unknown account: %s' % e)
def validate_network_action(network, action): if network.deleted: raise faults.BadRequest("Network has been deleted.") if action in ["DRAIN", "UNDRAIN"]: if not network.public: raise faults.BadRequest("Network is not public.") if action == "DRAIN" and network.drained: raise faults.BadRequest("Network is drained.") if action == "UNDRAIN" and not network.drained: raise faults.BadRequest("Network is not drained.")
def get_attribute(request, attribute, attr_type=None, required=True): value = request.get(attribute, None) if required and value is None: raise faults.BadRequest("Malformed request. Missing attribute '%s'." % attribute) if attr_type is not None and value is not None\ and not isinstance(value, attr_type): raise faults.BadRequest("Malformed request. Invalid '%s' field" % attribute) return value
def create_new_keypair(request): """Generates or imports a keypair. Normal response code: 201 Error response codes: badRequest(400), unauthorized(401), forbidden(403), conflict(409) """ userid = request.credentials.userid if PublicKeyPair.user_limit_exceeded(userid): return HttpResponseServerError("SSH keys limit exceeded") req = utils.get_json_body(request) try: keypair = req['keypair'] assert (isinstance(req, dict)) name = keypair['name'] except (KeyError, AssertionError): raise faults.BadRequest('Malformed request.') if re.match(key_name_regex, name) is None: raise faults.BadRequest('Invalid name format') try: # If the key with the same name exists in the database # a conflict error will be raised util.get_keypair(name, userid) # If we get past this point then the key is already present # in the database raise faults.Conflict('A keypair with that name already exists') except faults.ItemNotFound: new_keypair = PublicKeyPair(name=name, user=userid) gen_keypair = None try: new_keypair.content = keypair['public_key'] except KeyError: # If the public_key field is omitted, generate a new # keypair and return both the private and the public key if not SUPPORT_GENERATE_KEYS: raise faults.Forbidden( "Application does not support ssh keys generation") gen_keypair = generate_keypair() new_keypair.content = gen_keypair['public'] new_keypair.save() data = keypair_to_dict(new_keypair) if gen_keypair is not None: data['keypair']['private_key'] = gen_keypair['private'] return HttpResponse(json.dumps(data), status=201)
def os_get_vnc_console(request, server_id, args): # Normal Response Code: 200 # Error Response Codes: computeFault (400, 500), # serviceUnavailable (503), # unauthorized (401), # badRequest (400), # badMediaType(415), # itemNotFound (404), # buildInProgress (409), # overLimit (413) credentials = request.credentials log.debug("User: %s, VM: %s, Action: get_osVNC console, Request: %s", credentials.userid, server_id, args) console_type = args.get('type') if console_type is None: raise faults.BadRequest("No console 'type' specified.") supported_types = {'novnc': 'vnc-wss', 'xvpvnc': 'vnc'} if console_type not in supported_types: raise faults.BadRequest('Supported types: %s' % ', '.join(supported_types.keys())) console_info = servers.console(server_id, supported_types[console_type], credentials=credentials) global machines_console_url if machines_console_url is None: machines_console_url = reverse('synnefo.ui.views.machines_console') if console_type == 'novnc': # Return the URL of the WebSocket noVNC client url = settings.CYCLADES_BASE_URL + machines_console_url url += '?host=%(host)s&port=%(port)s&password=%(password)s' else: # Return a URL to paste into a Java VNC client # FIXME: VNC clients (and the TigerVNC Java applet) can't handle the # password. url = '%(host)s:%(port)s?password=%(password)s' resp = {'type': console_type, 'url': url % console_info} if request.serialization == 'xml': mimetype = 'application/xml' data = render_to_string('os-console.xml', {'console': resp}) else: mimetype = 'application/json' data = json.dumps({'console': resp}) log.info("User %s got VNC console for VM %s", credentials.userid, server_id) return HttpResponse(data, content_type=mimetype, status=200)
def update_metadata(request, server_id): # Normal Response Code: 201 # Error Response Codes: computeFault (400, 500), # serviceUnavailable (503), # unauthorized (401), # badRequest (400), # buildInProgress (409), # badMediaType(415), # overLimit (413) req = utils.get_json_body(request) credentials = request.credentials userid = credentials.userid log.debug("User: %s, VM: %s, Action: update_metadata, Request: %s", userid, server_id, req) vm = util.get_vm(server_id, credentials, non_suspended=True, non_deleted=True) metadata = utils.get_attribute(req, "metadata", required=True, attr_type=dict) if len(metadata) + len(vm.metadata.all()) - \ len(vm.metadata.all().filter(meta_key__in=metadata.keys())) > \ settings.CYCLADES_VM_MAX_METADATA: raise faults.BadRequest("Virtual Machines cannot have more than %s " "metadata items" % settings.CYCLADES_VM_MAX_METADATA) for key, val in metadata.items(): if len(key) > VirtualMachineMetadata.KEY_LENGTH: raise faults.BadRequest("Malformed Request. Metadata key is too" " long") if len(val) > VirtualMachineMetadata.VALUE_LENGTH: raise faults.BadRequest("Malformed Request. Metadata value is too" " long") if not isinstance(key, (basestring, int)) or\ not isinstance(val, (basestring, int)): raise faults.BadRequest("Malformed Request. Invalid metadata.") meta, created = vm.metadata.get_or_create(meta_key=key) meta.meta_value = val meta.save() vm.save() log.info("User %s updated metadata of VM %s", userid, vm.id) vm_meta = dict((m.meta_key, m.meta_value) for m in vm.metadata.all()) return util.render_metadata(request, vm_meta, status=201)
def remove_floating_ip(request, vm, args): address = args.get("address") if address is None: raise faults.BadRequest("Missing 'address' attribute") floating_ip = util.get_floating_ip_by_address(vm.userid, address, for_update=True) if floating_ip.nic is None: raise faults.BadRequest("Floating IP %s not attached to instance" % address) servers.delete_port(floating_ip.nic) return HttpResponse(status=202)
def create_common(user_id, size, name=None, description=None, source_volume_id=None, source_snapshot_id=None, source_image_id=None, volume_type=None, metadata=None, project_id=None, shared_to_project=False): """Common tasks and checks for the creation of a new volume. This function processes the necessary arguments in order to call the `_create_volume` function, which creates the volume in the DB. The main duty of `create_common` is to handle the metadata creation of the volume and to update the quota of the user. """ # Assert that not more than one source are used sources = filter(lambda x: x is not None, [source_volume_id, source_snapshot_id, source_image_id]) if len(sources) > 1: raise faults.BadRequest("Volume can not have more than one source!") if source_volume_id is not None: source_type = "volume" source_uuid = source_volume_id elif source_snapshot_id is not None: source_type = "snapshot" source_uuid = source_snapshot_id elif source_image_id is not None: source_type = "image" source_uuid = source_image_id else: source_type = "blank" source_uuid = None if project_id is None: project_id = user_id if metadata is not None and \ len(metadata) > settings.CYCLADES_VOLUME_MAX_METADATA: raise faults.BadRequest("Volumes cannot have more than %s metadata " "items" % settings.CYCLADES_VOLUME_MAX_METADATA) volume = _create_volume(user_id, project_id, size, source_type, source_uuid, volume_type=volume_type, name=name, description=description, index=None, shared_to_project=shared_to_project) if metadata is not None: for meta_key, meta_val in metadata.items(): utils.check_name_length(meta_key, VolumeMetadata.KEY_LENGTH, "Metadata key is too long") utils.check_name_length(meta_val, VolumeMetadata.VALUE_LENGTH, "Metadata value is too long") volume.metadata.create(key=meta_key, value=meta_val) return volume
def create_metadata_item(request, server_id, key): # Normal Response Code: 201 # Error Response Codes: computeFault (400, 500), # serviceUnavailable (503), # unauthorized (401), # itemNotFound (404), # badRequest (400), # buildInProgress (409), # badMediaType(415), # overLimit (413) req = utils.get_json_body(request) credentials = request.credentials userid = credentials.userid log.debug("User: %s, VM: %s, Action: create_metadata, Request: %s", userid, server_id, req) vm = util.get_vm(server_id, credentials, non_suspended=True, non_deleted=True) try: metadict = req['meta'] assert isinstance(metadict, dict) assert len(metadict) == 1 assert key in metadict except (KeyError, AssertionError): raise faults.BadRequest("Malformed request") value = metadict[key] # Check key, value length if len(key) > VirtualMachineMetadata.KEY_LENGTH: raise faults.BadRequest("Malformed Request. Metadata key is too long") if len(value) > VirtualMachineMetadata.VALUE_LENGTH: raise faults.BadRequest("Malformed Request. Metadata value is too" " long") # Check number of metadata items if vm.metadata.exclude(meta_key=key).count() == \ settings.CYCLADES_VM_MAX_METADATA: raise faults.BadRequest("Virtual Machines cannot have more than %s" " metadata items" % settings.CYCLADES_VM_MAX_METADATA) meta, created = VirtualMachineMetadata.objects.get_or_create(meta_key=key, vm=vm) meta.meta_value = value meta.save() vm.save() d = {meta.meta_key: meta.meta_value} return util.render_meta(request, d, status=201)
def update_volume_metadata(request, volume_id, reset=False): req = utils.get_json_body(request) log.debug("User: %s, Volume: %s Action: update_metadata, Request: %s", request.user_uniq, volume_id, req) meta_dict = utils.get_attribute(req, "metadata", required=True, attr_type=dict) for key, value in meta_dict.items(): check_name_length(key, VolumeMetadata.KEY_LENGTH, "Metadata key is too long.") check_name_length(value, VolumeMetadata.VALUE_LENGTH, "Metadata value is too long.") volume = util.get_volume(request.user_uniq, request.user_projects, volume_id, for_update=True, non_deleted=True) if reset: if len(meta_dict) > settings.CYCLADES_VOLUME_MAX_METADATA: raise faults.BadRequest("Volumes cannot have more than %s metadata" " items" % settings.CYCLADES_VOLUME_MAX_METADATA) volume.metadata.all().delete() for key, value in meta_dict.items(): volume.metadata.create(key=key, value=value) else: if len(meta_dict) + len(volume.metadata.all()) - \ len(volume.metadata.all().filter(key__in=meta_dict.keys())) > \ settings.CYCLADES_VOLUME_MAX_METADATA: raise faults.BadRequest("Volumes cannot have more than %s metadata" " items" % settings.CYCLADES_VOLUME_MAX_METADATA) for key, value in meta_dict.items(): try: # Update existing metadata meta = volume.metadata.get(key=key) meta.value = value meta.save() except VolumeMetadata.DoesNotExist: # Or create a new one volume.metadata.create(key=key, value=value) log.info("User %s updated metadata for volume %s", request.user_uniq, volume.id) metadata = volume.metadata.values_list('key', 'value') data = json.dumps({"metadata": dict(metadata)}) return HttpResponse(data, content_type="application/json", status=200)
def list_images(request, detail=False): """Return a list of available images. This includes images owned by the user, images shared with the user and public images. """ def get_request_params(keys): params = {} for key in keys: val = request.GET.get(key, None) if val is not None: params[key] = val return params log.debug('list_public_images detail=%s', detail) filters = get_request_params(FILTERS) params = get_request_params(PARAMS) params.setdefault('sort_key', 'created_at') params.setdefault('sort_dir', 'desc') if not params['sort_key'] in SORT_KEY_OPTIONS: raise faults.BadRequest("Invalid 'sort_key'") if not params['sort_dir'] in SORT_DIR_OPTIONS: raise faults.BadRequest("Invalid 'sort_dir'") if 'size_max' in filters: try: filters['size_max'] = int(filters['size_max']) except ValueError: raise faults.BadRequest("Malformed request.") if 'size_min' in filters: try: filters['size_min'] = int(filters['size_min']) except ValueError: raise faults.BadRequest("Malformed request.") with PlanktonBackend(request.user_uniq) as backend: images = backend.list_images(filters, params) # Remove keys that should not be returned fields = DETAIL_FIELDS if detail else LIST_FIELDS for image in images: for key in image.keys(): if key not in fields: del image[key] data = json.dumps(images, indent=settings.DEBUG) return HttpResponse(data)