Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support NTP server details in base, EOS #2160

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
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
15 changes: 13 additions & 2 deletions napalm/base/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -921,12 +921,23 @@ def get_ntp_servers(self) -> Dict[str, models.NTPServerDict]:
"""
Returns the NTP servers configuration as dictionary.
The keys of the dictionary represent the IP Addresses of the servers.
Inner dictionaries do not have yet any available keys.
Inner dictionaries MAY contain information regarding per-server configuration.

Example::

{
'192.168.0.1': {},
'192.168.0.1':
{
'address': '192.168.0.1',
'port': 123,
'version': 4,
'association_type': 'SERVER',
'iburst': False,
'prefer': False,
'network_instance': 'default',
'source_address': '192.0.2.1',
'key_id': -1,
},
'17.72.148.53': {},
'37.187.56.220': {},
'162.158.20.18': {}
Expand Down
14 changes: 10 additions & 4 deletions napalm/base/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -223,16 +223,22 @@

NTPPeerDict = TypedDict(
"NTPPeerDict",
{
# will populate it in the future wit potential keys
},
{},
total=False,
)

NTPServerDict = TypedDict(
"NTPServerDict",
{
# will populate it in the future wit potential keys
"address": str,
"port": int,
"version": int,
"association_type": str,
"iburst": bool,
"prefer": bool,
"network_instance": str,
"source_address": str,
"key_id": int,
},
total=False,
)
Expand Down
8 changes: 6 additions & 2 deletions napalm/base/test/getters.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,9 @@ def test_get_ntp_peers(self, test_case):

for peer, peer_details in get_ntp_peers.items():
assert isinstance(peer, str)
assert helpers.test_model(models.NTPPeerDict, peer_details)
assert helpers.test_model(
models.NTPPeerDict, peer_details, allow_subset=True
)

return get_ntp_peers

Expand All @@ -323,7 +325,9 @@ def test_get_ntp_servers(self, test_case):

for server, server_details in get_ntp_servers.items():
assert isinstance(server, str)
assert helpers.test_model(models.NTPServerDict, server_details)
assert helpers.test_model(
models.NTPServerDict, server_details, allow_subset=True
)

return get_ntp_servers

Expand Down
19 changes: 12 additions & 7 deletions napalm/base/test/helpers.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
"""Several methods to help with the tests."""


def test_model(model, data):
def test_model(model, data, allow_subset=False):
"""Return if the dictionary `data` complies with the `model`."""
# Access the underlying schema for a TypedDict directly
model = model.__annotations__
same_keys = set(model.keys()) == set(data.keys())
annotations = model.__annotations__
if allow_subset:
same_keys = set(data.keys()) <= set(annotations.keys())
bewing marked this conversation as resolved.
Show resolved Hide resolved
source = data
else:
same_keys = set(annotations.keys()) == set(data.keys())
source = annotations

if not same_keys:
print(
"model_keys: {}\ndata_keys: {}".format(
sorted(model.keys()), sorted(data.keys())
sorted(annotations.keys()), sorted(data.keys())
)
)

correct_class = True
for key, instance_class in model.items():
correct_class = isinstance(data[key], instance_class) and correct_class
for key in source.keys():
correct_class = isinstance(data[key], annotations[key]) and correct_class
if not correct_class:
print(
"key: {}\nmodel_class: {}\ndata_class: {}".format(
key, instance_class, data[key].__class__
key, annotations[key], data[key].__class__
)
)

Expand Down
79 changes: 68 additions & 11 deletions napalm/eos/eos.py
Original file line number Diff line number Diff line change
Expand Up @@ -1202,21 +1202,78 @@ def get_arp_table(self, vrf=""):
return arp_table

def get_ntp_servers(self):
commands = ["show running-config | section ntp"]
result = {}

raw_ntp_config = self._run_commands(commands, encoding="text")[0].get(
"output", ""
)
commands = ["show running-config | section ntp"]

ntp_config = napalm.base.helpers.textfsm_extractor(
self, "ntp_peers", raw_ntp_config
raw_ntp_config = (
self._run_commands(commands, encoding="text")[0]
.get("output", "")
.splitlines()
)

return {
str(ntp_peer.get("ntppeer")): {}
for ntp_peer in ntp_config
if ntp_peer.get("ntppeer", "")
}
for server in raw_ntp_config:
details = {
"port": 123,
"version": 4,
"association_type": "SERVER",
"iburst": False,
"prefer": False,
"network_instance": "default",
"source_address": "",
"key_id": -1,
}
tokens = server.split()
if tokens[2] == "vrf":
details["network_instance"] = tokens[3]
server_ip = details["address"] = tokens[4]
idx = 5
else:
server_ip = details["address"] = tokens[2]
idx = 3
try:
parsed_address = napalm.base.helpers.ipaddress.ip_address(server_ip)
family = parsed_address.version
except ValueError:
# Assume family of 4, unless local-interface has no IPv4 addresses
family = 4
while idx < len(tokens):
if tokens[idx] == "iburst":
details["iburst"] = True
idx += 1

elif tokens[idx] == "key":
details["key_id"] = int(tokens[idx + 1])
idx += 2

elif tokens[idx] == "local-interface":
interfaces = self.get_interfaces_ip()
intf = tokens[idx + 1]
if family == 6 and interfaces[intf]["ipv6"]:
details["source_address"] = list(
interfaces[intf]["ipv6"].keys()
)[0]
elif interfaces[intf]["ipv4"]:
details["source_address"] = list(
interfaces[intf]["ipv4"].keys()
)[0]
elif interfaces[intf]["ipv6"]:
details["source_address"] = list(
interfaces[intf]["ipv6"].keys()
)[0]
idx += 2

elif tokens[idx] == "version":
details["version"] = int(tokens[idx + 1])
idx += 2

elif tokens[idx] == "prefer":
details["prefer"] = True
idx += 1

result[server_ip] = details

return result

def get_ntp_stats(self):
ntp_stats = []
Expand Down
6 changes: 0 additions & 6 deletions napalm/eos/utils/textfsm_templates/ntp_peers.tpl

This file was deleted.

27 changes: 16 additions & 11 deletions napalm/nxos/nxos.py
Original file line number Diff line number Diff line change
Expand Up @@ -1204,27 +1204,32 @@ def get_arp_table(self, vrf: str = "") -> List[models.ARPTableDict]:
)
return arp_table

def _get_ntp_entity(
self, peer_type: str
) -> Dict[str, Union[models.NTPPeerDict, models.NTPServerDict]]:
ntp_entities: Dict[str, Union[models.NTPPeerDict, models.NTPServerDict]] = {}
def _filter_ntp_table(self, peer_type: str) -> List[str]:
ret = []
command = "show ntp peers"
ntp_peers_table = self._get_command_table(command, "TABLE_peers", "ROW_peers")

for ntp_peer in ntp_peers_table:
if ntp_peer.get("serv_peer", "").strip() != peer_type:
continue
peer_addr = napalm.base.helpers.ip(ntp_peer.get("PeerIPAddress").strip())
# Ignore the type of the following line until NTP data is modelled
ntp_entities[peer_addr] = {} # type: ignore

return ntp_entities
ret.append(peer_addr)
return ret

def get_ntp_peers(self) -> Dict[str, models.NTPPeerDict]:
return self._get_ntp_entity("Peer")
ntp_entities: Dict[str, models.NTPPeerDict] = {}
peers = self._filter_ntp_table("Peer")
for peer_addr in peers:
ntp_entities[peer_addr] = {}

return ntp_entities

def get_ntp_servers(self) -> Dict[str, models.NTPServerDict]:
return self._get_ntp_entity("Server")
ntp_entities: Dict[str, models.NTPServerDict] = {}
peers = self._filter_ntp_table("Server")
for peer_addr in peers:
ntp_entities[peer_addr] = {}

return ntp_entities

def get_ntp_stats(self) -> List[models.NTPStats]:
ntp_stats: List[models.NTPStats] = []
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"1.2.3.4": {
"port": 123,
"version": 4,
"association_type": "SERVER",
"iburst": true,
"prefer": true,
"network_instance": "FOO",
"source_address": "",
"key_id": -1,
"address": "1.2.3.4"
},
"4.3.2.1": {
"port": 123,
"version": 4,
"association_type": "SERVER",
"iburst": false,
"prefer": false,
"network_instance": "FOO",
"source_address": "172.20.20.2",
"key_id": -1,
"address": "4.3.2.1"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"interfaces": {
"Management0": {
"name": "Management0",
"lineProtocolStatus": "up",
"interfaceStatus": "connected",
"mtu": 1500,
"interfaceAddressBrief": {
"ipAddr": {
"address": "172.20.20.2",
"maskLen": 24
}
},
"ipv4Routable240": false,
"ipv4Routable0": false,
"enabled": true,
"description": "",
"interfaceAddress": {
"primaryIp": {
"address": "172.20.20.2",
"maskLen": 24
},
"secondaryIps": {},
"secondaryIpsOrderedList": [],
"virtualIp": {
"address": "0.0.0.0",
"maskLen": 0
},
"virtualSecondaryIps": {},
"virtualSecondaryIpsOrderedList": [],
"broadcastAddress": "255.255.255.255",
"dhcp": false
},
"proxyArp": false,
"proxyArpAllowDefault": false,
"localProxyArp": false,
"gratuitousArp": false,
"routedAddr": "00:1c:73:7b:8c:1d",
"isVrrpBackup": false,
"vrf": "default",
"urpf": "disable",
"addresslessForwarding": "isInvalid",
"directedBroadcastEnabled": false,
"maxMssIngress": 0,
"maxMssEgress": 0
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
{
"interfaces": {
"Management0": {
"name": "Management0",
"lineProtocolStatus": "up",
"interfaceStatus": "connected",
"mtu": 1500,
"linkLocal": {
"address": "fe80::21c:73ff:fe7b:8c1d",
"subnet": "fe80::/64",
"active": true,
"leastpref": false,
"dadfailed": false
},
"state": "enabled",
"addresses": [
{
"address": "2001:172:20:20::2",
"subnet": "2001:172:20:20::/64",
"active": true,
"leastpref": false,
"dadfailed": false
}
],
"globalAddressesAreVirtual": false,
"multicastGroupAddresses": [
"ff02::1",
"ff02::1:ff00:2",
"ff02::1:ff7b:8c1d"
],
"dadStatus": "unavailable",
"dadAttempts": -1,
"ndReachableTime": 30000,
"ndRetransmitInterval": 1000,
"enhancedDad": false,
"autoConfigStatus": "stateless",
"urpf": "disable",
"urpfV4V6Mismatch": false,
"vrf": "default",
"addrSource": "manual",
"maxMssIngress": 0,
"maxMssEgress": 0,
"acceptUnsolicitedNa": false
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ntp server vrf FOO 1.2.3.4 prefer iburst
ntp server vrf FOO 4.3.2.1 local-interface Management0
Loading
Loading