Skip to content

Commit

Permalink
Arista get interface support
Browse files Browse the repository at this point in the history
  • Loading branch information
jsoissan authored and lindycoder committed Nov 5, 2018
1 parent cd36932 commit 0064e41
Show file tree
Hide file tree
Showing 6 changed files with 273 additions and 12 deletions.
6 changes: 3 additions & 3 deletions constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ click==7.0 # via flask
ecdsa==0.13 # via paramiko
flask==1.0.2
idna==2.7 # via requests
itsdangerous==0.24 # via flask
itsdangerous==1.1.0 # via flask
jinja2==2.10 # via flask
lxml==4.2.5 # via ncclient
markupsafe==1.0 # via jinja2
ncclient==0.6.3
netaddr==0.7.19
paramiko==1.17.6
pbr==5.0.0
pbr==5.1.0
pycrypto==2.6.1 # via paramiko
pyeapi==0.8.2
requests==2.20.0
selectors2==2.0.1 # via ncclient
six==1.11.0 # via ncclient
urllib3==1.24 # via requests
urllib3==1.24.1 # via requests
werkzeug==0.14.1 # via flask
82 changes: 81 additions & 1 deletion netman/adapters/switches/arista.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,12 @@
from pyeapi.api.vlans import isvlan
from pyeapi.eapilib import CommandError

from netman import regex
from netman.core.objects.exceptions import VlanAlreadyExist, UnknownVlan, BadVlanNumber, BadVlanName, \
IPAlreadySet, IPNotAvailable, UnknownIP
IPAlreadySet, IPNotAvailable, UnknownIP, UnknownInterface
from netman.core.objects.interface import Interface
from netman.core.objects.interface_states import OFF, ON
from netman.core.objects.port_modes import ACCESS, TRUNK
from netman.core.objects.switch_base import SwitchBase
from netman.core.objects.vlan import Vlan

Expand Down Expand Up @@ -158,6 +162,82 @@ def remove_ip_from_vlan(self, vlan_number, ip_network):
else:
raise UnknownIP(ip_network)

def get_interface(self, interface_id):
commands = [
"show interfaces {}".format(interface_id),
"show interfaces {} switchport".format(interface_id)
]
try:
result = self.node.enable(commands, strict=True)
except CommandError:
raise UnknownInterface(interface_id)

interfaces = parse_interfaces(result[0]['result']['interfaces'], result[1]['result']['switchports'])
if len(interfaces) > 0:
return interfaces[0]
raise UnknownInterface(interface_id)

def get_interfaces(self):
commands = [
"show interfaces",
"show interfaces switchport"
]
result = self.node.enable(commands, strict=True)

interfaces = parse_interfaces(result[0]['result']['interfaces'], result[1]['result']['switchports'])
return interfaces


def parse_interfaces(interfaces_data, switchports_data):
interfaces = []
for interface_data in interfaces_data.values():
if regex.match("(\w*Ethernet[^\s]*)", interface_data["name"]) or \
regex.match("(Port-channel[^\s]*)", interface_data["name"]):

interface = Interface(name=interface_data["name"], shutdown=False)

if interface_data["lineProtocolStatus"] == "down":
interface.shutdown = True

interface.mtu = int(interface_data["mtu"])
interface.auto_negotiation = ON if interface_data["autoNegotiate"] == "on" else OFF

if interface.name in switchports_data:
patch_switchport(interface, switchports_data[interface.name]["switchportInfo"])

interfaces.append(interface)

return interfaces


def patch_switchport(interface, data):
if data["mode"] == "access":
interface.port_mode = ACCESS
elif data["mode"] == "trunk":
interface.port_mode = TRUNK

interface.trunk_native_vlan = data["trunkingNativeVlanId"]
interface.trunk_vlans = parse_vlan_ranges(data["trunkAllowedVlans"]) if data["trunkAllowedVlans"] else []


def parse_vlan_ranges(all_ranges):
if all_ranges is None or all_ranges == "ALL":
return range(1, 4094)
elif all_ranges == "NONE":
return []
else:
full_list = []
for vlan_list in [parse_range(r) for r in all_ranges.split(",")]:
full_list += vlan_list
return full_list


def parse_range(single_range):
if regex.match("(\d+)-(\d+)", single_range):
return range(int(regex[0]), int(regex[1]) + 1)
else:
return [int(single_range)]


