diff --git a/test/case/ietf_system/Readme.adoc b/test/case/ietf_system/Readme.adoc index 0b138e8b..c8d5173d 100644 --- a/test/case/ietf_system/Readme.adoc +++ b/test/case/ietf_system/Readme.adoc @@ -16,3 +16,5 @@ include::timezone_utc_offset/Readme.adoc[] include::ssh_key_authentication/Readme.adoc[] include::upgrade/Readme.adoc[] + +include::ntp_client/Readme.adoc[] diff --git a/test/case/ietf_system/ietf_system.yaml b/test/case/ietf_system/ietf_system.yaml index d5c31b36..fa15b9b0 100644 --- a/test/case/ietf_system/ietf_system.yaml +++ b/test/case/ietf_system/ietf_system.yaml @@ -16,3 +16,6 @@ - name: ssh_key_authentication case: ssh_key_authentication/test.py + +- name: ntp_client + case: ntp_client/test.py diff --git a/test/case/ietf_system/ntp_client/Readme.adoc b/test/case/ietf_system/ntp_client/Readme.adoc new file mode 100644 index 00000000..a955b729 --- /dev/null +++ b/test/case/ietf_system/ntp_client/Readme.adoc @@ -0,0 +1,24 @@ +=== Simple NTP client test +==== Description +Verify NTP client to multiple NTP servers and verify that one get selected + +==== Topology +ifdef::topdoc[] +image::../../test/case/ietf_system/ntp_client/topology.svg[Simple NTP client test topology] +endif::topdoc[] +ifndef::topdoc[] +ifdef::testgroup[] +image::ntp_client/topology.svg[Simple NTP client test topology] +endif::testgroup[] +ifndef::testgroup[] +image::topology.svg[Simple NTP client test topology] +endif::testgroup[] +endif::topdoc[] +==== Test sequence +. Set up topology and attach to target DUT +. Configure NTP client on 'target' +. Verify one source is in 'selected' state on 'target' + + +<<< + diff --git a/test/case/ietf_system/ntp_client/test.py b/test/case/ietf_system/ntp_client/test.py new file mode 100755 index 00000000..c7816515 --- /dev/null +++ b/test/case/ietf_system/ntp_client/test.py @@ -0,0 +1,109 @@ +#!/usr/bin/env python3 +""" +Basic NTP client test + +Verify NTP client with multiple servers, ensure one get selected. +""" + +import infamy +import infamy.ntp_server as ntp_server +import infamy.ntp as ntp +import infamy.util as util +def config_target(dut, data1, data2, data3): + dut.put_config_dicts({ + "ietf-interfaces": { + "interfaces": { + "interface": [ + { + "name": data1, + "enabled": True, + "ipv4": { + "address": [{ + "ip": "192.168.1.2", + "prefix-length": 24 + }] + } + }, + { + "name": data2, + "enabled": True, + "ipv4": { + "address": [{ + "ip": "192.168.2.2", + "prefix-length": 24 + }] + } + }, + { + "name": data3, + "enabled": True, + "ipv4": { + "address": [{ + + "ip": "192.168.3.2", + "prefix-length": 24 + }] + } + }] + } + }, + "ietf-system": { + "system": { + "ntp": { + "enabled": True, + "server": [{ + "name": "Server1", + "udp": { + "address": "192.168.1.1" + }, + "iburst": True + },{ + "name": "Server2", + "udp": { + "address": "192.168.2.1" + }, + "iburst": True + },{ + "name": "Server3", + "udp": { + "address": "192.168.3.1" + }, + "iburst": True + }] + } + } + } + }) + +with infamy.Test() as test: + with test.step("Set up topology and attach to target DUT"): + env = infamy.Env() + target = env.attach("target", "mgmt") + + with test.step("Configure NTP client on 'target'"): + _, data1 = env.ltop.xlate("target", "data1") + _, data2 = env.ltop.xlate("target", "data2") + _, data3 = env.ltop.xlate("target", "data3") + + config_target(target, data1, data2, data3) + + _, hport1 = env.ltop.xlate("host", "data1") + _, hport2 = env.ltop.xlate("host", "data2") + _, hport3 = env.ltop.xlate("host", "data3") + + with infamy.IsolatedMacVlan(hport1) as ns1, \ + infamy.IsolatedMacVlan(hport2) as ns2, \ + infamy.IsolatedMacVlan(hport3) as ns3: + ns1.addip("192.168.1.1") + ns2.addip("192.168.2.1") + ns3.addip("192.168.3.1") + + with ntp_server.Server(ns1) as ntp1, \ + ntp_server.Server(ns2) as ntp2, \ + ntp_server.Server(ns3) as ntp3: + with test.step("Verify one source is in 'selected' state on 'target'"): + util.until(lambda: ntp.any_source_selected(target), attempts=200) + with test.step("Verify three sources exist in NTP client on 'target'"): + assert(ntp.number_of_sources(target) == 3) + + test.succeed() diff --git a/test/case/ietf_system/ntp_client/topology.dot b/test/case/ietf_system/ntp_client/topology.dot new file mode 120000 index 00000000..fb634244 --- /dev/null +++ b/test/case/ietf_system/ntp_client/topology.dot @@ -0,0 +1 @@ +../../../infamy/topologies/1x4.dot \ No newline at end of file diff --git a/test/case/ietf_system/ntp_client/topology.svg b/test/case/ietf_system/ntp_client/topology.svg new file mode 100644 index 00000000..fa3360bd --- /dev/null +++ b/test/case/ietf_system/ntp_client/topology.svg @@ -0,0 +1,60 @@ + + + + + + +1x4 + + + +host + +host + +mgmt + +data1 + +data2 + +data3 + + + +target + +mgmt + +data1 + +data2 + +data3 + +target + + + +host:mgmt--target:mgmt + + + + +host:data1--target:data1 + + + + +host:data2--target:data2 + + + + +host:data3--target:data3 + + + + diff --git a/test/infamy/ntp.py b/test/infamy/ntp.py new file mode 100644 index 00000000..4704189e --- /dev/null +++ b/test/infamy/ntp.py @@ -0,0 +1,38 @@ +""" +NTP client helper +""" + + +def _get_ntp(target): + xpath = "/ietf-system:system-state/infix-system:ntp" + data = target.get_data(xpath) + + if data is None: + return None + + return data["system-state"].get("infix-system:ntp", None) or data["system-state"].get("ntp", None) + + +def _get_ntp_sources(target): + ntp = _get_ntp(target) + + if ntp is None: + return [] + + return ntp["sources"]["source"] + + +def any_source_selected(target): + sources = _get_ntp_sources(target) + + for source in sources: + if source["state"] == "selected": + return True + + return False + + +def number_of_sources(target): + sources = _get_ntp_sources(target) + + return len(sources) diff --git a/test/infamy/ntp_server.py b/test/infamy/ntp_server.py new file mode 100644 index 00000000..baa01d85 --- /dev/null +++ b/test/infamy/ntp_server.py @@ -0,0 +1,24 @@ +"""Start NTP server in the background""" +import subprocess + +class Server: + def __init__(self, netns, iface="iface"): + self.iface = iface + self.process = None + self.netns = netns + + def __enter__(self): + self.start() + + def __exit__(self, _, __, ___): + self.stop() + + def start(self): + cmd=f"ntpd -w -n -l -I {self.iface}" + self.process = self.netns.popen(cmd.split(" "),stderr=subprocess.DEVNULL) + + def stop(self): + if self.process: + self.process.terminate() + self.process.wait() + self.process = None diff --git a/test/infamy/topologies/1x4.dot b/test/infamy/topologies/1x4.dot index a76c5e5c..7b5240c6 100644 --- a/test/infamy/topologies/1x4.dot +++ b/test/infamy/topologies/1x4.dot @@ -7,20 +7,20 @@ graph "1x4" { edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"]; host [ - label="host | { tgt | data0 | data1 | data2 }", + label="host | { mgmt | data1 | data2 | data3 }", pos="0,12!", kind="controller", ]; target [ - label="{ mgmt | data0 | data1 | data2 } | target", + label="{ mgmt | data1 | data2 | data3 } | target", pos="10,12!", kind="infix", ]; - host:tgt -- target:mgmt [kind=mgmt] - host:data0 -- target:data0 [color=black] + host:mgmt -- target:mgmt [kind=mgmt] host:data1 -- target:data1 [color=black] host:data2 -- target:data2 [color=black] + host:data3 -- target:data3 [color=black] }