Пример #1
0
 def save(self, endpoint, request):
     dhcp_snippet = super().save()
     create_audit_event(
         EVENT_TYPES.SETTINGS,
         endpoint,
         request,
         None,
         description=(
             "%s DHCP snippet '%s'."
             % (
                 "Updated" if self.is_update else "Created",
                 dhcp_snippet.name,
             )
         ),
     )
     return dhcp_snippet
Пример #2
0
    def delete(self, request, system_id, id):
        """Delete bcache on a machine.

        Returns 404 if the machine or bcache is not found.
        Returns 409 if the machine is not Ready.
        """
        bcache = Bcache.objects.get_object_or_404(system_id, id, request.user,
                                                  NODE_PERMISSION.ADMIN)
        node = bcache.get_node()
        if node.status != NODE_STATUS.READY:
            raise NodeStateViolation(
                "Cannot delete Bcache because the machine is not Ready.")
        bcache.delete()
        create_audit_event(EVENT_TYPES.NODE, ENDPOINT.API, request, system_id,
                           "Deleted bcache.")
        return rc.DELETED
Пример #3
0
    def update(self, request, system_id, id):
        """@description-title Update a bcache set
        @description Update bcache cache set on a machine.

        Note: specifying both a cache_device and a cache_partition is not
        allowed.

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

        @param (string) "cache_device" [required=false] Cache block device to
        replace current one.

        @param (string) "cache_partition" [required=false] Cache partition to
        replace current one.

        @success (http-status-code) "server-success" 200
        @success (json) "success-json" A JSON object containing a bcache set.
        @success-example "success-json" [exkey=bcache-placeholder] placeholder
        text

        @error (http-status-code) "404" 404
        @error (content) "not-found" The requested machine 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.
        """
        cache_set = CacheSet.objects.get_cache_set_or_404(
            system_id, id, request.user, NodePermission.admin)
        node = cache_set.get_node()
        if node.status != NODE_STATUS.READY:
            raise NodeStateViolation(
                "Cannot update cache set because the machine is not Ready.")
        form = UpdateCacheSetForm(cache_set, data=request.data)
        if form.is_valid():
            create_audit_event(
                EVENT_TYPES.NODE,
                ENDPOINT.API,
                request,
                system_id,
                "Updated bcache cache set.",
            )
            return form.save()
        else:
            raise MAASAPIValidationError(form.errors)
Пример #4
0
    def create(self, request):
        """Create 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 username: Identifier-style username for the new user.
        :type username: unicode
        :param email: Email address for the new user.
        :type email: unicode
        :param password: Password for the new user.
        :type password: unicode
        :param is_superuser: Whether the new user is to be an administrator.
        :type is_superuser: bool ('0' for False, '1' for True)

        Returns 400 if any mandatory parameters are missing.
        """
        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)