def _extract_vlans(vlans_info):
vlan_list = []
Expand Down
12 changes: 6 additions & 6 deletions test-constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ cryptography==2.3.1 # via twisted
docutils==0.14 # via sphinx
ecdsa==0.13
enum34==1.1.6 # via cryptography, flake8
fake-switches==1.3.2
fake-switches==1.3.5
flake8==3.4.1
flask==1.0.2
flexmock==0.10.2
Expand All @@ -34,7 +34,7 @@ idna==2.7
imagesize==1.1.0 # via sphinx
incremental==17.5.0 # via twisted
ipaddress==1.0.22 # via cryptography
itsdangerous==0.24
itsdangerous==1.1.0
jabstract==0.1.2
jinja2==2.10
lxml==4.2.5
Expand All @@ -46,7 +46,7 @@ ncclient==0.6.3
netaddr==0.7.19
nose==1.3.7
paramiko==1.17.6
pbr==5.0.0
pbr==5.1.0
pyasn1==0.4.4 # via mockssh, twisted
pycodestyle==2.3.1 # via flake8
pycparser==2.19 # via cffi
Expand All @@ -55,7 +55,7 @@ pyeapi==0.8.2
pyflakes==1.5.0 # via flake8
pygments==2.2.0 # via sphinx
pyhamcrest==1.9.0
pytz==2018.5 # via babel
pytz==2018.7 # via babel
requests==2.20.0
rply==0.7.6 # via hy
selectors2==2.0.1
Expand All @@ -65,6 +65,6 @@ sphinx==1.5.6
sphinxcontrib-httpdomain==1.7.0
tftpy==0.8.0 # via fake-switches
twisted[conch]==16.6.0 # via fake-switches, mockssh
urllib3==1.24
urllib3==1.24.1
werkzeug==0.14.1
zope.interface==4.5.0 # via twisted
zope.interface==4.6.0 # via twisted
2 changes: 1 addition & 1 deletion test-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ nose>=1.2.1
mock>=1.0.1
pyhamcrest>=1.6
flexmock>=0.10.2
fake-switches>=1.3.2
fake-switches>=1.3.5
MockSSH>=1.4.2,!=1.4.5 # 1.4.5 has fixed dependency and freezes paramiko
gunicorn>=19.4.5
flake8==3.4.1
Expand Down
113 changes: 112 additions & 1 deletion tests/adapters/switches/arista_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,13 @@
from netman.adapters.switches.arista import Arista
from netman.core.objects.exceptions import BadVlanNumber, VlanAlreadyExist, BadVlanName, UnknownVlan, \
UnknownIP, IPNotAvailable, IPAlreadySet, UnknownInterface
from netman.core.objects.interface_states import OFF, ON
from netman.core.objects.port_modes import TRUNK
from netman.core.objects.switch_descriptor import SwitchDescriptor
from netman.core.objects.vlan import Vlan
from tests import ignore_deprecation_warnings
from tests.fixtures.arista import vlan_data, result_payload, interface_vlan_data, show_interfaces, interface_address
from tests.fixtures.arista import vlan_data, result_payload, interface_vlan_data, show_interfaces, interface_address, \
switchport_data, interface_data


class AristaTest(unittest.TestCase):
Expand Down Expand Up @@ -314,6 +317,114 @@ def test_transactions_commit_write_memory(self):
def test_transactions_rollback_does_nothing(self):
self.switch.rollback_transaction()

def test_get_interface(self):
interface_payload = result_payload(result={
'interfaces': {
'Ethernet1': interface_data(
name="Ethernet1",
lineProtocolStatus="up",
autoNegotiate="off",
mtu=1234
)
}
})

switchport_payload = result_payload(result={
'switchports': {
'Ethernet1': {
'enabled': True,
'switchportInfo': switchport_data(
mode="trunk",
trunkAllowedVlans="800-802,804",
trunkingNativeVlanId=1
)
}
}
})

self.switch.node.should_receive("enable") \
.with_args(["show interfaces Ethernet1", "show interfaces Ethernet1 switchport"], strict=True) \
.and_return([interface_payload, switchport_payload])

interface = self.switch.get_interface("Ethernet1")

assert_that(interface.name, equal_to("Ethernet1"))
assert_that(interface.shutdown, equal_to(False))
assert_that(interface.port_mode, equal_to(TRUNK))
assert_that(interface.trunk_vlans, equal_to([800, 801, 802, 804]))
assert_that(interface.auto_negotiation, equal_to(OFF))
assert_that(interface.mtu, equal_to(1234))

def test_get_interface_no_interface_raises(self):
self.switch.node.should_receive("enable") \
.with_args(["show interfaces InvalidInterface", "show interfaces InvalidInterface switchport"], strict=True) \
.and_raise(CommandError(1002,
"CLI command 4 of 4 'show interfaces InvalidInterface' failed: invalid command",
command_error="Invalid input (at token 0: 'show')"))

with self.assertRaises(UnknownInterface):
self.switch.get_interface("InvalidInterface")

