def read(self, request, system_id, id): """View a specific set of results. id can either by the script set id, current-commissioning, current-testing, or current-installation. :param hardware_type: Only return scripts for the given hardware type. Can be node, cpu, memory, or storage. Defaults to all. :type script_type: unicode :param include_output: Include base64 encoded output from the script. :type include_output: bool :param filters: A comma seperated list to show only results that ran with a script name, tag, or id. :type filters: unicode """ script_set = self._get_script_set(request, system_id, id) include_output = get_optional_param(request.GET, 'include_output', False, Bool) filters = get_optional_param(request.GET, 'filters', None, String) if filters is not None: filters = filters.split(',') hardware_type = get_optional_param(request.GET, 'hardware_type') if hardware_type is not None: try: hardware_type = translate_hardware_type(hardware_type) except ValidationError as e: raise MAASAPIValidationError(e) script_set.include_output = include_output script_set.filters = filters script_set.hardware_type = hardware_type return script_set
def statistics(self, request, id): """@description-title Get subnet statistics @description Returns statistics for the specified subnet, including: - **num_available**: the number of available IP addresses - **largest_available**: the largest number of contiguous free IP addresses - **num_unavailable**: the number of unavailable IP addresses - **total_addresses**: the sum of the available plus unavailable addresses - **usage**: the (floating point) usage percentage of this subnet - **usage_string**: the (formatted unicode) usage percentage of this subnet - **ranges**: the specific IP ranges present in ths subnet (if specified) Note: to supply additional optional parameters for this request, add them to the request URI: e.g. ``/subnets/1/?op=statistics&include_suggestions=1`` @param (int) "{id}" [required=true] A subnet ID. @param (int) "include_ranges" [required=false] If '1', includes detailed information about the usage of this range. '1' == True, '0' == False. @param (int) "include_suggestions" [required=false] If '1', includes the suggested gateway and dynamic range for this subnet, if it were to be configured. '1' == True, '0' == False. @success (http-status-code) "server-success" 200 @success (json) "success-json" A JSON object containing the statistics. @success-example "success-json" [exkey=subnets-statistics] placeholder text @error (http-status-code) "404" 404 @error (content) "not-found" The requested subnet is not found. @error-example "not-found" Not Found """ subnet = Subnet.objects.get_subnet_or_404( id, request.user, NodePermission.view ) include_ranges = get_optional_param( request.GET, "include_ranges", default=False, validator=StringBool ) include_suggestions = get_optional_param( request.GET, "include_suggestions", default=False, validator=StringBool, ) full_iprange = subnet.get_iprange_usage() statistics = IPRangeStatistics(full_iprange) return statistics.render_json( include_ranges=include_ranges, include_suggestions=include_suggestions, )
def read(self, request): """Return a list of stored scripts. :param type: Only return scripts with the given type. This can be testing or commissioning. Defaults to showing both. :type type: unicode :param hardware_type: Only return scripts for the given hardware type. Can be node, cpu, memory, or storage. Defaults to all. :type hardware_type: unicode :param include_script: Include the base64 encoded script content. :type include_script: bool :param filters: A comma seperated list to show only results with a script name or tag. :type filters: unicode """ qs = Script.objects.all() script_type = get_optional_param(request.GET, 'type') if script_type is not None: try: script_type = translate_script_type(script_type) except ValidationError as e: raise MAASAPIValidationError(e) else: qs = qs.filter(script_type=script_type) hardware_type = get_optional_param(request.GET, 'hardware_type') if hardware_type is not None: try: hardware_type = translate_hardware_type(hardware_type) except ValidationError as e: raise MAASAPIValidationError(e) else: qs = qs.filter(hardware_type=hardware_type) include_script = get_optional_param(request.GET, 'include_script', False, Bool) filters = get_optional_param(request.GET, 'filters', None, String) if filters is not None: filters = set(filters.split(',')) ret = [] for script in qs: if (filters is not None and script.name not in filters and filters.isdisjoint(script.tags)): continue else: script.include_script = include_script ret.append(script) return ret
def read(self, request, system_id): """Return a list of script results grouped by run. :param type: Only return scripts with the given type. This can be commissioning, testing, or installion. Defaults to showing all. :type type: unicode :param hardware_type: Only return scripts for the given hardware type. Can be node, cpu, memory, or storage. Defaults to all. :type script_type: unicode :param include_output: Include base64 encoded output from the script. :type include_output: bool :param filters: A comma seperated list to show only results with a script name or tag. :type filters: unicode """ node = Node.objects.get_node_or_404(system_id=system_id, user=request.user, perm=NODE_PERMISSION.VIEW) result_type = get_optional_param(request.GET, 'type') include_output = get_optional_param(request.GET, 'include_output', False, Bool) filters = get_optional_param(request.GET, 'filters', None, String) if filters is not None: filters = filters.split(',') if result_type is not None: try: result_type = translate_result_type(result_type) except ValidationError as e: raise MAASAPIValidationError(e) else: qs = ScriptSet.objects.filter(node=node, result_type=result_type) else: qs = ScriptSet.objects.filter(node=node) hardware_type = get_optional_param(request.GET, 'hardware_type') if hardware_type is not None: try: hardware_type = translate_hardware_type(hardware_type) except ValidationError as e: raise MAASAPIValidationError(e) ret = [] for script_set in qs: script_set.include_output = include_output script_set.filters = filters script_set.hardware_type = hardware_type ret.append(script_set) return ret
def read(self, request, system_id, id): """@description-title Get specific script result @description View a set of test results for a given system_id and script id. "id" can either by the script set id, ``current-commissioning``, ``current-testing``, or ``current-installation``. @param (string) "{system_id}" [required=true] The machine's system_id. @param (string) "{id}" [required=true] The script result id. @param (string) "hardware_type" [required=false] Only return scripts for the given hardware type. Can be ``node``, ``cpu``, ``memory``, or ``storage``. Defaults to all. @param (string) "include_output" [required=false] Include the base64 encoded output from the script if any value for include_output is given. @param (string) "filters" [required=false] A comma seperated list to show only results that ran with a script name, tag, or id. @success (http-status-code) "server-success" 200 @success (json) "success-json" A JSON object containing the requested script result object. @success-example "success-json" [exkey=script-results-read-by-id] placeholder text @error (http-status-code) "404" 404 @error (content) "not-found" The requested machine or script result is not found. @error-example "not-found" Not Found """ script_set = self._get_script_set(request, system_id, id) include_output = get_optional_param(request.GET, "include_output", False, Bool) filters = get_optional_param(request.GET, "filters", None, String) if filters is not None: filters = filters.split(",") hardware_type = get_optional_param(request.GET, "hardware_type") if hardware_type is not None: try: hardware_type = translate_hardware_type(hardware_type) except ValidationError as e: raise MAASAPIValidationError(e) script_set.include_output = include_output script_set.filters = filters script_set.hardware_type = hardware_type return script_set
def _store_results(self, node, script_set, request, status): """Store uploaded results.""" # Group files together with the ScriptResult they belong. results = {} for script_name, uploaded_file in request.FILES.items(): content = uploaded_file.read() process_file(results, script_set, script_name, content, request.POST) # Commit results to the database. for script_result, args in results.items(): script_result.store_result(**args, timedout=( status == SIGNAL_STATUS.TIMEDOUT)) script_set.last_ping = datetime.now() script_set.save() if status == SIGNAL_STATUS.WORKING: script_result_id = get_optional_param(request.POST, 'script_result_id', None, Int) if script_result_id is not None: script_result = script_set.find_script_result(script_result_id) # Only update the script status if it was in a pending state # incase the script result has been uploaded and proceeded # already. if script_result.status == SCRIPT_STATUS.PENDING: script_result.status = SCRIPT_STATUS.RUNNING script_result.save()
def create(self, request): """Add a new SSH key to the requesting or supplied user's account. The request payload should contain the public SSH key data in form data whose name is "key". """ user = request.user username = get_optional_param(request.POST, 'user') if username is not None and request.user.is_superuser: supplied_user = get_one(User.objects.filter(username=username)) if supplied_user is not None: user = supplied_user else: # Raise an error so that the user can know that their # attempt at specifying a user did not work. raise MAASAPIValidationError( "Supplied username does not match any current users.") elif username is not None and not request.user.is_superuser: raise MAASAPIValidationError( "Only administrators can specify a user" " when creating an SSH key.") form = SSHKeyForm(user=user, data=request.data) if form.is_valid(): sshkey = form.save(ENDPOINT.API, request) emitter = JSONEmitter(sshkey, typemapper, None, DISPLAY_SSHKEY_FIELDS) stream = emitter.render(request) return HttpResponse(stream, content_type='application/json; charset=utf-8', status=int(http.client.CREATED)) else: raise MAASAPIValidationError(form.errors)
def read(self, request): """List all resources for the specified criteria. :param domain: restrict the listing to entries for the domain. :param name: restrict the listing to entries of the given name. :param rrtype: restrict the listing to entries which have records of the given rrtype. :param all: if True, also include implicit DNS records created for nodes registered in MAAS. """ data = request.GET fqdn = data.get('fqdn', None) name = data.get('name', None) domainname = data.get('domain', None) rrtype = data.get('rrtype', None) if rrtype is not None: rrtype = rrtype.upper() _all = get_optional_param(request.GET, 'all', default=False, validator=StringBool) if domainname is None and name is None and fqdn is not None: # We need a type for resource separation. If the user didn't give # us a rrtype, then assume it's an address of some sort. (name, domainname) = separate_fqdn(fqdn, rrtype) user = request.user return get_dnsresource_queryset(_all, domainname, name, rrtype, user)
def delete(self, request, system_id): """Delete a specific rack controller. A rack controller cannot be deleted if it is set to `primary_rack` on a `VLAN` and another rack controller cannot be used to provide DHCP for said VLAN. Use `force` to override this behavior. Using `force` will also allow deleting a rack controller that is hosting pod virtual machines. (The pod will also be deleted.) Rack controllers that are also region controllers will be converted to a region controller (and hosted pods will not be affected). :param force: Always delete the rack controller even if its the `primary_rack` on a `VLAN` and another rack controller cannot provide DHCP on said VLAN. This will disable DHCP on those VLANs. :type force: boolean Returns 404 if the node is not found. Returns 403 if the user does not have permission to delete the node. Returns 400 if the node cannot be deleted. Returns 204 if the node is successfully deleted. """ node = self.model.objects.get_node_or_404(system_id=system_id, user=request.user, perm=NodePermission.admin) node.as_self().delete( force=get_optional_param(request.GET, 'force', False, StringBool)) return rc.DELETED
def create_authorisation_token(self, request): """Create an authorisation OAuth token and OAuth consumer. :param name: Optional name of the token that will be generated. :type name: unicode :return: a json dict with four keys: 'token_key', 'token_secret', 'consumer_key' and 'name'(e.g. {token_key: 's65244576fgqs', token_secret: 'qsdfdhv34', consumer_key: '68543fhj854fg', name: 'MAAS consumer'}). :rtype: string (json) """ profile = request.user.userprofile consumer_name = get_optional_param(request.data, 'name') consumer, token = profile.create_authorisation_token(consumer_name) create_audit_event( EVENT_TYPES.AUTHORISATION, ENDPOINT.API, request, None, "Created token.") auth_info = { 'token_key': token.key, 'token_secret': token.secret, 'consumer_key': consumer.key, 'name': consumer.name } return HttpResponse( json.dumps(auth_info), content_type='application/json; charset=utf-8', status=int(http.client.OK))
def create_authorisation_token(self, request): """@description-title Create an authorisation token @description Create an authorisation OAuth token and OAuth consumer. @param (string) "name" [required=false] Optional name of the token that will be generated. @success (http-status-code) "200" 200 @success (json) "success-json" A JSON object containing: ``token_key``, ``token_secret``, ``consumer_key``, and ``name``. @success-example "success-json" [exkey=account-create-token] placeholder text """ profile = request.user.userprofile consumer_name = get_optional_param(request.data, "name") consumer, token = profile.create_authorisation_token(consumer_name) create_audit_event( EVENT_TYPES.AUTHORISATION, ENDPOINT.API, request, None, "Created token.", ) auth_info = { "token_key": token.key, "token_secret": token.secret, "consumer_key": consumer.key, "name": consumer.name, } return HttpResponse( json.dumps(auth_info), content_type="application/json; charset=utf-8", status=int(http.client.OK), )
def read(self, request, name): """@description-title Return script metadata @description Return metadata belonging to the script with the given name. @param (string) "{name}" [required=true] The script's name. @param (string) "include_script" [required=false] Include the base64 encoded script content if any value is given for include_script. @success (http-status-code) "server-success" 200 @success (json) "success-json" A JSON object containing information about the script. @success-example "success-json" [exkey=scripts-read-by-name] placeholder text @error (http-status-code) "404" 404 @error (content) "not-found" The requested script is not found. @error-example "not-found" Not Found """ if name.isdigit(): script = get_object_or_404(Script, id=int(name)) else: script = get_object_or_404(Script, name=name) script.include_script = get_optional_param(request.GET, 'include_script', False, Bool) return script
def power_off(self, request, system_id): """Power off a node. :param stop_mode: An optional power off mode. If 'soft', perform a soft power down if the node's power type supports it, otherwise perform a hard power off. For all values other than 'soft', and by default, perform a hard power off. A soft power off generally asks the OS to shutdown the system gracefully before powering off, while a hard power off occurs immediately without any warning to the OS. :type stop_mode: unicode :param comment: Optional comment for the event log. :type comment: unicode Returns 404 if the node is not found. Returns 403 if the user does not have permission to stop the node. """ stop_mode = request.POST.get('stop_mode', 'hard') comment = get_optional_param(request.POST, 'comment') node = self.model.objects.get_node_or_404( system_id=system_id, user=request.user, perm=NodePermission.edit) power_action_sent = node.stop( request.user, stop_mode=stop_mode, comment=comment) if power_action_sent: return node else: return None
def ip_addresses(self, request, id): """@description-title Summary of IP addresses @description Returns a summary of IP addresses assigned to this subnet. @param (int) "{id}" [required=true] A subnet ID. @param (int) "with_username" [required=false] If '0', suppresses the display of usernames associated with each address. '1' == True, '0' == False. (Default: '1') @param (int) "with_summary" [required=false] If '0', suppresses the display of nodes, BMCs, and and DNS records associated with each address. '1' == True, '0' == False. (Default: True) @param (int) "with_node_summary" [required=false] Deprecated. Use 'with_summary'. @success (http-status-code) "server-success" 200 @success (json) "success-json" A JSON object containing a list of IP addresses and information about each. @success-example "success-json" [exkey=subnets-ip-addresses] placeholder text @error (http-status-code) "404" 404 @error (content) "not-found" The requested subnet is not found. @error-example "not-found" Not Found """ subnet = Subnet.objects.get_subnet_or_404(id, request.user, NodePermission.view) with_username = get_optional_param(request.GET, 'with_username', default=True, validator=StringBool) with_summary = get_optional_param(request.GET, 'with_summary', True, validator=StringBool) with_node_summary = get_optional_param(request.GET, 'with_node_summary', True, validator=StringBool) # Handle deprecated with_node_summary parameter. if with_node_summary is False: with_summary = False return subnet.render_json_for_related_ips(with_username=with_username, with_summary=with_summary)
def clear(self, request, **kwargs): """@description-title Delete all discovered neighbours @description Deletes all discovered neighbours and/or mDNS entries. Note: One of ``mdns``, ``neighbours``, or ``all`` parameters must be supplied. @param (boolean) "mdns" [required=false] Delete all mDNS entries. @param (boolean) "neighbours" [required=false] Delete all neighbour entries. @param (boolean) "all" [required=false] Delete all discovery data. @success (http-status-code) "server-success" 204 """ all = get_optional_param(request.POST, "all", default=False, validator=StringBool) mdns = get_optional_param(request.POST, "mdns", default=False, validator=StringBool) neighbours = get_optional_param(request.POST, "neighbours", default=False, validator=StringBool) if not request.user.has_perm(NodePermission.admin, Discovery): response = HttpResponseForbidden( content_type="text/plain", content="Must be an administrator to clear discovery entries.", ) return response if True not in (mdns, neighbours, all): content = dedent("""\ Bad request: could not determine what data to clear. Must specify mdns=True, neighbours=True, or all=True.""") response = HttpResponseBadRequest(content_type="text/plain", content=content) return response Discovery.objects.clear(user=request.user, all=all, mdns=mdns, neighbours=neighbours) return rc.DELETED
def read(self, request, system_id): """Return a list of script results grouped by run. :param type: Only return scripts with the given type. This can be commissioning, testing, or installion. Defaults to showing all. :type type: unicode :param include_output: Include base64 encoded output from the script. :type include_output: bool :param filters: A comma seperated list to show only results with a script name or tag. :type filters: unicode """ node = Node.objects.get_node_or_404(system_id=system_id, user=request.user, perm=NODE_PERMISSION.VIEW) result_type = get_optional_param(request.GET, 'type') include_output = get_optional_param(request.GET, 'include_output', False, Bool) filters = get_optional_param(request.GET, 'filters', None, String) if filters is not None: filters = filters.split(',') if result_type is not None: if result_type.isdigit(): result_type = int(result_type) elif result_type in ['test', 'testing']: result_type = RESULT_TYPE.TESTING elif result_type in ['commission', 'commissioning']: result_type = RESULT_TYPE.COMMISSIONING elif result_type in ['install', 'installation']: result_type = RESULT_TYPE.INSTALLATION else: raise MAASAPIValidationError( 'Unknown type "%s": type must be the type numeric value ' 'testing, commissioning, or installation.') qs = ScriptSet.objects.filter(node=node, result_type=result_type) else: qs = ScriptSet.objects.filter(node=node) ret = [] for script_set in qs: script_set.include_output = include_output script_set.filters = filters ret.append(script_set) return ret
def read(self, request): """Return a list of stored scripts. :param type: Only return scripts with the given type. This can be testing or commissioning. Defaults to showing both. :type script_type: unicode :param include_script: Include the base64 encoded script content. :type include_script: bool :param filters: A comma seperated list to show only results with a script name or tag. :type filters: unicode """ qs = Script.objects.all() script_type = get_optional_param(request.GET, 'type') if script_type is not None: if script_type.isdigit(): script_type = int(script_type) elif script_type in ['test', 'testing']: script_type = SCRIPT_TYPE.TESTING elif script_type in ['commission', 'commissioning']: script_type = SCRIPT_TYPE.COMMISSIONING else: raise MAASAPIValidationError('Unknown script type') qs = qs.filter(script_type=script_type) include_script = get_optional_param(request.GET, 'include_script', False, Bool) filters = get_optional_param(request.GET, 'filters', None, String) if filters is not None: filters = set(filters.split(',')) ret = [] for script in qs: if (filters is not None and script.name not in filters and filters.isdisjoint(script.tags)): continue else: script.include_script = include_script ret.append(script) return ret
def create(self, request): """@description-title Add a new SSH key @description Add a new SSH key to the requesting or supplied user's account. @param (string) "key" [required=true,formatting=true] A public SSH key should be provided in the request payload as form data with the name 'key': key: "key-type public-key-data" - ``key-type``: ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, ssh-dss, ssh-ed25519, ssh-rsa - ``public key data``: Base64-encoded key data. @success (http-status-code) "201" 201 @success (json) "success-json" A JSON object containing the new key. @success-example "success-json" [exkey=ssh-keys-create] placeholder text """ user = request.user username = get_optional_param(request.POST, "user") if username is not None and request.user.is_superuser: supplied_user = get_one(User.objects.filter(username=username)) if supplied_user is not None: user = supplied_user else: # Raise an error so that the user can know that their # attempt at specifying a user did not work. raise MAASAPIValidationError( "Supplied username does not match any current users." ) elif username is not None and not request.user.is_superuser: raise MAASAPIValidationError( "Only administrators can specify a user" " when creating an SSH key." ) form = SSHKeyForm(user=user, data=request.data) if form.is_valid(): sshkey = form.save(ENDPOINT.API, request) emitter = JSONEmitter( sshkey, typemapper, None, DISPLAY_SSHKEY_FIELDS ) stream = emitter.render(request) return HttpResponse( stream, content_type="application/json; charset=utf-8", status=int(http.client.CREATED), ) else: raise MAASAPIValidationError(form.errors)
def statistics(self, request, id): """\ Returns statistics for the specified subnet, including: num_available: the number of available IP addresses largest_available: the largest number of contiguous free IP addresses num_unavailable: the number of unavailable IP addresses total_addresses: the sum of the available plus unavailable addresses usage: the (floating point) usage percentage of this subnet usage_string: the (formatted unicode) usage percentage of this subnet ranges: the specific IP ranges present in ths subnet (if specified) Optional parameters ------------------- include_ranges If True, includes detailed information about the usage of this range. include_suggestions If True, includes the suggested gateway and dynamic range for this subnet, if it were to be configured. Returns 404 if the subnet is not found. """ subnet = Subnet.objects.get_subnet_or_404(id, request.user, NodePermission.view) include_ranges = get_optional_param(request.GET, 'include_ranges', default=False, validator=StringBool) include_suggestions = get_optional_param(request.GET, 'include_suggestions', default=False, validator=StringBool) full_iprange = subnet.get_iprange_usage() statistics = IPRangeStatistics(full_iprange) return statistics.render_json(include_ranges=include_ranges, include_suggestions=include_suggestions)
def read(self, request, system_id, id): """View a specific set of results. id can either by the script set id, current-commissioning, current-testing, or current-installation. :param include_output: Include base64 encoded output from the script. :type include_output: bool :param filters: A comma seperated list to show only results that ran with a script name, tag, or id. :type filters: unicode """ script_set = self._get_script_set(request, system_id, id) include_output = get_optional_param(request.GET, 'include_output', False, Bool) filters = get_optional_param(request.GET, 'filters', None, String) if filters is not None: filters = filters.split(',') script_set.include_output = include_output script_set.filters = filters return script_set
def download(self, request, name): """Download a script. :param revision: What revision to download, latest by default. Can use rev as a shortcut. :type revision: integer """ if name.isdigit(): script = get_object_or_404(Script, id=int(name)) else: script = get_object_or_404(Script, name=name) revision = get_optional_param(request.GET, 'revision', None, Int) if revision is None: revision = get_optional_param(request.GET, 'rev', None, Int) if revision is not None: for rev in script.script.previous_versions(): if rev.id == revision: return HttpResponse(rev.data, content_type='application/binary') raise MAASAPIValidationError("%s not found in history" % revision) else: return HttpResponse(script.script.data, content_type='application/binary')
def read(self, request, name): """Return a script's metadata. :param include_script: Include the base64 encoded script content. :type include_script: bool """ if name.isdigit(): script = get_object_or_404(Script, id=int(name)) else: script = get_object_or_404(Script, name=name) script.include_script = get_optional_param(request.GET, 'include_script', False, Bool) return script
def ip_addresses(self, request, id): """\ Returns a summary of IP addresses assigned to this subnet. Optional parameters ------------------- with_username If False, suppresses the display of usernames associated with each address. (Default: True) with_summary If False, suppresses the display of nodes, BMCs, and and DNS records associated with each address. (Default: True) with_node_summary Deprecated form of with_summary. """ subnet = Subnet.objects.get_subnet_or_404(id, request.user, NodePermission.view) with_username = get_optional_param(request.GET, 'with_username', default=True, validator=StringBool) with_summary = get_optional_param(request.GET, 'with_summary', True, validator=StringBool) with_node_summary = get_optional_param(request.GET, 'with_node_summary', True, validator=StringBool) # Handle deprecated with_node_summary parameter. if with_node_summary is False: with_summary = False return subnet.render_json_for_related_ips(with_username=with_username, with_summary=with_summary)
def clear(self, request, **kwargs): """Deletes all discovered neighbours and/or mDNS entries. :param mdns: if True, deletes all mDNS entries. :param neighbours: if True, deletes all neighbour entries. :param all: if True, deletes all discovery data. """ all = get_optional_param(request.POST, 'all', default=False, validator=StringBool) mdns = get_optional_param(request.POST, 'mdns', default=False, validator=StringBool) neighbours = get_optional_param(request.POST, 'neighbours', default=False, validator=StringBool) if not request.user.has_perm(NodePermission.admin, Discovery): response = HttpResponseForbidden( content_type='text/plain', content="Must be an administrator to clear discovery entries.") return response if True not in (mdns, neighbours, all): content = dedent("""\ Bad request: could not determine what data to clear. Must specify mdns=True, neighbours=True, or all=True.""") response = HttpResponseBadRequest(content_type='text/plain', content=content) return response Discovery.objects.clear(user=request.user, all=all, mdns=mdns, neighbours=neighbours) return rc.DELETED
def power_on(self, request, system_id): """@description-title Turn on a node @description Turn on the given node with optional user-data and comment. @param (string) "user_data" [required=false] Base64-encoded blob of data to be made available to the nodes through the metadata service. @param (string) "comment" [required=false] Comment for the event log. @success (http-status-code) "204" 204 @success (json) "success_json" A JSON object containing the node's information. @success-example "success_json" [exkey=power-on] placeholder text @error (http-status-code) "404" 404 @error (content) "not-found" The requested node is not found. @error-example "not-found" Not Found @error (http-status-code) "403" 403 @error (content) "no-perms" The user is not authorized to power on the node. @error (http-status-code) "503" 503 @error (content) "no-ips" Returns 503 if the start-up attempted to allocate an IP address, and there were no IP addresses available on the relevant cluster interface. """ user_data = request.POST.get('user_data', None) comment = get_optional_param(request.POST, 'comment') node = self.model.objects.get_node_or_404( system_id=system_id, user=request.user, perm=NodePermission.edit) if node.owner is None and node.node_type != NODE_TYPE.RACK_CONTROLLER: raise NodeStateViolation( "Can't start node: it hasn't been allocated.") if user_data is not None: user_data = b64decode(user_data) try: node.start(request.user, user_data=user_data, comment=comment) except StaticIPAddressExhaustion: # The API response should contain error text with the # system_id in it, as that is the primary API key to a node. raise StaticIPAddressExhaustion( "%s: Unable to allocate static IP due to address" " exhaustion." % system_id) return node
def download(self, request, name): """@description-title Download a script @description Download a script with the given name. @param (string) "{name}" [required=true] The name of the script. @param (int) "revision" [required=false] What revision to download, latest by default. Can use rev as a shortcut. @success (http-status-code) "server-success" 200 @success (content) "success-text" A plain-text representation of the requested script. @success-example "success-text" [exkey=scripts-download] placeholder text @error (http-status-code) "404" 404 @error (content) "not-found" The requested script is not found. @error-example "not-found" Not Found """ if name.isdigit(): script = get_object_or_404(Script, id=int(name)) else: script = get_object_or_404(Script, name=name) revision = get_optional_param(request.GET, 'revision', None, Int) if revision is None: revision = get_optional_param(request.GET, 'rev', None, Int) if revision is not None: for rev in script.script.previous_versions(): if rev.id == revision: return HttpResponse(rev.data, content_type='application/binary') raise MAASAPIValidationError("%s not found in history" % revision) else: return HttpResponse(script.script.data, content_type='application/binary')
def override_failed_testing(self, request, system_id): """Ignore failed tests and put node back into a usable state. :param comment: Optional comment for the event log. :type comment: unicode Returns 404 if the machine is not found. Returns 403 if the user does not have permission to ignore tests for the node. """ comment = get_optional_param(request.POST, 'comment') node = self.model.objects.get_node_or_404( user=request.user, system_id=system_id, perm=NodePermission.admin) node.override_failed_testing(request.user, comment) return node
def abort(self, request, system_id): """Abort a node's current operation. :param comment: Optional comment for the event log. :type comment: unicode Returns 404 if the node could not be found. Returns 403 if the user does not have permission to abort the current operation. """ comment = get_optional_param(request.POST, 'comment') node = self.model.objects.get_node_or_404( system_id=system_id, user=request.user, perm=NodePermission.edit) node.abort_operation(request.user, comment) return node
def delete(self, request, system_id): """Delete a specific region controller. A region controller cannot be deleted if it hosts pod virtual machines. Use `force` to override this behavior. Forcing deletion will also remove hosted pods. Returns 404 if the node is not found. Returns 403 if the user does not have permission to delete the node. Returns 400 if the node cannot be deleted. Returns 204 if the node is successfully deleted. """ node = self.model.objects.get_node_or_404(system_id=system_id, user=request.user, perm=NodePermission.admin) node.as_self().delete( force=get_optional_param(request.GET, 'force', False, StringBool)) return rc.DELETED
def read(self, request): """@description-title List resources @description List all resources for the specified criteria. @param (string) "domain" [required=false] Restricts the listing to entries for the domain. @param (string) "name" [required=false] Restricts the listing to entries of the given name. @param (string) "rrtype" [required=false] Restricts the listing to entries which have records of the given rrtype. @param (boolean) "all" [required=false] Include implicit DNS records created for nodes registered in MAAS if true. @success (http-status-code) "server-success" 200 @success (json) "success-json" A JSON object containing a list of the requested DNS resource objects. @success-example "success-json" [exkey=dnsresources-read] placeholder text @error (http-status-code) "404" 404 @error (content) "not-found" The requested DNS resources are not found. @error-example "not-found" Not Found """ data = request.GET fqdn = data.get("fqdn", None) name = data.get("name", None) domainname = data.get("domain", None) rrtype = data.get("rrtype", None) if rrtype is not None: rrtype = rrtype.upper() _all = get_optional_param( request.GET, "all", default=False, validator=StringBool ) if domainname is None and name is None and fqdn is not None: # We need a type for resource separation. If the user didn't give # us a rrtype, then assume it's an address of some sort. (name, domainname) = separate_fqdn(fqdn, rrtype) user = request.user return get_dnsresource_queryset(_all, domainname, name, rrtype, user)
def mark_broken(self, request, system_id): """Mark a node as 'broken'. If the node is allocated, release it first. :param error_description: An optional description of the reason the node is being marked broken. :type error_description: unicode Returns 404 if the node is not found. Returns 403 if the user does not have permission to mark the node broken. """ node = Node.objects.get_node_or_404( user=request.user, system_id=system_id, perm=NODE_PERMISSION.EDIT) error_description = get_optional_param( request.POST, 'error_description', '') node.mark_broken(error_description) return node