Merge pull request #840 from kernelkit/ntp-status

Ntp status
This commit is contained in:
Tobias Waldekranz
2024-12-04 10:38:32 +01:00
committed by GitHub
22 changed files with 494 additions and 47 deletions
+10 -2
View File
@@ -3,6 +3,15 @@ Change Log
All notable changes to the project are documented in this file.
[v24.12.0][UNRELEASED]
-------------------------
### Changes
- NTP client status is now availible in YANG.
### Fixes
[v24.11.1][] - 2024-11-29
-------------------------
@@ -52,7 +61,6 @@ All notable changes to the project are documented in this file.
forwarded, when the underlying ports are simultaneously attached to
a VLAN filtering bridge.
[v24.11.0][] - 2024-11-20
-------------------------
@@ -221,7 +229,7 @@ renamed to ease maintenance, more info below.
with `custom-phys-address` to allow for constructing more free-form
MAC addresses based on the chassis MAC (a.k.a., base MAC) address.
For more information, see the YANG model, a few examples are listed in
the updated documentation.
the updated documentation.
The syntax will be automatically updated in the `startup-config` and
`factory-config` -- make sure to verify the changes and update any
static `factory-config` used for your products
+29 -35
View File
@@ -24,14 +24,14 @@ available in the system authentication configuration context.
```
admin@host:/config/> edit system authentication user admin
admin@host:/config/system/authentication/user/admin/> change password
New password:
Retype password:
New password:
Retype password:
admin@host:/config/system/authentication/user/admin/> leave
```
The `change password` command starts an interactive dialogue that asks
for the new password, with a confirmation, and then salts and encrypts
the password with sha512crypt.
the password with sha512crypt.
It is also possible to use the `set password ...` command. This allows
setting an already hashed password. To manually hash a password, use
@@ -55,7 +55,7 @@ With SSH keys in place it is possible to disable password login, just
remember to verify SSH login and network connectivity before doing so.
```
admin@host:/config/> edit system authentication user admin
admin@host:/config/> edit system authentication user admin
admin@host:/config/system/authentication/user/admin/> edit authorized-key example@host
admin@host:/config/system/authentication/user/admin/authorized-key/example@host/> set algorithm ssh-rsa
admin@host:/config/system/authentication/user/admin/authorized-key/example@host/> set key-data AAAAB3NzaC1yc2EAAAADAQABAAABgQC8iBL42yeMBioFay7lty1C4ZDTHcHyo739gc91rTTH8SKvAE4g8Rr97KOz/8PFtOObBrE9G21K7d6UBuPqmd0RUF2CkXXN/eN2PBSHJ50YprRFt/z/304bsBYkDdflKlPDjuSmZ/+OMp4pTsq0R0eNFlX9wcwxEzooIb7VPEdvWE7AYoBRUdf41u3KBHuvjGd1M6QYJtbFLQMMTiVe5IUfyVSZ1RCxEyAB9fR9CBhtVheTVsY3iG0fZc9eCEo89ErDgtGUTJK4Hxt5yCNwI88YaVmkE85cNtw8YwubWQL3/tGZHfbbQ0fynfB4kWNloyRHFr7E1kDxuX5+pbv26EqRdcOVGucNn7hnGU6C1+ejLWdBD7vgsoilFrEaBWF41elJEPKDzpszEijQ9gTrrWeYOQ+x++lvmOdssDu4KvGmj2K/MQTL2jJYrMJ7GDzsUu3XikChRL7zNfS2jYYQLzovboUCgqfPUsVba9hqeX3U67GsJo+hy5MG9RSry4+ucHs=
@@ -145,7 +145,7 @@ is committed by issuing the `leave` command.
admin@host:/config/> edit system
admin@host:/config/system/> set hostname example
admin@host:/config/system/> leave
admin@host:/>
admin@host:/>
```
The hostname is advertised over mDNS-SD in the `.local` domain. If
@@ -203,8 +203,8 @@ admin@host:/>
## NTP Client Configuration
Below is an example configuration for enabling NTP
with a specific server and the `iburst` option for faster initial
Below is an example configuration for enabling NTP
with a specific server and the `iburst` option for faster initial
synchronization.
```
@@ -216,23 +216,37 @@ admin@host:/config/> set system ntp server ntp-pool iburst
admin@host:/config/> set system ntp server ntp-pool prefer
```
This configuration enables the NTP client and sets the NTP server to
`pool.ntp.org` with the `iburst` and `prefer` options. The `iburst`
option ensures faster initial synchronization, and the `prefer` option
This configuration enables the NTP client and sets the NTP server to
`pool.ntp.org` with the `iburst` and `prefer` options. The `iburst`
option ensures faster initial synchronization, and the `prefer` option
designates this server as preferred.
* `prefer false`: The NTP client will choose the best available source
based on several factors, such as network delay, stratum, and other
based on several factors, such as network delay, stratum, and other
metrics (default config).
* `prefer true`: The NTP client will try to use the preferred server
* `prefer true`: The NTP client will try to use the preferred server
as the primary source unless it becomes unreachable or unusable.
### Show NTP Sources
The status for NTP sources is availble in YANG and accessable with
CLI/NETCONF/RESTCONF.
To view the sources being used by the NTP client, run:
```
admin@target:/> show ntp
ADDRESS MODE STATE STRATUM POLL-INTERVAL
192.168.1.1 server candidate 1 6
192.168.2.1 server candidate 1 6
192.168.3.1 server selected 1 6
```
### Show NTP Status
To check the status of NTP synchronization (only availble in CLI), use the following command:
To check the status of NTP synchronization, use the following command:
```
admin@host:/> show ntp
admin@host:/> show ntp tracking
Reference ID : C0248F86 (192.36.143.134)
Stratum : 2
Ref time (UTC) : Mon Oct 21 10:06:45 2024
@@ -249,29 +263,9 @@ Leap status : Normal
admin@host:/>
```
This output provides detailed information about the NTP status, including
This output provides detailed information about the NTP status, including
reference ID, stratum, time offsets, frequency, and root delay.
### Show NTP Sources
To view the sources being used by the NTP client, run:
```
admin@host:/> show ntp sources
.-- Source mode '^' = server, '=' = peer, '#' = local clock.
/ .- Source state '*' = current best, '+' = combined, '-' = not combined,
| / 'x' = may be in error, '~' = too variable, '?' = unusable.
|| .- xxxx [ yyyy ] +/- zzzz
|| Reachability register (octal) -. | xxxx = adjusted offset,
|| Log2(Polling interval) --. | | yyyy = measured offset,
|| \ | | zzzz = estimated error.
|| | | \
MS Name/IP address Stratum Poll Reach LastRx Last sample
===============================================================================
^* 192.36.143.134 1 6 177 9 +278ms[ -3845s] +/- 514ms
admin@host:/>
```
> The system uses `chronyd` for Network Time Protocol (NTP)
> synchronization. The output shown here is best explained in the
+1 -1
View File
@@ -32,7 +32,7 @@ MODULES=(
"infix-lldp@2023-08-23.yang"
"infix-dhcp-client@2024-09-20.yang"
"infix-meta@2024-10-18.yang"
"infix-system@2024-09-13.yang"
"infix-system@2024-11-27.yang"
"infix-services@2024-05-30.yang"
"ieee802-ethernet-interface@2019-06-21.yang"
"infix-ethernet-interface@2024-02-27.yang"
+77
View File
@@ -10,12 +10,22 @@ module infix-system {
prefix iana-tz;
}
import ietf-inet-types {
prefix inet;
reference
"RFC 6991: Common YANG Data Types";
}
include infix-system-software;
organization "KernelKit";
contact "kernelkit@googlegroups.com";
description "Infix augments and deviations to ietf-system.";
revision 2024-11-27 {
description "Add NTP status";
reference "internal";
}
revision 2024-09-13 {
description "Add some informative help about different shells and security.";
reference "internal";
@@ -115,6 +125,42 @@ module infix-system {
base shell-type;
}
typedef source-state {
reference "rfc5905: Network Time Protocol Version 4: Protocol and Algorithms Specification";
type enumeration {
enum selected {
description "Selected NTP server for synchronization";
}
enum candidate {
description "Valid for synchronization, is used together with thes elected best server and also acting as backup";
}
enum outlier {
description "Valid timesource but discarded by the clustering algorithm";
}
enum unusable {
description "Unreachable or unresponsable";
}
enum falseticker {
description "suspected of providing incorrect time";
}
enum unstable {
description "Too variable or unstable source";
}
}
}
typedef source-mode {
type enumeration {
enum server {
description "Normal NTP server";
}
enum peer {
description "Symmetric peer";
}
enum local-clock {
description "Reference clock";
}
}
}
/*
* Typedefs
*/
@@ -256,6 +302,37 @@ module infix-system {
}
}
augment "/sys:system-state" {
container ntp {
description "NTP status";
container sources {
list source {
key address;
leaf address {
type inet:ip-address;
description "Address to NTP server";
}
leaf mode {
type source-mode;
description "Source mode";
}
leaf state {
type source-state;
description "Source state";
}
leaf stratum {
type uint8;
description "Stratum of NTP server";
}
leaf poll {
type uint8;
description "Interval between transmitted NTP messages, expressed as a power of two in seconds";
}
}
}
}
}
deviation "/sys:system/sys:hostname" {
deviate replace {
type infix-sys:hostname;
+7 -3
View File
@@ -279,10 +279,14 @@
</COMMAND>
<COMMAND name="ntp" help="Show NTP (client) status">
<ACTION sym="script">doas ntp tracking</ACTION>
<ACTION sym="script">
sysrepocfg -d operational -X -f json -x \
/ietf-system:system-state/infix-system:ntp | \
/usr/libexec/statd/cli-pretty "show-ntp"
</ACTION>
<SWITCH name="optional" min="0">
<COMMAND name="sources" help="Show NTP sources">
<ACTION sym="script">doas ntp sources</ACTION>
<COMMAND name="tracking" help="Show NTP tracking">
<ACTION sym="script">doas ntp tracking</ACTION>
</COMMAND>
</SWITCH>
</COMMAND>
+30
View File
@@ -54,6 +54,12 @@ class PadUsbPort:
name = 20
state = 10
class PadNtpSource:
address = 16
mode = 13
state = 13
stratum = 11
poll = 14
class Decore():
@staticmethod
@@ -719,6 +725,26 @@ def show_hardware(json):
port = USBport(component)
port.print()
def show_ntp(json):
if not json.get("ietf-system:system-state"):
print(f"Error, top level \"ietf-system:system-state\" missing")
sys.exit(1)
hdr = (f"{'ADDRESS':<{PadNtpSource.address}}"
f"{'MODE':<{PadNtpSource.mode}}"
f"{'STATE':<{PadNtpSource.state}}"
f"{'STRATUM':>{PadNtpSource.stratum}}"
f"{'POLL-INTERVAL':>{PadNtpSource.poll}}"
)
print(Decore.invert(hdr))
sources = get_json_data({}, json, 'ietf-system:system-state', 'infix-system:ntp', 'sources', 'source')
for source in sources:
row = f"{source['address']:<{PadNtpSource.address}}"
row += f"{source['mode']:<{PadNtpSource.mode}}"
row += f"{source['state'] if source['state'] != 'not-combined' else 'not combined':<{PadNtpSource.state}}"
row += f"{source['stratum']:>{PadNtpSource.stratum}}"
row += f"{source['poll']:>{PadNtpSource.poll}}"
print(row)
def main():
global UNIT_TEST
@@ -749,6 +775,8 @@ def main():
parser_show_routing_table = subparsers.add_parser('show-hardware', help='Show USB ports')
parser_show_ntp_sources = subparsers.add_parser('show-ntp', help='Show NTP sources')
args = parser.parse_args()
UNIT_TEST = args.test
@@ -762,6 +790,8 @@ def main():
show_bridge_mdb(json_data)
elif args.command == "show-hardware":
show_hardware(json_data)
elif args.command == "show-ntp":
show_ntp(json_data)
else:
print(f"Error, unknown command {args.command}")
sys.exit(1)
+36
View File
@@ -1053,6 +1053,36 @@ def add_interface(ifname, yang_ifaces):
add_container_ifaces(yang_ifaces)
def add_system_ntp(out):
data = run_cmd(["chronyc", "-c", "sources"], "chronyc-sources.txt", "")
source = []
state_mode_map = {
"^": "server",
"=": "peer",
"#": "local-clock"
}
source_state_map = {
"*": "selected",
"+": "candidate",
"-": "outlier",
"?": "unusable",
"x": "falseticker",
"~": "unstable"
}
for line in data:
src = {}
line = line.split(',')
src["address"] = line[2]
src["mode"] = state_mode_map[line[0]]
src["state"] = source_state_map[line[1]]
src["stratum"] = int(line[3])
src["poll"] = int(line[4])
source.append(src)
insert(out, "infix-system:ntp", "sources", "source", source)
def add_system(yang_data):
add_system_ntp(yang_data)
def main():
global TESTPATH
global logger
@@ -1133,6 +1163,12 @@ def main():
}
add_container(yang_data['infix-containers:containers']['container'])
elif args.model == 'ietf-system':
yang_data = {
"ietf-system:system-state": {
}
}
add_system(yang_data['ietf-system:system-state'])
else:
logger.warning(f"Unsupported model {args.model}", file=sys.stderr)
sys.exit(1)
+3
View File
@@ -37,6 +37,7 @@
#define XPATH_ROUTING_BASE "/ietf-routing:routing/control-plane-protocols/control-plane-protocol"
#define XPATH_ROUTING_TABLE "/ietf-routing:routing/ribs"
#define XPATH_HARDWARE_BASE "/ietf-hardware:hardware"
#define XPATH_SYSTEM_BASE "/ietf-system:system-state"
#define XPATH_ROUTING_OSPF XPATH_ROUTING_BASE "/ospf"
#define XPATH_CONTAIN_BASE "/infix-containers:containers"
@@ -340,6 +341,8 @@ static int subscribe_to_all(struct statd *statd)
return SR_ERR_INTERNAL;
if (subscribe(statd, "ietf-hardware", XPATH_HARDWARE_BASE, sr_generic_cb))
return SR_ERR_INTERNAL;
if (subscribe(statd, "ietf-system", XPATH_SYSTEM_BASE, sr_generic_cb))
return SR_ERR_INTERNAL;
#ifdef CONTAINERS
if (subscribe(statd, "infix-containers", XPATH_CONTAIN_BASE, sr_generic_cb))
return SR_ERR_INTERNAL;
+9
View File
@@ -0,0 +1,9 @@
ADDRESS MODE STATE STRATUM POLL-INTERVAL
185.125.190.56 peer candidate 2 8
185.125.190.58 server unstable 2 7
185.125.190.57 server falseticker 2 8
91.189.91.157 local-clock outlier 2 6
192.121.108.100 server candidate 3 8
85.24.237.72 server selected 2 8
162.159.200.1 server unusable 3 7
85.24.237.71 server candidate 2 8
+13 -1
View File
@@ -59,6 +59,8 @@ if [ $# -eq 2 ] && [ $1 = "update" ]; then
"$SR_EMULATOR_TOOL" | "$CLI_PRETTY_TOOL" "-t" "show-routing-table" -i "ipv6" > "$CLI_OUTPUT_PATH/show-routes-ipv6.txt"
elif [ $2 = "show-bridge-mdb" ]; then
"$SR_EMULATOR_TOOL" | "$CLI_PRETTY_TOOL" "show-bridge-mdb" > "$CLI_OUTPUT_PATH/show-bridge-mdb.txt"
elif [ $2 = "show-ntp" ]; then
"$SR_EMULATOR_TOOL" | "$CLI_PRETTY_TOOL" "show-ntp" > "$CLI_OUTPUT_PATH/show-ntp.txt"
else
echo "Unsupported cli-pretty command $2"
exit 1
@@ -67,7 +69,7 @@ if [ $# -eq 2 ] && [ $1 = "update" ]; then
exit 0
fi
echo "1..10"
echo "1..11"
echo "# Running:"
# Show interfaces
@@ -89,6 +91,16 @@ if ! diff -u "$CLI_OUTPUT_PATH/show-bridge-mdb.txt" "$CLI_OUTPUT_FILE"; then
fi
ok "\"show bridge mdb\" output looks intact"
# Show NTP
echo "# $SR_EMULATOR_TOOL | $CLI_PRETTY_TOOL show-ntp"
"$SR_EMULATOR_TOOL" | "$CLI_PRETTY_TOOL" "show-ntp" > "$CLI_OUTPUT_FILE"
if ! diff -u "$CLI_OUTPUT_PATH/show-ntp.txt" "$CLI_OUTPUT_FILE"; then
print_update_txt
fail "\"show ntp\" output has changed"
fi
ok "\"show ntp\" output looks intact"
# Show ipv4 routes
echo "# $SR_EMULATOR_TOOL | $CLI_PRETTY_TOOL -t show-routing-table -i ipv4"
"$SR_EMULATOR_TOOL" | "$CLI_PRETTY_TOOL" "-t" "show-routing-table" -i "ipv4" > "$CLI_OUTPUT_FILE"
+5 -1
View File
@@ -7,9 +7,11 @@ YANGER_TOOL="$ROOT_PATH/src/statd/python/yanger/yanger.py"
INTERFACES_OUTPUT_FILE="$(mktemp)"
ROUTES_OUTPUT_FILE="$(mktemp)"
SYSTEM_OUTPUT_FILE="$(mktemp)"
cleanup() {
rm -f "$INTERFACES_OUTPUT_FILE"
rm -f "$ROUTES_OUTPUT_FILE"
rm -f "$SYSTEM_OUTPUT_FILE"
}
trap cleanup EXIT
@@ -26,5 +28,7 @@ fi
$YANGER_TOOL "ietf-routing" -t "$SCRIPT_PATH/system-output/" > "$ROUTES_OUTPUT_FILE"
$YANGER_TOOL "ietf-system" -t "$SCRIPT_PATH/system-output/" > "$SYSTEM_OUTPUT_FILE"
# Merge all module files
jq -s '.[0] * .[1]' $ROUTES_OUTPUT_FILE $INTERFACES_OUTPUT_FILE
jq -s '.[0] * .[1] * .[2]' $ROUTES_OUTPUT_FILE $INTERFACES_OUTPUT_FILE $SYSTEM_OUTPUT_FILE
@@ -0,0 +1,8 @@
=,+,185.125.190.56,2,8,377,166,0.005469983,0.005822986,0.033335429
^,~,185.125.190.58,2,7,377,37,0.003233160,0.003233160,0.028441129
^,x,185.125.190.57,2,8,377,167,-0.000418652,-0.000065722,0.027703809
#,-,91.189.91.157,2,6,377,488,0.000970410,0.001490889,0.077348366
^,+,192.121.108.100,3,8,377,101,0.005757879,0.006114979,0.024731735
^,*,85.24.237.72,2,8,377,37,0.006444688,0.006805896,0.017862743
^,?,162.159.200.1,3,7,377,34,-0.000447366,-0.000447366,0.009887429
^,+,85.24.237.71,2,8,377,169,0.003875532,0.004228305,0.015323908
+2
View File
@@ -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[]
+3
View File
@@ -16,3 +16,6 @@
- name: ssh_key_authentication
case: ssh_key_authentication/test.py
- name: ntp_client
case: ntp_client/test.py
@@ -0,0 +1,25 @@
=== Basic NTP client test
==== Description
Verify NTP client with multiple servers, ensure one get selected.
==== Topology
ifdef::topdoc[]
image::../../test/case/ietf_system/ntp_client/topology.svg[Basic NTP client test topology]
endif::topdoc[]
ifndef::topdoc[]
ifdef::testgroup[]
image::ntp_client/topology.svg[Basic NTP client test topology]
endif::testgroup[]
ifndef::testgroup[]
image::topology.svg[Basic 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'
. Verify three sources exist in NTP client on 'target'
<<<
+109
View File
@@ -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()
+1
View File
@@ -0,0 +1 @@
../../../infamy/topologies/1x4.dot
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<!-- Title: 1x4 Pages: 1 -->
<svg width="440pt" height="101pt"
viewBox="0.00 0.00 440.03 101.00" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="graph0" class="graph" transform="scale(1 1) rotate(0) translate(4 97)">
<title>1x4</title>
<polygon fill="white" stroke="transparent" points="-4,4 -4,-97 436.03,-97 436.03,4 -4,4"/>
<!-- host -->
<g id="node1" class="node">
<title>host</title>
<polygon fill="none" stroke="black" points="0,-0.5 0,-92.5 108,-92.5 108,-0.5 0,-0.5"/>
<text text-anchor="middle" x="25" y="-42.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">host</text>
<polyline fill="none" stroke="black" points="50,-0.5 50,-92.5 "/>
<text text-anchor="middle" x="79" y="-77.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="50,-69.5 108,-69.5 "/>
<text text-anchor="middle" x="79" y="-54.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
<polyline fill="none" stroke="black" points="50,-46.5 108,-46.5 "/>
<text text-anchor="middle" x="79" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
<polyline fill="none" stroke="black" points="50,-23.5 108,-23.5 "/>
<text text-anchor="middle" x="79" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data3</text>
</g>
<!-- target -->
<g id="node2" class="node">
<title>target</title>
<polygon fill="none" stroke="black" points="308.03,-0.5 308.03,-92.5 432.03,-92.5 432.03,-0.5 308.03,-0.5"/>
<text text-anchor="middle" x="337.03" y="-77.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
<polyline fill="none" stroke="black" points="308.03,-69.5 366.03,-69.5 "/>
<text text-anchor="middle" x="337.03" y="-54.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
<polyline fill="none" stroke="black" points="308.03,-46.5 366.03,-46.5 "/>
<text text-anchor="middle" x="337.03" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
<polyline fill="none" stroke="black" points="308.03,-23.5 366.03,-23.5 "/>
<text text-anchor="middle" x="337.03" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data3</text>
<polyline fill="none" stroke="black" points="366.03,-0.5 366.03,-92.5 "/>
<text text-anchor="middle" x="399.03" y="-42.8" font-family="DejaVu Sans Mono, Book" font-size="14.00">target</text>
</g>
<!-- host&#45;&#45;target -->
<g id="edge1" class="edge">
<title>host:mgmt&#45;&#45;target:mgmt</title>
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M108,-81.5C108,-81.5 308.03,-81.5 308.03,-81.5"/>
</g>
<!-- host&#45;&#45;target -->
<g id="edge2" class="edge">
<title>host:data1&#45;&#45;target:data1</title>
<path fill="none" stroke="black" stroke-width="2" d="M108,-58.5C108,-58.5 308.03,-58.5 308.03,-58.5"/>
</g>
<!-- host&#45;&#45;target -->
<g id="edge3" class="edge">
<title>host:data2&#45;&#45;target:data2</title>
<path fill="none" stroke="black" stroke-width="2" d="M108,-34.5C108,-34.5 308.03,-34.5 308.03,-34.5"/>
</g>
<!-- host&#45;&#45;target -->
<g id="edge4" class="edge">
<title>host:data3&#45;&#45;target:data3</title>
<path fill="none" stroke="black" stroke-width="2" d="M108,-11.5C108,-11.5 308.03,-11.5 308.03,-11.5"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

+38
View File
@@ -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)
+24
View File
@@ -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
+4 -4
View File
@@ -7,20 +7,20 @@ graph "1x4" {
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
host [
label="host | { <tgt> tgt | <data0> data0 | <data1> data1 | <data2> data2 }",
label="host | { <mgmt> mgmt | <data1> data1 | <data2> data2 | <data3> data3 }",
pos="0,12!",
kind="controller",
];
target [
label="{ <mgmt> mgmt | <data0> data0 | <data1> data1 | <data2> data2 } | target",
label="{ <mgmt> mgmt | <data1> data1 | <data2> data2 | <data3> 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]
}