Ejemplo n.º 1
0
    def is_registered(self, request):
        """Returns whether or not the given MAC address is registered within
        this MAAS (and attached to a non-retired node).

        :param mac_address: The mac address to be checked.
        :type mac_address: unicode
        :return: 'true' or 'false'.
        :rtype: unicode

        Returns 400 if any mandatory parameters are missing.
        """
        mac_address = get_mandatory_param(request.GET, 'mac_address')
        mac_addresses = MACAddress.objects.filter(mac_address=mac_address)
        mac_addresses = mac_addresses.exclude(node__status=NODE_STATUS.RETIRED)
        return mac_addresses.exists()
Ejemplo n.º 2
0
    def add_tag(self, request, name):
        """@description-title Add a tag
        @description Add a single tag to a script with the given name.

        @param (string) "{name}" [required=true] The name of the script.

        @param (string) "tag" [required=false] The tag being added.

        @success (http-status-code) "server-success" 200
        @success (json) "success-json" A JSON object containing information
        about the updated script.
        @success-example "success-json" [exkey=scripts-add-tag] placeholder
        text

        @error (http-status-code) "404" 404
        @error (content) "not-found" The requested script is not found.
        @error-example "not-found"
            Not Found
        """
        tag = get_mandatory_param(request.data, 'tag', String)

        if ',' in tag:
            raise MAASAPIValidationError('Tag may not contain a ",".')

        if name.isdigit():
            script = get_object_or_404(Script, id=int(name))
        else:
            script = get_object_or_404(Script, name=name)

        script.add_tag(tag)
        script.save()
        create_audit_event(EVENT_TYPES.SETTINGS,
                           ENDPOINT.API,
                           request,
                           None,
                           description=("Added tag '%s' to script '%s'." %
                                        (tag, script.name)))
        return script
Ejemplo n.º 3
0
    def get_config(self, request):
        """@description-title Get a configuration value
        @description Get a configuration value.

        @param (string) "name" [required=true,formatting=true] The name of the
        configuration item to be retrieved.

        %s

        @success (http-status-code) "server-success" 200
        @success (content) "default_distro_series" A plain-text string
        containing the requested value, e.g. ``default_distro_series``.
        @success-example "default_distro_series"
            "bionic"
        """
        name = get_mandatory_param(request.GET, "name")
        name = rewrite_config_name(name)
        if name in migrated_config_values:
            value = migrated_config_values[name].getter()
        else:
            validate_config_name(name)
            value = Config.objects.get_config(name)
        return HttpResponse(json.dumps(value), content_type="application/json")
Ejemplo n.º 4
0
    def remove_tag(self, request, system_id, id):
        """@description-title Remove a tag
        @description Remove a tag from block device on a given machine.

        @param (string) "{system_id}" [required=true] The machine system_id.
        @param (string) "{id}" [required=true] The block device's id.

        @param (string) "tag" [required=false] The tag being removed.

        @success (http-status-code) "server-success" 200
        @success (json) "success-json" A JSON object containing the updated
        block device.
        @success-example "success-json" [exkey=block-devs-remove-tag]
        placeholder text

        @error (http-status-code) "403" 403
        @error (content) "no-perms" The user does not have permissions to
        remove a tag.

        @error (http-status-code) "404" 404
        @error (content) "not-found" The requested machine or block device is
        not found.
        @error-example "not-found"
            Not Found

        @error (http-status-code) "409" 409
        @error (content) "not-ready" The requested machine is not ready.
        """
        device = BlockDevice.objects.get_block_device_or_404(
            system_id, id, request.user, NodePermission.admin)
        node = device.get_node()
        if node.status != NODE_STATUS.READY:
            raise NodeStateViolation(
                "Cannot update block device because the machine is not Ready.")
        device.remove_tag(get_mandatory_param(request.POST, "tag"))
        device.save()
        return device
