]> granicus.if.org Git - pdns/commitdiff
API: Add an endpoint to request only a specific statistic
authorRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 19 Feb 2019 13:43:49 +0000 (14:43 +0100)
committerRemi Gacogne <remi.gacogne@powerdns.com>
Tue, 5 Mar 2019 08:32:45 +0000 (09:32 +0100)
docs/common/api/endpoint-statistics.rst
docs/http-api/swagger/authoritative-api-swagger.yaml
pdns/ws-api.cc
pdns/ws-api.hh
pdns/ws-auth.cc
pdns/ws-recursor.cc
regression-tests.api/test_Servers.py

index 761980757e5649eb6a913e7db3c9f01bddb10e4a..771dae08b9fb31ad47d79840762e6be406ecba87 100644 (file)
@@ -1,7 +1,7 @@
 Statistics endpoint
 ===================
 
-.. http:get:: /api/v1/servers/:server_id/statistics
+.. http:get:: /api/v1/servers/:server_id/statistics?statistic=:statistic
 
   Query PowerDNS internal statistics.
   Returns a list of :json:object:`StatisticItem` elements.
@@ -10,6 +10,10 @@ Statistics endpoint
 
   :param server_id: The name of the server
 
+  .. versionadded:: 4.2.0
+
+  :query statistic: If set to the name of a specific statistic, only this value is returned. If no statistic with that name exists, the response has a 422 status and an error message
+
   **Example response:**
 
   .. code-block:: json
index 8eed2164f4e41a3a8993beb616588ec78389c817..818f0b8c291995b454f098bccaa77be0ebef787b 100644 (file)
@@ -399,6 +399,14 @@ paths:
           required: true
           description: The id of the server to retrieve
           type: string
+        - name: statistic
+          in: query
+          required: false
+          type: string
+          description: |
+            When set to the name of a specific statistic, only this value is returned.
+            If no statistic with that name exists, the response has a 422 status and an error message.
+
       responses:
         '200':
           description: List of Statistic Items
@@ -408,6 +416,8 @@ paths:
             - $ref: '#/definitions/StatisticItem'
             - $ref: '#/definitions/MapStatisticItem'
             - $ref: '#/definitions/RingStatisticItem'
+        '422':
+          description: 'Returned when a non-existing statistic name has been requested. Contains an error message'
 
   '/servers/{server_id}/search-data':
     get:
index 6106d25f3329d76107fc6134f991ee1347be0e22..ae62d7dd7f02b9381cd394b1b4c9b707864edad6 100644 (file)
@@ -159,15 +159,29 @@ void apiServerStatistics(HttpRequest* req, HttpResponse* resp) {
   if(req->method != "GET")
     throw HttpMethodNotAllowedException();
 
+  Json::array doc;
+  string name = req->getvars["statistic"];
+  if (!name.empty()) {
+    auto stat = productServerStatisticsFetch(name);
+    if (!stat) {
+      throw ApiException("Unknown statistic name");
+    }
+
+    doc.push_back(Json::object {
+      { "type", "StatisticItem" },
+      { "name", name },
+      { "value", std::to_string(*stat) },
+    });
+
+    resp->setBody(doc);
+
+    return;
+  }
+
   typedef map<string, string> stat_items_t;
   stat_items_t general_stats;
   productServerStatisticsFetch(general_stats);
 
-  auto resp_qtype_stats = g_rs.getQTypeResponseCounts();
-  auto resp_size_stats = g_rs.getSizeResponseCounts();
-  auto resp_rcode_stats = g_rs.getRCodeResponseCounts();
-
-  Json::array doc;
   for(const auto& item : general_stats) {
     doc.push_back(Json::object {
       { "type", "StatisticItem" },
@@ -176,6 +190,9 @@ void apiServerStatistics(HttpRequest* req, HttpResponse* resp) {
     });
   }
 
+  auto resp_qtype_stats = g_rs.getQTypeResponseCounts();
+  auto resp_size_stats = g_rs.getSizeResponseCounts();
+  auto resp_rcode_stats = g_rs.getRCodeResponseCounts();
   {
     Json::array values;
     for(const auto& item : resp_qtype_stats) {
index 0de6eda580b93760b2aaece1179bac32cc20d508..e280a20abed8f8645a14a25a65296acd80ea9201 100644 (file)
@@ -41,5 +41,6 @@ DNSName apiNameToDNSName(const string& name);
 
 // To be provided by product code.
 void productServerStatisticsFetch(std::map<string,string>& out);
+boost::optional<uint64_t> productServerStatisticsFetch(const std::string& name);
 
 #endif /* PDNS_WSAPI_HH */
index eb6a9707b723a2c41a708a1c86055352ac22d411..d2c3f9ae0db3a53decb4afdb72d649b9bcc34964 100644 (file)
@@ -503,6 +503,17 @@ void productServerStatisticsFetch(map<string,string>& out)
   out["uptime"] = std::to_string(time(0) - s_starttime);
 }
 
+boost::optional<uint64_t> productServerStatisticsFetch(const std::string& name)
+{
+  try {
+    // ::read() calls ::exists() which throws a PDNSException when the key does not exist
+    return S.read(name);
+  }
+  catch(...) {
+    return boost::none;
+  }
+}
+
 static void validateGatheredRRType(const DNSResourceRecord& rr) {
   if (rr.qtype.getCode() == QType::OPT || rr.qtype.getCode() == QType::TSIG) {
     throw ApiException("RRset "+rr.qname.toString()+" IN "+rr.qtype.getName()+": invalid type given");
index 6d262656b5e04857920a6269454aac733be6538a..a1889a3a676449076234204072a0786da5cb19cf 100644 (file)
@@ -52,6 +52,11 @@ void productServerStatisticsFetch(map<string,string>& out)
   out.swap(stats);
 }
 
+boost::optional<uint64_t> productServerStatisticsFetch(const std::string& name)
+{
+  return getStatByName(name);
+}
+
 static void apiWriteConfigFile(const string& filebasename, const string& content)
 {
   if (::arg()["api-config-dir"].empty()) {
index 80835f999ed644c77096861e1aed81cefb2e7af2..bc24d08c5b8f5a04295b87546ab64e19000bf9c7 100644 (file)
@@ -57,3 +57,14 @@ class Servers(ApiTestCase):
             self.assertIn('60', [e['name'] for e in respsize_stats])
             self.assertIn('example.com/A', [e['name'] for e in queries_stats])
             self.assertIn('No Error', [e['name'] for e in rcode_stats])
+
+    def test_read_one_statistic(self):
+        r = self.session.get(self.url("/api/v1/servers/localhost/statistics?statistic=uptime"))
+        self.assert_success_json(r)
+        data = r.json()
+        self.assertIn('uptime', [e['name'] for e in data])
+
+    def test_read_one_non_existent_statistic(self):
+        r = self.session.get(self.url("/api/v1/servers/localhost/statistics?statistic=uptimeAAAA"))
+        self.assertEquals(r.status_code, 422)
+        self.assertIn("Unknown statistic name", r.json()['error'])