Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 52 additions & 3 deletions oc_config_validate/docs/testclasses.md
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ Update paths received in the Subscription reply are checked against the
Leafs of the Model (Update paths must match an OC Model Leaf).

Optionally, use `check_missing_model_paths` to assert that all OC model paths
are present in the Updates. Usually, the Subscription replies might not
are present in the Updates. The Subscription replies might not
have all Leaf paths that the OC mode has.

> This check does NOT check the type of the values returned.
Expand All @@ -406,6 +406,23 @@ Args:
* *check_missing_model_paths*: If True, it asserts that all OC Model Leaf
paths are in the received Updates. Defaults to False.

#### `telemetry_once.CheckLeafsFromList`

In addition to the default checks of the module, this test checks that the
subscription responses have all expected update paths.

The Update paths received in the Subscription reply are checked against the
provided list of paths in the test. The Subscription reply must have all paths
in the list.

> This check does NOT check the type of the values returned.

> Update paths not in the list of expected paths are ignored.

Args:
* **xpaths**: List of gNMI paths to subscribe to.
* **update_paths**: List of gNMI paths that the Subscription response must have.

### Module telemetry_sample

Uses gNMI Subscribe messages, of type STREAM, mode SAMPLE.
Expand Down Expand Up @@ -497,12 +514,44 @@ Update path | Time
> This check does NOT check the type of the values returned.

Args:
* **xpath**: gNMI path to subscribe to.
Can contain wildcard '*' only in keys.
* **xpath**: gNMI path to subscribe to. Can contain wildcard '*' only in keys.
* **model**: Python binding class to check the replies against.
* *check_missing_model_paths*: If True, it asserts that all OC Model Leaf
paths are in the received Updates. Defaults to False.

#### `telemetry_sample.CheckLeafsFromList`

In addition to the default checks of the module, this test checks that the
subscription updates have all have the expected paths in the responses.

E.g:
Suppose the test is subscribing to an xpath `/<root>/<container>`, with
15 secs interval for 65 secs. After collecting subscription responses for 65
secs, the test checks that there are updates for all paths listed in the test.

Assuming the test expects a Updates for 2 paths `[/<root>/<container>/<leaf1>, /<root>/<container>/<leaf2>]`, the following Subscription Updates will FAIL this test:

Update path | Time
----------- | ----
`/<root>/<container>/<leaf1>` | Update[t0] | Update[t15] | Update[t30] |
`/<root>/<container>/<leaf_not_listed>` | Update[t0] | Update[t15] | Update[t30] |

In the same condition, the following Subscription Updates will PASS this test:

Update path | Time
----------- | ----
`/<root>/<container>/<leaf1>` | Update[t0] | Update[t15] | Update[t30] |
`/<root>/<container>/<leaf2>` | Update[t0] | Update[t15] | Update[t30] |
`/<root>/<container>/<leaf_not_listed>` | Update[t0] | Update[t15] | Update[t30] |

> This check does NOT check the type of the values returned.

> Update paths not in the list of expected paths are ignored.

Args:
* **xpath**: gNMI paths to subscribe to. Can contain wildcard '*' only in keys.
* **update_paths**: List of gNMI paths that the Subscription response must have.

### Module telemetry_onchange

Uses gNMI Subscribe messages, of type STREAM, mode ON_CHANGE.
Expand Down
32 changes: 32 additions & 0 deletions oc_config_validate/oc_config_validate/testcases/telemetry_once.py
Original file line number Diff line number Diff line change
Expand Up @@ -155,3 +155,35 @@ def testSubscribeOnce(self):
self.assertIn(
want_path, list(got_paths),
f"Missing update path for OC model {self.model}")


class CheckLeafsFromList(SubsOnceTestCase):
"""Subscribes ONCE and checks the updates againts a list of expected paths.

All arguments are read from the Test YAML description.

Args:
xpaths: List of gNMI paths to subscribe to.
update_paths: List of gNMI paths that the Subscription response must have.
"""
update_paths = None

def testSubscribeOnce(self):
""""""
self.assertArgs(["xpaths", "update_paths"])

self.subscribeOnce()

got_updates = []
for n in self.responses:
got_updates.extend(n.update)

got_paths = set()
for u in got_updates:
got_path = schema.pathToString(u.path)
got_paths.add(got_path)

for want_path in self.update_paths:
self.assertIn(
want_path, list(got_paths),
"Missing update path")
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ class CountUpdatePaths(SubsSampleTestCase):
This tests that a Subscription consistenly reports all Update paths.

