Prepare network_checker for ci

Vagrant will spawn virtualbox vm with 3 interfaces
with 2 of them in same network (which will be handy for some debug)

eth1 and eth2 will be controlled by dhcp server in same network

This approach has couple of requirements:
1. virtualbox installed on jenkins node
2. vagrant installed on jenkins node
3. added precise64 box for vagrant

vagrant up - will create vm and install all necessery packages

to run tests:
vagrant ssh -c 'sudo py.test /vagrant/'

will run tests for network_checker and dhcpchecker
exit status will be non-zero in case of errors
sudo is required for capturing data on ifaces

To make docker approach to work:
1. Share linux drivers for docker containter (netchecker uses modprobe 8021q)
2. setup dnsmasq on same bridge which this docker image is using

Tests can be started on local machine with help of vde switch:
py.test --vde ./
This will create vde_switch and tap ifaces (tap11, tap12)

Related to blueprint fuel-network-checks-ci
Change-Id: Ieefca56f31db988a610870710ec9cb0ad1a0d6a6
This commit is contained in:
Dima Shulyak 2014-03-27 13:14:21 +02:00
parent 3df411acf2
commit 34995c649a
6 changed files with 110 additions and 67 deletions

View File

@ -0,0 +1,10 @@
FROM phusion/baseimage
ENV ARCH amd64
ENV DIST precise
RUN echo 'deb http://fuel-repository.mirantis.com/fwm/5.0/ubuntu precise main' >> /etc/apt/sources.list
RUN apt-get -q update
RUN apt-get -y --force-yes install cliff-tablib python-pyparsing python-pypcap scapy python-pip wget openssh-server
RUN pip install pytest mock
RUN sudo locale-gen en_US.UTF-8
RUN mkdir -p /app

View File

@ -6,39 +6,20 @@ VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "centos"
config.vm.box_url = "http://developer.nrel.gov/downloads/vagrant-boxes/CentOS-6.4-i386-v20130427.box"
config.vm.box = "precise64"
config.vm.provider "virtualbox" do |v|
v.customize ["modifyvm", :id, "--memory", 348]
end
config.vm.define :develop do |config|
config.vm.network :private_network, ip: "192.168.0.2"
config.vm.network :private_network, ip: "10.10.0.5"
config.vm.network :private_network, ip: '10.100.0.0', type: "dhcp"
config.vm.network :private_network, ip: '10.200.0.0', type: 'dhcp'
config.vm.provision :shell, :inline => "echo 'deb http://fuel-repository.mirantis.com/fwm/5.0/ubuntu precise main' >> /etc/apt/sources.list"
config.vm.provision :shell, :inline => "sudo apt-get update"
config.vm.provision :shell, :inline => "sudo apt-get -y --force-yes install cliff-tablib python-pyparsing python-pypcap scapy python-pip vde2"
config.vm.provision :shell, :inline => "sudo pip install pytest mock"
config.vm.provision :shell, :inline => "cd /vagrant && sudo python setup.py develop"
end
config.vm.define :dhcp2 do |config|
config.vm.network :private_network, ip: "10.10.0.8"
config.vm.provision :shell, :inline => "sudo yum -y install dhcp"
config.vm.provision :shell, :inline => "sudo route add -host 255.255.255.255 dev eth1"
config.vm.provision :shell, :inline => "sudo cp /vagrant/test_utils/dhcpd.conf.sample2 /etc/dhcp/dhcpd.conf"
config.vm.provision :shell, :inline => "sudo /usr/sbin/dhcpd eth1"
end
config.vm.define :dhcp_relay do |config|
config.vm.network :private_network, ip: "10.10.0.10"
config.vm.provision :shell, :inline => "sudo yum -y install dhcp"
config.vm.provision :shell, :inline => "cp /vagrant/test_utils/dhcp_relay.conf /etc/sysconfig/dhcrelay"
config.vm.provision :shell, :inline => "service dhcrelay start"
end
config.vm.define :dhcp1 do |config|
config.vm.network :private_network, ip: "192.168.0.5"
config.vm.provision :shell, :inline => "sudo yum -y install dhcp"
config.vm.provision :shell, :inline => "sudo route add -host 255.255.255.255 dev eth1"
config.vm.provision :shell, :inline => "sudo cp /vagrant/test_utils/dhcpd.conf.sample /etc/dhcp/dhcpd.conf"
config.vm.provision :shell, :inline => "sudo /usr/sbin/dhcpd eth1"
end
end

View File

