Skip to content

Commit

Permalink
Arista support for ip helper
Browse files Browse the repository at this point in the history
  • Loading branch information
Stephane Robert authored and lindycoder committed Nov 6, 2018
1 parent 4ec8bd8 commit 8c6d0b7
Show file tree
Hide file tree
Showing 3 changed files with 193 additions and 5 deletions.
46 changes: 44 additions & 2 deletions netman/adapters/switches/arista.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
import warnings

import pyeapi
from netaddr import IPNetwork
from netaddr import IPNetwork, IPAddress, AddrFormatError
from pyeapi.api.vlans import isvlan
from pyeapi.eapilib import CommandError

from netman import regex
from netman.adapters.switches.util import split_on_dedent
from netman.core.objects.exceptions import VlanAlreadyExist, UnknownVlan, BadVlanNumber, BadVlanName, \
IPAlreadySet, IPNotAvailable, UnknownIP, UnknownInterface
IPAlreadySet, IPNotAvailable, UnknownIP, DhcpRelayServerAlreadyExists, UnknownDhcpRelayServer, 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
Expand Down Expand Up @@ -82,6 +83,7 @@ def get_vlan(self, number):

vlans = _extract_vlans(vlans_info)
_apply_interface_data(interfaces_info, vlans)
self._apply_interface_vlan_data(vlans)

return vlans[0]

Expand All @@ -90,6 +92,7 @@ def get_vlans(self):

vlans = _extract_vlans(vlans_result['result'])
_apply_interface_data(interfaces_result['result'], vlans)
self._apply_interface_vlan_data(vlans)

return sorted(vlans, key=lambda v: v.number)

Expand Down Expand Up @@ -220,6 +223,45 @@ def remove_trunk_vlan(self, interface_id, vlan):
]
self.node.config(commands)

def add_dhcp_relay_server(self, vlan_number, ip_address):
vlan = self.get_vlan(vlan_number)

if ip_address in vlan.dhcp_relay_servers:
raise DhcpRelayServerAlreadyExists(vlan_number=vlan_number, ip_address=ip_address)

self.node.config(['interface Vlan{}'.format(vlan_number),
'ip helper-address {}'.format(ip_address)])

def remove_dhcp_relay_server(self, vlan_number, ip_address):
vlan = self.get_vlan(vlan_number)

if ip_address not in vlan.dhcp_relay_servers:
raise UnknownDhcpRelayServer(vlan_number=vlan_number, ip_address=ip_address)

self.node.config(['interface Vlan{}'.format(vlan_number),
'no ip helper-address {}'.format(ip_address)])

def _apply_interface_vlan_data(self, vlans):
config = self._fetch_interface_vlans_config(vlans)

for interface in split_on_dedent(config):
if regex.match("^.*Vlan(\d+)$", interface[0]):
vlan = _find_vlan_by_number(vlans, regex[0])
for line in interface[1:]:
if regex.match(" *ip helper-address (.*)", line):
try:
vlan.dhcp_relay_servers.append(IPAddress(regex[0]))
except AddrFormatError:
self.logger.warning('Unsupported IP Helper address found in Vlan {} : {}'.format(vlan.number, regex[0]))

def _fetch_interface_vlans_config(self, vlans):
all_interface_vlans = sorted('Vlan{}'.format(vlan.number) for vlan in vlans)
return self.node.get_config(params='interfaces {}'.format(' '.join(all_interface_vlans)))


def _find_vlan_by_number(vlans, number):
return next((vlan for vlan in vlans if vlan.number == int(number)))


def parse_interfaces(interfaces_data, switchports_data):
interfaces = []
Expand Down
150 changes: 148 additions & 2 deletions tests/adapters/switches/arista_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@
import pyeapi
from flexmock import flexmock, flexmock_teardown
from hamcrest import assert_that, has_length, equal_to, is_, contains_string
from netaddr import IPNetwork
from netaddr import IPNetwork, IPAddress
from pyeapi.eapilib import CommandError

from netman.adapters.switches import arista
from netman.adapters.switches.arista import Arista, parse_vlan_ranges
from netman.core.objects.exceptions import BadVlanNumber, VlanAlreadyExist, BadVlanName, UnknownVlan, \
UnknownIP, IPNotAvailable, IPAlreadySet, UnknownInterface
UnknownIP, IPNotAvailable, IPAlreadySet, UnknownInterface, UnknownDhcpRelayServer, DhcpRelayServerAlreadyExists
from netman.core.objects.interface import Interface
from netman.core.objects.interface_states import OFF, ON
from netman.core.objects.port_modes import TRUNK
Expand Down Expand Up @@ -56,6 +56,18 @@ def test_get_vlans(self):
.and_return([result_payload(result=vlans_payload),
result_payload(result=interfaces_payload)])