Args:
xpath: gNMI paths to subscribe to. Can contain wildcards.
xpath: gNMI path to subscribe to. Can contain wildcards.
update_paths_count: Number of expected disctinct Update paths.
"""
update_paths_count = None
Expand All @@ -116,7 +116,7 @@ class CheckLeafsFromModel(SubsSampleTestCase):
that the paths corresponds to Leafs in the OC model.

Args:
xpath: gNMI paths to subscribe to. Can contain wildcards only on the
xpath: gNMI path to subscribe to. Can contain wildcards only on the
keys.
model: Python binding class to check the replies against.
check_missing_model_paths: If True, missing OC Model leaf paths in the
Expand Down Expand Up @@ -147,3 +147,29 @@ def testSubscribeSample(self):
self.assertIn(
want_path, got_paths,
f"Missing update path for OC model {self.model}")


class CheckLeafsFromList(SubsSampleTestCase):
"""Subscribes SAMPLE and checks the updates againts a list of expeted paths.

Tests that a Sample Subscription consistenly reports all Update paths, and
that the all paths listed as expected are there.

Args:
xpath: gNMI paths to subscribe to. Can contain wildcard '*' only in keys.
update_paths: List of gNMI paths that the Subscription responses must have.
"""
update_paths = None

def testSubscribeSample(self):
""""""
self.assertArgs(["update_paths", "xpath"])

self.subscribeSample()

got_paths = list(self.responses.keys())

for want_path in self.update_paths:
self.assertIn(
want_path, got_paths,
"Missing update path")
84 changes: 84 additions & 0 deletions oc_config_validate/py_tests/testcases/test_telemetry_once.py
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,29 @@ def check_ok(self, mock_gNMISubsOnce):
mock_gNMISubsOnce.return_value = [response_interface_status]
self.testSubscribeOnce()

def check_path_not_in_model(self, mock_gNMISubsOnce):
"""Test that CheckLeafsFromModel fails as expected."""
self.xpaths = ['/interfaces/interface[name=eth0]/state']
self.model = "interfaces.openconfig_interfaces"
mock_gNMISubsOnce.return_value = [gnmi_pb2.Notification(
timestamp=(int(time.time())) * 1000000000,
update=[
gnmi_pb2.Update(
path=gnmi_pb2.Path(elem=[
gnmi_pb2.PathElem(name='interfaces'),
gnmi_pb2.PathElem(
name='interface', key={'name': 'eth0'}),
gnmi_pb2.PathElem(name='state'),
gnmi_pb2.PathElem(name='foo')
]),
val=gnmi_pb2.TypedValue(string_val='bar')
)])]
with self.assertRaisesRegex(
AssertionError,
"Update path /interfaces/interface\\[name=eth0\\]/state/foo "
"NOT in OC Model interfaces.openconfig_interfaces"):
self.testSubscribeOnce()

def check_missing_paths(self, mock_gNMISubsOnce):
"""Test that CheckLeafsFromModel works as expected with missing paths from model."""
self.check_missing_model_paths = True
Expand Down Expand Up @@ -452,5 +475,66 @@ def check_bad_model(self, mock_gNMISubsOnce):
self.testSubscribeOnce()


@mock.patch('oc_config_validate.testbase.TestCase.gNMISubsOnce')
class TestCheckLeafsFromList(telemetry_once.CheckLeafsFromList):
"""Test for CheckLeafsFromList class."""

def check_bad_args(self, mock_gNMISubsOnce):
"""Test that CheckLeafsFromList raises an error for missing argument."""
with self.assertRaises(AssertionError):
self.testSubscribeOnce()

self.xpaths = ['/valid/path']
with self.assertRaises(AssertionError):
self.testSubscribeOnce()

self.update_paths = ['/valid/update/path']
self.xpaths = None
with self.assertRaises(AssertionError):
self.testSubscribeOnce()

mock_gNMISubsOnce.assert_not_called()

def check_ok(self, mock_gNMISubsOnce):
"""Test that CheckLeafsFromList works as expected."""
self.xpaths = ['/interfaces/interface[name=eth0]/state']
self.update_paths = ['/interfaces/interface[name=eth0]/state/oper-status',
'/interfaces/interface[name=eth0]/state/enabled']
mock_gNMISubsOnce.return_value = [response_interface_eth0_state]
self.testSubscribeOnce()

self.xpaths = ['/interfaces/interface[name=*]/state']
self.update_paths = ['/interfaces/interface[name=eth0]/state/oper-status',
'/interfaces/interface[name=eth1]/state/oper-status']
mock_gNMISubsOnce.return_value = [response_interface_status]
self.testSubscribeOnce()

self.xpaths = ['/interfaces/interface[name=eth0]/state']
self.update_paths = []
mock_gNMISubsOnce.return_value = [response_interface_eth0_state]
self.testSubscribeOnce()

def check_missing_paths(self, mock_gNMISubsOnce):
"""Test that CheckLeafsFromList works as expected with missing paths from list."""

self.xpaths = ['/interfaces/interface[name=eth0]/state']
self.update_paths = ['/interfaces/interface[name=eth0]/state/oper-status',
'/interfaces/interface[name=eth0]/state/admin-status']
mock_gNMISubsOnce.return_value = [response_interface_eth0_state]
with self.assertRaisesRegex(
AssertionError,
"Missing update path"):
self.testSubscribeOnce()

self.xpaths = ['/interfaces/interface[name=*]/state/oper-status']
self.update_paths = ['/interfaces/interface[name=eth0]/state/oper-status',
'/interfaces/interface[name=eth1]/state/oper-status',
'/interfaces/interface[name=eth2]/state/oper-status']
mock_gNMISubsOnce.return_value = [response_interface_status]
with self.assertRaises(
AssertionError):
self.testSubscribeOnce()


if __name__ == '__main__':
unittest.main()
131 changes: 131 additions & 0 deletions oc_config_validate/py_tests/testcases/test_telemetry_sample.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,5 +497,136 @@ def check_bad_model(self, mock_gNMISubsStreamSample):
self.testSubscribeSample()


@mock.patch('oc_config_validate.testbase.TestCase.gNMISubsStreamSample')
class TestCheckLeafsFromList(telemetry_sample.CheckLeafsFromList):
"""Test for CheckLeafsFromList class."""

def setUp(self):
self.sample_interval = 10
self.sample_timeout = 30
self.xpath = '/interfaces/interface[name=*]/state'

def check_bad_args(self, mock_gNMISubsStreamSample):
"""Test that CheckLeafsFromList raises an error for missing argument."""
self.xpath = None
with self.assertRaises(AssertionError):
self.testSubscribeSample()

self.xpath = '/valid/path'
with self.assertRaises(AssertionError):
self.testSubscribeSample()

self.update_paths = ['/valid/update/path']
self.xpath = None
with self.assertRaises(AssertionError):
self.testSubscribeSample()

mock_gNMISubsStreamSample.assert_not_called()

def check_ok(self, mock_gNMISubsStreamSample):
"""Test that CheckLeafsFromList works as expected."""
now = int(time.time())

self.update_paths = ['/interfaces/interface[name=eth0]/state/oper-status',
'/interfaces/interface[name=eth1]/state/oper-status']

mock_gNMISubsStreamSample.return_value = [
gnmi_pb2.Notification(
timestamp=now * 1000000000,
update=updates_interface_status
),
gnmi_pb2.Notification(
timestamp=(now + 11) * 1000000000,
update=updates_interface_status
),
gnmi_pb2.Notification(
timestamp=(now + 20) * 1000000000,
update=updates_interface_status
),
gnmi_pb2.Notification(
timestamp=(now + 30) * 1000000000,
update=updates_interface_status
)
]
self.testSubscribeSample()

self.update_paths = ['/interfaces/interface[name=eth0]/state/oper-status',
'/interfaces/interface[name=eth0]/state/enabled']
mock_gNMISubsStreamSample.return_value = [
gnmi_pb2.Notification(
timestamp=now * 1000000000,
update=updates_interface_eth0_state
),
gnmi_pb2.Notification(
timestamp=(now + 11) * 1000000000,
update=updates_interface_eth0_state
),
gnmi_pb2.Notification(
timestamp=(now + 20) * 1000000000,
update=updates_interface_eth0_state
),
gnmi_pb2.Notification(
timestamp=(now + 30) * 1000000000,
update=updates_interface_eth0_state
)
]
self.testSubscribeSample()

def check_missing_paths(self, mock_gNMISubsStreamSample):
"""Test that CheckLeafsFromList works as expected with missing paths from list."""

now = int(time.time())
self.update_paths = ['/interfaces/interface[name=eth0]/state/oper-status',
'/interfaces/interface[name=eth1]/state/oper-status',
'/interfaces/interface[name=eth2]/state/oper-status']
mock_gNMISubsStreamSample.return_value = [
gnmi_pb2.Notification(
timestamp=now * 1000000000,
update=updates_interface_status
),
gnmi_pb2.Notification(
timestamp=(now + 11) * 1000000000,
update=updates_interface_status
),
gnmi_pb2.Notification(
timestamp=(now + 20) * 1000000000,
update=updates_interface_status
),
gnmi_pb2.Notification(
timestamp=(now + 30) * 1000000000,
update=updates_interface_status
)
]
with self.assertRaisesRegex(
AssertionError,
"Missing update path"):
self.testSubscribeSample()

self.update_paths = ['/interfaces/interface[name=eth0]/state/oper-status',
'/interfaces/interface[name=eth0]/state/admin-status']
mock_gNMISubsStreamSample.return_value = [
gnmi_pb2.Notification(
timestamp=now * 1000000000,
update=updates_interface_eth0_state
),
gnmi_pb2.Notification(
timestamp=(now + 11) * 1000000000,
update=updates_interface_eth0_state
),
gnmi_pb2.Notification(
timestamp=(now + 20) * 1000000000,
update=updates_interface_eth0_state
),
gnmi_pb2.Notification(
timestamp=(now + 30) * 1000000000,
update=updates_interface_eth0_state
)
]
with self.assertRaisesRegex(
AssertionError,
"Missing update path"):
self.testSubscribeSample()


if __name__ == '__main__':
unittest.main()