@ -0,0 +1,44 @@
# Copyright 2014 Mirantis, Inc.
#
# 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 os
PIDFILE = '/tmp/vde_network_checker'
IFACES = ['tap11', 'tap12']
def pytest_addoption(parser):
parser.addoption("--vde", action='store_true', default=False,
help="Use vde switch for network verification.")
def pytest_configure(config):
if config.getoption('vde'):
base = 'vde_switch -p {pidfile} -d'.format(pidfile=PIDFILE)
command = [base]
taps = ['-tap {tap}'.format(tap=tap) for tap in IFACES]
full_command = command + taps
os.system(' '.join(full_command))
for tap in IFACES:
os.system('ifconfig {tap} up'.format(tap=tap))
os.environ['NET_CHECK_IFACE_1'] = IFACES[0]
os.environ['NET_CHECK_IFACE_2'] = IFACES[1]
def pytest_unconfigure(config):
if os.path.exists(PIDFILE):
with open(PIDFILE) as f:
pid = f.read().strip()
os.kill(int(pid), 15)

View File

@ -24,26 +24,21 @@ from dhcp_checker import utils
class TestDhcpServers(unittest.TestCase):
def test_dhcp_server_on_eth0(self):
"""Test verifies dhcp server on eth0 iface
"""
response = api.check_dhcp_on_eth('eth0', 2)
self.assertEqual(len(response), 1)
self.assertEqual(response[0]['server_ip'], '10.0.2.2')
def test_dhcp_server_on_eth1(self):
"""Test verifies dhcp server on eth1 iface
"""
response = api.check_dhcp_on_eth('eth1', 2)
self.assertEqual(len(response), 1)
self.assertEqual(response[0]['server_ip'], '192.168.0.5')
# we need to guarantee that received answer has server_ip
# but dont want to check its real address
self.assertTrue(response[0]['server_ip'])
def test_dhcp_server_on_eth2(self):
"""Test verifies dhcp server on eth2 iface
"""
response = api.check_dhcp_on_eth('eth2', 2)
self.assertEqual(len(response), 1)
self.assertEqual(response[0]['server_ip'], '10.10.0.8')
self.assertTrue(response[0]['server_ip'])
class TestDhcpUtils(unittest.TestCase):
@ -83,7 +78,7 @@ class TestDhcpWithNetworkDown(unittest.TestCase):
response = api.check_dhcp_on_eth(iface, 2)
self.assertEqual(len(response), 1)
self.assertEqual(response[0]['server_ip'], '10.10.0.8')
self.assertTrue(response[0]['server_ip'])
self.assertEqual(manager.pre_iface_state, 'DOWN')
self.assertEqual(manager.iface_state, 'UP')
self.assertEqual(manager.post_iface_state, 'DOWN')
@ -96,7 +91,7 @@ class TestDhcpWithNetworkDown(unittest.TestCase):
response = api.check_dhcp_on_eth(iface, 2)
self.assertEqual(len(response), 1)
self.assertEqual(response[0]['server_ip'], '10.0.2.2')
self.assertTrue(response[0]['server_ip'])
self.assertEqual(manager.pre_iface_state, 'UP')
self.assertEqual(manager.iface_state, 'UP')
self.assertEqual(manager.post_iface_state, 'UP')
@ -116,15 +111,15 @@ class TestDhcpWithNetworkDown(unittest.TestCase):
class TestMainFunctions(unittest.TestCase):
def test_with_vlans(self):
config = {'eth0': (100, 101), 'eth1': (103, 105),
config = {'eth1': (103, 105),
'eth2': range(106, 120)}
result = api.check_dhcp_with_vlans(config)
self.assertEqual(len(list(result)), 3)
self.assertEqual(len(list(result)), 2)
def test_with_duplicated_with_repeat(self):
ifaces = ['eth0', 'eth1', 'eth2']
ifaces = ['eth1', 'eth2']
result = api.check_dhcp(ifaces, repeat=3)
self.assertEqual(len(list(result)), 3)
self.assertEqual(len(list(result)), 2)
if __name__ == '__main__':

View File

@ -29,10 +29,11 @@ from net_check import api
class BaseListenerTestCase(unittest.TestCase):
def setUp(self, config=None):
self.iface = os.environ.get('NET_CHECK_IFACE_1', 'eth1')
default_config = {
"src": "1.0.0.0", "ready_port": None,
"ready_address": "localhost", "dst": "1.0.0.0",
"interfaces": {"eth0": "0,100,100,101,102,103,104,105,106,107"},
"interfaces": {self.iface: "0,100,101,102,103,104,105,106,107"},
"action": "listen",
"cookie": "Nailgun:", "dport": 31337, "sport": 31337,
"src_mac": None, "dump_file": "/var/tmp/net-probe-dump"
@ -72,34 +73,48 @@ class BaseListenerTestCase(unittest.TestCase):
os.unlink(self.config['dump_file'])
class TestCaseListenerPcapFile(BaseListenerTestCase):
class TestCaseListenerPcap(BaseListenerTestCase):
def send_packets(self):
directory_path = os.path.dirname(__file__)
scapy_data = scapy.rdpcap(os.path.join(directory_path, 'vlan.pcap'))
for p in scapy_data:
scapy.sendp(p, iface='eth0')
for vlan in self.config['interfaces'][self.iface].split(','):
p = self.get_packet(vlan)
for i in xrange(5):
scapy.sendp(p, iface=self.iface)
def get_packet(self, vlan):
normal_data = 'Nailgun:{iface} 1'.format(iface=self.iface)
p = scapy.Ether(src='64:0b:36:0e:0a:b7',
dst="ff:ff:ff:ff:ff:ff")
if int(vlan) > 0:
p = p / scapy.Dot1Q(vlan=int(vlan))
message_len = len(normal_data) + 8
p = p / scapy.IP(src=self.config['src'], dst=self.config['dst'])
p = p / scapy.UDP(sport=self.config['sport'],
dport=self.config['dport'],
len=message_len) / normal_data
return p
def test_listener_pcap_file(self):
with open(self.config['dump_file'], 'r') as f:
data = json.loads(f.read())
self.assertEqual(data, {u'eth0': {
u'102': {u'1': [u'eth0'], u'2': [u'eth0']},
u'103': {u'1': [u'eth0'], u'2': [u'eth0']},
u'100': {u'1': [u'eth0'], u'2': [u'eth0']},
u'101': {u'1': [u'eth0'], u'2': [u'eth0']},
u'106': {u'1': [u'eth0'], u'2': [u'eth0']},
u'107': {u'1': [u'eth0'], u'2': [u'eth0']},
u'104': {u'1': [u'eth0'], u'2': [u'eth0']},
u'105': {u'1': [u'eth0'], u'2': [u'eth0']}}})
self.assertEqual(data, {self.iface: {
u'0': {u'1': [self.iface]},
u'102': {u'1': [self.iface]},
u'103': {u'1': [self.iface]},
u'100': {u'1': [self.iface]},
u'101': {u'1': [self.iface]},
u'106': {u'1': [self.iface]},
u'107': {u'1': [self.iface]},
u'104': {u'1': [self.iface]},
u'105': {u'1': [self.iface]}}})
class TestCaseListenerCorruptedData(BaseListenerTestCase):
def send_packets(self):
normal_data = 'Nailgun:eth0 2'
normal_data = 'Nailgun:{iface} 2'.format(iface=self.iface)
corrupted_data = normal_data + '7h 7\00\00\00'
message_len = len(normal_data) + 8
p = scapy.Ether(src=self.config['src_mac'],
@ -109,26 +124,24 @@ class TestCaseListenerCorruptedData(BaseListenerTestCase):
dport=self.config['dport'],
len=message_len) / corrupted_data
for i in xrange(5):
scapy.sendp(p, iface='eth0')
scapy.sendp(p, iface=self.iface)
def test_listener_corrupted_data(self):
with open(self.config['dump_file'], 'r') as f:
data = json.loads(f.read())
self.assertEqual(data, {u'eth0': {u'0': {u'2': [u'eth0']}}})
self.assertEqual(data, {self.iface: {u'0': {u'2': [self.iface]}}})
class TestNetCheckSender(unittest.TestCase):
def setUp(self):
directory_path = os.path.dirname(__file__)
self.scapy_data = scapy.rdpcap(os.path.join(directory_path,
'vlan.pcap'))
self.iface = os.environ.get('NET_CHECK_IFACE_1', 'eth1')
self.config = {
"src": "1.0.0.0", "ready_port": 31338,
"ready_address": "localhost", "dst": "1.0.0.0",
"interfaces": {"eth0": "0,100,101,102,106,107,108"},
"interfaces": {self.iface: "0,100,101,102,106,107,108"},
"action": "listen",
"cookie": "Nailgun:", "dport": 31337, "sport": 31337,
"src_mac": None,
@ -137,8 +150,8 @@ class TestNetCheckSender(unittest.TestCase):
}
def start_pcap_listener(self):
self.pcap_listener = pcap.pcap('eth0')
self.vlan_pcap_listener = pcap.pcap('eth0')
self.pcap_listener = pcap.pcap(self.iface)
self.vlan_pcap_listener = pcap.pcap(self.iface)
filter_string = 'udp and dst port {0}'.format(self.config['dport'])
self.vlan_pcap_listener.setfilter('vlan and {0}'.format(filter_string))
self.pcap_listener.setfilter(filter_string)
@ -173,5 +186,5 @@ class TestNetCheckSender(unittest.TestCase):
time.sleep(3)
self.sender.join()
expected_vlans = set(self.config['interfaces']['eth0'].split(','))
expected_vlans = set(self.config['interfaces'][self.iface].split(','))
self.assertEqual(expected_vlans, self.received_vlans)