self.switch.node.should_receive("get_config").with_args(
params="interfaces Vlan1 Vlan123 Vlan1234 Vlan456 Vlan789").once() \
.and_return(['interface Vlan123',
' ip helper-address 10.10.30.200',
' ip helper-address 10.10.30.201',
'interface Vlan456',
'interface Vlan789',
' ip helper-address 10.10.30.203',
' ip helper-address 10.10.30.204',
' ip helper-address 10.10.30.205',
'interface Vlan1234'])

vlan1, vlan123, vlan456, vlan789, vlan1234 = self.switch.get_vlans()

assert_that(vlan1.number, equal_to(1))
Expand All @@ -65,17 +77,26 @@ def test_get_vlans(self):
assert_that(vlan123.number, equal_to(123))
assert_that(vlan123.name, equal_to(None))
assert_that(vlan123.ips, has_length(0))
dhcp_ip1, dhcp_ip2 = vlan123.dhcp_relay_servers
assert_that(dhcp_ip1, is_(IPAddress('10.10.30.200')))
assert_that(dhcp_ip2, is_(IPAddress('10.10.30.201')))

assert_that(vlan456.number, equal_to(456))
assert_that(vlan456.name, equal_to('Patate'))
ip11, ip13, ip12 = vlan456.ips
assert_that(ip11, is_(IPNetwork("192.168.11.1/29")))
assert_that(ip13, is_(IPNetwork("192.168.13.1/29")))
assert_that(ip12, is_(IPNetwork("192.168.12.1/29")))
assert_that(vlan456.dhcp_relay_servers, has_length(0))

assert_that(vlan789.ips, has_length(0))
dhcp_ip1, dhcp_ip2, dhcp_ip3 = vlan789.dhcp_relay_servers
assert_that(dhcp_ip1, is_(IPAddress('10.10.30.203')))
assert_that(dhcp_ip2, is_(IPAddress('10.10.30.204')))
assert_that(dhcp_ip3, is_(IPAddress('10.10.30.205')))

assert_that(vlan1234.ips, has_length(0))
assert_that(vlan1234.dhcp_relay_servers, has_length(0))

def test_get_vlan(self):
vlans_payload = {'vlans': {'456': vlan_data(name='Patate')}}
Expand All @@ -95,6 +116,11 @@ def test_get_vlan(self):
.and_return([result_payload(result=vlans_payload),
result_payload(result=interfaces_payload)])

self.switch.node.should_receive("get_config").with_args(params="interfaces Vlan456").once() \
.and_return(['interface Vlan456',
' ip helper-address 10.10.30.200',
' ip helper-address 10.10.30.201'])

vlan = self.switch.get_vlan(456)

assert_that(vlan.number, equal_to(456))
Expand All @@ -104,6 +130,33 @@ def test_get_vlan(self):
assert_that(ip13, is_(IPNetwork("192.168.13.1/29")))
assert_that(ip12, is_(IPNetwork("192.168.12.1/29")))

helper_addr1, helper_addr2 = vlan.dhcp_relay_servers
assert_that(helper_addr1, is_(IPAddress('10.10.30.200')))
assert_that(helper_addr2, is_(IPAddress('10.10.30.201')))

def test_get_vlan_invalid_ip_does_not_fail(self):
vlans_payload = {'vlans': {'456': vlan_data(name='Patate')}}

interfaces_payload = show_interfaces(
interface_vlan_data(name="Vlan456")
)

self.switch.node.should_receive("enable") \
.with_args(["show vlan 456", "show interfaces Vlan456"], strict=True) \
.and_return([result_payload(result=vlans_payload),
result_payload(result=interfaces_payload)])

self.switch.node.should_receive("get_config").with_args(params="interfaces Vlan456").once() \
.and_return(['interface Vlan456',
' ip helper-address say_what',
' ip helper-address 10.10.30.201'])

vlan = self.switch.get_vlan(456)

assert_that(vlan.number, equal_to(456))
assert_that(vlan.dhcp_relay_servers, has_length(1))
assert_that(vlan.dhcp_relay_servers[0], is_(IPAddress('10.10.30.201')))

def test_get_vlan_doesnt_exist(self):
self.switch.node.should_receive("enable") \
.with_args(["show vlan 111", "show interfaces Vlan111"], strict=True) \
Expand Down Expand Up @@ -133,6 +186,9 @@ def test_get_vlan_without_interface(self):
{'errors': ['Interface does not exist']}
]))