Ejemplo n.º 5
0
    def remove_tag(self, request, name):
        """@description-title Remove a tag
        @description Remove a tag from a script with the given name.

        @param (string) "{name}" [required=true] The name of the script.

        @param (string) "tag" [required=false] The tag being removed.

        @success (http-status-code) "server-success" 200
        @success (json) "success-json" A JSON object containing information
        about the updated script.
        @success-example "success-json" [exkey=scripts-remove-tag] placeholder
        text

        @error (http-status-code) "404" 404
        @error (content) "not-found" The requested script is not found.
        @error-example "not-found"
            Not Found
        """
        tag = get_mandatory_param(request.data, "tag", String)

        if name.isdigit():
            script = get_object_or_404(Script, id=int(name))
        else:
            script = get_object_or_404(Script, name=name)

        script.remove_tag(tag)
        script.save()
        create_audit_event(
            EVENT_TYPES.SETTINGS,
            ENDPOINT.API,
            request,
            None,
            description=("Removed tag '%s' from script '%s'." %
                         (tag, script.name)),
        )
        return script
Ejemplo n.º 6
0
    def remove_tag(self, request, system_id, id):
        """@description-title Remove a tag from an interface
        @description Remove a tag from an interface with the given system_id
        and interface id.

        @param (string) "{system_id}" [required=true] A system_id.

        @param (int) "{id}" [required=true] An interface id.

        @param (string) "tag" [required=false] The tag to remove.

        @success (http-status-code) "server-success" 200
        @success (json) "success-json" A JSON object containing the updated
        interface object.
        @success-example "success-json" [exkey=interfaces-remove-tag]
        placeholder text

        @error (http-status-code) "403" 403
        @error (content) "403" If the user does not have the permission to add
        a tag.

        @error (http-status-code) "404" 404
        @error (content) "not-found" The requested machine or interface is not
        found.
        @error-example "not-found"
            Not Found
        """
        interface = Interface.objects.get_interface_or_404(
            system_id,
            id,
            request.user,
            NodePermission.admin,
            NodePermission.edit,
        )
        interface.remove_tag(get_mandatory_param(request.POST, "tag"))
        interface.save()
        return interface
Ejemplo n.º 7
0
    def remove_tag(self, request, name):
        """Remove a single tag to a script.

        :param tag: The tag being removed.
        :type tag: unicode

        Returns 404 if the script is not found.
        """
        tag = get_mandatory_param(request.data, 'tag', String)

        if name.isdigit():
            script = get_object_or_404(Script, id=int(name))
        else:
            script = get_object_or_404(Script, name=name)

        script.remove_tag(tag)
        script.save()
        create_audit_event(EVENT_TYPES.SETTINGS,
                           ENDPOINT.API,
                           request,
                           None,
                           description=("Removed tag '%s' from script '%s'." %
                                        (tag, script.name)))
        return script
Ejemplo n.º 8
0
    def delete(self, request, **kwargs):
        """@description-title Delete a file
        @description Delete a stored file.

        @param (string) "filename" [required=true] The filename of the object
        to be deleted.

        @success (http-status-code) "server-success" 204

        @error (http-status-code) "404" 404
        @error (content) "not-found" The requested file is not found.
        @error-example "not-found"
            Not Found
        """
        # It is valid for a path in a POST, PUT, or DELETE (or any other HTTP
        # method) to contain a query string. However, Django only makes
        # parameters from the query string available in the badly named
        # request.GET object.
        filename = get_mandatory_param(request.GET, "filename")
        stored_file = get_object_or_404(FileStorage,
                                        filename=filename,
                                        owner=request.user)
        stored_file.delete()
        return rc.DELETED
Ejemplo n.º 9
0
    def revert(self, request, name):
        """Revert a script to an earlier version.

        :param to: What revision in the script's history to revert to. This can
            either be an ID or a negative number representing how far back to
            go.
        :type to: integer

        Returns 404 if the script is not found.
        """
        revert_to = get_mandatory_param(request.data, 'to', Int)

        if name.isdigit():
            script = get_object_or_404(Script, id=int(name))
        else:
            script = get_object_or_404(Script, name=name)
        try:
            if script.default:
                raise MAASAPIValidationError("Unable to revert default script")

            def gc_hook(value):
                script.script = value
                script.save()

            script.script.revert(revert_to, gc_hook=gc_hook)
            create_audit_event(
                EVENT_TYPES.SETTINGS,
                ENDPOINT.API,
                request,
                None,
                description=("Script %s" % script.name +
                             " reverted to revision %s" % revert_to +
                             " for '%(username)s'."))
            return script
        except ValueError as e:
            raise MAASAPIValidationError(e.args[0])
