Implement dynamic procedure at REST interface
authorEduardo Ramos Testillano (eramedu) <eduardo.ramos.testillano@ericsson.com>
Fri, 15 May 2020 15:42:29 +0000 (17:42 +0200)
committerEduardo Ramos Testillano (eramedu) <eduardo.ramos.testillano@ericsson.com>
Fri, 15 May 2020 15:42:29 +0000 (17:42 +0200)
17 files changed:
dynamic/launcher/default/Procedure.cpp
dynamic/launcher/default/Procedure.hpp
dynamic/launcher/gx/00001/Procedure.cpp
dynamic/launcher/gx/00001/Procedure.hpp
example/diameter/launcher/EventOperation.cpp
example/diameter/launcher/EventOperation.hpp
example/diameter/launcher/MyHandler.cpp
example/diameter/launcher/resources/HELP.md
example/diameter/launcher/resources/rest_api/ct/ct.sh
example/diameter/launcher/resources/rest_api/ct/dynamic-procedure/__init__.py [new file with mode: 0644]
example/diameter/launcher/resources/rest_api/ct/dynamic-procedure/dynamic_test.py [new file with mode: 0644]
example/diameter/launcher/resources/rest_api/ct/hot-changes/services_test.py
example/diameter/launcher/resources/rest_api/ct/pytest.ini
example/diameter/launcher/resources/rest_api/ct/resources/arguments-request.json [new file with mode: 0644]
example/diameter/launcher/resources/rest_api/ct/resources/servicesGxJson-request.json [new file with mode: 0644]
example/diameter/launcher/resources/rest_api/ct/resources/servicesJson-request.json [deleted file]
example/diameter/launcher/resources/rest_api/ct/resources/servicesRxJson-request.json [new file with mode: 0644]

index 598912e..6649e62 100644 (file)
@@ -24,3 +24,6 @@ void Procedure::execute(const std::string &args, std::string &response) throw(an
   }
 }
 
+void Procedure::execute(const nlohmann::json &args, std::string &response)  throw(anna::RuntimeException) {
+  execute(args.dump(), response);
+}
index b8096bc..4d85411 100644 (file)
@@ -11,6 +11,7 @@
 
 // Project
 #include <anna/comm/comm.hpp>