self.switch.node.should_receive("get_config").with_args(params="interfaces Vlan456").once() \
.and_return(['interface Vlan456'])

vlan = self.switch.get_vlan(456)

assert_that(vlan.number, equal_to(456))
Expand Down Expand Up @@ -541,6 +597,96 @@ def test_remove_trunk_vlan_no_port_mode_still_working(self):

self.switch.remove_trunk_vlan("Ethernet1", 800)

def test_add_dhcp_relay_server(self):
vlans_payload = {'vlans': {'123': vlan_data(name='Patate')}}

interfaces_payload = show_interfaces(
interface_vlan_data(name="Vlan123")
)

self.switch.node.should_receive("enable") \
.with_args(["show vlan 123", "show interfaces Vlan123"], strict=True) \
.and_return([result_payload(result=vlans_payload),
result_payload(result=interfaces_payload)])

self.switch.node.should_receive("get_config").once() \
.with_args(params="interfaces Vlan123") \
.and_return(['interface Vlan123',
' ip helper-address 10.10.30.200',
' ip helper-address 10.10.30.201'])

self.switch.node.should_receive("config").once() \
.with_args(['interface Vlan123',
'ip helper-address 10.10.30.202'])

self.switch.add_dhcp_relay_server(123, IPAddress('10.10.30.202'))

def test_add_same_dhcp_relay_server_fails(self):
vlans_payload = {'vlans': {'123': vlan_data(name='Patate')}}

interfaces_payload = show_interfaces(
interface_vlan_data(name="Vlan123")
)

self.switch.node.should_receive("enable") \
.with_args(["show vlan 123", "show interfaces Vlan123"], strict=True) \
.and_return([result_payload(result=vlans_payload),
result_payload(result=interfaces_payload)])

self.switch.node.should_receive("get_config").once() \
.with_args(params="interfaces Vlan123") \
.and_return(['interface Vlan123',
' ip helper-address 10.10.30.200',
' ip helper-address 10.10.30.201'])

with self.assertRaises(DhcpRelayServerAlreadyExists):
self.switch.add_dhcp_relay_server(123, IPAddress('10.10.30.201'))

def test_remove_dhcp_relay_server(self):
vlans_payload = {'vlans': {'123': vlan_data(name='Patate')}}

interfaces_payload = show_interfaces(
interface_vlan_data(name="Vlan123")
)

self.switch.node.should_receive("enable") \
.with_args(["show vlan 123", "show interfaces Vlan123"], strict=True) \
.and_return([result_payload(result=vlans_payload),
result_payload(result=interfaces_payload)])

self.switch.node.should_receive("get_config").once() \
.with_args(params="interfaces Vlan123") \
.and_return(['interface Vlan123',
' ip helper-address 10.10.30.200',
' ip helper-address 10.10.30.202'])

self.switch.node.should_receive("config").once() \
.with_args(['interface Vlan123',
'no ip helper-address 10.10.30.202'])

self.switch.remove_dhcp_relay_server(123, IPAddress('10.10.30.202'))

def test_remove_non_existent_dhcp_relay_server_fails(self):
vlans_payload = {'vlans': {'123': vlan_data(name='Patate')}}

interfaces_payload = show_interfaces(
interface_vlan_data(name="Vlan123")
)

self.switch.node.should_receive("enable") \
.with_args(["show vlan 123", "show interfaces Vlan123"], strict=True) \
.and_return([result_payload(result=vlans_payload),
result_payload(result=interfaces_payload)])

self.switch.node.should_receive("get_config").once() \
.with_args(params="interfaces Vlan123") \
.and_return(['interface Vlan123',
' ip helper-address 10.10.30.200',
' ip helper-address 10.10.30.202'])

with self.assertRaises(UnknownDhcpRelayServer):
self.switch.remove_dhcp_relay_server(123, IPAddress('10.10.30.222'))


class AristaFactoryTest(unittest.TestCase):
def tearDown(self):
Expand Down
2 changes: 1 addition & 1 deletion tests/adapters/unified_tests/dhcp_relay_server_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
class DhcpRelayServerTest(ConfiguredTestCase):
__test__ = False

@skip_on_switches("juniper", "juniper_qfx_copper", "juniper_mx", "dell", "dell_telnet", "dell10g", "dell10g_telnet", "arista_http")
@skip_on_switches("juniper", "juniper_qfx_copper", "juniper_mx", "dell", "dell_telnet", "dell10g", "dell10g_telnet")
def test_add_and_get_and_delete_dhcp_relay_server(self):
try:
self.client.add_vlan(2999, name="my-test-vlan")
Expand Down

0 comments on commit 8c6d0b7

Please sign in to comment.