def test_get_interface_unsupported_interface_raises(self):
interface_payload = result_payload(result={
'interfaces': {
'UndesiredInterface': interface_data(name="UndesiredInterface", )
}
})
switchport_payload = result_payload(result={'switchports': {}})
self.switch.node.should_receive("enable") \
.with_args(["show interfaces Unsupported1", "show interfaces Unsupported1 switchport"], strict=True) \
.and_return([interface_payload, switchport_payload])

with self.assertRaises(UnknownInterface):
self.switch.get_interface("Unsupported1")

def test_get_interfaces(self):
interface_payload = result_payload(result={
'interfaces': {
'Ethernet1': interface_data(
name="Ethernet1",
lineProtocolStatus="up",
autoNegotiate="off",
mtu=1234
),
'Ethernet2': interface_data(
name="Ethernet2",
lineProtocolStatus="down",
autoNegotiate="on"
)}})

switchport_payload = result_payload(result={
'switchports': {
'Ethernet1': {
'enabled': True,
'switchportInfo': switchport_data(
mode="trunk",
trunkAllowedVlans="800-802,804",
trunkingNativeVlanId=1
)
}
}
})

self.switch.node.should_receive("enable") \
.with_args(["show interfaces", "show interfaces switchport"], strict=True) \
.and_return([interface_payload, switchport_payload])

if1, if2 = sorted(self.switch.get_interfaces(), key=lambda i: i.name)

assert_that(if1.name, equal_to("Ethernet1"))
assert_that(if1.shutdown, equal_to(False))
assert_that(if1.port_mode, equal_to(TRUNK))
assert_that(if1.trunk_vlans, equal_to([800, 801, 802, 804]))
assert_that(if1.auto_negotiation, equal_to(OFF))
assert_that(if1.mtu, equal_to(1234))

assert_that(if2.name, equal_to("Ethernet2"))
assert_that(if2.shutdown, equal_to(True))
assert_that(if2.trunk_vlans, equal_to([]))
assert_that(if2.auto_negotiation, equal_to(ON))


class AristaFactoryTest(unittest.TestCase):
def tearDown(self):
Expand Down
70 changes: 70 additions & 0 deletions tests/fixtures/arista.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,76 @@
'physicalAddress': 'fa:16:3e:02:b9:e2'
})

interface_data = jabstract({
'autoNegotiate': 'off',
'bandwidth': 0,
'burnedInAddress': 'fa:16:3e:0e:ee:81',
'description': '',
'duplex': 'duplexFull',
'forwardingModel': 'bridged',
'hardware': 'ethernet',
'interfaceAddress': [],
'interfaceCounters': {
'counterRefreshTime': 1541082601.226264,
'inBroadcastPkts': 0,
'inDiscards': 0,
'inMulticastPkts': 30902,
'inOctets': 2183607,
'inTotalPkts': 30948,
'inUcastPkts': 46,
'inputErrorsDetail': {
'alignmentErrors': 0,
'fcsErrors': 0,
'giantFrames': 0,
'runtFrames': 0,
'rxPause': 0,
'symbolErrors': 0},
'linkStatusChanges': 4,
'outBroadcastPkts': 0,
'outDiscards': 0,
'outMulticastPkts': 447388,
'outOctets': 32423850,
'outUcastPkts': 0,
'outputErrorsDetail': {
'collisions': 0,
'deferredTransmissions': 0,
'lateCollisions': 0,
'txPause': 0},
'totalInErrors': 0,
'totalOutErrors': 0},
'interfaceStatistics': {
'inBitsRate': 0.0,
'inPktsRate': 0.0,
'outBitsRate': 0.0,
'outPktsRate': 0.0,
'updateInterval': 300.0},
'interfaceStatus': 'disabled',
'lastStatusChangeTimestamp': 1541080271.3714354,
'lineProtocolStatus': 'down',
'loopbackMode': 'loopbackNone',
'mtu': 9214,
'name': 'Ethernet1',
'physicalAddress': 'fa:16:3e:0e:ee:81'
})

switchport_data = jabstract({
'accessVlanId': 1,
'accessVlanName': 'default',
'dot1qVlanTagRequired': False,
'dot1qVlanTagRequiredStatus': False,
'dynamicAllowedVlans': {},
'dynamicTrunkGroups': [],
'macLearning': True,
'mode': 'access',
'sourceportFilterMode': 'enabled',
'staticTrunkGroups': [],
'tpid': '0x8100',
'tpidStatus': True,
'trunkAllowedVlans': '800,805',
'trunkingNativeVlanId': 1,
'trunkingNativeVlanName': 'default'
})


def show_interfaces(*interface_data):
return {'interfaces': {i["name"]: i for i in interface_data}}

0 comments on commit 0064e41

Please sign in to comment.