Ejemplo n.º 10
0
Archivo: api.py Proyecto: zhangrb/maas
    def signal(self, request, version=None, mac=None):
        """Signal ephemeral environment status.

        A node booted into an ephemeral environment can call this to report
        progress of any scripts given to it by MAAS.

        Calling this from a node in an invalid state is an error.  Signaling
        completion more than once is not an error; all but the first
        successful call are ignored.

        :param status: An ephemeral environment signal. This may be "OK" to
            signal completion, WORKING for progress reports, FAILED for,
            FAILURE, COMMISSIONING for requesting the node be put into
            COMMISSIONING when NEW, TESTING for requesting the node be put
            into TESTING from COMMISSIONING, TIMEDOUT if the script has run
            past its time limit, or INSTALLING for installing required
            packages.
        :param error: An optional error string. If given, this will be stored
            (overwriting any previous error string), and displayed in the MAAS
            UI. If not given, any previous error string will be cleared.
        :param script_result_id: What ScriptResult this signal is for. If the
            signal contains a file upload the id will be used to find the
            ScriptResult row. If the status is "WORKING" the ScriptResult
            status will be set to running.
        :param exit_status: The return code of the script run.
        """
        node = get_queried_node(request, for_mac=mac)
        status = get_mandatory_param(request.POST, 'status', String)
        target_status = None
        if (node.status not in self.signalable_states
                and node.node_type == NODE_TYPE.MACHINE):
            raise NodeStateViolation(
                "Machine status isn't valid (status is %s)" %
                NODE_STATUS_CHOICES_DICT[node.status])

        # These statuses are acceptable for commissioning, disk erasing,
        # entering rescue mode and deploying.
        if status not in [choice[0] for choice in SIGNAL_STATUS_CHOICES]:
            raise MAASAPIBadRequest("Unknown status: '%s'" % status)

        if (node.status not in self.effective_signalable_states
                and node.node_type == NODE_TYPE.MACHINE):
            # If commissioning, it is already registered.  Nothing to be done.
            # If it is installing, should be in deploying state.
            return rc.ALL_OK

        if node.node_type == NODE_TYPE.MACHINE:
            process_status_dict = {
                NODE_STATUS.NEW: self._process_new,
                NODE_STATUS.TESTING: self._process_testing,
                NODE_STATUS.COMMISSIONING: self._process_commissioning,
                NODE_STATUS.DEPLOYING: self._process_deploying,
                NODE_STATUS.DISK_ERASING: self._process_disk_erasing,
                NODE_STATUS.ENTERING_RESCUE_MODE:
                self._process_entering_rescue_mode,
                NODE_STATUS.RESCUE_MODE: self._process_rescue_mode,
            }
            process = process_status_dict[node.status]
        else:
            # Non-machine nodes can send testing results when in testing
            # state, otherwise accept all signals as commissioning signals
            # regardless of the node's state. This is because devices and
            # controllers which were not deployed by MAAS will be in a NEW
            # or other unknown state but may send commissioning data.
            if node.status == NODE_STATUS.TESTING:
                process = self._process_testing
            else:
                process = self._process_commissioning

        target_status = process(node, request, status)

        if target_status in (None, node.status):
            # No status change.  Nothing to be done.
            return rc.ALL_OK

        # Only machines can change their status. This is to allow controllers
        # to send refresh data without having their status changed to READY.
        # The exception to this is if testing was run.
        if (node.node_type == NODE_TYPE.MACHINE
                or node.status == NODE_STATUS.TESTING):
            node.status = target_status

        node.error = get_optional_param(request.POST, 'error', '', String)

        # Done.
        node.save()
        return rc.ALL_OK
Ejemplo n.º 11
0
def get_content_parameter(request):
    """Get the "content" parameter from a CommissioningScript POST or PUT."""
    content_file = get_mandatory_param(request.FILES, "content")
    return content_file.read()
