sc-napalm 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- custom_napalm/__init__.py +30 -0
- custom_napalm/base.py +113 -0
- custom_napalm/eos/__init__.py +1 -0
- custom_napalm/eos/eos.py +93 -0
- custom_napalm/get.py +83 -0
- custom_napalm/iosxr/__init__.py +1 -0
- custom_napalm/iosxr/constants.py +144 -0
- custom_napalm/iosxr/iosxr.py +55 -0
- custom_napalm/junos/__init__.py +1 -0
- custom_napalm/junos/junos.py +223 -0
- custom_napalm/junos/junos_views.py +17 -0
- custom_napalm/junos/junos_views.yml +228 -0
- custom_napalm/models.py +27 -0
- custom_napalm/nos/__init__.py +1 -0
- custom_napalm/nos/constants.py +111 -0
- custom_napalm/nos/nos.py +230 -0
- custom_napalm/nxos/__init__.py +1 -0
- custom_napalm/nxos/nxos.py +88 -0
- custom_napalm/nxos/utils/textfsm_templates/sh_int_transceiver.tpl +27 -0
- custom_napalm/nxos/utils/textfsm_templates/sh_inventory.tpl +8 -0
- custom_napalm/srl/__init__.py +1 -0
- custom_napalm/srl/srl.py +160 -0
- custom_napalm/srl_ssh/__init__.py +1 -0
- custom_napalm/srl_ssh/srl_ssh.py +45 -0
- custom_napalm/sros/__init__.py +1 -0
- custom_napalm/sros/nc_filters.py +18 -0
- custom_napalm/sros/sros.py +59 -0
- custom_napalm/utils.py +74 -0
- sc_napalm-1.0.0.dist-info/METADATA +106 -0
- sc_napalm-1.0.0.dist-info/RECORD +32 -0
- sc_napalm-1.0.0.dist-info/WHEEL +4 -0
- sc_napalm-1.0.0.dist-info/entry_points.txt +2 -0
custom_napalm/nos/nos.py
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
from typing import Dict, List
|
|
2
|
+
|
|
3
|
+
from ncclient import manager
|
|
4
|
+
from ncclient.xml_ import to_ele
|
|
5
|
+
from lxml import etree
|
|
6
|
+
|
|
7
|
+
from napalm.base import NetworkDriver
|
|
8
|
+
from napalm.base.exceptions import ConnectionException
|
|
9
|
+
from napalm.base import models as napalm_models
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
|
|
14
|
+
from ..base import SCBaseNetconfDriver
|
|
15
|
+
from .. import models as sc_models
|
|
16
|
+
from . import constants as C
|
|
17
|
+
from .constants import NS
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SCNOSDriver(NetworkDriver, SCBaseNetconfDriver):
|
|
23
|
+
"""
|
|
24
|
+
Drivenets class is heavily based on Napalm IOSXR, which is
|
|
25
|
+
also a netconf/yang device.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
netmiko_host_type = "linux"
|
|
29
|
+
|
|
30
|
+
def __init__(self, hostname, username, password, timeout=60, optional_args=None):
|
|
31
|
+
self.hostname = hostname
|
|
32
|
+
self.username = username
|
|
33
|
+
self.password = password
|
|
34
|
+
self.timeout = timeout
|
|
35
|
+
self.pending_changes = False
|
|
36
|
+
self.replace = False
|
|
37
|
+
self.locked = False
|
|
38
|
+
self.optional_args = optional_args if optional_args else {}
|
|
39
|
+
self.port = self.optional_args.pop("port", 830)
|
|
40
|
+
self.key_file = self.optional_args.pop("key_file", None)
|
|
41
|
+
if "ssh_config_file" in self.optional_args:
|
|
42
|
+
# self.optional_args["ssh_config"] = self.optional_args["ssh_config_file"]
|
|
43
|
+
del self.optional_args["ssh_config_file"]
|
|
44
|
+
|
|
45
|
+
self.optional_args["hostkey_verify"] = False
|
|
46
|
+
self.device = None
|
|
47
|
+
self.NS = NS
|
|
48
|
+
|
|
49
|
+
def open(self):
|
|
50
|
+
"""Open the connection with the device."""
|
|
51
|
+
try:
|
|
52
|
+
self.device = manager.connect(
|
|
53
|
+
host=self.hostname,
|
|
54
|
+
port=self.port,
|
|
55
|
+
username=self.username,
|
|
56
|
+
password=self.password,
|
|
57
|
+
key_filename=self.key_file,
|
|
58
|
+
timeout=self.timeout,
|
|
59
|
+
**self.optional_args,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
except Exception as conn_err:
|
|
63
|
+
logger.error(conn_err.args[0])
|
|
64
|
+
raise ConnectionException(conn_err.args[0])
|
|
65
|
+
|
|
66
|
+
def close(self):
|
|
67
|
+
"""Close the connection."""
|
|
68
|
+
logger.debug("Closed connection with device %s" % (self.hostname))
|
|
69
|
+
self.device.close_session()
|
|
70
|
+
|
|
71
|
+
def get_config(
|
|
72
|
+
self, retrieve="all", full=False, sanitized=False, format="text"
|
|
73
|
+
) -> napalm_models.ConfigDict:
|
|
74
|
+
"""
|
|
75
|
+
Get config adapted from iosxr_netconf driver. Note that there doesn't appear to
|
|
76
|
+
be a way to get a non-xml version of the config via netconf, so we're doing
|
|
77
|
+
in a separate SSH CLI session.
|
|
78
|
+
"""
|
|
79
|
+
|
|
80
|
+
if sanitized:
|
|
81
|
+
raise NotImplementedError("Sanitized nos config not supported")
|
|
82
|
+
if format not in ["text", "xml"]:
|
|
83
|
+
raise NotImplementedError("Only XML and text config formats supported")
|
|
84
|
+
|
|
85
|
+
config = {"startup": "", "running": "", "candidate": ""}
|
|
86
|
+
|
|
87
|
+
# for text format we're going to SSH into the device and do 'show config'
|
|
88
|
+
# in this scenario 'candidate' isn't really relevant.
|
|
89
|
+
if format == "text":
|
|
90
|
+
with self.ssh_conn() as ssh_device:
|
|
91
|
+
config["running"] = ssh_device.send_command("show config")
|
|
92
|
+
|
|
93
|
+
# otherwise we're just doing netconf 'get-config'
|
|
94
|
+
else:
|
|
95
|
+
if retrieve.lower() in ["running", "all"]:
|
|
96
|
+
config["running"] = str(self.device.get_config(source="running").xml)
|
|
97
|
+
if retrieve.lower() in ["candidate", "all"]:
|
|
98
|
+
config["candidate"] = str(
|
|
99
|
+
self.device.get_config(source="candidate").xml
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
for datastore in config:
|
|
103
|
+
if not config[datastore]:
|
|
104
|
+
continue
|
|
105
|
+
|
|
106
|
+
config[datastore] = etree.tostring(
|
|
107
|
+
etree.fromstring(config[datastore][0]),
|
|
108
|
+
pretty_print=True,
|
|
109
|
+
encoding="unicode",
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
return config
|
|
113
|
+
|
|
114
|
+
def get_facts(self) -> napalm_models.FactsDict:
|
|
115
|
+
"""napalm get_facts"""
|
|
116
|
+
rpc_reply = self.device.dispatch(to_ele(C.FACTS_RPC_REQ)).xml
|
|
117
|
+
facts_xml = etree.fromstring(rpc_reply)
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
"vendor": "Drivenets",
|
|
121
|
+
"os_version": self._text(facts_xml, "//dn-sys:system-version", default=""),
|
|
122
|
+
"hostname": self._text(facts_xml, "//dn-sys:name", default=""),
|
|
123
|
+
"uptime": self._text(facts_xml, "//dn-sys:system-uptime", default=-1.0),
|
|
124
|
+
"serial_number": self._text(
|
|
125
|
+
facts_xml, "//dn-platform:serial-number", default=""
|
|
126
|
+
),
|
|
127
|
+
"fqdn": self._text(facts_xml, "//dn-sys-dns:domain-name", default=""),
|
|
128
|
+
"model": self._text(facts_xml, "//dn-sys:system-type", default=""),
|
|
129
|
+
"interface_list": [i.text for i in self._xpath(facts_xml, "//dn-if:name")],
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
def get_optics(self) -> Dict[str, napalm_models.OpticsDict]:
|
|
133
|
+
"""napalm get_optics"""
|
|
134
|
+
rpc_reply = self.device.dispatch(to_ele(C.OPTICS_RPC_REQ)).xml
|
|
135
|
+
optics_xml = etree.fromstring(rpc_reply)
|
|
136
|
+
|
|
137
|
+
# print(etree.tostring(optics_xml, pretty_print=True, encoding="unicode"))
|
|
138
|
+
|
|
139
|
+
output = {}
|
|
140
|
+
for i in self._xpath(optics_xml, "//dn-if:interface"):
|
|
141
|
+
if not self._text(i, ".//dn-trans:transceiver-voltage"):
|
|
142
|
+
continue
|
|
143
|
+
i_name = self._text(i, "dn-if:name")
|
|
144
|
+
output[i_name] = {"physical_channels": {"channels": {}}}
|
|
145
|
+
|
|
146
|
+
i_channels = output[i_name]["physical_channels"]["channels"]
|
|
147
|
+
for c in self._xpath(i, ".//dn-trans:channel"):
|
|
148
|
+
if self._text(c, ".//dn-trans:receive-power"):
|
|
149
|
+
i_channels[self._text(c, ".//dn-trans:lane")] = {
|
|
150
|
+
"input_power": self._text(c, ".//dn-trans:receive-power"),
|
|
151
|
+
"output_power": self._text(c, ".//dn-trans:transmit-power"),
|
|
152
|
+
"laser_bias_current": self._text(
|
|
153
|
+
c, ".//dn-trans:laser-bias-current"
|
|
154
|
+
),
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return output
|
|
158
|
+
|
|
159
|
+
def get_lldp_neighbors(self) -> Dict[str, List[napalm_models.LLDPNeighborDict]]:
|
|
160
|
+
"""napalm get lldp neighbors"""
|
|
161
|
+
rpc_reply = self.device.dispatch(to_ele(C.LLDP_NEIGH_RPC_REQ)).xml
|
|
162
|
+
lldp_xml = etree.fromstring(rpc_reply)
|
|
163
|
+
|
|
164
|
+
# print(etree.tostring(lldp_xml, pretty_print=True, encoding="unicode"))
|
|
165
|
+
|
|
166
|
+
output = {}
|
|
167
|
+
for i in self._xpath(lldp_xml, "//dn-lldp:interface"):
|
|
168
|
+
if not self._xpath(i, ".//dn-lldp:system-name"):
|
|
169
|
+
continue
|
|
170
|
+
i_name = self._text(i, ".//dn-lldp:name")
|
|
171
|
+
output[i_name] = []
|
|
172
|
+
|
|
173
|
+
for n in self._xpath(i, ".//dn-lldp:neighbor"):
|
|
174
|
+
output[i_name].append(
|
|
175
|
+
{
|
|
176
|
+
"hostname": self._text(n, ".//dn-lldp:system-name"),
|
|
177
|
+
"port": self._text(n, ".//dn-lldp:port-id"),
|
|
178
|
+
}
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
return output
|
|
182
|
+
|
|
183
|
+
def get_inventory(self) -> List[sc_models.InventoryDict]:
|
|
184
|
+
"""
|
|
185
|
+
sc-napalm get inventory
|
|
186
|
+
"""
|
|
187
|
+
rpc_reply = self.device.dispatch(to_ele(C.INVENTORY_RPC_REQ)).xml
|
|
188
|
+
inv_xml = etree.fromstring(rpc_reply)
|
|
189
|
+
# print(etree.tostring(inv_xml, pretty_print=True, encoding="unicode"))
|
|
190
|
+
|
|
191
|
+
output = []
|
|
192
|
+
for i in self._xpath(inv_xml, "//dn-if:interface"):
|
|
193
|
+
if not self._xpath(i, ".//dn-trans:ethernet-pmd"):
|
|
194
|
+
continue
|
|
195
|
+
|
|
196
|
+
i_name = self._text(i, "//dn-if:name")
|
|
197
|
+
output.append(
|
|
198
|
+
{
|
|
199
|
+
"type": "optic",
|
|
200
|
+
"subtype": self._get_optic_subtype(i),
|
|
201
|
+
"name": i_name,
|
|
202
|
+
"part_number": self._text(i, ".//dn-trans:vendor-part").strip(),
|
|
203
|
+
"serial_number": self._text(i, ".//dn-trans:serial-no").strip(),
|
|
204
|
+
}
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
return output
|
|
208
|
+
|
|
209
|
+
def _get_optic_subtype(self, i):
|
|
210
|
+
"""
|
|
211
|
+
Optic subtypes are a bit tricky and sometimes have to be
|
|
212
|
+
inferred
|
|
213
|
+
"""
|
|
214
|
+
# the ethernet-pmd attribute gives us an official
|
|
215
|
+
# 'transport type' which matches the data we want here
|
|
216
|
+
eth_pmd = self._text(i, ".//dn-trans:ethernet-pmd")
|
|
217
|
+
eth_pmd = eth_pmd.replace("dn-transport-types:", "")
|
|
218
|
+
|
|
219
|
+
# terrible assumptions - right now on the one nos switch I only
|
|
220
|
+
# see 'undefined' for 400G and 100G, wavelengths are all "1311"
|
|
221
|
+
if eth_pmd == "ETH_UNDEFINED":
|
|
222
|
+
speed = int(self._text(i, ".//dn-if:interface-speed")) / 1000
|
|
223
|
+
if self._text(i, ".//dn-trans:wavelength") == "1311":
|
|
224
|
+
return f"ETH_{int(speed)}GBASE-LR4"
|
|
225
|
+
|
|
226
|
+
raise ValueError(
|
|
227
|
+
f"ETH_UNDEFINED {i[0].text} speed {speed} WAVELENGTH {self._text(i, './/dn-trans:wavelength')}"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
return eth_pmd
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .nxos import SCNXOS
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
import logging
|
|
3
|
+
|
|
4
|
+
from napalm.nxos_ssh import NXOSSSHDriver
|
|
5
|
+
from napalm.base.helpers import textfsm_extractor
|
|
6
|
+
|
|
7
|
+
from ..base import SCBaseDriver
|
|
8
|
+
from ..models import (
|
|
9
|
+
InventoryDict,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SCNXOS(NXOSSSHDriver, SCBaseDriver):
|
|
16
|
+
"""
|
|
17
|
+
NXOS Parser
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
# for nexus we're going to map the 'description' provided by
|
|
21
|
+
# show inventory to the type
|
|
22
|
+
INVENTORY_TO_TYPE = {
|
|
23
|
+
# note we're using the fact that this dict gets evaluated
|
|
24
|
+
# sequentially to catch the linecards, whose descriptions are varied
|
|
25
|
+
# but all end in 'Module'
|
|
26
|
+
r"Fabric Module": "fabric_module",
|
|
27
|
+
r"Fabric card": "fabric_module", # N7K fabric modules in umd
|
|
28
|
+
r"Fabric Extender": None, # FEXes in Dearborn
|
|
29
|
+
r"N2K-C2": "stack_member", # FEXes in Dearborn
|
|
30
|
+
r"Eth\s?Mod": None, # chassis for fixed config Nexus
|
|
31
|
+
r"Supervisor Module": "re",
|
|
32
|
+
r"Fan Module": "fan",
|
|
33
|
+
r"Module": "linecard",
|
|
34
|
+
r"System Controller": None,
|
|
35
|
+
r"Chassis": None,
|
|
36
|
+
r"Power Supply": "psu",
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
I_ABBRS = {
|
|
40
|
+
"Lo": "loopback",
|
|
41
|
+
"Po": "port-channel",
|
|
42
|
+
"Eth": "Ethernet",
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
def get_inventory(self) -> List[InventoryDict]:
|
|
46
|
+
"""
|
|
47
|
+
Parses "show inventory" and "show interface transciever"
|
|
48
|
+
"""
|
|
49
|
+
|
|
50
|
+
raw_inventory = self._send_command("show inventory")
|
|
51
|
+
inventory = textfsm_extractor(self, "sh_inventory", raw_inventory)
|
|
52
|
+
|
|
53
|
+
output = []
|
|
54
|
+
for entry in inventory:
|
|
55
|
+
inventory_type = self._get_inventory_type(entry["desc"])
|
|
56
|
+
if not inventory_type:
|
|
57
|
+
continue
|
|
58
|
+
|
|
59
|
+
output.append(
|
|
60
|
+
{
|
|
61
|
+
"type": inventory_type,
|
|
62
|
+
"name": entry["name"],
|
|
63
|
+
"part_number": entry["pid"],
|
|
64
|
+
"serial_number": entry["sn"],
|
|
65
|
+
}
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
raw_trans = self._send_command("show interface transceiver")
|
|
69
|
+
trans = textfsm_extractor(self, "sh_int_transceiver", raw_trans)
|
|
70
|
+
for entry in trans:
|
|
71
|
+
if "AOC" in entry["type"]:
|
|
72
|
+
db_type = "aoc"
|
|
73
|
+
elif "DAC" in entry["type"]:
|
|
74
|
+
db_type = "dac"
|
|
75
|
+
else:
|
|
76
|
+
db_type = "optic"
|
|
77
|
+
|
|
78
|
+
output.append(
|
|
79
|
+
{
|
|
80
|
+
"type": db_type,
|
|
81
|
+
"subtype": entry["type"],
|
|
82
|
+
"name": entry["interface"],
|
|
83
|
+
"part_number": entry["pn"],
|
|
84
|
+
"serial_number": entry["sn"],
|
|
85
|
+
}
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
return output
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
Value Required INTERFACE (\S+)
|
|
2
|
+
Value TYPE (\S+)
|
|
3
|
+
Value PN (\S+)
|
|
4
|
+
Value Required SN (\w+)
|
|
5
|
+
|
|
6
|
+
Start
|
|
7
|
+
^\S+ -> Continue.Record
|
|
8
|
+
^${INTERFACE}$$
|
|
9
|
+
^\s+type is ${TYPE}
|
|
10
|
+
^\s+part number is ${PN}
|
|
11
|
+
^\s+serial number is ${SN}
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
#Ethernet1/101
|
|
15
|
+
# transceiver is present
|
|
16
|
+
# type is QSFP-100G-SR4
|
|
17
|
+
# name is FS
|
|
18
|
+
# part number is QSFP28-SR4-100G
|
|
19
|
+
# revision is 04
|
|
20
|
+
# serial number is G2240392123
|
|
21
|
+
# nominal bitrate is 25500 MBit/sec
|
|
22
|
+
# Link length supported for 50/125um OM3 fiber is 70 m
|
|
23
|
+
# cisco id is 17
|
|
24
|
+
# cisco extended id number is 220
|
|
25
|
+
#
|
|
26
|
+
#Ethernet1/102
|
|
27
|
+
# transceiver is not present
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .srl import SCNokiaSRLDriver
|
custom_napalm/srl/srl.py
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
from napalm_srl import NokiaSRLDriver
|
|
2
|
+
from napalm_srl.srl import SRLAPI
|
|
3
|
+
from napalm.base import models as napalm_models
|
|
4
|
+
from ..base import SCBaseNetconfDriver
|
|
5
|
+
|
|
6
|
+
from ipaddress import IPv6Address
|
|
7
|
+
|
|
8
|
+
from pprint import pprint
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SCSLRAPI(SRLAPI):
|
|
12
|
+
"""
|
|
13
|
+
This override fixes an issue with IPv6 addresses embedded in URLs when talking
|
|
14
|
+
over GRPCs. Not sure if it's moot with the various TLS
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, hostname, username, password, timeout=60, optional_args=None):
|
|
18
|
+
super().__init__(hostname, username, password, timeout, optional_args)
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
IPv6Address(hostname)
|
|
22
|
+
self.target = f"[{hostname}]:{self.gnmi_port}"
|
|
23
|
+
except ValueError:
|
|
24
|
+
pass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class SCNokiaSRLDriver(NokiaSRLDriver, SCBaseNetconfDriver):
|
|
28
|
+
def __init__(self, hostname, username, password, timeout=60, optional_args=None):
|
|
29
|
+
"""
|
|
30
|
+
Forcing insecure connection for testing purposes
|
|
31
|
+
"""
|
|
32
|
+
optional_args = optional_args if optional_args else {}
|
|
33
|
+
optional_args["insecure"] = True
|
|
34
|
+
optional_args["skip_verify"] = True
|
|
35
|
+
|
|
36
|
+
optional_args["encoding"] = "JSON_IETF"
|
|
37
|
+
|
|
38
|
+
super().__init__(
|
|
39
|
+
hostname, username, password, timeout=timeout, optional_args=optional_args
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
self.device = SCSLRAPI(
|
|
43
|
+
hostname, username, password, timeout=timeout, optional_args=optional_args
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
def get_config(
|
|
47
|
+
self, retrieve="all", full=False, sanitized=False, format="text"
|
|
48
|
+
) -> napalm_models.ConfigDict:
|
|
49
|
+
"""
|
|
50
|
+
pulling running config via ssh sow we can have it in 'display set' format.
|
|
51
|
+
"""
|
|
52
|
+
if format != "text":
|
|
53
|
+
return super().get_config(
|
|
54
|
+
retrieve=retrieve, full=full, sanitized=sanitized, format=format
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
config = {"startup": "", "running": "", "candidate": ""}
|
|
58
|
+
|
|
59
|
+
with self.ssh_conn() as ssh_device:
|
|
60
|
+
if retrieve in ["all", "running"]:
|
|
61
|
+
config["running"] = ssh_device.send_command("info from running flat")
|
|
62
|
+
if retrieve in ["all", "startup"]:
|
|
63
|
+
config["startup"] = ssh_device.send_command("info from startup flat")
|
|
64
|
+
if retrieve in ["all", "candidate"]:
|
|
65
|
+
config["candidate"] = ssh_device.send_command(
|
|
66
|
+
"info from candidate flat"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
return config
|
|
70
|
+
|
|
71
|
+
def get_optics(self):
|
|
72
|
+
path = {"/interface"}
|
|
73
|
+
path_type = "STATE"
|
|
74
|
+
output = self.device._gnmiGet("", path, path_type)
|
|
75
|
+
interfaces = self._getObj(
|
|
76
|
+
output, *["srl_nokia-interfaces:interface"], default=[]
|
|
77
|
+
)
|
|
78
|
+
channel_data = {}
|
|
79
|
+
|
|
80
|
+
for i in interfaces:
|
|
81
|
+
if not self._getObj(i, *["transceiver", "channel"], default=False):
|
|
82
|
+
continue
|
|
83
|
+
|
|
84
|
+
name = self._getObj(i, *["name"])
|
|
85
|
+
channel = self._getObj(i, *["transceiver", "channel"], default={})[0]
|
|
86
|
+
channel_data.update(
|
|
87
|
+
{
|
|
88
|
+
name: {
|
|
89
|
+
"physical_channels": {
|
|
90
|
+
"channel": [
|
|
91
|
+
{
|
|
92
|
+
"index": self._getObj(
|
|
93
|
+
channel, *["index"], default=-1
|
|
94
|
+
),
|
|
95
|
+
"state": {
|
|
96
|
+
"input_power": {
|
|
97
|
+
"instant": self._getObj(
|
|
98
|
+
channel,
|
|
99
|
+
*["input-power", "latest-value"],
|
|
100
|
+
default=-1.0,
|
|
101
|
+
),
|
|
102
|
+
"avg": -1.0,
|
|
103
|
+
"min": -1.0,
|
|
104
|
+
"max": -1.0,
|
|
105
|
+
},
|
|
106
|
+
"output_power": {
|
|
107
|
+
"instant": self._getObj(
|
|
108
|
+
channel,
|
|
109
|
+
*["output-power", "latest-value"],
|
|
110
|
+
default=-1.0,
|
|
111
|
+
),
|
|
112
|
+
"avg": -1.0,
|
|
113
|
+
"min": -1.0,
|
|
114
|
+
"max": -1.0,
|
|
115
|
+
},
|
|
116
|
+
"laser_bias_current": {
|
|
117
|
+
"instant": self._getObj(
|
|
118
|
+
channel,
|
|
119
|
+
*["laser-bias-current", "latest-value"],
|
|
120
|
+
default=-1.0,
|
|
121
|
+
),
|
|
122
|
+
"avg": -1.0,
|
|
123
|
+
"min": -1.0,
|
|
124
|
+
"max": -1.0,
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
}
|
|
128
|
+
]
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
)
|
|
133
|
+
|
|
134
|
+
return channel_data
|
|
135
|
+
|
|
136
|
+
def get_inventory(self):
|
|
137
|
+
path = {"/interface/transceiver"}
|
|
138
|
+
path_type = "STATE"
|
|
139
|
+
output = self.device._gnmiGet("", path, path_type)
|
|
140
|
+
transceivers = self._getObj(
|
|
141
|
+
output, *["srl_nokia-interfaces:interface"], default=[]
|
|
142
|
+
)
|
|
143
|
+
result = []
|
|
144
|
+
for t_if in transceivers:
|
|
145
|
+
trans = t_if["transceiver"]
|
|
146
|
+
|
|
147
|
+
if "serial-number" not in trans:
|
|
148
|
+
continue
|
|
149
|
+
|
|
150
|
+
result.append(
|
|
151
|
+
{
|
|
152
|
+
"type": "optic",
|
|
153
|
+
"subtype": trans["ethernet-pmd"],
|
|
154
|
+
"name": t_if["name"],
|
|
155
|
+
"part_number": trans["vendor-part-number"],
|
|
156
|
+
"serial_number": trans["serial-number"],
|
|
157
|
+
}
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
return result
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .srl_ssh import NokiaSRLSSHDriver
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from typing import Optional, Dict
|
|
2
|
+
from napalm.base import NetworkDriver
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class NokiaSRLSSHDriver(NetworkDriver):
|
|
6
|
+
"""
|
|
7
|
+
Netmiko based Nokia SRL driver :(
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
def __init__(
|
|
11
|
+
self,
|
|
12
|
+
hostname: str,
|
|
13
|
+
username: str,
|
|
14
|
+
password: str,
|
|
15
|
+
timeout: int = 60,
|
|
16
|
+
optional_args: Optional[Dict] = None,
|
|
17
|
+
) -> None:
|
|
18
|
+
if optional_args is None:
|
|
19
|
+
optional_args = {}
|
|
20
|
+
self.hostname = hostname
|
|
21
|
+
self.username = username
|
|
22
|
+
self.password = password
|
|
23
|
+
self.timeout = timeout
|
|
24
|
+
|
|
25
|
+
def open(self):
|
|
26
|
+
self.device = self._netmiko_open("nokia_srl")
|
|
27
|
+
|
|
28
|
+
def close(self):
|
|
29
|
+
self._netmiko_close()
|
|
30
|
+
|
|
31
|
+
def send_command(self, cmd: str) -> str:
|
|
32
|
+
"""
|
|
33
|
+
Sends command with netmiko and returns the result
|
|
34
|
+
"""
|
|
35
|
+
return self.device.send_command(cmd)
|
|
36
|
+
|
|
37
|
+
def get_config(self, retrieve="all", full=False, sanitized=False, format="text"):
|
|
38
|
+
result = {"startup": "", "candidate": "", "running": ""}
|
|
39
|
+
if retrieve in ["all", "running"]:
|
|
40
|
+
result["running"] = self.send_command("info from running flat")
|
|
41
|
+
if retrieve in ["all", "startup"]:
|
|
42
|
+
result["startup"] = self.send_command("info from startup flat")
|
|
43
|
+
if retrieve in ["all", "candidate"]:
|
|
44
|
+
result["candidate"] = self.send_command("info from candidate flat")
|
|
45
|
+
return result
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .sros import SCNokiaSROSDriver
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
GET_INVENTORY = {
|
|
2
|
+
"_": """
|
|
3
|
+
<filter xmlns="urn:ietf:params:xml:ns:netconf:base:1.0">
|
|
4
|
+
<state xmlns="urn:nokia.com:sros:ns:yang:sr:state">
|
|
5
|
+
<port>
|
|
6
|
+
<port-id/>
|
|
7
|
+
<transceiver>
|
|
8
|
+
<vendor-part-number/>
|
|
9
|
+
<model-number/>
|
|
10
|
+
<vendor-serial-number/>
|
|
11
|
+
<connector-type/>
|
|
12
|
+
<type/>
|
|
13
|
+
</transceiver>
|
|
14
|
+
</port>
|
|
15
|
+
</state>
|
|
16
|
+
</filter>
|
|
17
|
+
"""
|
|
18
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
from napalm_sros import NokiaSROSDriver
|
|
2
|
+
from napalm.base import models as napalm_models
|
|
3
|
+
|
|
4
|
+
from lxml import etree
|
|
5
|
+
from ncclient.xml_ import to_ele, to_xml
|
|
6
|
+
|
|
7
|
+
from ..base import SCBaseNetconfDriver, sc_models
|
|
8
|
+
from .nc_filters import GET_INVENTORY
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class SCNokiaSROSDriver(NokiaSROSDriver, SCBaseNetconfDriver):
|
|
12
|
+
netmiko_host_type = "alcatel_sros"
|
|
13
|
+
|
|
14
|
+
NS = {
|
|
15
|
+
"state": "urn:nokia.com:sros:ns:yang:sr:state",
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
def get_config(
|
|
19
|
+
self, retrieve="all", full=False, sanitized=False, format="text"
|
|
20
|
+
) -> napalm_models.ConfigDict:
|
|
21
|
+
if format != "text":
|
|
22
|
+
return super().get_config(
|
|
23
|
+
retrieve=retrieve, full=full, sanitized=sanitized, format=format
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
config = {"startup": "", "running": "", "candidate": ""}
|
|
27
|
+
|
|
28
|
+
with self.ssh_conn() as ssh_device:
|
|
29
|
+
config["running"] = ssh_device.send_command("admin show configuration flat")
|
|
30
|
+
config["running"] += ssh_device.send_command(
|
|
31
|
+
"admin show configuration bof flat"
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return config
|
|
35
|
+
|
|
36
|
+
def get_inventory(self) -> list[sc_models.InventoryDict]:
|
|
37
|
+
inv_xml = to_ele(
|
|
38
|
+
self.conn.get(
|
|
39
|
+
filter=GET_INVENTORY["_"], with_defaults="report-all"
|
|
40
|
+
).data_xml
|
|
41
|
+
)
|
|
42
|
+
# print(etree.tostring(result, pretty_print=True, encoding="unicode"))
|
|
43
|
+
|
|
44
|
+
results = []
|
|
45
|
+
for i in self._xpath(inv_xml, ".//state:port"):
|
|
46
|
+
if not self._xpath(i, ".//state:transceiver"):
|
|
47
|
+
continue
|
|
48
|
+
|
|
49
|
+
results.append(
|
|
50
|
+
{
|
|
51
|
+
"type": "optic",
|
|
52
|
+
"subtype": self._text(i, ".//state:vendor-part-number").strip(),
|
|
53
|
+
"name": self._text(i, ".//state:port-id"),
|
|
54
|
+
"part_number": self._text(i, ".//state:model-number").strip(),
|
|
55
|
+
"serial_number": self._text(i, ".//state:vendor-serial-number"),
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return results
|