Repository: google/gnxi (language: python)

Issue:
gnmi_target supports Sample STREAM Subscriptions
Add support for Sample STREAM Subscriptions to gnmi_target.

Attempting to resolve https://github.com/google/gnxi/issues/266 to some extend.

Functionally tested with clients gnmi_target and gnmic. Capable of serving multiple subscribers.


Discussion / hints:

Fix patch:
diff --git a/oc_config_validate/demo/README.md b/oc_config_validate/demo/README.md
index cb7b2239..2dc0ed59 100644
--- a/oc_config_validate/demo/README.md
+++ b/oc_config_validate/demo/README.md
@@ -3,9 +3,9 @@
 1. It starts an instance of [gnmmi_target](../gnmi_target) with a sample
    configuration.
 
-1. It runs `oc_config_validate` with a [sample test file](demo/tests.yaml).
+1. It runs `oc_config_validate` with [sample test files](demo/*_tests.yaml).
 
-1. It writes the results to [a file](demo/results.json).
+1. It writes the results to [JSON file](demo/*_results.json).
 
 ## On Docker
 
diff --git a/oc_config_validate/demo/results.json b/oc_config_validate/demo/config_results.json
similarity index 90%
rename from oc_config_validate/demo/results.json
rename to oc_config_validate/demo/config_results.json
index 6bf9e550..ed2c4ba6 100644
--- a/oc_config_validate/demo/results.json
+++ b/oc_config_validate/demo/config_results.json
@@ -5,8 +5,8 @@
     "FOO",
     "BAR"
   ],
-  "start_time_sec": 1674626964.7746754,
-  "end_time_sec": 1674626975.5594006,
+  "start_time_sec": 1681289912.9265878,
+  "end_time_sec": 1681289923.6248236,
   "results": [
     {
       "test_name": "Get System Config",
@@ -17,7 +17,7 @@
           "test_case": "test0200",
           "test_class": "Get",
           "test_module": "oc_config_validate.testcases",
-          "start_time_sec": 1674626964,
+          "start_time_sec": 1681289912,
           "duration_sec": 0,
           "result": "PASS",
           "log": ""
@@ -27,14 +27,14 @@
     {
       "test_name": "Check System Config",
       "success": true,
-      "duration_sec": 0,
+      "duration_sec": 1,
       "results": [
         {
           "test_case": "testGetJsonCheck",
           "test_class": "GetJsonCheck",
           "test_module": "oc_config_validate.testcases",
-          "start_time_sec": 1674626964,
-          "duration_sec": 0,
+          "start_time_sec": 1681289912,
+          "duration_sec": 1,
           "result": "PASS",
           "log": ""
         }
@@ -49,7 +49,7 @@
           "test_case": "testGetJsonCheck",
           "test_class": "GetJsonCheck",
           "test_module": "oc_config_validate.testcases",
-          "start_time_sec": 1674626964,
+          "start_time_sec": 1681289913,
           "duration_sec": 0,
           "result": "PASS",
           "log": ""
@@ -65,7 +65,7 @@
           "test_case": "testGetCompare",
           "test_class": "GetCompare",
           "test_module": "oc_config_validate.testcases",
-          "start_time_sec": 1674626964,
+          "start_time_sec": 1681289913,
           "duration_sec": 0,
           "result": "PASS",
           "log": ""
@@ -81,7 +81,7 @@
           "test_case": "testGetCompare",
           "test_class": "GetCompare",
           "test_module": "oc_config_validate.testcases",
-          "start_time_sec": 1674626964,
+          "start_time_sec": 1681289913,
           "duration_sec": 0,
           "result": "PASS",
           "log": ""
@@ -97,7 +97,7 @@
           "test_case": "testGetCompare",
           "test_class": "GetCompare",
           "test_module": "oc_config_validate.testcases",
-          "start_time_sec": 1674626964,
+          "start_time_sec": 1681289913,
           "duration_sec": 0,
           "result": "PASS",
           "log": ""
@@ -113,10 +113,10 @@
           "test_case": "testGetCompare",
           "test_class": "GetCompare",
           "test_module": "oc_config_validate.testcases",
-          "start_time_sec": 1674626964,
+          "start_time_sec": 1681289913,
           "duration_sec": 0,
           "result": "FAIL",
-          "log": "\n Traceback (most recent call last):\n  File \"/home/itamayo/gnxi/oc_config_validate/oc_config_validate/testbase.py\", line 56, in inner\n    method(self, *args, **kw)\n  File \"/home/itamayo/gnxi/oc_config_validate/oc_config_validate/testcases/get.py\", line 50, in testGetCompare\n    self.assertTrue(cmp,  diff)\nAssertionError: False is not true : key openconfig-interfaces:enabled: got 'True', wanted 'False'\n"
+          "log": "\n Traceback (most recent call last):\n  File \"/home/itamayo/gnxi/oc_config_validate/oc_config_validate/testbase.py\", line 56, in inner\n    method(self, *args, **kw)\n  File \"/home/itamayo/gnxi/oc_config_validate/oc_config_validate/testcases/get.py\", line 49, in testGetCompare\n    self.assertTrue(cmp,  diff)\nAssertionError: False is not true : key openconfig-interfaces:enabled: got 'True', wanted 'False'\n"
         }
       ]
     },
@@ -129,7 +129,7 @@
           "test_case": "testSetUpdate",
           "test_class": "SetUpdate",
           "test_module": "oc_config_validate.testcases",
-          "start_time_sec": 1674626964,
+          "start_time_sec": 1681289913,
           "duration_sec": 2,
           "result": "PASS",
           "log": ""
@@ -145,7 +145,7 @@
           "test_case": "testGetJsonCheckCompare",
           "test_class": "GetJsonCheckCompare",
           "test_module": "oc_config_validate.testcases",
-          "start_time_sec": 1674626966,
+          "start_time_sec": 1681289915,
           "duration_sec": 0,
           "result": "PASS",
           "log": ""
@@ -155,14 +155,14 @@
     {
       "test_name": "Bad model to compare System config",
       "success": true,
-      "duration_sec": 1,
+      "duration_sec": 0,
       "results": [
         {
           "test_case": "testGetJsonCheckCompare",
           "test_class": "GetJsonCheckCompare",
           "test_module": "oc_config_validate.testcases",
-          "start_time_sec": 1674626966,
-          "duration_sec": 1,
+          "start_time_sec": 1681289915,
+          "duration_sec": 0,
           "result": "PASS",
           "log": ""
         }
@@ -177,10 +177,10 @@
           "test_case": "testGetJsonCheckCompare",
           "test_class": "GetJsonCheckCompare",
           "test_module": "oc_config_validate.testcases",
-          "start_time_sec": 1674626967,
+          "start_time_sec": 1681289915,
           "duration_sec": 0,
           "result": "FAIL",
-          "log": "\n Traceback (most recent call last):\n  File \"/home/itamayo/gnxi/oc_config_validate/oc_config_validate/testbase.py\", line 56, in inner\n    method(self, *args, **kw)\n  File \"/home/itamayo/gnxi/oc_config_validate/oc_config_validate/testcases/get.py\", line 117, in testGetJsonCheckCompare\n    self.assertTrue(cmp, diff)\nAssertionError: False is not true : key openconfig-system:domain-name: got 'foo.bar.com', wanted 'la.la.la.com'\n"
+          "log": "\n Traceback (most recent call last):\n  File \"/home/itamayo/gnxi/oc_config_validate/oc_config_validate/testbase.py\", line 56, in inner\n    method(self, *args, **kw)\n  File \"/home/itamayo/gnxi/oc_config_validate/oc_config_validate/testcases/get.py\", line 108, in testGetJsonCheckCompare\n    self.assertTrue(cmp, diff)\nAssertionError: False is not true : key openconfig-system:domain-name: got 'foo.bar.com', wanted 'la.la.la.com'\n"
         }
       ]
     },
@@ -193,7 +193,7 @@
           "test_case": "testJsonCheckSetUpdate",
           "test_class": "JsonCheckSetUpdate",
           "test_module": "oc_config_validate.testcases",
-          "start_time_sec": 1674626967,
+          "start_time_sec": 1681289915,
           "duration_sec": 2,
           "result": "PASS",
           "log": ""
@@ -209,7 +209,7 @@
           "test_case": "testGetJsonCheckCompare",
           "test_class": "GetJsonCheckCompare",
           "test_module": "oc_config_validate.testcases",
-          "start_time_sec": 1674626969,
+          "start_time_sec": 1681289917,
           "duration_sec": 0,
           "result": "PASS",
           "log": ""
@@ -225,7 +225,7 @@
           "test_case": "testSetGetJsonCheck",
           "test_class": "SetGetJsonCheck",
           "test_module": "oc_config_validate.testcases",
-          "start_time_sec": 1674626969,
+          "start_time_sec": 1681289917,
           "duration_sec": 2,
           "result": "PASS",
           "log": ""
@@ -241,7 +241,7 @@
           "test_case": "testSetGetJsonCheckCompare",
           "test_class": "SetGetJsonCheckCompare",
           "test_module": "oc_config_validate.testcases",
-          "start_time_sec": 1674626971,
+          "start_time_sec": 1681289919,
           "duration_sec": 2,
           "result": "PASS",
           "log": ""
@@ -257,7 +257,7 @@
           "test_case": "testSetConfigCheckState",
           "test_class": "SetConfigCheckState",
           "test_module": "oc_config_validate.testcases",
-          "start_time_sec": 1674626973,
+          "start_time_sec": 1681289921,
           "duration_sec": 2,
           "result": "FAIL",
           "log": "\n Traceback (most recent call last):\n  File \"/home/itamayo/gnxi/oc_config_validate/oc_config_validate/testcases/config_state.py\", line 72, in testSetConfigCheckState\n    self._get_check_retry()\n  File \"/home/itamayo/gnxi/oc_config_validate/oc_config_validate/testbase.py\", line 56, in inner\n    method(self, *args, **kw)\n  File \"/home/itamayo/gnxi/oc_config_validate/oc_config_validate/testcases/config_state.py\", line 64, in _get_check_retry\n    self.assertTrue(cmp, diff)\nAssertionError: False is not true : key openconfig-system:timezone-name: got 'Europe/Stockholm', wanted 'Europe/Paris'\n"
diff --git a/oc_config_validate/demo/run_demo.sh b/oc_config_validate/demo/run_demo.sh
index f45abd5b..27054a23 100755
--- a/oc_config_validate/demo/run_demo.sh
+++ b/oc_config_validate/demo/run_demo.sh
@@ -65,9 +65,12 @@ start_oc_config_validate() {
     if [[ "$VERBOSE" -eq 1 ]]; then
         OPTS="$OPTS --verbose"
     fi
-    echo "--- Start oc_config_validate $OPTS"
-    PYTHONPATH="$PYTHONPATH:${BASEDIR}/.." python3 -m oc_config_validate --target "localhost:$1" --tests_file $BASEDIR/tests.yaml --results_file $BASEDIR/results.json --init_config_file $BASEDIR/init_config.json --init_config_xpath "/system/config" --target_cert_as_root_ca $OPTS
-
+    for t in config telemetry; do
+      echo
+      echo "--- Run oc_config_validate $OPTS for $t"
+      echo
+      PYTHONPATH="$PYTHONPATH:${BASEDIR}/.." python3 -m oc_config_validate --target "localhost:$1" --tests_file $BASEDIR/${t}_tests.yaml --results_file $BASEDIR/${t}_results.json --init_config_file $BASEDIR/init_config.json --init_config_xpath "/system/config" --target_cert_as_root_ca $OPTS
+    done
   }
 
 parse_options() {
@@ -120,7 +123,10 @@ main() {
         fi
         if [[ ! -f ${BASEDIR}/gnmi_target ]]; then
             echo "--- Building gNMI TARGET"
-            build_gnmi_target
+            if ! build_gnmi_target; then
+              echo "ERR: Unable to build gnmi_target"
+              return 1
+            fi
         fi
 
         start_gnmi_target "${GNMI_PORT}"
diff --git a/oc_config_validate/demo/telemetry_results.json b/oc_config_validate/demo/telemetry_results.json
new file mode 100644
index 00000000..e37908a9
--- /dev/null
+++ b/oc_config_validate/demo/telemetry_results.json
@@ -0,0 +1,175 @@
+{
+  "test_target": "localhost:9339",
+  "description": "Example of test profile for oc_config_validator",
+  "labels": [
+    "FOO",
+    "BAR"
+  ],
+  "start_time_sec": 1683576220.4079309,
+  "end_time_sec": 1683576239.5437305,
+  "results": [
+    {
+      "test_name": "Subscribe ONCE to System String values",
+      "success": true,
+      "duration_sec": 0,
+      "results": [
+        {
+          "test_case": "test100",
+          "test_class": "CountUpdatesCheckType",
+          "test_module": "oc_config_validate.testcases",
+          "start_time_sec": 1683576220,
+          "duration_sec": 0,
+          "result": "PASS",
+          "log": ""
+        }
+      ]
+    },
+    {
+      "test_name": "Subscribe ONCE to AAA State",
+      "success": true,
+      "duration_sec": 0,
+      "results": [
+        {
+          "test_case": "test100",
+          "test_class": "CheckLeafs",
+          "test_module": "oc_config_validate.testcases",
+          "start_time_sec": 1683576220,
+          "duration_sec": 0,
+          "result": "PASS",
+          "log": ""
+        }
+      ]
+    },
+    {
+      "test_name": "Subscribe ONCE to all Interfaces oper-status",
+      "success": true,
+      "duration_sec": 0,
+      "results": [
+        {
+          "test_case": "test100",
+          "test_class": "CountUpdatesCheckType",
+          "test_module": "oc_config_validate.testcases",
+          "start_time_sec": 1683576220,
+          "duration_sec": 0,
+          "result": "PASS",
+          "log": ""
+        }
+      ]
+    },
+    {
+      "test_name": "Bad - 2 Notifications expected",
+      "success": false,
+      "duration_sec": 0,
+      "results": [
+        {
+          "test_case": "test100",
+          "test_class": "CountUpdatesCheckType",
+          "test_module": "oc_config_validate.testcases",
+          "start_time_sec": 1683576220,
+          "duration_sec": 0,
+          "result": "FAIL",
+          "log": "\n Traceback (most recent call last):\n  File \"/home/itamayo/gnxi/oc_config_validate/oc_config_validate/testcases/telemetry_once.py\", line 63, in test100\n    self.subscribeOnce()\n  File \"/home/itamayo/gnxi/oc_config_validate/oc_config_validate/testcases/telemetry_once.py\", line 41, in subscribeOnce\n    self.assertEqual(\nAssertionError: 1 != 2 : Expected 2 notifications, got 1\n"
+        }
+      ]
+    },
+    {
+      "test_name": "Bad - Expected Boolean value",
+      "success": false,
+      "duration_sec": 0,
+      "results": [
+        {
+          "test_case": "test100",
+          "test_class": "CountUpdatesCheckType",
+          "test_module": "oc_config_validate.testcases",
+          "start_time_sec": 1683576220,
+          "duration_sec": 0,
+          "result": "FAIL",
+          "log": "\n Traceback (most recent call last):\n  File \"/home/itamayo/gnxi/oc_config_validate/oc_config_validate/testcases/telemetry_once.py\", line 73, in test100\n    self.assertTrue(\nAssertionError: False is not true : Value of Update /interfaces/interface[name=mgmt]/state/enabled is not of type string_val: bool_val: true\n\n"
+        }
+      ]
+    },
+    {
+      "test_name": "Bad - Expected 4 Updates",
+      "success": false,
+      "duration_sec": 0,
+      "results": [
+        {
+          "test_case": "test100",
+          "test_class": "CountUpdatesCheckType",
+          "test_module": "oc_config_validate.testcases",
+          "start_time_sec": 1683576220,
+          "duration_sec": 0,
+          "result": "FAIL",
+          "log": "\n Traceback (most recent call last):\n  File \"/home/itamayo/gnxi/oc_config_validate/oc_config_validate/testcases/telemetry_once.py\", line 78, in test100\n    self.assertEqual(\nAssertionError: 3 != 4 : Expected 4 Updates, got: 3\n"
+        }
+      ]
+    },
+    {
+      "test_name": "Subscribe SAMPLE to AAA State",
+      "success": true,
+      "duration_sec": 7,
+      "results": [
+        {
+          "test_case": "testSubscribeSample",
+          "test_class": "CheckLeafs",
+          "test_module": "oc_config_validate.testcases",
+          "start_time_sec": 1683576220,
+          "duration_sec": 7,
+          "result": "PASS",
+          "log": ""
+        }
+      ]
+    },
+    {
+      "test_name": "Subscribe SAMPLE to all Interfaces oper-status",
+      "success": true,
+      "duration_sec": 7,
+      "results": [
+        {
+          "test_case": "testSubscribeSample",
+          "test_class": "CountUpdates",
+          "test_module": "oc_config_validate.testcases",
+          "start_time_sec": 1683576227,
+          "duration_sec": 7,
+          "result": "PASS",
+          "log": ""
+        }
+      ]
+    },
+    {
+      "test_name": "Bad - Wrong model",
+      "success": false,
+      "duration_sec": 0,
+      "results": [
+        {
+          "test_case": "testSubscribeSample",
+          "test_class": "CheckLeafs",
+          "test_module": "oc_config_validate.testcases",
+          "start_time_sec": 1683576234,
+          "duration_sec": 0,
+          "result": "FAIL",
+          "log": "\n Traceback (most recent call last):\n  File \"/home/itamayo/gnxi/oc_config_validate/oc_config_validate/testcases/telemetry_sample.py\", line 116, in testSubscribeSample\n    self.assertModelXpath(self.model, self.xpath)\n  File \"/home/itamayo/gnxi/oc_config_validate/oc_config_validate/testbase.py\", line 422, in assertModelXpath\n    self.assertTrue(\nAssertionError: False is not true : xpath interfaces/interface[name=mgmt]/state/enabled does not point to a container in model system.openconfig_system: 'openconfig_system' object has no attribute 'interfaces'\n"
+        }
+      ]
+    },
+    {
+      "test_name": "Bad - Expected 4 Update Paths",
+      "success": false,
+      "duration_sec": 5,
+      "results": [
+        {
+          "test_case": "testSubscribeSample",
+          "test_class": "CountUpdates",
+          "test_module": "oc_config_validate.testcases",
+          "start_time_sec": 1683576234,
+          "duration_sec": 5,
+          "result": "FAIL",
+          "log": "\n Traceback (most recent call last):\n  File \"/home/itamayo/gnxi/oc_config_validate/oc_config_validate/testcases/telemetry_sample.py\", line 93, in testSubscribeSample\n    self.assertEqual(\nAssertionError: 3 != 4 : Expected 4 Update paths, got: 3\n"
+        }
+      ]
+    }
+  ],
+  "tests_pass": 5,
+  "tests_total": 10,
+  "tests_fail": 5
+}
\ No newline at end of file


Test patch:
diff --git a/oc_config_validate/demo/tests.yaml b/oc_config_validate/demo/config_tests.yaml
similarity index 100%
rename from oc_config_validate/demo/tests.yaml
rename to oc_config_validate/demo/config_tests.yaml
diff --git a/oc_config_validate/demo/telemetry_tests.yaml b/oc_config_validate/demo/telemetry_tests.yaml
new file mode 100644
index 00000000..558fa5a9
--- /dev/null
+++ b/oc_config_validate/demo/telemetry_tests.yaml
@@ -0,0 +1,105 @@
+!TestContext
+description: Example of test profile for oc_config_validator
+
+labels:
+- FOO
+- BAR
+
+target:
+  !Target
+  target: "localhost:8080"
+  tls_host_override: "target.com"
+
+tests:
+
+- !TestCase
+  name: "Subscribe ONCE to System String values"
+  class_name: telemetry_once.CountUpdatesCheckType
+  args:
+    xpaths: 
+    - "system/state/hostname"
+    - "system/clock/state/timezone-name"
+    values_type: string_val
+    updates_count: 2
+    max_timestamp_drift_secs: 5
+  
+- !TestCase
+  name: "Subscribe ONCE to AAA State"
+  class_name: telemetry_once.CheckLeafs
+  args:
+    xpaths:
+    - "system/aaa/authentication/state"
+    model: system.openconfig_system
+
+- !TestCase
+  name: "Subscribe ONCE to all Interfaces oper-status"
+  class_name: telemetry_once.CountUpdatesCheckType
+  args:
+    xpaths:
+    - "interfaces/interface[name=*]/state/oper-status"
+    values_type: string_val
+    updates_count: 3
+
+- !TestCase
+  name: "Bad - 2 Notifications expected"
+  class_name: telemetry_once.CountUpdatesCheckType
+  args:
+    xpaths:
+    - "interfaces/interface[name=mgmt]/state/enabled"
+    values_type: bool_val
+    updates_count: 1
+    notifications_count: 2
+
+- !TestCase
+  name: "Bad - Expected Boolean value"
+  class_name: telemetry_once.CountUpdatesCheckType
+  args:
+    xpaths: 
+    - "interfaces/interface[name=mgmt]/state/enabled"
+    values_type: string_val
+    updates_count: 1
+
+- !TestCase
+  name: "Bad - Expected 4 Updates"
+  class_name: telemetry_once.CountUpdatesCheckType
+  args:
+    xpaths: 
+    - "interfaces/interface[name=*]/state/oper-status"
+    values_type: string_val
+    updates_count: 4
+  
+- !TestCase
+  name: "Subscribe SAMPLE to AAA State"
+  class_name: telemetry_sample.CheckLeafs
+  args:
+    xpath: "system/aaa/authentication/state"
+    model: system.openconfig_system
+    sample_interval: 2
+    sample_timeout: 7
+
+- !TestCase
+  name: "Subscribe SAMPLE to all Interfaces oper-status"
+  class_name: telemetry_sample.CountUpdates
+  args:
+    xpath: "interfaces/interface[name=*]/state/oper-status"
+    update_paths_count: 3
+    sample_interval: 2
+    sample_timeout: 7
+
+- !TestCase
+  name: "Bad - Wrong model"
+  class_name: telemetry_sample.CheckLeafs
+  args:
+    xpath: "interfaces/interface[name=mgmt]/state/enabled"
+    model: system.openconfig_system
+    sample_interval: 2
+    sample_timeout: 5
+    
+- !TestCase
+  name: "Bad - Expected 4 Update Paths"
+  class_name: telemetry_sample.CountUpdates
+  args:
+    xpath: "interfaces/interface[name=*]/state/oper-status"
+    sample_interval: 2
+    sample_timeout: 5
+    update_paths_count: 4