Ejemplo n.º 12
0
    def release(self, request):
        """Release an IP address that was previously reserved by the user.

        :param ip: The IP address to release.
        :type ip: unicode

        :param force: If True, allows a MAAS administrator to force an IP
            address to be released, even if it is not a user-reserved IP
            address or does not belong to the requesting user. Use with
            caution.
        :type force: bool

        :param discovered: If True, allows a MAAS administrator to release
            a discovered address. Only valid if 'force' is specified. If not
            specified, MAAS will attempt to release any type of address except
            for discovered addresses.

        Returns 404 if the provided IP address is not found.
        """
        ip = get_mandatory_param(request.POST, "ip")
        force = get_optional_param(request.POST,
                                   'force',
                                   default=False,
                                   validator=StringBool)
        discovered = get_optional_param(request.POST,
                                        'discovered',
                                        default=False,
                                        validator=StringBool)

        if force is True and not request.user.is_superuser:
            return HttpResponseForbidden(
                content_type='text/plain',
                content="Force-releasing an IP address requires admin "
                "privileges.")

        form = ReleaseIPForm(request.POST)
        if not form.is_valid():
            raise MAASAPIValidationError(form.errors)

        if force:
            query = StaticIPAddress.objects.filter(ip=ip)
            if discovered:
                query = query.filter(alloc_type=IPADDRESS_TYPE.DISCOVERED)
            else:
                query = query.exclude(alloc_type=IPADDRESS_TYPE.DISCOVERED)
        else:
            query = StaticIPAddress.objects.filter(
                ip=ip,
                alloc_type=IPADDRESS_TYPE.USER_RESERVED,
                user=request.user)

        # Get the reserved IP address, or raise bad request.
        ip_address = query.first()
        if ip_address is None:
            if force:
                error = "IP address %s does not exist."
            else:
                error = (
                    "IP address %s does not exist, is not the correct type of "
                    "address, or does not belong to the requesting user.\n"
                    "If you are sure you want to release this address, use "
                    "force=true as a MAAS administrator.")
            raise MAASAPIBadRequest(error % ip) from None

        # Unlink the IP address from the interfaces it is connected.
        interfaces = list(ip_address.interface_set.all())
        if len(interfaces) > 0:
            for interface in interfaces:
                interface.unlink_ip_address(ip_address)
        else:
            ip_address.delete()

        # Delete any interfaces that no longer have any IP addresses and have
        # no associated nodes.
        for interface in interfaces:
            if interface.node is None and interface.only_has_link_up():
                interface.delete()

        maaslog.info("User %s%s released IP address: %s (%s).",
                     request.user.username, " forcibly" if force else "", ip,
                     ip_address.alloc_type_name)

        return rc.DELETED
Ejemplo n.º 13
0
    def create(self, request):
        """@description-title Create a MAAS user account
        @description Creates a MAAS user account.

        This is not safe: the password is sent in plaintext.  Avoid it for
        production, unless you are confident that you can prevent eavesdroppers
        from observing the request.

        @param (string) "username" [required=true] Identifier-style username
        for the new user.

        @param (string) "email" [required=true] Email address for the new user.

        @param (string) "password" [required=true] Password for the new user.

        @param (boolean) "is_superuser" [required=true] Whether the new user is
        to be an administrator. ('0' == False, '1' == True)

        @success (http-status-code) "200" 200
        @success (json) "success-json" A JSON object containing information
        about the new user.
        @success-example "success-json" [exkey=create] placeholder text

        @error (http-status-code) "400" 400
        @error (content) "error-content" Mandatory parameters are missing.
        @error-example "error-content"
            No provided username!
        """
        username = get_mandatory_param(request.data, "username")
        email = get_mandatory_param(request.data, "email")
        if request.external_auth_info:
            password = request.data.get("password")
        else:
            password = get_mandatory_param(request.data, "password")
        is_superuser = extract_bool(
            get_mandatory_param(request.data, "is_superuser")
        )

        create_audit_event(
            EVENT_TYPES.AUTHORISATION,
            ENDPOINT.API,
            request,
            None,
            description=(
                "Created %s '%s'."
                % ("admin" if is_superuser else "user", username)
            ),
        )
        if is_superuser:
            user = User.objects.create_superuser(
                username=username, password=password, email=email
            )
            if request.data.get("key") is not None:
                request.user = user
                sshkeys_handler = SSHKeysHandler()
                sshkeys_handler.create(request)
            return user
        else:
            return User.objects.create_user(
                username=username, password=password, email=email
            )