Skip to content

Commit

Permalink
Merge pull request #237 from simon-begin/add-vlan-load-interval
Browse files Browse the repository at this point in the history
Add load-interval support on interface vlan
  • Loading branch information
stephanerobert authored Jan 10, 2019
2 parents 519fee9 + f0c7a85 commit 98039e1
Show file tree
Hide file tree
Showing 20 changed files with 332 additions and 28 deletions.
10 changes: 5 additions & 5 deletions constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,24 @@
#
# pip-compile --no-index --no-emit-trusted-host --output-file constraints.txt requirements.txt
#
certifi==2018.10.15 # via requests
certifi==2018.11.29 # via requests
chardet==3.0.4 # via requests
click==7.0 # via flask
ecdsa==0.13 # via paramiko
flask==1.0.2
idna==2.7 # via requests
idna==2.8 # via requests
itsdangerous==1.1.0 # via flask
jinja2==2.10 # via flask
lxml==4.2.5 # via ncclient
lxml==4.3.0 # via ncclient
markupsafe==1.1.0 # via jinja2
ncclient==0.6.3
netaddr==0.7.19
paramiko==1.17.6
pbr==5.1.1
pycrypto==2.6.1 # via paramiko
pyeapi==0.8.2
requests==2.20.0
requests==2.21.0
selectors2==2.0.1 # via ncclient
six==1.11.0 # via ncclient
six==1.12.0 # via ncclient
urllib3==1.24.1 # via requests
werkzeug==0.14.1 # via flask
19 changes: 18 additions & 1 deletion netman/adapters/switches/arista.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from netman.adapters.switches.util import split_on_dedent
from netman.core.objects.exceptions import VlanAlreadyExist, UnknownVlan, BadVlanNumber, BadVlanName, \
IPAlreadySet, IPNotAvailable, UnknownIP, DhcpRelayServerAlreadyExists, UnknownDhcpRelayServer, UnknownInterface, \
UnknownBond, VarpAlreadyExistsForVlan, VarpDoesNotExistForVlan
UnknownBond, VarpAlreadyExistsForVlan, VarpDoesNotExistForVlan, BadLoadIntervalNumber
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 @@ -256,6 +256,21 @@ def remove_dhcp_relay_server(self, vlan_number, ip_address):
self.node.config(['interface Vlan{}'.format(vlan_number),
'no ip helper-address {}'.format(ip_address)])

def set_vlan_load_interval(self, vlan_number, time_interval):
self.get_vlan(vlan_number)

try:
self.node.config(['interface Vlan{}'.format(vlan_number),
'load-interval {}'.format(time_interval)])
except CommandError:
raise BadLoadIntervalNumber()

def unset_vlan_load_interval(self, vlan_number):
self.get_vlan(vlan_number)

self.node.config(['interface Vlan{}'.format(vlan_number),
'no load-interval'])

def add_vlan_varp_ip(self, vlan_number, ip_network):
vlan = self.get_vlan(vlan_number)

Expand Down Expand Up @@ -294,6 +309,8 @@ def _apply_interface_vlan_data(self, vlans):
'Unsupported IP Helper address found in Vlan {} : {}'.format(vlan.number, regex[0]))
if regex.match(" *ip virtual-router address (.*)", line):
vlan.varp_ips.append(IPNetwork(regex[0]))
if regex.match(" *load-interval (.*)", line):
vlan.load_interval = int(regex[0])

def _fetch_interface_vlans_config(self, vlans):
all_interface_vlans = sorted('Vlan{}'.format(vlan.number) for vlan in vlans)
Expand Down
8 changes: 8 additions & 0 deletions netman/adapters/switches/cached.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,3 +421,11 @@ def unset_bond_mtu(self, bond_number):
def set_vlan_arp_routing_state(self, vlan_number, state):
self.real_switch.set_vlan_arp_routing_state(vlan_number, state)
self.vlans_cache[vlan_number].arp_routing = (state == ON)

def set_vlan_load_interval(self, vlan_number, time_interval):
self.real_switch.set_vlan_load_interval(vlan_number, time_interval)
self.vlans_cache[vlan_number].load_interval = time_interval

def unset_vlan_load_interval(self, vlan_number):
self.real_switch.unset_vlan_load_interval(vlan_number)
self.vlans_cache[vlan_number].load_interval = None
8 changes: 8 additions & 0 deletions netman/adapters/switches/remote.py
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,14 @@ def set_vlan_vrf(self, vlan_number, vrf_name):
def unset_vlan_vrf(self, vlan_number):
self.delete('/vlans/{vlan_number}/vrf-forwarding'.format(vlan_number=vlan_number))

def set_vlan_load_interval(self, vlan_number, time_interval):
self.put('/vlans/{vlan_number}/load-interval'.format(
vlan_number=vlan_number
), raw_data=str(time_interval))

def unset_vlan_load_interval(self, vlan_number):
self.delete('/vlans/{vlan_number}/load-interval'.format(vlan_number=vlan_number))

