From: Otto Moerbeek Date: Thu, 15 Jan 2026 09:49:31 +0000 (+0100) Subject: Docs and tweaks X-Git-Tag: rec-5.4.0-beta1~19^2~4 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9f12c28bdc9467d855780fd691b0d1dc92c53c76;p=thirdparty%2Fpdns.git Docs and tweaks Signed-off-by: Otto Moerbeek --- diff --git a/.github/actions/spell-check/expect.txt b/.github/actions/spell-check/expect.txt index 20217ed791..10a03803aa 100644 --- a/.github/actions/spell-check/expect.txt +++ b/.github/actions/spell-check/expect.txt @@ -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 index 0000000000..88dc842536 --- /dev/null +++ b/pdns/recursordist/docs/http-api/endpoint-ottraceconditions.rst @@ -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``. diff --git a/pdns/recursordist/docs/http-api/index.rst b/pdns/recursordist/docs/http-api/index.rst index 808a38b7a2..ff18543f2e 100644 --- a/pdns/recursordist/docs/http-api/index.rst +++ b/pdns/recursordist/docs/http-api/index.rst @@ -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 index 0000000000..8229479a39 --- /dev/null +++ b/pdns/recursordist/docs/http-api/ottracecondition.rst @@ -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" + } + diff --git a/pdns/recursordist/rec-main.cc b/pdns/recursordist/rec-main.cc index c577e031c6..b06b482d25 100644 --- a/pdns/recursordist/rec-main.cc +++ b/pdns/recursordist/rec-main.cc @@ -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()) { diff --git a/pdns/recursordist/rec-rust-lib/rust/src/web.rs b/pdns/recursordist/rec-rust-lib/rust/src/web.rs index 500d0ad2a2..e06ffbdb2d 100644 --- a/pdns/recursordist/rec-rust-lib/rust/src/web.rs +++ b/pdns/recursordist/rec-rust-lib/rust/src/web.rs @@ -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), diff --git a/pdns/recursordist/ws-recursor.cc b/pdns/recursordist/ws-recursor.cc index e5dcaaceb4..1f25401705 100644 --- a/pdns/recursordist/ws-recursor.cc +++ b/pdns/recursordist/ws-recursor.cc @@ -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}, }; diff --git a/regression-tests.api/test_RecursorOTConditions.py b/regression-tests.api/test_RecursorOTConditions.py index a579f08dc2..c282a3b2ca 100644 --- a/regression-tests.api/test_RecursorOTConditions.py +++ b/regression-tests.api/test_RecursorOTConditions.py @@ -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'])