+#include <anna/json/json.hpp>
 
 
 class Procedure {
@@ -20,6 +21,7 @@ class Procedure {
     Procedure(anna::comm::Application *app) : a_app(app) {;}
 
     virtual void execute(const std::string &args, std::string &response)  throw(anna::RuntimeException);
+    virtual void execute(const nlohmann::json &args, std::string &response)  throw(anna::RuntimeException);
 };
 
 #endif
index 4bd974f..042efee 100644 (file)
@@ -28,8 +28,19 @@ namespace {
    void usage (std::string &response) {
      response += "\n\nInvalid arguments. Provide these ones:";
      response += "\n";
+     response += "\nSIGUSR2 Interface:";
      response += "\n<initial sequence>|<final sequence>|<test timeout ms (0: no timeout step)>|<digits>|<CCR-I xml file>[|CCR-T xml file]";
      response += "\n";
+     response += "\nREST Interface:";
+     response += "\n{";
+     response += "\n   \"seqI\":\"<initial sequence>\"";
+     response += "\n   ,\"seqF\":\"<final sequence>\"";
+     response += "\n   ,\"msecsTimeout\":\"<test timeout ms (0: no timeout step)>\"";
+     response += "\n   ,\"digits\":\"<digits>\"";
+     response += "\n   ,\"ccrI\":\"<CCR-I xml file>\"";
+     response += "\n   [,\"ccrT\":\"<CCR-T xml file>\"]";
+     response += "\n}";
+     response += "\n";
      response += "\nSequences are parsed when needed, over AVPs or internal values:";
      response += "\n";
      response += "\nSession-Id: <DiameterIdentity>;<high 32 bits>;<low 32 bits>[;<optional value>]";
@@ -278,8 +289,30 @@ void Procedure::execute(const std::string &args, std::string &response)  throw(a
     }
   } // loop
 
-  response = "Completed provision for pid "; response += anna::functions::asString(a_app->getPid()); response += "; range [";
+  response = "Completed provision: range [";
   response += seq_i; response += ", "; response += seq_f; response += "]; scenary: ";
   response += "CCR-Initial"; if (haveTermination) response += " + CCR-Termination";
 }
 
+void Procedure::execute(const nlohmann::json &args, std::string &response)  throw(anna::RuntimeException) {
+
+  // Build the arguments string and call the previous centralized logic procedure execution:
+  //   <initial sequence>|<final sequence>|<test timeout ms (0: no timeout step)>|<digits>|<CCR-I xml file>[|CCR-T xml file]
+  const char *arg_names[6] = { "seqI", "seqF", "msecsTimeout", "digits", "ccrI", "ccrT" };
+  std::string args_string, arg, pipe("|");
+
+  for (int i = 0; i < 6; i++)
+  {
+    auto it = args.find(arg_names[i]);
+    arg = (it != args.end() && it->is_string()) ? *it : "";
+    if (arg != "") args_string += arg + pipe;
+  }
+
+  // Remove last 'pipe':
+  if (args_string != "")
+    args_string = args_string.substr(0, args_string.size()-1);
+
+  execute(args_string, response);
+}
+
+
index d6afe5b..7c833f9 100644 (file)
@@ -11,6 +11,7 @@
 
 // Project
 #include <anna/comm/comm.hpp>
+#include <anna/json/json.hpp>
 
 
 class Procedure {
@@ -20,6 +21,7 @@ class Procedure {
     Procedure(anna::comm::Application *app) : a_app(app) {;}
 
     virtual void execute(const std::string &args, std::string &response)  throw(anna::RuntimeException);
+    virtual void execute(const nlohmann::json &args, std::string &response)  throw(anna::RuntimeException);
 };
 
 #endif
index 8d8a97d..a3ee7ba 100644 (file)
 // Process
 #include <EventOperation.hpp>
 #include <Launcher.hpp>
+#include <Procedure.hpp>
+#include <MyDiameterEngine.hpp>
+#include <MyLocalServer.hpp>
+#include <anna/testing/TestManager.hpp>
 
-// Project
-#include <anna/diameter.comm/OriginHost.hpp>
-
-
-//// Standard
-//#include <sstream>      // std::istringstream
-//#include <iostream>     // std::cout
+// Standard
 #include <fstream>
-//#include <math.h> // ceil
-//#include <climits>
 #include <unistd.h> // chdir
-//#include <stdio.h>
-//
-//// Project
+
+// Project
+#include <anna/diameter.comm/OriginHost.hpp>
 #include <anna/json/functions.hpp>
 #include <anna/diameter/codec/Message.hpp>
-//#include <anna/timex/Engine.hpp>
-//#include <anna/statistics/Engine.hpp>
-//#include <anna/diameter/codec/functions.hpp>
-//#include <anna/diameter/codec/Engine.hpp>
-//#include <anna/diameter/codec/EngineManager.hpp>
-//#include <anna/http/Transport.hpp>
-//#include <anna/diameter/stack/Engine.hpp>
 #include <anna/diameter/helpers/base/functions.hpp>
 #include <anna/time/functions.hpp>
 #include <anna/core/functions.hpp>
-//#include <anna/diameter.comm/ApplicationMessageOamModule.hpp>
-//#include <anna/testing/defines.hpp>
 #include <anna/xml/xml.hpp>
-//#include <anna/diameter.comm/OriginHost.hpp>
-//#include <anna/diameter.comm/OriginHostManager.hpp>
 #include <anna/diameter.comm/Message.hpp>
-//
-//// Process
-//#include <Launcher.hpp>
-//#include <Procedure.hpp>
-//#include <EventOperation.hpp>
-#include <MyDiameterEngine.hpp>
-#include <MyLocalServer.hpp>
-#include <anna/testing/TestManager.hpp>
-//#include <anna/testing/TestCase.hpp>
 
 
 /////////////////////
@@ -1236,3 +1212,21 @@ bool EventOperation::test__dump_stdout(std::string &response, bool enable) {
   return true; // OK
 }
 
+bool EventOperation::test__dynamic(std::string &response, const nlohmann::json &arguments) {
+
+  Launcher& my_app = static_cast <Launcher&>(anna::app::functions::getApp());
+
+  Procedure p(&my_app);
+  try {
+    p.execute(arguments, response);
+  }
+  catch(anna::RuntimeException &ex) {
+    ex.trace();
+    response += ex.asString();
+    return false;
+  }
+
+  return true; // OK
+}
+
+
index 1f0a4ab..dd54020 100644 (file)
@@ -9,42 +9,11 @@
 #ifndef example_diameter_launcher_EventOperation_hpp
 #define example_diameter_launcher_EventOperation_hpp
 
-// Project
-
 // STL
 #include <string>
 
-//// Standard
-//#include <sstream>      // std::istringstream
-//#include <iostream>     // std::cout
-//#include <math.h> // ceil
-//#include <climits>
-//#include <unistd.h> // chdir
-//#include <stdio.h>
-//
 // Project
-//#include <anna/timex/Engine.hpp>
-//#include <anna/statistics/Engine.hpp>
-//#include <anna/diameter/codec/functions.hpp>
-//#include <anna/diameter/codec/Engine.hpp>
-//#include <anna/diameter/codec/EngineManager.hpp>
-//#include <anna/http/Transport.hpp>
-//#include <anna/diameter/stack/Engine.hpp>
-//#include <anna/diameter/helpers/base/functions.hpp>
-//#include <anna/time/functions.hpp>
-//#include <anna/diameter.comm/ApplicationMessageOamModule.hpp>
-//#include <anna/testing/defines.hpp>
-//#include <anna/xml/xml.hpp>
-//#include <anna/diameter.comm/OriginHost.hpp>
-//#include <anna/diameter.comm/OriginHostManager.hpp>
-//
-//// Process
-//#include <Launcher.hpp>
-//#include <Procedure.hpp>
-//#include <EventOperation.hpp>
-//#include <MyDiameterEngine.hpp>
-//#include <anna/testing/TestManager.hpp>
-//#include <anna/testing/TestCase.hpp>
+#include <anna/json/json.hpp>
 
 
 class EventOperation {
@@ -150,6 +119,9 @@ public:
                     const std::string & state = "all" /* initialized|in-progress|failed|success|[all]|none */, bool enable = true);
   bool test__report_hex(std::string &response, bool enable = true);
   bool test__dump_stdout(std::string &response, bool enable = true);
+
+  // Dynamic procedure
+  bool test__dynamic(std::string &response, const nlohmann::json &arguments);
 };
 
 #endif
index d2433c8..9086582 100644 (file)
@@ -368,31 +368,6 @@ bool MyHandler::doPOST(const std::string &uri, const nlohmann::json &j, std::str
     auto it = j.find("condition");
     if (it != j.end() && it->is_object()) {
 
-/*
-      auto j2 = it->get<nlohmann::json::object_t>();
-
-      // [code]|[bitR]|[hopByHop]|[applicationId]|[sessionId]|[resultCode]|[msisdn]|[imsi]|[serviceContextId]
-      auto it_code = j2.find("code");
-      auto it_bitR = j2.find("bitR");
-      auto it_hopByHop = j2.find("hopByHop");
-      auto it_applicationId = j2.find("applicationId");
-      auto it_sessionId = j2.find("sessionId");
-      auto it_resultCode = j2.find("resultCode");
-      auto it_msisdn = j2.find("msisdn");
-      auto it_imsi = j2.find("imsi");
-      auto it_serviceContextId = j2.find("serviceContextId");
-
-      std::string p1 = (it_code != j2.end() && it_code->is_string()) ? *it_code : "";
-      std::string p2 = (it_bitR != j2.end() && it_bitR->is_string()) ? *it_bitR : "";
-      std::string p3 = (it_hopByHop != it->end() && it_hopByHop->is_string()) ? *it_hopByHop : "";
-      std::string p4 = (it_applicationId != it->end() && it_applicationId->is_string()) ? *it_applicationId : "";
-      std::string p5 = (it_sessionId != it->end() && it_sessionId->is_string()) ? *it_sessionId : "";
-      std::string p6 = (it_resultCode != it->end() && it_resultCode->is_string()) ? *it_resultCode : "";
-      std::string p7 = (it_msisdn != it->end() && it_msisdn->is_string()) ? *it_msisdn : "";
-      std::string p8 = (it_imsi != it->end() && it_imsi->is_string()) ? *it_imsi : "";
-      std::string p9 = (it_serviceContextId != it->end() && it_serviceContextId->is_string()) ? *it_serviceContextId : "";
-*/
-
       // [code]|[bitR]|[hopByHop]|[applicationId]|[sessionId]|[resultCode]|[msisdn]|[imsi]|[serviceContextId]
       auto it_code = it->find("code");
       auto it_bitR = it->find("bitR");
@@ -561,6 +536,16 @@ bool MyHandler::doPOST(const std::string &uri, const nlohmann::json &j, std::str
       response += "invalid 'action' string field (allowed: enable|disable)";
   }
 
+  // Dynamic procedure
+  else if ((opType == "/dynamic")) {
+    auto it = j.find("arguments");
+    if (it != j.end() && it->is_object()) {
+      result = eop.test__dynamic(response, *it);
+    }
+    else
+      response += "missing 'arguments' object field";
+  }
+
 
   return result;
 }
index abb842a..732e938 100644 (file)
@@ -1523,7 +1523,7 @@ Value '-1' means 'no limit' (full parallel). Be careful with resources consumpti
 }
 ```
 
-### EXECUTION ACTIONS
+### FSM TESTING EXECUTION ACTIONS
 
 #### POST /test-ttps
 
@@ -1903,5 +1903,26 @@ As it could be very large, it will be dumped on provided target directory, '*/tm
 }
 ```
 
+### DYNAMIC PROCEDURE
 
+Used for system test. Arguments are determined by the way in a specific dynamic library is designed/documented.
+
+#### POST /dynamic
+
+**Request body**:
+
+```
+{
+    "dynamic": <diameter arguments json object>
+}
+```
+
+**Response body**:
+
+```
+{
+    "result":"<true or false>",
+    "response":"<response>"
+}
+```
 
index f51f0d1..0f9fd18 100755 (executable)
@@ -7,13 +7,12 @@
 REPO_DIR="$(git rev-parse --show-toplevel 2>/dev/null)"
 [ -z "$REPO_DIR" ] && { echo "You must execute under a valid git repository !" ; exit 1 ; }
 
-SERVICES=${REPO_DIR}/example/diameter/launcher/resources/rest_api/ct/resources/servicesJson-request.json
+SERVICES=${REPO_DIR}/example/diameter/launcher/resources/rest_api/ct/resources/servicesRxJson-request.json
 ENDPOINT=http://localhost:8074
 PORT=$(echo ${ENDPOINT} | cut -d: -f3)
-C_NAME=anna-adml-http
+SVC_NAME=anna-adml-http
 # Entrypoint arguments (ft/st):
 EXTRA_ARGUMENTS= # default is ft (function test: debug traces and traffic logs)
-EXTRA_ARGUMENTS=st
 
 VARIANT=${1:-Release}
 
@@ -34,22 +33,28 @@ nghttp --version 2>/dev/null
 echo "Requirement found !"
 
 echo
-echo "Rebuild ADML HTTP service image (y/n) [n]:"
+echo "Rebuild ADML HTTP service image (y/n) [y]:"
 read opt
-[ -z "${opt}" ] && opt=n
+[ -z "${opt}" ] && opt=y
 if [ "${opt}" = "y" ]
 then
   ${REPO_DIR}/tools/build-anna-adml-http ${VARIANT}
 fi
 
 version=$(${REPO_DIR}/tools/version)
-[ "${VARIANT}" = "Debug" ] && { version=${version}-debug ; C_NAME=${C_NAME}-debug ; }
-echo "Restart ADML HTTP service image (version '${version}') ..."
-docker kill ${C_NAME} &>/dev/null
-cid=$(docker run --rm -d -p ${PORT}:${PORT} --name ${C_NAME} anna-adml-http:${version} ${EXTRA_ARGUMENTS} >/dev/null)
-[ $? -ne 0 ] && exit 1
-
-echo "Container id: ${cid} (deployed as '${C_NAME}')"
+cname=${SVC_NAME}
+[ "${VARIANT}" = "Debug" ] && { version=${version}-debug ; cname=${cname}-debug ; }
+echo "Restart ADML HTTP service image (version '${version}') (y/n) [y]:"
+read opt
+[ -z "${opt}" ] && opt=y
+if [ "${opt}" = "y" ]
+then
+  docker kill ${SVC_NAME} &>/dev/null
+  docker kill ${SVC_NAME}-debug &>/dev/null
+  cid=$(docker run --rm -d -p ${PORT}:${PORT} --name ${cname} anna-adml-http:${version} ${EXTRA_ARGUMENTS} >/dev/null)
+  [ $? -ne 0 ] && exit 1
+  echo "Container id: ${cid} (deployed as '${cname}')"
+fi
 
 echo
 echo "Configuring services (${SERVICES}) ..."
@@ -68,7 +73,7 @@ echo
 echo -n "Waiting for server-client connection ..."
 while true
 do
-  docker exec -it ${C_NAME} bash -c "netstat -p \$(pgrep ADML) --numeric-ports | grep -w 3868 | grep -w ESTABLISHED" >/dev/null
+  docker exec -it ${cname} bash -c "netstat -p \$(pgrep ADML) --numeric-ports | grep -w 3868 | grep -w ESTABLISHED" >/dev/null
   [ $? -eq 0 ] && break
   echo -n .
   sleep 1
diff --git a/example/diameter/launcher/resources/rest_api/ct/dynamic-procedure/__init__.py b/example/diameter/launcher/resources/rest_api/ct/dynamic-procedure/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/example/diameter/launcher/resources/rest_api/ct/dynamic-procedure/dynamic_test.py b/example/diameter/launcher/resources/rest_api/ct/dynamic-procedure/dynamic_test.py
new file mode 100644 (file)
index 0000000..6876e21
--- /dev/null
@@ -0,0 +1,30 @@
+import pytest
+
+
+@pytest.mark.dynamic
+@pytest.mark.xfail(reason="This will fail if already provisioned (if you want success here, restart the ADML HTTP Service)")
+def test_001_given_servicesGx_json_representation_i_want_to_load_it_through_adml_service(resources, admlc):
+
+  # Load services for Gx interface (client/server nodes & Gx dictionary):
+  requestBody = resources("servicesGxJson-request.json")
+  responseBodyRef = { "success":"true", "response":"loaded services correctly" }
+
+  # Send POST
+  response = admlc.post("/services", requestBody)
+
+  ### Verify response
+  admlc.assert_response__status_body_headers(response, 200, responseBodyRef)
+
+@pytest.mark.dynamic
+def test_002_provision_dynamic_operation_for_gx_at_adml(resources, admlc):
+
+  # Dynamic procedure:
+  requestBody = resources("arguments-request.json")
+  responseBodyRef = { "success":"true", "response":"Completed provision: range [0000, 9000]; scenary: CCR-Initial + CCR-Termination" }
+
+  # Send POST
+  response = admlc.post("/dynamic", requestBody)
+
+  # Verify response
+  admlc.assert_response__status_body_headers(response, 200, responseBodyRef)
+
index 5c90efc..4d8d18e 100644 (file)
@@ -5,7 +5,8 @@ import pytest
 @pytest.mark.xfail(reason="This will fail if already provisioned (if you want success here, restart the ADML HTTP Service)")
 def test_001_given_services_json_representation_i_want_to_load_it_through_adml_service(resources, admlc):
 
-  requestBody = resources("servicesJson-request.json")
+  # Load services for Rx interface (client/server nodes & Gx dictionary):
+  requestBody = resources("servicesRxJson-request.json")
   responseBodyRef = { "success":"true", "response":"loaded services correctly" }
 
   # Send POST
index e2fc311..cd7b596 100644 (file)
@@ -1,5 +1,5 @@
 [pytest]
-addopts = -v --junitxml=/tmp/junit.xml -n 0
+addopts = -v --junitxml=/tmp/junit.xml -n 0 -m "not dynamic"
 
 log_format=%(asctime)s|%(name)s|%(filename)s:%(lineno)d|%(levelname)s|%(message)s
 log_date_format = %Y-%m-%d %H:%M:%S
@@ -12,3 +12,12 @@ log_level=INFO
 junit_suite_name = "ADML REST API"
 junit_logging = out-err
 junit_family = xunit1
+
+# Naming conventions
+#python_files = check_*.py
+#python_classes = Check
+#python_functions = *_check
+
+# Markers
+markers =
+ dynamic: this test requires ADML dynamic test library enabled (/opt/adml/dynlibs)
diff --git a/example/diameter/launcher/resources/rest_api/ct/resources/arguments-request.json b/example/diameter/launcher/resources/rest_api/ct/resources/arguments-request.json
new file mode 100644 (file)
index 0000000..78f591b
--- /dev/null
@@ -0,0 +1,10 @@
+{
+  "arguments": {
+    "seqI":"0000",
+    "seqF":"9000",
+    "msecsTimeout":"5000",
+    "digits":"4",
+    "ccrI":"/opt/adml/dynlibs/gx/00001/CCR-I.xml",
+    "ccrT":"/opt/adml/dynlibs/gx/00001/CCR-T.xml"
+  }
+}
diff --git a/example/diameter/launcher/resources/rest_api/ct/resources/servicesGxJson-request.json b/example/diameter/launcher/resources/rest_api/ct/resources/servicesGxJson-request.json
new file mode 100644 (file)
index 0000000..521b88f
--- /dev/null
@@ -0,0 +1,25 @@
+{
+   "servicesJson": {
+      "services": {
+         "node": [
+            {
+               "@applicationId": "16777238",
+               "@entity": "localhost:3869",
+               "@originHost": "ggsnNodeHostname.nodeHostRealm.com"
+            },
+            {
+               "@applicationId": "16777238",
+               "@diameterServer": "localhost:3869",
+               "@diameterServerSessions": "1",
+               "@originHost": "ownHostIdGx.operatorRealm.com"
+            }
+         ],
+         "stack": {
+            "@dictionary": "stacks/DictionaryGx.16777238.xml",
+            "@fixMode": "Always",
+            "@id": "16777238",
+            "@ignoreFlagsOnValidation": "yes"
+         }
+      }
+   }
+}
diff --git a/example/diameter/launcher/resources/rest_api/ct/resources/servicesJson-request.json b/example/diameter/launcher/resources/rest_api/ct/resources/servicesJson-request.json
deleted file mode 100644 (file)
index 0aa21cd..0000000
+++ /dev/null
@@ -1,25 +0,0 @@
-{
-   "servicesJson": {
-      "services": {
-         "node": [
-            {
-               "@applicationId": "16777236",
-               "@entity": "localhost:3868",
-               "@originHost": "afHost.afRealm.com"
-            },
-            {
-               "@applicationId": "16777236",
-               "@diameterServer": "localhost:3868",
-               "@diameterServerSessions": "1",
-               "@originHost": "ownHostId.operatorRealm.com"
-            }
-         ],
-         "stack": {
-            "@dictionary": "stacks/DictionaryRx.16777236.xml",
-            "@fixMode": "Always",
-            "@id": "16777236",
-            "@ignoreFlagsOnValidation": "yes"
-         }
-      }
-   }
-}
diff --git a/example/diameter/launcher/resources/rest_api/ct/resources/servicesRxJson-request.json b/example/diameter/launcher/resources/rest_api/ct/resources/servicesRxJson-request.json
new file mode 100644 (file)
index 0000000..0aa21cd
--- /dev/null
@@ -0,0 +1,25 @@
+{
+   "servicesJson": {
+      "services": {
+         "node": [
+            {
+               "@applicationId": "16777236",
+               "@entity": "localhost:3868",
+               "@originHost": "afHost.afRealm.com"
+            },
+            {
+               "@applicationId": "16777236",
+               "@diameterServer": "localhost:3868",
+               "@diameterServerSessions": "1",
+               "@originHost": "ownHostId.operatorRealm.com"
+            }
+         ],
+         "stack": {
+            "@dictionary": "stacks/DictionaryRx.16777236.xml",
+            "@fixMode": "Always",
+            "@id": "16777236",
+            "@ignoreFlagsOnValidation": "yes"
+         }
+      }
+   }
+}