]> git.ipfire.org Git - thirdparty/pdns.git/commitdiff
Docs and tweaks
authorOtto Moerbeek <otto.moerbeek@open-xchange.com>
Thu, 15 Jan 2026 09:49:31 +0000 (10:49 +0100)
committerOtto Moerbeek <otto.moerbeek@open-xchange.com>
Thu, 15 Jan 2026 10:21:53 +0000 (11:21 +0100)
Signed-off-by: Otto Moerbeek <otto.moerbeek@open-xchange.com>
.github/actions/spell-check/expect.txt
pdns/recursordist/docs/http-api/endpoint-ottraceconditions.rst [new file with mode: 0644]
pdns/recursordist/docs/http-api/index.rst
pdns/recursordist/docs/http-api/ottracecondition.rst [new file with mode: 0644]
pdns/recursordist/rec-main.cc
pdns/recursordist/rec-rust-lib/rust/src/web.rs
pdns/recursordist/ws-recursor.cc
regression-tests.api/test_RecursorOTConditions.py

index 20217ed791fade9a48658338f6413cf689def814..10a03803aa75128af259ea8b8d96349bd635b730 100644 (file)
@@ -979,6 +979,7 @@ oskar
 otherdomain
 otherpool
 othervariant
+ottracecondition
 ourname
 ourserial
 outpacket
diff --git a/pdns/recursordist/docs/http-api/endpoint-ottraceconditions.rst b/pdns/recursordist/docs/http-api/endpoint-ottraceconditions.rst
new file mode 100644 (file)
index 0000000..88dc842
--- /dev/null
@@ -0,0 +1,32 @@
+OpenTelemetryTraceConditions endpoint
+=====================================
+
+.. Note::
+   All modifications using this endpoint are not persistent.
+   Reloading the configuration using ``rec_control reload-yaml`` will revert the trace conditions back to the conditions read from the configuration file(s).
+
+.. http:get:: /api/v1/servers/:server_id/ottraceconditions
+
+  Get all :json:object:`OpenTelemetryTraceCondition` from the server. Note that while the settings file allows a list of subnets to be associated with a condition, this list is flattened: one subnet per condition.
+
+  :query server_id: The name of the server
+
+.. http:post:: /api/v1/servers/:server_id/ottraceconditions
+
+  Creates a new trace condition. The client body must contain a :json:object:`OpenTelemetryTraceCondition`.
+
+  :query server_id: The name of the server
+
+.. http:get:: /api/v1/servers/:server_id/ottraceconditions/:subnet
+
+  Returns trace condition information.
+
+  :query server_id: The name of the server
+  :query subnet: The subnet of the :json:object:`OpenTelemetryTraceCondition`. URL encode subnet, for example ``192.0.2.1/32`` becomes ``192.0.2.1%2F32``.
+
+.. http:delete:: /api/v1/servers/:server_id/ottraceconditions/:subnet
+
+  Deletes this zone, all attached metadata and rrsets.
+
+  :query server_id: The name of the server
+  :query subnet: The subnet of the :json:object:`OpenTelemetryTraceCondition`. URL encode subnet, for example ``192.0.2.1/32`` becomes ``192.0.2.1%2F32``.
index 808a38b7a27441e6811d486453e7fddfd9efa40f..ff18543f2ede9d49cbe5077a12efd2fd453a380e 100644 (file)
@@ -14,7 +14,7 @@ The following documents contain the information for the PowerDNS API:
     zone
     ../common/api/configsetting
     ../common/api/statisticitem
-
+    ottracecondition
 
 Webserver
 ---------
@@ -104,3 +104,4 @@ All API endpoints for the PowerDNS Recursor are documented here:
   endpoint-failure
   endpoint-rpz-stats
   endpoint-jsonstat