def set_access_mode(self, interface_id):
self.put("/interfaces/" + interface_id + '/port-mode', raw_data='access')

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
"ntp": false,
"varp_ips": [
{"address": "3.3.3.3", "mask": 24}
]
],
"load_interval": null
},
{
"number": 2,
Expand Down Expand Up @@ -70,6 +71,7 @@
"varp_ips": [
{"address": "4.4.4.4", "mask": 24},
{"address": "5.5.5.5", "mask": 24}
]
],
"load_interval": null
}
]
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@
"icmp_redirects": false,
"unicast_rpf_mode": null,
"ntp": null,
"varp_ips": []
"varp_ips": [],
"load_interval": null
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
30
3 changes: 2 additions & 1 deletion netman/api/objects/vlan.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ def to_api(vlan):
icmp_redirects=vlan.icmp_redirects,
unicast_rpf_mode=vlan.unicast_rpf_mode,
ntp=vlan.ntp,
varp_ips=serialize_ip_network(vlan.varp_ips)
varp_ips=serialize_ip_network(vlan.varp_ips),
load_interval=vlan.load_interval
)


Expand Down
32 changes: 32 additions & 0 deletions netman/api/switch_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ def hook_to(self, server):
server.add_url_rule('/switches/<hostname>/vlans/<vlan_number>/ntp', view_func=self.set_vlan_ntp_state, methods=['PUT'])
server.add_url_rule('/switches/<hostname>/vlans/<vlan_number>/unicast-rpf-mode', view_func=self.set_vlan_unicast_rpf_mode, methods=['PUT'])
server.add_url_rule('/switches/<hostname>/vlans/<vlan_number>/unicast-rpf-mode', view_func=self.unset_vlan_unicast_rpf_mode, methods=['DELETE'])
server.add_url_rule('/switches/<hostname>/vlans/<vlan_number>/load-interval', view_func=self.set_vlan_load_interval, methods=['PUT'])
server.add_url_rule('/switches/<hostname>/vlans/<vlan_number>/load-interval', view_func=self.unset_vlan_load_interval, methods=['DELETE'])
server.add_url_rule('/switches/<hostname>/interfaces', view_func=self.get_interfaces, methods=['GET'])
server.add_url_rule('/switches/<hostname>/interfaces/<path:interface_id>', view_func=self.reset_interface, methods=['PUT'])
server.add_url_rule('/switches/<hostname>/interfaces/<path:interface_id>', view_func=self.get_interface, methods=['GET'])
Expand Down Expand Up @@ -350,6 +352,36 @@ def unset_vlan_access_group(self, switch, vlan_number, direction):
switch.unset_vlan_access_group(vlan_number, direction)
return 204, None

@to_response
@content(is_int)
@resource(Switch, Vlan)
def set_vlan_load_interval(self, switch, vlan_number, value):
"""
Sets the load interval of a vlan
:arg str hostname: Hostname or IP of the switch
:arg int vlan_number: Vlan number, between 1 and 4096
:body:
.. literalinclude:: ../doc_config/api_samples/put_switch_hostname_vlans_vlanid_load_interval.txt
"""

switch.set_vlan_load_interval(vlan_number, value)

return 204, None

@to_response
@resource(Switch, Vlan)
def unset_vlan_load_interval(self, switch, vlan_number):
"""
Unsets the load interval of a vlan
:arg str hostname: Hostname or IP of the switch
:arg int vlan_number: Vlan number, between 1 and 4096
"""

switch.unset_vlan_load_interval(vlan_number)
return 204, None

@to_response
@resource(Switch)
def get_interface(self, switch, interface_id):
Expand Down
5 changes: 5 additions & 0 deletions netman/core/objects/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,11 @@ def __init__(self):
super(MalformedSwitchSessionRequest, self).__init__("Malformed switch session request")


class BadLoadIntervalNumber(InvalidValue):
def __init__(self):
super(BadLoadIntervalNumber, self).__init__("Load interval number is invalid")


class Timeout(Exception):
pass

Expand Down
8 changes: 8 additions & 0 deletions netman/core/objects/switch_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,14 @@ def add_vlan_varp_ip(self, vlan_number, ip_network):
def remove_vlan_varp_ip(self, vlan_number, ip_network):
pass

@not_implemented
def set_vlan_load_interval(self, vlan_number, time_interval):
pass

@not_implemented
def unset_vlan_load_interval(self, vlan_number):
pass


