mirror of
https://github.com/napalm-automation-community/napalm-vyos.git
synced 2026-09-22 12:11:15 +00:00
NAPALM Vyos Driver
This commit is contained in:
16
napalm_vyos/__init__.py
Normal file
16
napalm_vyos/__init__.py
Normal file
@@ -0,0 +1,16 @@
|
||||
# Copyright 2016 Dravetech AB. All rights reserved.
|
||||
#
|
||||
# The contents of this file are licensed under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under
|
||||
# the License.
|
||||
|
||||
"""napalm_vyos package."""
|
||||
from vyos import VyOSDriver
|
||||
0
napalm_vyos/templates/.placeholder
Normal file
0
napalm_vyos/templates/.placeholder
Normal file
1
napalm_vyos/utils/__init__.py
Normal file
1
napalm_vyos/utils/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
"""napalm.utils package."""
|
||||
0
napalm_vyos/utils/textfsm_templates/.placeholder
Normal file
0
napalm_vyos/utils/textfsm_templates/.placeholder
Normal file
788
napalm_vyos/vyos.py
Normal file
788
napalm_vyos/vyos.py
Normal file
@@ -0,0 +1,788 @@
|
||||
# Copyright 2016 Dravetech AB. All rights reserved.
|
||||
#
|
||||
# The contents of this file are licensed under the Apache License, Version 2.0
|
||||
# (the "License"); you may not use this file except in compliance with the
|
||||
# License. You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations under
|
||||
# the License.
|
||||
|
||||
"""
|
||||
Napalm driver for VyOS.
|
||||
|
||||
Read napalm.readthedocs.org for more information.
|
||||
|
||||
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
|
||||
import vyattaconfparser
|
||||
|
||||
|
||||
from netmiko import ConnectHandler
|
||||
from netmiko import SCPConn
|
||||
|
||||
|
||||
# NAPALM base
|
||||
from napalm_base.base import NetworkDriver
|
||||
from napalm_base.exceptions import ConnectionException, SessionLockedException, \
|
||||
MergeConfigException, ReplaceConfigException,\
|
||||
CommandErrorException
|
||||
|
||||
|
||||
class VyOSDriver(NetworkDriver):
|
||||
|
||||
_MINUTE_SECONDS = 60
|
||||
_HOUR_SECONDS = 60 * _MINUTE_SECONDS
|
||||
_DAY_SECONDS = 24 * _HOUR_SECONDS
|
||||
_WEEK_SECONDS = 7 * _DAY_SECONDS
|
||||
_YEAR_SECONDS = 365 * _DAY_SECONDS
|
||||
_DEST_FILENAME = "/var/tmp/candidate_running.conf"
|
||||
_BACKUP_FILENAME = "/var/tmp/backup_running.conf"
|
||||
_BOOT_FILENAME = "/config/config.boot"
|
||||
|
||||
def __init__(self, hostname, username, password, timeout=60, optional_args=None):
|
||||
self._hostname = hostname
|
||||
self._username = username
|
||||
self._password = password
|
||||
self._timeout = timeout
|
||||
self._device = None
|
||||
self._scp_client = None
|
||||
self._new_config = None
|
||||
self._old_config = None
|
||||
self._ssh_usekeys = False
|
||||
|
||||
if optional_args is None:
|
||||
optional_args = {}
|
||||
self._port = optional_args.get('port', 22)
|
||||
self._ssh_keyfile = optional_args.get('ssh_keyfile', None)
|
||||
if self._ssh_keyfile != None:
|
||||
self._ssh_usekeys = True
|
||||
|
||||
|
||||
|
||||
|
||||
def open(self):
|
||||
|
||||
device = {
|
||||
'device_type': 'vyos',
|
||||
'ip': self._hostname,
|
||||
'username': self._username,
|
||||
'password': self._password,
|
||||
'use_keys': self._ssh_usekeys,
|
||||
'key_file': self._ssh_keyfile,
|
||||
'port' : self._port, # optional, defaults to 22
|
||||
'secret': 'secret', # optional, defaults to ''
|
||||
'verbose': False, # optional, defaults to False
|
||||
}
|
||||
|
||||
self._device = ConnectHandler(**device)
|
||||
self._scp_client = SCPConn(self._device)
|
||||
|
||||
|
||||
def close(self):
|
||||
self._device.close()
|
||||
|
||||
def load_replace_candidate(self, filename=None, config=None):
|
||||
"""
|
||||
Only configuration files are supported with load_replace_candidate.
|
||||
It must be a full config file like /config/config.boot
|
||||
Due to the OS nature, we do not
|
||||
support a replace using a configuration string.
|
||||
"""
|
||||
if filename is not None:
|
||||
if os.path.exists(filename) == True:
|
||||
self._scp_client.scp_transfer_file(filename, self._DEST_FILENAME)
|
||||
print self._device.send_command("cp "+self._BOOT_FILENAME+" "+self._BACKUP_FILENAME)
|
||||
output_loadcmd = self._device.send_config_set(['load '+self._DEST_FILENAME])
|
||||
match = re.findall("Load complete.", output_loadcmd)
|
||||
if not match:
|
||||
raise ReplaceConfigException("Failed replace config: "
|
||||
+output_loadcmd)
|
||||
else:
|
||||
raise ReplaceConfigException("config file is not found")
|
||||
else:
|
||||
raise ReplaceConfigException("no configuration found")
|
||||
|
||||
def load_merge_candidate(self, filename=None, config=None):
|
||||
"""
|
||||
Only configuration in set-format is supported with load_merge_candidate.
|
||||
"""
|
||||
if filename is not None:
|
||||
if os.path.exists(filename) == True:
|
||||
with open(filename) as f:
|
||||
print self._device.send_command("cp "+self._BOOT_FILENAME+" "+self._BACKUP_FILENAME)
|
||||
self._new_config = f.read()
|
||||
cfg = [x for x in self._new_config.split("\n") if x is not ""]
|
||||
self._device.send_config_set(cfg)
|
||||
else:
|
||||
raise MergeConfigException("config file is not found")
|
||||
elif config is not None:
|
||||
self._new_config = config
|
||||
else:
|
||||
raise MergeConfigException("no configuration found")
|
||||
|
||||
|
||||
def discard_config(self):
|
||||
self._device.send_config_set(['discard'])
|
||||
|
||||
def compare_config(self):
|
||||
output_compare = self._device.send_config_set(['compare'])
|
||||
match = re.findall("No changes between working and active configurations",
|
||||
output_compare)
|
||||
if match:
|
||||
return
|
||||
else:
|
||||
return output_compare
|
||||
|
||||
def commit_config(self):
|
||||
self._device.send_config_set(['commit', 'save'])
|
||||
|
||||
def rollback(self, filename=None):
|
||||
"""Rollback configuration to filename or to self.rollback_cfg file."""
|
||||
if filename is None:
|
||||
filename=self._BACKUP_FILENAME
|
||||
|
||||
output_loadcmd = self._device.send_config_set(['load '+filename])
|
||||
match = re.findall("Load complete.", output_loadcmd)
|
||||
if not match:
|
||||
raise ReplaceConfigException("Failed rollback config: "
|
||||
+output_loadcmd)
|
||||
else:
|
||||
self._device.send_config_set(['commit', 'save'])
|
||||
|
||||
|
||||
|
||||
def get_environment(self):
|
||||
|
||||
"""
|
||||
'vmstat' output:
|
||||
procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
|
||||
r b swpd free buff cache si so bi bo in cs us sy id wa
|
||||
0 0 0 61404 139624 139360 0 0 0 0 9 14 0 0 100 0
|
||||
"""
|
||||
output_cpu = self._device.send_command("vmstat").split("\n")[-1]
|
||||
cpu = 100 - int(output_cpu.split()[-2])
|
||||
|
||||
"""
|
||||
'free' output:
|
||||
total used free shared buffers cached
|
||||
Mem: 508156 446784 61372 0 139624 139360
|
||||
-/+ buffers/cache: 167800 340356
|
||||
Swap: 0 0 0
|
||||
"""
|
||||
output_ram = self._device.send_command("free").split("\n")[1]
|
||||
available_ram, used_ram = output_ram.split()[1:3]
|
||||
|
||||
environment = {
|
||||
"fans": {
|
||||
"status": None
|
||||
},
|
||||
"temperature": {
|
||||
"temperature": None,
|
||||
"is_alert" : None,
|
||||
"is_critical": None
|
||||
},
|
||||
"power": {
|
||||
"status" : None,
|
||||
"capacity": None,
|
||||
"output" : None
|
||||
},
|
||||
"cpu": {
|
||||
"0": {
|
||||
"%usage": cpu
|
||||
},
|
||||
},
|
||||
"memory": {
|
||||
"available_ram": int(available_ram),
|
||||
"used_ram" : int(used_ram)
|
||||
}
|
||||
}
|
||||
|
||||
return environment
|
||||
|
||||
|
||||
def get_interfaces(self):
|
||||
"""
|
||||
"show interfaces" output example:
|
||||
Interface IP Address S/L Description
|
||||
--------- ---------- --- -----------
|
||||
br0 - u/D
|
||||
eth0 192.168.1.1/24 u/u Management
|
||||
eth1 192.168.1.2/24 u/u
|
||||
eth2 192.168.3.1/24 u/u foobar
|
||||
192.168.2.2/24
|
||||
lo 127.0.0.1/8 u/u
|
||||
::1/128
|
||||
"""
|
||||
output_iface = self._device.send_command("show interfaces")
|
||||
|
||||
# Collect all interfaces' name and status
|
||||
match = re.findall("(\S+)\s+[:\-\d/\.]+\s+([uAD])/([uAD])", output_iface)
|
||||
|
||||
# 'match' example:
|
||||
# [("br0", "u", "D"), ("eth0", "u", "u"), ("eth1", "u", "u")...]
|
||||
iface_state = {iface_name:{"State": state, "Link": link} for iface_name, state, link in match}
|
||||
|
||||
output_conf = self._device.send_command("show configuration")
|
||||
|
||||
# Convert the configuration to dictionary
|
||||
config = vyattaconfparser.parse_conf(output_conf)
|
||||
|
||||
iface_dict = dict()
|
||||
|
||||
for iface_type in config["interfaces"]:
|
||||
|
||||
ifaces_detail = config["interfaces"][iface_type]
|
||||
|
||||
for iface_name in ifaces_detail:
|
||||
|
||||
description = self._get_value("description", ifaces_detail[iface_name])
|
||||
speed = self._get_value("speed", ifaces_detail[iface_name])
|
||||
hw_id = self._get_value("hw-id", ifaces_detail[iface_name])
|
||||
|
||||
is_up = (iface_state[iface_name]["Link"] == "u")
|
||||
is_enabled = (iface_state[iface_name]["State"] == "u")
|
||||
|
||||
iface_dict.update({
|
||||
iface_name: {
|
||||
"is_up" : is_up,
|
||||
"is_enabled" : is_enabled,
|
||||
"description" : description,
|
||||
"last_flapped" : -1,
|
||||
"speed" : speed,
|
||||
"mac_address" : hw_id
|
||||
}
|
||||
})
|
||||
|
||||
return iface_dict
|
||||
|
||||
|
||||
# for avoiding KeyError
|
||||
@staticmethod
|
||||
def _get_value(key, target_dict):
|
||||
if key in target_dict:
|
||||
return target_dict[key]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def get_arp_table(self):
|
||||
# 'age' is not implemented yet
|
||||
|
||||
"""
|
||||
'show arp' output example:
|
||||
Address HWtype HWaddress Flags Mask Iface
|
||||
10.129.2.254 ether 00:50:56:97:af:b1 C eth0
|
||||
192.168.1.134 (incomplete) eth1
|
||||
192.168.1.1 ether 00:50:56:ba:26:7f C eth1
|
||||
10.129.2.97 ether 00:50:56:9f:64:09 C eth0
|
||||
192.168.1.3 ether 00:50:56:86:7b:06 C eth1
|
||||
"""
|
||||
output = self._device.send_command("show arp")
|
||||
output = output.split("\n")
|
||||
|
||||
# Skip the header line
|
||||
output = output[1:-1]
|
||||
|
||||
arp_table = list()
|
||||
for line in output:
|
||||
|
||||
line = line.split()
|
||||
# 'line' example:
|
||||
# ["10.129.2.254", "ether", "00:50:56:97:af:b1", "C", "eth0"]
|
||||
# [u'10.0.12.33', u'(incomplete)', u'eth1']
|
||||
if "incomplete" in line[1]:
|
||||
macaddr=None
|
||||
else:
|
||||
macaddr=unicode(line[2])
|
||||
|
||||
arp_table.append({
|
||||
"interface" : unicode(line[-1]),
|
||||
"mac" : macaddr,
|
||||
"ip" : unicode(line[0]),
|
||||
"age" : None
|
||||
})
|
||||
|
||||
return arp_table
|
||||
|
||||
|
||||
def get_ntp_stats(self):
|
||||
"""
|
||||
'ntpq -np' output example
|
||||
remote refid st t when poll reach delay offset jitter
|
||||
==============================================================================
|
||||
116.91.118.97 133.243.238.244 2 u 51 64 377 5.436 987971. 1694.82
|
||||
219.117.210.137 .GPS. 1 u 17 64 377 17.586 988068. 1652.00
|
||||
133.130.120.204 133.243.238.164 2 u 46 64 377 7.717 987996. 1669.77
|
||||
"""
|
||||
|
||||
output = self._device.send_command("ntpq -np").split("\n")[2:]
|
||||
ntp_stats = list()
|
||||
|
||||
for ntp_info in output:
|
||||
|
||||
remote, refid, st, t, when, hostpoll, reachability, delay, offset, jitter = ntp_info.split()
|
||||
|
||||
# 'remote' contains '*' if the machine synchronized with NTP server
|
||||
synchronized = "*" in remote
|
||||
|
||||
match = re.search("(\d+\.\d+\.\d+\.\d+)", remote)
|
||||
ip = match.group(1)
|
||||
|
||||
when = when if when != '-' else 0
|
||||
|
||||
ntp_stats.append({
|
||||
"remote" : unicode(ip),
|
||||
"referenceid" : unicode(refid),
|
||||
"synchronized": synchronized,
|
||||
"stratum" : int(st),
|
||||
"type" : unicode(t),
|
||||
"when" : int(when),
|
||||
"hostpoll" : int(hostpoll),
|
||||
"reachability": int(reachability),
|
||||
"delay" : float(delay),
|
||||
"offset" : float(offset),
|
||||
"jitter" : float(jitter)
|
||||
})
|
||||
|
||||
return ntp_stats
|
||||
|
||||
|
||||
def get_ntp_peers(self):
|
||||
output = self._device.send_command("ntpq -np").split("\n")[2:]
|
||||
|
||||
ntp_peers = dict()
|
||||
|
||||
for line in output:
|
||||
match = re.search("(\d+\.\d+\.\d+\.\d+)\s+", line)
|
||||
ntp_peers.update({
|
||||
unicode(match.group(1)): {}
|
||||
})
|
||||
|
||||
return ntp_peers
|
||||
|
||||
def get_bgp_neighbors(self):
|
||||
# 'description', 'sent_prefixes' and 'received_prefixes' are not implemented yet
|
||||
|
||||
"""
|
||||
'show ip bgp summary' output example:
|
||||
BGP router identifier 192.168.1.2, local AS number 64520
|
||||
IPv4 Unicast - max multipaths: ebgp 1 ibgp 1
|
||||
RIB entries 3, using 288 bytes of memory
|
||||
Peers 3, using 13 KiB of memory
|
||||
|
||||
Neighbor V AS MsgRcvd MsgSent TblVer InQ OutQ Up/Down State/PfxRcd
|
||||
192.168.1.1 4 64519 7226 7189 0 0 0 4d23h40m 1
|
||||
192.168.1.3 4 64521 7132 7103 0 0 0 4d21h05m 0
|
||||
192.168.1.4 4 64522 0 0 0 0 0 never Active
|
||||
"""
|
||||
|
||||
|
||||
output = self._device.send_command("show ip bgp summary").split("\n")
|
||||
|
||||
match = re.search(".* router identifier (\d+\.\d+\.\d+\.\d+), local AS number (\d+)", output[0])
|
||||
if not match:
|
||||
return {}
|
||||
router_id = unicode(match.group(1))
|
||||
local_as = int(match.group(2))
|
||||
|
||||
bgp_neighbor_data = dict()
|
||||
bgp_neighbor_data["global"] = dict()
|
||||
bgp_neighbor_data["global"]["router_id"] = router_id
|
||||
bgp_neighbor_data["global"]["peers"] = {}
|
||||
|
||||
# delete the header and empty element
|
||||
bgp_info = [i.strip() for i in output[6:-2] if i is not ""]
|
||||
|
||||
for i in bgp_info:
|
||||
peer_id , bgp_version, remote_as, msg_rcvd, msg_sent, table_version, \
|
||||
in_queue, out_queue, up_time, state_prefix = i.split()
|
||||
|
||||
is_enabled = "(Admin)" not in state_prefix
|
||||
|
||||
received_prefixes = None
|
||||
|
||||
try:
|
||||
state_prefix = int(state_prefix)
|
||||
received_prefixes = int(state_prefix)
|
||||
is_up = True
|
||||
except ValueError:
|
||||
is_up = False
|
||||
|
||||
if bgp_version == "4":
|
||||
address_family = "ipv4"
|
||||
elif bgp_version == "6":
|
||||
address_family = "ipv6"
|
||||
else:
|
||||
raise ValueError("BGP neighbor parsing failed")
|
||||
|
||||
"""
|
||||
'show ip bgp neighbors 192.168.1.1' output example:
|
||||
BGP neighbor is 192.168.1.1, remote AS 64519, local AS 64520, external link
|
||||
BGP version 4, remote router ID 192.168.1.1
|
||||
For address family: IPv4 Unicast
|
||||
~~~
|
||||
Community attribute sent to this neighbor(both)
|
||||
1 accepted prefixes
|
||||
~~~
|
||||
"""
|
||||
bgp_detail = self._device.send_command("show ip bgp neighbors %s" % peer_id)
|
||||
|
||||
match_rid = re.search("remote router ID (\d+\.\d+\.\d+\.\d+).*", bgp_detail)
|
||||
remote_rid = match_rid.group(1)
|
||||
|
||||
match_prefix_accepted = re.search("(\d+) accepted prefixes", bgp_detail)
|
||||
accepted_prefixes = match_prefix_accepted.group(1)
|
||||
|
||||
bgp_neighbor_data["global"]["peers"].setdefault(peer_id, {})
|
||||
peer_dict = {
|
||||
"description": "",
|
||||
"is_enabled" : is_enabled,
|
||||
"local_as" : local_as,
|
||||
"is_up" : is_up,
|
||||
"remote_id" : unicode(remote_rid),
|
||||
"uptime" : self._bgp_time_conversion(up_time),
|
||||
"remote_as" : int(remote_as)
|
||||
}
|
||||
|
||||
af_dict = dict()
|
||||
af_dict[address_family] = {
|
||||
"sent_prefixes" : None,
|
||||
"accepted_prefixes": int(accepted_prefixes),
|
||||
"received_prefixes": received_prefixes
|
||||
}
|
||||
|
||||
peer_dict["address_family"] = af_dict
|
||||
bgp_neighbor_data["global"]["peers"][peer_id] = peer_dict
|
||||
|
||||
return bgp_neighbor_data
|
||||
|
||||
|
||||
def _bgp_time_conversion(self, bgp_uptime):
|
||||
uptime_letters = set(["y", "w", "h", "d"])
|
||||
|
||||
if "never" in bgp_uptime:
|
||||
return -1
|
||||
else:
|
||||
if "y" in bgp_uptime:
|
||||
match = re.search("(\d+)(\w)(\d+)(\w)(\d+)(\w)", bgp_uptime)
|
||||
uptime = ((int(match.group(1)) * self._YEAR_SECONDS) +
|
||||
(int(match.group(3)) * self._WEEK_SECONDS) +
|
||||
(int(match.group(5)) * self._DAY_SECONDS))
|
||||
return uptime
|
||||
elif "w" in bgp_uptime:
|
||||
match = re.search("(\d+)(\w)(\d+)(\w)(\d+)(\w)", bgp_uptime)
|
||||
uptime = ((int(match.group(1)) * self._WEEK_SECONDS) +
|
||||
(int(match.group(3)) * self._DAY_SECONDS) +
|
||||
(int(match.group(5)) * self._HOUR_SECONDS))
|
||||
return uptime
|
||||
elif "d" in bgp_uptime:
|
||||
match = re.search("(\d+)(\w)(\d+)(\w)(\d+)(\w)", bgp_uptime)
|
||||
uptime = ((int(match.group(1)) * self._DAY_SECONDS) +
|
||||
(int(match.group(3)) * self._HOUR_SECONDS) +
|
||||
(int(match.group(5)) * self._MINUTE_SECONDS))
|
||||
return uptime
|
||||
else:
|
||||
hours, minutes, seconds = map(int, bgp_uptime.split(":"))
|
||||
uptime = ((hours * self._HOUR_SECONDS) +
|
||||
(minutes * self._MINUTE_SECONDS) + seconds)
|
||||
return uptime
|
||||
|
||||
|
||||
def get_interfaces_counters(self):
|
||||
# 'rx_unicast_packet', 'rx_broadcast_packets', 'tx_unicast_packets',
|
||||
# 'tx_multicast_packets' and 'tx_broadcast_packets' are not implemented yet
|
||||
|
||||
"""
|
||||
'show interfaces detail' output example:
|
||||
eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
|
||||
link/ether 00:50:56:86:8c:26 brd ff:ff:ff:ff:ff:ff
|
||||
~~~
|
||||
RX: bytes packets errors dropped overrun mcast
|
||||
35960043 464584 0 221 0 407
|
||||
TX: bytes packets errors dropped carrier collisions
|
||||
32776498 279273 0 0 0 0
|
||||
"""
|
||||
output = self._device.send_command("show interfaces detail")
|
||||
|
||||
interfaces = re.findall("(\S+): <.*", output)
|
||||
count = re.findall("(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+", output)
|
||||
|
||||
counters = dict()
|
||||
|
||||
j = 0
|
||||
|
||||
for i in count:
|
||||
if j % 2 == 0:
|
||||
rx_errors = i[2]
|
||||
rx_discards = i[3]
|
||||
rx_octets = i[0]
|
||||
rx_unicast_packets = i[1]
|
||||
rx_multicast_packets = i[5]
|
||||
rx_broadcast_packets = None
|
||||
else:
|
||||
counters.update({
|
||||
interfaces[j / 2]: {
|
||||
"tx_errors" : i[2],
|
||||
"tx_discards" : i[3],
|
||||
"tx_octets" : i[0],
|
||||
"tx_unicast_packets" : i[1],
|
||||
"tx_multicast_packets": None,
|
||||
"tx_broadcast_packets": None,
|
||||
"rx_errors" : rx_errors,
|
||||
"rx_discards" : rx_discards,
|
||||
"rx_octets" : rx_octets,
|
||||
"rx_unicast_packets" : rx_unicast_packets,
|
||||
"rx_multicast_packets": rx_multicast_packets,
|
||||
"rx_broadcast_packets": rx_broadcast_packets
|
||||
}
|
||||
})
|
||||
j += 1
|
||||
|
||||
return counters
|
||||
|
||||
|
||||
def get_snmp_information(self):
|
||||
# 'acl' is not implemented yet
|
||||
|
||||
output = self._device.send_command("show configuration")
|
||||
# convert the configuration to dictionary
|
||||
config = vyattaconfparser.parse_conf(output)
|
||||
|
||||
snmp = dict()
|
||||
snmp["community"] = dict()
|
||||
|
||||
|
||||
try:
|
||||
for i in config["service"]["snmp"]["community"]:
|
||||
snmp["community"].update({
|
||||
i: {
|
||||
"acl": None,
|
||||
"mode": config["service"]["snmp"]["community"][i]["authorization"]
|
||||
}
|
||||
})
|
||||
|
||||
snmp.update({
|
||||
"contact": config["service"]["snmp"]["contact"],
|
||||
"location": config["service"]["snmp"]["location"]
|
||||
})
|
||||
|
||||
return snmp
|
||||
except KeyError:
|
||||
return {}
|
||||
|
||||
def get_facts(self):
|
||||
output_uptime = self._device.send_command("cat /proc/uptime | awk '{print $1}'")
|
||||
|
||||
|
||||
uptime = int(float(output_uptime))
|
||||
|
||||
|
||||
output = self._device.send_command("show version").split("\n")
|
||||
ver_str = [line for line in output if "Version" in line][0]
|
||||
version = self.parse_version(ver_str)
|
||||
|
||||
sn_str = [line for line in output if "S/N" in line][0]
|
||||
snumber = self.parse_snumber(sn_str)
|
||||
|
||||
hwmodel_str = [line for line in output if "HW model" in line][0]
|
||||
hwmodel = self.parse_hwmodel(hwmodel_str)
|
||||
|
||||
output = self._device.send_command("show configuration")
|
||||
config = vyattaconfparser.parse_conf(output)
|
||||
|
||||
if "host-name" in config["system"]:
|
||||
hostname = config["system"]["host-name"]
|
||||
else:
|
||||
hostname = None
|
||||
|
||||
if "domain-name" in config["system"]:
|
||||
fqdn = config["system"]["domain-name"]
|
||||
else:
|
||||
fqdn = None
|
||||
|
||||
iface_list = list()
|
||||
for iface_type in config["interfaces"]:
|
||||
for iface_name in config["interfaces"][iface_type]:
|
||||
iface_list.append(iface_name)
|
||||
|
||||
facts = {
|
||||
"uptime" : int(uptime),
|
||||
"vendor" : "VyOS",
|
||||
"os_version" : unicode(version),
|
||||
"serial_number" : unicode(snumber),
|
||||
"model" : unicode(hwmodel),
|
||||
"hostname" : unicode(hostname),
|
||||
"fqdn" : unicode(fqdn),
|
||||
"interface_list": iface_list
|
||||
}
|
||||
|
||||
return facts
|
||||
|
||||
|
||||
@staticmethod
|
||||
def parse_version(ver_str):
|
||||
version = ver_str.split()[-1]
|
||||
return version
|
||||
|
||||
|
||||
@staticmethod
|
||||
def parse_snumber(sn_str):
|
||||
sn = sn_str.split(":")
|
||||
return sn[1].strip()
|
||||
|
||||
@staticmethod
|
||||
def parse_hwmodel(model_str):
|
||||
model = model_str.split(":")
|
||||
return model[1].strip()
|
||||
|
||||
|
||||
def get_interfaces_ip(self):
|
||||
output = self._device.send_command("show interfaces")
|
||||
output = output.split("\n")
|
||||
|
||||
# delete the header line and the interfaces which has no ip address
|
||||
ifaces = [x for x in output[3:-1] if "-" not in x]
|
||||
|
||||
ifaces_ip = dict()
|
||||
|
||||
for iface in ifaces:
|
||||
iface = iface.split()
|
||||
|
||||
if len(iface) != 1:
|
||||
|
||||
iface_name = iface[0]
|
||||
|
||||
# Delete the "Interface" column
|
||||
iface = iface[1:-1]
|
||||
# Key initialization
|
||||
ifaces_ip[iface_name] = dict()
|
||||
|
||||
ip_addr, mask = iface[0].split("/")
|
||||
ip_ver = self._get_ip_version(ip_addr)
|
||||
|
||||
# Key initialization
|
||||
if ip_ver not in ifaces_ip[iface_name]:
|
||||
ifaces_ip[iface_name][ip_ver] = dict()
|
||||
|
||||
ifaces_ip[iface_name][ip_ver][ip_addr] = { "prefix_length": mask }
|
||||
|
||||
return ifaces_ip
|
||||
|
||||
|
||||
@staticmethod
|
||||
def _get_ip_version(ip_address):
|
||||
if ":" in ip_address:
|
||||
return "ipv6"
|
||||
elif "." in ip_address:
|
||||
return "ipv4"
|
||||
|
||||
|
||||
def get_users(self):
|
||||
output = self._device.send_command("show configuration commands").split("\n")
|
||||
|
||||
user_conf = [x.split() for x in output if "login user" in x]
|
||||
|
||||
# Collect all users' name
|
||||
user_name = list(set([x[4] for x in user_conf]))
|
||||
|
||||
user_auth = dict()
|
||||
|
||||
for user in user_name:
|
||||
|
||||
sshkeys = list()
|
||||
|
||||
# extract the configuration which relates to 'user'
|
||||
for line in [x for x in user_conf if user in x]:
|
||||
|
||||
# "set system login user alice authentication encrypted-password 'abc'"
|
||||
if line[6] == "encrypted-password":
|
||||
password = line[7].strip("'")
|
||||
|
||||
# set system login user alice level 'admin'
|
||||
elif line[5] == "level":
|
||||
if line[6].strip("'") == "admin":
|
||||
level = 15
|
||||
else:
|
||||
level = 0
|
||||
|
||||
# "set system login user alice authentication public-keys alice@example.com key 'ABC'"
|
||||
elif len(line) == 10 and line[8] == "key":
|
||||
sshkeys.append(line[9].strip("'"))
|
||||
|
||||
user_auth.update({
|
||||
user: {
|
||||
"level": level,
|
||||
"password": password,
|
||||
"sshkeys": sshkeys
|
||||
}
|
||||
})
|
||||
|
||||
return user_auth
|
||||
|
||||
|
||||
def ping(self, destination, source="", ttl=255, timeout=5, size=100, count=5):
|
||||
# does not support multiple destination yet
|
||||
|
||||
command = "ping %s " % destination
|
||||
command += "ttl %d " % ttl
|
||||
command += "deadline %d " % timeout
|
||||
command += "size %d " % size
|
||||
command += "count %d " % count
|
||||
if source != "":
|
||||
command += "interface %s " % source
|
||||
|
||||
ping_result = dict()
|
||||
|
||||
output_ping = self._device.send_command(command)
|
||||
|
||||
if "Unknown host" in output_ping:
|
||||
err ="Unknown host"
|
||||
else:
|
||||
err =""
|
||||
|
||||
if err is not "":
|
||||
ping_result["error"] = err
|
||||
else:
|
||||
# 'packet_info' example:
|
||||
# ['5', 'packets', 'transmitted,' '5', 'received,' '0%', 'packet', 'loss,', 'time', '3997ms']
|
||||
packet_info = output_ping.split("\n")[-2]
|
||||
|
||||
packet_info = [x.strip() for x in packet_info.split()]
|
||||
|
||||
|
||||
sent = int(packet_info[0])
|
||||
received = int(packet_info[3])
|
||||
lost = sent - received
|
||||
|
||||
# 'rtt_info' example:
|
||||
# ["0.307/0.396/0.480/0.061"]
|
||||
rtt_info = output_ping.split("\n")[-1]
|
||||
match = re.search("([\d\.]+)/([\d\.]+)/([\d\.]+)/[\d\.]+", rtt_info)
|
||||
|
||||
if match is not None:
|
||||
rtt_min = float(match.group(1))
|
||||
rtt_avg = float(match.group(2))
|
||||
else:
|
||||
rtt_min = None
|
||||
rtt_avg = None
|
||||
|
||||
ping_result["success"] = dict()
|
||||
ping_result["success"] = {
|
||||
"probes_sent": sent,
|
||||
"packet_loss": lost,
|
||||
"rtt_min" : rtt_min,
|
||||
"rtt_avg" : rtt_avg,
|
||||
"rtt_stdev" : None,
|
||||
"results" : {"ip_address": destination, "rtt": rtt_avg}
|
||||
}
|
||||
|
||||
return ping_result
|
||||
Reference in New Issue
Block a user