]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
kresctl: 'cache-clear' command created
authorAleš Mrázek <ales.mrazek@nic.cz>
Tue, 14 Nov 2023 13:04:29 +0000 (14:04 +0100)
committerAleš Mrázek <ales.mrazek@nic.cz>
Wed, 14 Feb 2024 12:14:30 +0000 (13:14 +0100)
manager/knot_resolver_manager/cli/cmd/cache_clear.py [new file with mode: 0644]
manager/knot_resolver_manager/datamodel/cache_schema.py
manager/knot_resolver_manager/utils/modeling/base_schema.py

diff --git a/manager/knot_resolver_manager/cli/cmd/cache_clear.py b/manager/knot_resolver_manager/cli/cmd/cache_clear.py
new file mode 100644 (file)
index 0000000..de55465
--- /dev/null
@@ -0,0 +1,79 @@
+import argparse
+import sys
+from typing import Any, Dict, List, Tuple, Type
+
+from knot_resolver_manager.cli.command import Command, CommandArgs, CompWords, register_command
+from knot_resolver_manager.datamodel.cache_schema import CacheClearRPCSchema
+from knot_resolver_manager.utils.modeling.exceptions import AggregateDataValidationError, DataValidationError
+from knot_resolver_manager.utils.modeling.parsing import DataFormat
+from knot_resolver_manager.utils.requests import request
+
+
+@register_command
+class CacheClearCommand(Command):
+    def __init__(self, namespace: argparse.Namespace) -> None:
+        super().__init__(namespace)
+
+        config_dict: Dict[str, Any] = {"exact-name": namespace.exact_name}
+
+        if hasattr(namespace, "name"):
+            config_dict["name"] = namespace.name
+        if hasattr(namespace, "rr_type"):
+            config_dict["rr-type"] = namespace.rr_type
+        if hasattr(namespace, "chunk_size"):
+            config_dict["chunk-size"] = namespace.chunk_size
+
+        try:
+            self.config = CacheClearRPCSchema(config_dict)
+        except (AggregateDataValidationError, DataValidationError) as e:
+            print(e, file=sys.stderr)
+            sys.exit(1)
+
+    @staticmethod
+    def register_args_subparser(
+        subparser: "argparse._SubParsersAction[argparse.ArgumentParser]",
+    ) -> Tuple[argparse.ArgumentParser, "Type[Command]"]:
+        cache_clear = subparser.add_parser("cache-clear", help="Purge cache records matching specified criteria.")
+        cache_clear.set_defaults(exact_name=False)
+        cache_clear.add_argument(
+            "--exact-name",
+            help="If set, only records with the same name are removed.",
+            action="store_true",
+            dest="exact_name",
+        )
+        cache_clear.add_argument(
+            "--rr-type",
+            help="Optional, you may additionally specify the type to remove, but that is only supported with '--exact-name' flag set.",
+            action="store",
+            type=str,
+        )
+        cache_clear.add_argument(
+            "--chunk-size",
+            help="Optional, the number of records to remove in one round; default: 100."
+            " The purpose is not to block the resolver for long. The resolver repeats the command after one millisecond until all matching data are cleared.",
+            action="store",
+            type=int,
+            default=100,
+        )
+        cache_clear.add_argument(
+            "name",
+            type=str,
+            nargs="?",
+            help="Optional, subtree to purge; if the name isn't provided, whole cache is purged (and any other parameters are disregarded).",
+            default=None,
+        )
+
+        return cache_clear, CacheClearCommand
+
+    @staticmethod
+    def completion(args: List[str], parser: argparse.ArgumentParser) -> CompWords:
+        return {}
+
+    def run(self, args: CommandArgs) -> None:
+        body: str = DataFormat.JSON.dict_dump(self.config.get_unparsed_data())
+        response = request(args.socket, "POST", "cache-clear", body)
+
+        if response.status != 200:
+            print(response, file=sys.stderr)
+            sys.exit(1)
+        print(response.body)
index 4608ee5dd9d3b433cb5a32d6196d457ad17b84b2..580a031d088e2c59e9c68f2e98d609aff1bf1dea 100644 (file)
@@ -4,10 +4,12 @@ from typing_extensions import Literal
 
 from knot_resolver_manager.datamodel.types import (
     Dir,
+    DNSRecordTypeEnum,
     DomainName,
     EscapedStr,
     File,
     IntNonNegative,
+    IntPositive,
     Percent,
     SizeUnit,
     TimeUnit,
@@ -16,6 +18,13 @@ from knot_resolver_manager.utils.modeling import ConfigSchema
 from knot_resolver_manager.utils.modeling.base_schema import lazy_default
 
 
+class CacheClearRPCSchema(ConfigSchema):
+    name: Optional[DomainName] = None
+    exact_name: bool = False
+    rr_type: Optional[DNSRecordTypeEnum] = None
+    chunk_size: IntPositive = IntPositive(100)
+
+
 class PrefillSchema(ConfigSchema):
     """
     Prefill the cache periodically by importing zone data obtained over HTTP.
index b9b584c3cfaa3ae3c8041ff951f1a12334974935..06a6cc22b3542d512fa775e64f2963c9774817b9 100644 (file)
@@ -642,7 +642,7 @@ class ObjectMapper:
         try:
             obj._validate()
         except ValueError as e:
-            raise DataValidationError(e.args[0] if len(e.args) > 0 else "Validation error", object_path) from e
+            raise DataValidationError(e.args[0] if len(e.args) > 0 else "Validation error", object_path or "/") from e
 
 
 class BaseSchema(Serializable):