+  endpoint-ottraceconditions
diff --git a/pdns/recursordist/docs/http-api/ottracecondition.rst b/pdns/recursordist/docs/http-api/ottracecondition.rst
new file mode 100644 (file)
index 0000000..8229479
--- /dev/null
@@ -0,0 +1,32 @@
+OpenTelemetryTraceCondition
+---------------------------
+
+An ``OpenTelemetryTraceCondition`` object represents a condition to trigger generating :ref:`opentelemetry_tracing`.
+These conditions con be configured in a settings file (see :ref:`setting-yaml-logging.opentelemetry_trace_conditions`) or manipulated runtime using the REST API calls listed in :doc:`endpoint-ottraceconditions`.
+
+.. json:object:: OpenTelemetryTraceCondition
+
+  Represents an OpenTelemetryTrace condition.
+
+  :property string acl: The subnet of the entry. Note that the YAML settings file allows multiple subnets for convenience. This object does not allow multiple subnets to be specified.
+  :property string type: set to "OpenTelemetryTraceCondition"
+  :property bool edns_option_required: See :ref:`opentelemetry_tracing`
+  :property number qid: A specific query id
+  :property [DNSName] qnames: List of names
+  :property [QType] qtypes: List of qtypes, represented as string
+  :property bool traceid_only: See :ref:`opentelemetry_tracing`
+
+**Example**:
+
+.. code-block:: json
+
+   {
+     "acl": "192.0.2.1/32",
+     "edns_option_required": false,
+     "qid": 1,
+     "qnames": ["example.com.", "example.net."],
+     "qtypes": ["AAAA", "A"],
+     "traceid_only": false
+     "type": "OpenTelemetryTraceCondition"
+   }
+
index c577e031c6cf589d1a9d002b8a90879b6e134206..b06b482d25bc3f8d649840d64a8aa46329777eb4 100644 (file)
@@ -1485,6 +1485,7 @@ void broadcastFunction(const pipefunc_t& func)
     */
     func();
   }
