astara-appliance/test/unit/drivers/test_arp.py

187 lines
6.3 KiB
Python

# Copyright 2014 DreamHost, LLC
#
# Author: DreamHost, LLC
#
# 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.
import mock
import socket
import unittest2
from astara_router import models
from astara_router.drivers import arp
config = mock.Mock()
network = mock.Mock()
alloc = mock.Mock()
network.address_allocations = [alloc]
config.networks = [network]
def _AF_PACKET_supported():
try:
socket.AF_PACKET
return True
except:
return False
class ARPTest(unittest2.TestCase):
def setUp(self):
self.mgr = arp.ARPManager()
@mock.patch.object(alloc, 'dhcp_addresses', ['10.10.10.200'])
def test_ip_not_in_table(self):
# If there's a DHCP address that isn't in ARP, do nothing.
output = '''
? (10.10.10.2) at fa:16:3e:4b:7a:f0 on vio2
? (10.10.10.3) at fa:16:3e:4c:0e:27 on vio2
? (208.113.176.1) at 78:fe:3d:d3:b5:c1 on vio1
'''
with mock.patch.object(self.mgr, 'sudo') as sudo:
sudo.return_value = output
self.mgr.remove_stale_entries(
config
)
sudo.assert_called_with('-an')
@mock.patch.object(alloc, 'mac_address', 'fa:16:3e:4b:7a:f0')
@mock.patch.object(alloc, 'dhcp_addresses', ['10.10.10.2'])
def test_ip_mac_address_matches(self):
# If a DHCP address is in ARP, and the mac address is correct, do
# nothing.
output = '''
? (10.10.10.2) at fa:16:3e:4b:7a:f0 on vio2
? (10.10.10.3) at fa:16:3e:4c:0e:27 on vio2
? (208.113.176.1) at 78:fe:3d:d3:b5:c1 on vio1
'''
with mock.patch.object(self.mgr, 'sudo') as sudo:
sudo.return_value = output
self.mgr.remove_stale_entries(
config
)
sudo.assert_called_with('-an')
@mock.patch.object(alloc, 'mac_address', 'fa:20:30:40:50:f0')
@mock.patch.object(alloc, 'dhcp_addresses', ['10.10.10.2'])
def test_ip_mac_address_mismatch(self):
# If a DHCP address is in ARP, and the mac address has changed,
# delete the old record.
output = '''
? (10.10.10.2) at fa:16:3e:4b:7a:f0 on vio2
? (10.10.10.3) at fa:16:3e:4c:0e:27 on vio2
? (208.113.176.1) at 78:fe:3d:d3:b5:c1 on vio1
'''
with mock.patch.object(self.mgr, 'sudo') as sudo:
sudo.return_value = output
self.mgr.remove_stale_entries(
config
)
sudo.assert_has_calls([
mock.call('-an'),
mock.call('-d', '10.10.10.2')
])
def test_send_gratuitous_arp_for_config(self):
config = models.RouterConfiguration({
'networks': [{
'network_id': 'ABC456',
'interface': {
'ifname': 'ge1',
'name': 'ext',
},
'subnets': [{
'id': 'theid',
'cidr': '172.16.77.0/24',
'gateway_ip': '172.16.77.1',
'dhcp_enabled': True,
'dns_nameservers': []
}],
'network_type': models.Network.TYPE_EXTERNAL,
}],
'floating_ips': [{
'fixed_ip': '192.168.0.2',
'floating_ip': '172.16.77.50'
}, {
'fixed_ip': '192.168.0.3',
'floating_ip': '172.16.77.51'
}, {
'fixed_ip': '192.168.0.4',
'floating_ip': '172.16.77.52'
}, {
'fixed_ip': '192.168.0.5',
'floating_ip': '172.16.77.53'
}]
})
with mock.patch('astara_router.utils.execute') as execute:
self.mgr.send_gratuitous_arp_for_floating_ips(
config,
lambda x: x.replace('ge', 'eth')
)
assert execute.call_args_list == [
mock.call(
['astara-gratuitous-arp', 'eth1', '172.16.77.50'],
'sudo astara-rootwrap /etc/rootwrap.conf'
),
mock.call(
['astara-gratuitous-arp', 'eth1', '172.16.77.51'],
'sudo astara-rootwrap /etc/rootwrap.conf'
),
mock.call(
['astara-gratuitous-arp', 'eth1', '172.16.77.52'],
'sudo astara-rootwrap /etc/rootwrap.conf'
),
mock.call(
['astara-gratuitous-arp', 'eth1', '172.16.77.53'],
'sudo astara-rootwrap /etc/rootwrap.conf'
)
]
@unittest2.skipIf(
not _AF_PACKET_supported(),
'socket.AF_PACKET not supported on this platform'
)
@mock.patch('socket.socket')
def test_send_gratuitous_arp(self, socket_constr):
socket_inst = socket_constr.return_value
socket_inst.getsockname.return_value = (
None, None, None, None, 'A1:B2:C3:D4:E5:F6'
)
arp._send_gratuitous_arp('eth1', '1.2.3.4')
socket_constr.assert_called_once_with(
socket.AF_PACKET, socket.SOCK_RAW
)
socket_inst.bind.assert_called_once_with((
'eth1',
0x0806
))
data = socket_inst.send.call_args_list[0][0][0]
assert data == ''.join([
'\xff\xff\xff\xff\xff\xff', # Broadcast destination
'A1:B2:C3:D4:E5:F6', # Source hardware address
'\x08\x06', # HTYPE ARP
'\x00\x01', # Ethernet
'\x08\x00', # Protocol IPv4
'\x06', # HADDR length, 6 for IEEE 802 MAC addresses
'\x04', # PADDR length, 4 for IPv4
'\x00\x02', # OPER, 2 = ARP Reply
'A1:B2:C3:D4:E5:F6', # Source MAC
'\x01\x02\x03\x04', # Source IP
'A1:B2:C3:D4:E5:F6', # Target MAC matches
'\x01\x02\x03\x04' # Target IP matches
])