Пример #5
0
    def revert(self, request, name):
        """@description-title Revert a script version
        @description Revert a script with the given name to an earlier version.

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

        @param (int) "to" [required=false] 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.

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

        @error (http-status-code) "404" 404
        @error (content) "not-found" The requested script is not found.
        @error-example "not-found"
            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=("Reverted script '%s' to revision '%s'." %
                             (script.name, revert_to)),
            )
            return script
        except ValueError as e:
            raise MAASAPIValidationError(e.args[0])
Пример #6
0
 def test_create_audit_event_creates_audit_event_with_user_agent(self):
     node = factory.make_Node()
     request = HttpRequest()
     request.user = node.owner
     request.META = {
         'HTTP_USER_AGENT': factory.make_name('user_agent'),
         'HTTP_HOST': factory.make_ipv4_address(),
     }
     endpoint = factory.pick_choice(ENDPOINT_CHOICES)
     create_audit_event(EVENT_TYPES.NODE_PXE_REQUEST,
                        endpoint,
                        request,
                        system_id=node.system_id)
     event = Event.objects.get(node=node, user=node.owner)
     self.assertIsNotNone(event)
     self.assertIsNotNone(EventType.objects.get(level=AUDIT))
     self.assertEquals(request.META['HTTP_USER_AGENT'], event.user_agent)
Пример #7
0
    def delete(self, request, name):
        """Delete a script."""
        if name.isdigit():
            script = get_object_or_404(Script, id=int(name))
        else:
            script = get_object_or_404(Script, name=name)

        if script.default:
            raise MAASAPIValidationError("Unable to delete default script")

        script.delete()
        create_audit_event(EVENT_TYPES.SETTINGS,
                           ENDPOINT.API,
                           request,
                           None,
                           description="Deleted script '%s'." % script.name)
        return rc.DELETED
Пример #8
0
    def import_ssh_keys(self, request):
        """@description-title Import SSH keys
        @description Import the requesting user's SSH keys for a given protocol
        and authorization ID in protocol:auth_id format.

        @param (string) "keysource" [required=true,formatting=true] The source
        of the keys to import should be provided in the request payload as form
        data:

        E.g.

            source:user

        - ``source``: lp (Launchpad), gh (GitHub)
        - ``user``: User login

        @success (http-status-code) "200" 200
        @success (json) "success-json" A JSON object containing a list of
        imported keys.
        @success-example "success-json" [exkey=ssh-keys-import] placeholder
        text
        """
        keysource = request.data.get("keysource", None)
        if keysource is not None:
            if ":" in keysource:
                protocol, auth_id = keysource.split(":", 1)
            else:
                protocol = KEYS_PROTOCOL_TYPE.LP
                auth_id = keysource
            try:
                keysource = KeySource.objects.save_keys_for_user(
                    user=request.user, protocol=protocol, auth_id=auth_id)
                create_audit_event(
                    EVENT_TYPES.AUTHORISATION,
                    ENDPOINT.API,
                    request,
                    None,
                    description="Imported SSH keys.",
                )
                return keysource
            except (ImportSSHKeysError, RequestException) as e:
                raise MAASAPIBadRequest(e.args[0])
        else:
            raise MAASAPIBadRequest(
                "Importing SSH keys failed. "
                "Input needs to be in protocol:auth_id or auth_id format.")
Пример #9
0
    def delete(self, request, username):
        """@description-title Delete a user
        @description Deletes a given username.

        @param (string) "{username}" [required=true] The username to delete.

        @param (string) "transfer_resources_to" [required=false] An optional
        username. If supplied, the allocated resources of the user being
        deleted will be transferred to this user. A user can't be removed
        unless its resources (machines, IP addresses, ...), are released or
        transfered to another user.

        @success (http-status-code) "204" 204
        """
        if request.user.username == username:
            raise ValidationError("An administrator cannot self-delete.")

        form = DeleteUserForm(data=request.GET)
        if not form.is_valid():
            raise MAASAPIValidationError(form.errors)

        user = get_one(User.objects.filter(username=username))
        if user is not None:
            new_owner_username = form.cleaned_data["transfer_resources_to"]
            if new_owner_username:
                new_owner = get_object_or_404(User,
                                              username=new_owner_username)
                if new_owner is not None:
                    user.userprofile.transfer_resources(new_owner)

            Consumer.objects.filter(user=user).delete()
            try:
                user.userprofile.delete()
                create_audit_event(
                    EVENT_TYPES.AUTHORISATION,
                    ENDPOINT.API,
                    request,
                    None,
                    description=(
                        "Deleted %s '%s'." %
                        ("admin" if user.is_superuser else "user", username)),
                )
            except CannotDeleteUserException as e:
                raise ValidationError(str(e))

        return rc.DELETED
Пример #10
0
    def execute(self, *args, **kwargs):
        """Perform this action.

        Even though this is not the API, the action may raise
        :class:`MAASAPIException` exceptions.  When this happens, the view
        will return to the client an http response reflecting the exception.
        """
        self._execute(*args, **kwargs)
        description = self.get_node_action_audit_description(self)
        # Log audit event for the action.
        create_audit_event(
            EVENT_TYPES.NODE,
            self.endpoint,
            self.request,
            self.node.system_id,
            description=description,
        )
Пример #11
0
 def delete(self, request, *args, **kwargs):
     profile = self.get_object()
     username = profile.user.username
     try:
         create_audit_event(
             EVENT_TYPES.AUTHORISATION,
             ENDPOINT.UI,
             request,
             None,
             description=("%s %s" %
                          ('Admin' if profile.user.is_superuser else 'User',
                           username) + " deleted by '%(username)s'."))
         profile.delete()
         messages.info(request, "User %s deleted." % username)
     except CannotDeleteUserException as e:
         messages.info(request, str(e))
     return HttpResponseRedirect(self.get_next_url())
Пример #12
0
def logout(request):
    if request.method == 'POST':
        form = LogoutForm(request.POST)
        if form.is_valid():
            create_audit_event(
                EVENT_TYPES.AUTHORISATION,
                ENDPOINT.UI,
                request,
                None,
                description=(
                    "%s" % ('Admin' if request.user.is_superuser else 'User') +
                    " '%(username)s' logged out."))
            return dj_logout(request, next_page=reverse('login'))
    else:
        form = LogoutForm()

    return render(request, 'maasserver/logout_confirm.html', {'form': form})
Пример #13
0
    def revert(self, request, id):
        """@description-title Revert DHCP snippet to earlier version
        @description Revert the value of a DHCP snippet with the given id to an
        earlier revision.

        @param (int) "{id}" [required=true] A DHCP snippet id.

        @param (int) "to" [required=true] What revision in the DHCP snippet's
        history to revert to.  This can either be an ID or a negative number
        representing how far back to go.

        @success (http-status-code) "server-success" 200
        @success (json) "success-json" A JSON object containing
        information about the reverted DHCP snippet.
        @success-example "success-json" [exkey=dhcp-snippets-revert]
        placeholder text

        @error (http-status-code) "404" 404
        @error (content) "not-found" The requested DHCP snippet is not found.
        @error-example "not-found"
            Not Found
        """
        revert_to = request.data.get('to')
        if revert_to is None:
            raise MAASAPIValidationError('You must specify where to revert to')
        try:
            revert_to = int(revert_to)
        except ValueError:
            raise MAASAPIValidationError(
                "%s is an invalid 'to' value" % revert_to)

        dhcp_snippet = DHCPSnippet.objects.get_dhcp_snippet_or_404(id)
        try:
            def gc_hook(value):
                dhcp_snippet.value = value
                dhcp_snippet.save()
            dhcp_snippet.value.revert(revert_to, gc_hook=gc_hook)
            create_audit_event(
                EVENT_TYPES.SETTINGS, ENDPOINT.API, request, None,
                description=(
                    "Reverted DHCP snippet '%s' to revision '%s'." % (
                        dhcp_snippet.name, revert_to)))
            return dhcp_snippet
        except ValueError as e:
            raise MAASAPIValidationError(e.args[0])
Пример #14
0
    def import_keys(self, params):
        """Import the requesting user's SSH keys.

        Import SSH keys for a given protocol and authorization ID in
        protocol:auth_id format.
        """
        try:
            KeySource.objects.save_keys_for_user(
                user=self.user,
                protocol=params['protocol'],
                auth_id=params['auth_id'])
            request = HttpRequest()
            request.user = self.user
            create_audit_event(
                EVENT_TYPES.AUTHORISATION, ENDPOINT.UI, request,
                None, description="Imported SSH keys.")
        except ImportSSHKeysError as e:
            raise HandlerError(str(e))
Пример #15
0
def logout(request):
    if request.method == "POST":
        form = LogoutForm(request.POST)
        if form.is_valid():
            create_audit_event(
                EVENT_TYPES.AUTHORISATION,
                ENDPOINT.UI,
                request,
                None,
                description=(
                    "Logged out %s." %
                    ("admin" if request.user.is_superuser else "user")),
            )
            auth_logout(request)
            return HttpResponse(status=204)
        return HttpResponseBadRequest(json.dumps(form.errors),
                                      content_type="application/json")
    return HttpResponseNotAllowed([request.method])
Пример #16
0
    def delete(self, request, id):
        """@description-title Delete a DHCP snippet
        @description Delete a DHCP snippet with the given id.

        @param (int) "{id}" [required=true] A DHCP snippet id.

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

        @error (http-status-code) "404" 404
        @error (content) "not-found" The requested DHCP snippet is not found.
        @error-example "not-found"
            Not Found
        """
        dhcp_snippet = DHCPSnippet.objects.get_dhcp_snippet_or_404(id)
        dhcp_snippet.delete()
        create_audit_event(
            EVENT_TYPES.SETTINGS, ENDPOINT.API, request, None, description=(
                "Deleted DHCP snippet '%s'." % dhcp_snippet.name))
        return rc.DELETED
Пример #17
0
def logout(request):
    if request.method == "POST":
        form = LogoutForm(request.POST)
        if form.is_valid():
            create_audit_event(
                EVENT_TYPES.AUTHORISATION,
                ENDPOINT.UI,
                request,
                None,
                description=(
                    "Logged out %s."
                    % ("admin" if request.user.is_superuser else "user")
                ),
            )
            return dj_logout(request, next_page=reverse("login"))
    else:
        form = LogoutForm()

    return render(request, "maasserver/logout_confirm.html", {"form": form})
Пример #18
0
    def delete(self, request, id):
        """DELETE an SSH key.

        Returns 404 if the key does not exist.
        Returns 401 if the key does not belong to the calling user.
        """
        key = get_object_or_404(SSHKey, id=id)
        if key.user != request.user:
            return HttpResponseForbidden(
                "Can't delete a key you don't own.",
                content_type=("text/plain; charset=%s" %
                              settings.DEFAULT_CHARSET))
        key.delete()
        create_audit_event(EVENT_TYPES.AUTHORISATION,
                           ENDPOINT.API,
                           request,
                           None,
                           description="Deleted SSH key id='%s'." % id)
        return rc.DELETED
Пример #19
0
    def delete(self, request, id):
        """DELETE an SSL key.

        Returns 401 if the key does not belong to the requesting user.
        Returns 204 if the key is successfully deleted.
        """
        key = get_object_or_404(SSLKey, id=id)
        if key.user != request.user:
            return HttpResponseForbidden(
                "Can't delete a key you don't own.",
                content_type=("text/plain; charset=%s" %
                              settings.DEFAULT_CHARSET))
        key.delete()
        create_audit_event(EVENT_TYPES.AUTHORISATION,
                           ENDPOINT.API,
                           request,
                           None,
                           description=("SSL key id=%s" % id +
                                        " deleted by '%(username)s'."))
        return rc.DELETED
Пример #20
0
    def update(self, params):
        """Update a Token.

        Only the name can be updated.
        """
        token = self.get_object(params)
        name = params.get("name")
        if name != token.consumer.name:
            token.consumer.name = params.get("name")
            token.consumer.save()
            request = HttpRequest()
            request.user = self.user
            create_audit_event(
                EVENT_TYPES.AUTHORISATION,
                ENDPOINT.UI,
                request,
                None,
                "Modified consumer name of token.",
            )
        return self.full_dehydrate(token)
Пример #21
0
    def create_authorisation_token(self, params):
        """Create an authorisation token for the user.

        This is only for the authenticated user. This cannot be performed on
        a different user.
        """
        request = HttpRequest()
        request.user = self.user
        profile = self.user.userprofile
        consumer, token = profile.create_authorisation_token()
        create_audit_event(EVENT_TYPES.AUTHORISATION, ENDPOINT.UI, request,
                           None, "Created token.")
        return {
            'key': token.key,
            'secret': token.secret,
            'consumer': {
                'key': consumer.key,
                'name': consumer.name,
            },
        }
Пример #22
0
def login(request):
    if request.method == "POST":
        if request.user.is_authenticated:
            return HttpResponse(status=204)
        form = AuthenticationForm(data=request.POST, request=request)
        if form.is_valid():
            auth_login(request, form.get_user())
            create_audit_event(
                EVENT_TYPES.AUTHORISATION,
                ENDPOINT.UI,
                request,
                None,
                description=(
                    "Logged in %s." %
                    ("admin" if request.user.is_superuser else "user")),
            )
            return HttpResponse(status=204)
        return HttpResponseBadRequest(json.dumps(form.errors),
                                      content_type="application/json")
    raise Http404()
Пример #23
0
    def post(self, request, *args, **kwargs):
        """Called by `TemplateView`: handle a POST request."""
        self.object = user = self.get_object()
        next_page = reverse('settings')

        # Process the profile-editing form, if that's what was submitted.
        profile_form, response = process_form(request, EditUserForm, next_page,
                                              'profile', "Profile updated.",
                                              {'instance': user})
        if response is not None:
            create_audit_event(
                EVENT_TYPES.AUTHORISATION,
                ENDPOINT.UI,
                request,
                None,
                description=(
                    "User profile (username: %s, full name: %s, email: %s, "
                    "administrator: %s)" %
                    (profile_form.cleaned_data['username'],
                     profile_form.cleaned_data['last_name'],
                     profile_form.cleaned_data['email'],
                     profile_form.cleaned_data['is_superuser']) +
                    " updated by '%(username)s'."))
            return response

        # Process the password change form, if that's what was submitted.
        password_form, response = process_form(request,
                                               AdminPasswordChangeForm,
                                               next_page, 'password',
                                               "Password updated.",
                                               {'user': user})
        if response is not None:
            create_audit_event(
                EVENT_TYPES.AUTHORISATION,
                ENDPOINT.UI,
                request,
                None,
                description="Password changed for '%(username)s'.")
            return response

        return self.respond(request, profile_form, password_form)
Пример #24
0
    def update_token_name(self, request):
        """Modify the consumer name of an authorisation OAuth token.

        :param token: Can be the whole token or only the token key.
        :type token: unicode
        :param name: New name of the token.
        :type name: unicode
        """
        profile = request.user.userprofile
        token = get_mandatory_param(request.data, 'token')
        token_fields = token.split(":")
        if len(token_fields) == 3:
            token_key = token_fields[1]
        else:
            token_key = token
        consumer_name = get_mandatory_param(request.data, 'name')
        profile.modify_consumer_name(token_key, consumer_name)
        create_audit_event(
            EVENT_TYPES.AUTHORISATION, ENDPOINT.API, request,
            None, "Modified consumer name of token.")
        return rc.ACCEPTED
Пример #25
0
    def delete_authorisation_token(self, request):
        """@description-title Delete an authorisation token
        @description Delete an authorisation OAuth token and the related OAuth
        consumer.

        @param (string) "token_key" [required=true] The key of the token to be
        deleted.

        @success (http-status-code) "204" 204
        """
        profile = request.user.userprofile
        token_key = get_mandatory_param(request.data, "token_key")
        profile.delete_authorisation_token(token_key)
        create_audit_event(
            EVENT_TYPES.AUTHORISATION,
            ENDPOINT.API,
            request,
            None,
            "Deleted token.",
        )
        return rc.DELETED
Пример #26
0
    def delete(self, request, system_id, id):
        """Delete bcache cache set on a machine.

        Returns 400 if the cache set is in use.
        Returns 404 if the machine or cache set is not found.
        Returns 409 if the machine is not Ready.
        """
        cache_set = CacheSet.objects.get_cache_set_or_404(
            system_id, id, request.user, NODE_PERMISSION.ADMIN)
        node = cache_set.get_node()
        if node.status != NODE_STATUS.READY:
            raise NodeStateViolation(
                "Cannot delete cache set because the machine is not Ready.")
        if cache_set.filesystemgroup_set.exists():
            raise MAASAPIBadRequest(
                "Cannot delete cache set; it's currently in use.")
        else:
            cache_set.delete()
            create_audit_event(EVENT_TYPES.NODE, ENDPOINT.API, request,
                               system_id, "Deleted bcache cache set.")
            return rc.DELETED
Пример #27
0
 def test_create_audit_event_creates_audit_event_with_description(self):
     node = factory.make_Node()
     request = HttpRequest()
     request.user = node.owner
     request.META = {
         "HTTP_USER_AGENT": factory.make_name("user_agent"),
         "HTTP_HOST": factory.make_ipv4_address(),
     }
     endpoint = factory.pick_choice(ENDPOINT_CHOICES)
     description = factory.make_name("description")
     create_audit_event(
         EVENT_TYPES.NODE_PXE_REQUEST,
         endpoint,
         request,
         system_id=node.system_id,
         description=description,
     )
     event = Event.objects.get(node=node, type__level=AUDIT)
     self.assertIsNotNone(event)
     self.assertEqual(request.META["HTTP_USER_AGENT"], event.user_agent)
     self.assertEqual(description, event.description)
Пример #28
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
Пример #29
0
    def delete(self, request, username):
        """@description-title Delete a user
        @description Deletes a given username.

        @param (string) "{username}" [required=true] The username to delete.

        @success (http-status-code) "204" 204
        """
        if request.user.username == username:
            raise ValidationError("An administrator cannot self-delete.")

        form = DeleteUserForm(data=request.GET)
        if not form.is_valid():
            raise MAASAPIValidationError(form.errors)

        user = get_one(User.objects.filter(username=username))
        if user is not None:
            new_owner_username = form.cleaned_data['transfer_resources_to']
            if new_owner_username:
                new_owner = get_object_or_404(User,
                                              username=new_owner_username)
                if new_owner is not None:
                    user.userprofile.transfer_resources(new_owner)

            Consumer.objects.filter(user=user).delete()
            try:
                user.userprofile.delete()
                create_audit_event(
                    EVENT_TYPES.AUTHORISATION,
                    ENDPOINT.API,
                    request,
                    None,
                    description=(
                        "Deleted %s '%s'." %
                        ('admin' if user.is_superuser else 'user', username)))
            except CannotDeleteUserException as e:
                raise ValidationError(str(e))

        return rc.DELETED
Пример #30
0
    def delete(self, request, system_id, id):
        """@description-title Delete a bcache set
        @description Delete bcache cache set on a machine.

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

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

        @error (http-status-code) "400" 400
        @error (content) "not-ready" The cache set is in use.

        @error (http-status-code) "404" 404
        @error (content) "not-found" The requested machine 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.
        """
        cache_set = CacheSet.objects.get_cache_set_or_404(
            system_id, id, request.user, NodePermission.admin)
        node = cache_set.get_node()
        if node.status != NODE_STATUS.READY:
            raise NodeStateViolation(
                "Cannot delete cache set because the machine is not Ready.")
        if cache_set.filesystemgroup_set.exists():
            raise MAASAPIBadRequest(
                "Cannot delete cache set; it's currently in use.")
        else:
            cache_set.delete()
            create_audit_event(
                EVENT_TYPES.NODE,
                ENDPOINT.API,
                request,
                system_id,
                "Deleted bcache cache set.",
            )
            return rc.DELETED