class SwitchBase(SwitchOperations):
def __init__(self, switch_descriptor):
Expand Down
3 changes: 2 additions & 1 deletion netman/core/objects/vlan.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
class Vlan(Model):
def __init__(self, number=None, name=None, ips=None, vrrp_groups=None, vrf_forwarding=None, access_group_in=None,
access_group_out=None, dhcp_relay_servers=None, arp_routing=None, icmp_redirects=None,
unicast_rpf_mode=None, ntp=None, varp_ips=None):
unicast_rpf_mode=None, ntp=None, varp_ips=None, load_interval=None):
self.number = number
self.name = name
self.access_groups = {IN: access_group_in, OUT: access_group_out}
Expand All @@ -32,3 +32,4 @@ def __init__(self, number=None, name=None, ips=None, vrrp_groups=None, vrf_forwa
self.unicast_rpf_mode = unicast_rpf_mode
self.ntp = ntp
self.varp_ips = varp_ips or []
self.load_interval = load_interval
20 changes: 10 additions & 10 deletions test-constraints.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,18 @@ args==0.1.0 # via clint
asn1crypto==0.24.0 # via cryptography
astor==0.7.1 # via hy
babel==2.6.0 # via sphinx
certifi==2018.10.15
certifi==2018.11.29
cffi==1.11.5 # via cryptography
chardet==3.0.4
click==7.0
clint==0.5.1 # via hy
configparser==3.5.0 # via flake8
constantly==15.1.0 # via twisted
cryptography==2.3.1 # via twisted
cryptography==2.4.2 # via twisted
docutils==0.14 # via sphinx
ecdsa==0.13
enum34==1.1.6 # via cryptography, flake8
fake-switches==1.3.6
fake-switches==1.3.7
flake8==3.4.1
flask==1.0.2
flexmock==0.10.2
Expand All @@ -30,14 +30,14 @@ funcsigs==1.0.2 # via mock
futures==3.2.0
gunicorn==19.9.0
hy==0.15.0 # via mockssh
idna==2.7
idna==2.8
imagesize==1.1.0 # via sphinx
incremental==17.5.0 # via twisted
ipaddress==1.0.22 # via cryptography
itsdangerous==1.1.0
jabstract==0.1.2
jinja2==2.10
lxml==4.2.5
lxml==4.3.0
markupsafe==1.1.0
mccabe==0.6.1 # via flake8
mock==2.0.0
Expand All @@ -47,19 +47,19 @@ netaddr==0.7.19
nose==1.3.7
paramiko==1.17.6
pbr==5.1.1
pyasn1==0.4.4 # via mockssh, twisted
pyasn1==0.4.5 # via mockssh, twisted
pycodestyle==2.3.1 # via flake8
pycparser==2.19 # via cffi
pycrypto==2.6.1
pyeapi==0.8.2
pyflakes==1.5.0 # via flake8
pygments==2.2.0 # via sphinx
pygments==2.3.1 # via sphinx
pyhamcrest==1.9.0
pytz==2018.7 # via babel
requests==2.20.0
pytz==2018.9 # via babel
requests==2.21.0
rply==0.7.6 # via hy
selectors2==2.0.1
six==1.11.0
six==1.12.0
snowballstemmer==1.2.1 # via sphinx
sphinx==1.5.6
sphinxcontrib-httpdomain==1.7.0
Expand Down
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.6
fake-switches>=1.3.7
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
50 changes: 50 additions & 0 deletions tests/adapters/compliance_tests/set_vlan_load_interval_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Copyright 2019 Internap.
#
# 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.


from hamcrest import assert_that, is_, contains_string

from netman.core.objects.exceptions import BadLoadIntervalNumber, UnknownVlan
from tests import has_message
from tests.adapters.compliance_test_case import ComplianceTestCase


class SetVlanLoadIntervalTest(ComplianceTestCase):
_dev_sample = "arista_http"

def test_sets_load_interval_for_the_vlan(self):
self.client.add_vlan(1000)

self.client.set_vlan_load_interval(1000, 30)

vlan = self.client.get_vlan(1000)
assert_that(vlan.load_interval, is_(30))

def test_fails_if_the_value_is_not_within_the_supported_range(self):
self.client.add_vlan(1000)

with self.assertRaises(BadLoadIntervalNumber) as expect:
self.client.set_vlan_load_interval(1000, 800)

assert_that(str(expect.exception), contains_string("Load interval number is invalid"))

def test_fails_if_the_vlan_does_not_exist(self):
with self.assertRaises(UnknownVlan) as expect:
self.client.set_vlan_load_interval(1000, 30)

assert_that(expect.exception, has_message("Vlan 1000 not found"))

def tearDown(self):
self.janitor.remove_vlan(1000)
super(SetVlanLoadIntervalTest, self).tearDown()
43 changes: 43 additions & 0 deletions tests/adapters/compliance_tests/unset_vlan_load_interval_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# Copyright 2019 Internap.
#
# 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.


from hamcrest import assert_that, is_

from netman.core.objects.exceptions import UnknownVlan
from tests import has_message
from tests.adapters.compliance_test_case import ComplianceTestCase


class UnsetVlanLoadIntervalTest(ComplianceTestCase):
_dev_sample = "arista_http"

def test_unset_load_interval_from_the_vlan(self):
self.client.add_vlan(1000)
self.try_to.set_vlan_load_interval(1000, 30)

self.client.unset_vlan_load_interval(1000)
vlan = self.client.get_vlan(1000)

assert_that(vlan.load_interval, is_(None))

def test_fails_if_the_vlan_does_not_exist(self):
with self.assertRaises(UnknownVlan) as expect:
self.client.unset_vlan_load_interval(1000)

assert_that(expect.exception, has_message("Vlan 1000 not found"))

def tearDown(self):
self.janitor.remove_vlan(1000)
super(UnsetVlanLoadIntervalTest, self).tearDown()
Loading

0 comments on commit 98039e1

Please sign in to comment.