+
   unsigned int thread = 0;
   for (const auto& threadInfo : RecThreadInfo::infos()) {
     if (thread++ == RecThreadInfo::thread_local_id()) {
index 500d0ad2a2c700dde455bc1a721f5e78adf0341c..e06ffbdb2d95b7bdac1e0f121751954d49f08d56 100644 (file)
@@ -399,10 +399,10 @@ fn matcher(
         (&Method::GET, ["api", "v1"]) => *apifunc = Some(rustweb::apiDiscoveryV1),
         (&Method::GET, ["api"]) => *apifunc = Some(rustweb::apiDiscovery),
         (&Method::GET, ["metrics"]) => *rawfunc = Some(rustweb::prometheusMetrics),
-        (&Method::GET, ["api", "v1", "servers", "localhost", "otconditions"]) => {
+        (&Method::GET, ["api", "v1", "servers", "localhost", "ottraceconditions"]) => {
             *apifunc = Some(rustweb::apiServerOTConditionsGET);
         }
-        (&Method::GET, ["api", "v1", "servers", "localhost", "otconditions", acl]) => {
+        (&Method::GET, ["api", "v1", "servers", "localhost", "ottraceconditions", acl]) => {
             let decoded = form_urlencoded::parse(acl.as_bytes());
             // decoded should contain a single key without value
             if let Some(kv) = decoded.last() {
@@ -413,7 +413,7 @@ fn matcher(
             }
             *apifunc = Some(rustweb::apiServerOTConditionDetailGET)
         }
-        (&Method::DELETE, ["api", "v1", "servers", "localhost", "otconditions", acl]) => {
+        (&Method::DELETE, ["api", "v1", "servers", "localhost", "ottraceconditions", acl]) => {
             let decoded = form_urlencoded::parse(acl.as_bytes());
             // decoded should contain a single key without value
             if let Some(kv) = decoded.last() {
@@ -424,7 +424,7 @@ fn matcher(
             }
             *apifunc = Some(rustweb::apiServerOTConditionDetailDELETE)
         }
-        (&Method::POST, ["api", "v1", "servers", "localhost", "otconditions"]) => {
+        (&Method::POST, ["api", "v1", "servers", "localhost", "ottraceconditions"]) => {
             *apifunc = Some(rustweb::apiServerOTConditionDetailPOST)
         }
         _ => *filefunc = Some(file),
index e5dcaaceb46c4331e62e51579063c0e8f8c45c5d..1f254017055cfd1903e861de71cfc09a897021ca 100644 (file)
@@ -551,6 +551,7 @@ static void apiServerOTConditionsGET(HttpRequest* /* req */, HttpResponse* resp)
     for (const auto& condition : **lock) {
       Json::object object{
         {"acl", condition.first.toString()},
+        {"type", "OpenTelemetryTraceCondition"},
         {"edns_option_required", condition.second.d_edns_option_required},
         {"traceid_only", condition.second.d_traceid_only},
       };
@@ -586,6 +587,7 @@ static void fillOTCondition(const Netmask& netmask, HttpResponse* resp)
     if (condition != nullptr && condition->first == netmask) { // exact match
       Json::object object{
         {"acl", condition->first.toString()},
+        {"type", "OpenTelemetryTraceCondition"},
         {"edns_option_required", condition->second.d_edns_option_required},
         {"traceid_only", condition->second.d_traceid_only},
       };
index a579f08dc2d2569390f2ae695d01cbf154556b0b..c282a3b2cad0937ded498fd9d60813aa203a3472 100644 (file)
@@ -21,28 +21,28 @@ class RecursorOT(ApiTestCase):
     def test_basic_ot_conditions(self):
         # initial list is empty
         r = self.session.get(
-            self.url("/api/v1/servers/localhost/otconditions"),
+            self.url("/api/v1/servers/localhost/ottraceconditions"),
             headers={'content-type': 'application/json'})
         self.assertEqual(r.status_code, 200)
         self.assertEqual(r.json(), [])
 
         # nonexistent condition
         r = self.session.get(
-            self.url("/api/v1/servers/localhost/otconditions/1.2.3.4%2F32"),
+            self.url("/api/v1/servers/localhost/ottraceconditions/1.2.3.4%2F32"),
             headers={'content-type': 'application/json'})
         self.assertEqual(r.status_code, 422)
         self.assert_in_json_error('Could not find otcondition', r.json())
 
         # malformed netmask
         r = self.session.get(
-            self.url("/api/v1/servers/localhost/otconditions/1.2.3%2F32"),
+            self.url("/api/v1/servers/localhost/ottraceconditions/1.2.3%2F32"),
             headers={'content-type': 'application/json'})
         self.assertEqual(r.status_code, 422)
         self.assert_in_json_error('Could not parse netmask', r.json())
 
         # deleting non-existent netmask
         r = self.session.delete(
-            self.url("/api/v1/servers/localhost/otconditions/1.2.3.4%2F32"),
+            self.url("/api/v1/servers/localhost/ottraceconditions/1.2.3.4%2F32"),
             headers={'content-type': 'application/json'})
         self.assertEqual(r.status_code, 422)
         self.assert_in_json_error('Could not find otcondition', r.json())
@@ -52,7 +52,7 @@ class RecursorOT(ApiTestCase):
             "acl": "1.2.3.4"
         }
         r = self.session.post(
-            self.url("/api/v1/servers/localhost/otconditions"),
+            self.url("/api/v1/servers/localhost/ottraceconditions"),
             data=json.dumps(payload),
             headers={'content-type': 'application/json'})
         self.assertEqual(r.status_code, 201)
@@ -60,7 +60,9 @@ class RecursorOT(ApiTestCase):
         self.assertIn('acl', data)
         self.assertIn('edns_option_required', data)
         self.assertIn('traceid_only', data)
+        self.assertIn('type', data)
         self.assertEqual(data['acl'], '1.2.3.4/32')
+        self.assertEqual(data['type'], 'OpenTelemetryTraceCondition')
         self.assertFalse(data['edns_option_required'])
         self.assertFalse(data['traceid_only'])
 
@@ -69,7 +71,7 @@ class RecursorOT(ApiTestCase):
             "acl": "1.2.3.4"
         }
         r = self.session.post(
-            self.url("/api/v1/servers/localhost/otconditions"),
+            self.url("/api/v1/servers/localhost/ottraceconditions"),
             data=json.dumps(payload),
             headers={'content-type': 'application/json'})
         self.assertEqual(r.status_code, 422)
@@ -77,7 +79,7 @@ class RecursorOT(ApiTestCase):
 
         # list has one element
         r = self.session.get(
-            self.url("/api/v1/servers/localhost/otconditions"),
+            self.url("/api/v1/servers/localhost/ottraceconditions"),
             headers={'content-type': 'application/json'})
         self.assertEqual(r.status_code, 200)
         self.assertEqual(len(r.json()), 1)
@@ -87,7 +89,7 @@ class RecursorOT(ApiTestCase):
             "acl": "1.2.3.0/24"
         }
         r = self.session.post(
-            self.url("/api/v1/servers/localhost/otconditions"),
+            self.url("/api/v1/servers/localhost/ottraceconditions"),
             data=json.dumps(payload),
             headers={'content-type': 'application/json'})
         self.assertEqual(r.status_code, 201)
@@ -101,27 +103,27 @@ class RecursorOT(ApiTestCase):
 
         # list has two elements
         r = self.session.get(
-            self.url("/api/v1/servers/localhost/otconditions"),
+            self.url("/api/v1/servers/localhost/ottraceconditions"),
             headers={'content-type': 'application/json'})
         self.assertEqual(r.status_code, 200)
         self.assertEqual(len(r.json()), 2)
 
         # querying by more specific key
         r = self.session.get(
-            self.url("/api/v1/servers/localhost/otconditions/1.2.3.4%2F31"),
+            self.url("/api/v1/servers/localhost/ottraceconditions/1.2.3.4%2F31"),
             headers={'content-type': 'application/json'})
         self.assertEqual(r.status_code, 422)
         self.assert_in_json_error('Could not find otcondition', r.json())
 
         # deleting specific netmask
         r = self.session.delete(
-            self.url("/api/v1/servers/localhost/otconditions/1.2.3.4%2F32"),
+            self.url("/api/v1/servers/localhost/ottraceconditions/1.2.3.4%2F32"),
             headers={'content-type': 'application/json'})
         self.assertEqual(r.status_code, 204)
 
         # list has one elements
         r = self.session.get(
-            self.url("/api/v1/servers/localhost/otconditions"),
+            self.url("/api/v1/servers/localhost/ottraceconditions"),
             headers={'content-type': 'application/json'})
         self.assertEqual(r.status_code, 200)
         self.assertEqual(len(r.json()), 1)
@@ -136,7 +138,7 @@ class RecursorOT(ApiTestCase):
             "edns_option_required": True
         }
         r = self.session.post(
-            self.url("/api/v1/servers/localhost/otconditions"),
+            self.url("/api/v1/servers/localhost/ottraceconditions"),
             data=json.dumps(payload),
             headers={'content-type': 'application/json'})
         self.assertEqual(r.status_code, 201)
@@ -147,16 +149,18 @@ class RecursorOT(ApiTestCase):
         self.assertIn('qtypes', data)
         self.assertIn('traceid_only', data)
         self.assertIn('edns_option_required', data)
+        self.assertIn('type', data)
         self.assertEqual(data['acl'], '::/0')
         self.assertEqual(data['qid'], 99)
         self.assertEqual(len(data['qnames']), 3)
         self.assertEqual(len(data['qtypes']), 2)
+        self.assertEqual(data['type'], 'OpenTelemetryTraceCondition')
         self.assertTrue(data['edns_option_required'])
         self.assertTrue(data['traceid_only'])
 
         # and GET the newly created one in a separate call
         r = self.session.get(
-            self.url("/api/v1/servers/localhost/otconditions/::1%2F0"),
+            self.url("/api/v1/servers/localhost/ottraceconditions/::1%2F0"),
             headers={'content-type': 'application/json'})
         self.assertEqual(r.status_code, 200)
         data = r.json()
@@ -166,9 +170,11 @@ class RecursorOT(ApiTestCase):
         self.assertIn('qtypes', data)
         self.assertIn('traceid_only', data)
         self.assertIn('edns_option_required', data)
+        self.assertIn('type', data)
         self.assertEqual(data['acl'], '::/0')
         self.assertEqual(data['qid'], 99)
         self.assertEqual(len(data['qnames']), 3)
         self.assertEqual(len(data['qtypes']), 2)
+        self.assertEqual(data['type'], 'OpenTelemetryTraceCondition')
         self.assertTrue(data['edns_option_required'])
         self.assertTrue(data['traceid_only'])