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.
@@ -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,8 @@
1
+ Value NAME (.+)
2
+ Value DESC (.+)
3
+ Value PID (\S*)
4
+ Value SN (\S+)
5
+
6
+ Start
7
+ ^NAME: "${NAME}",\s+DESCR: "${DESC}"
8
+ ^PID: ${PID}\s*,\s+VID:.*,\s+SN: ${SN} -> Record
@@ -0,0 +1 @@
1
+ from .srl import SCNokiaSRLDriver
@@ -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