Deprecate network_checker directory

Change-Id: I549f6c177a4e1fd459666410954accea9c30ae3c
Related-Bug: #1506896
This commit is contained in:
Vladimir Kozhukalov 2015-10-28 20:04:48 +03:00
parent 73a7dd0757
commit ff842dd813
64 changed files with 1 additions and 3661 deletions

View File

@ -40,15 +40,6 @@ maintainers:
email: ikalnitsky@mirantis.com
IRC: ikalnitsky
- network_checker/:
- name: Dmytro Shuliak
email: dshulyak@mirantis.com
IRC: dshulyak
- name: Lukasz Oles
email: loles@mirantis.com
IRC: salmon_
- nailgun/:
- name: Aleksandr Kislitskii
email: akislitsky@mirantis.com

8
debian/control vendored
View File

@ -15,11 +15,5 @@ Depends: ohai (<< 7),
ruby-cstruct,
ruby-json,
${misc:Depends}
Description: <insert up to 60 chars description>
<insert long description, indented with spaces>
Package: nailgun-net-check
Architecture: all
Depends: ${misc:Depends}, ${python:Depends}, python-pypcap, vlan, python-scapy, cliff-tablib, python-stevedore, python-daemonize, python-yaml, tcpdump, python-requests, python-six
Description: NailGun client net-check
Description: Fencing agent
.

3
debian/rules vendored
View File

@ -7,12 +7,9 @@ topdir=$(shell pwd)
dh $@ --with python2
override_dh_auto_install:
cd network_checker && python setup.py install -O0 --single-version-externally-managed --install-layout=deb --root=$(topdir)/debian/nailgun-net-check
dh_auto_install
override_dh_auto_build:
dh_clean
cd network_checker && python setup.py build
dh_auto_build
override_dh_auto_clean:
cd network_checker && python setup.py clean
dh_auto_clean

View File

@ -1,10 +0,0 @@
FROM phusion/baseimage
ENV ARCH amd64
ENV DIST trusty
RUN echo 'deb http://fuel-repository.mirantis.com/fwm/5.0/ubuntu trusty 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

@ -1 +0,0 @@
recursive-include network_checker *

View File

@ -1,25 +0,0 @@
# -*- mode: ruby -*-
# vi: set ft=ruby :
# Vagrantfile API/syntax version. Don't touch unless you know what you're doing!
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
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: '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 trusty 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
end

View File

@ -1,44 +0,0 @@
# 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

@ -1,35 +0,0 @@
nailgun (8.0.0-1) trusty; urgency=low
* Change version to 8.0.0
-- Vladimir Kozhukalov <vkozhukalov@mirantis.com> Mon, 03 Sep 2015 11:12:00 +0300
nailgun (7.0.0-1) trusty; urgency=low
* Update version to 7.0.0
-- Aleksandra Fedorova <afedorova@mirantis.com> Mon, 08 Jun 2015 18:15:00 +0300
nailgun (6.1.0-1) trusty; urgency=low
* Update version to 6.1.0
-- Matthew Mosesohn <mmosesohn@mirantis.com> Wed, 22 Apr 2015 14:44:00 +0300
nailgun (6.0.0-1) trusty; urgency=low
* Update code from upstream
-- Igor Kalnitsky <ikalnitsky@mirantis.com> Wed, 26 Nov 2014 19:49:00 +0200
nailgun (0.1.0-ubuntu1) precise; urgency=low
* Update code from upstream
-- OSCI Jenkins <dburmistrov@mirantis.com> Wed, 03 Sep 2014 15:17:07 +0400
nailgun (0.1.0-1ubuntu0) precise; urgency=low
* Add fencing agent script as a separated task
-- Bogdan Dobrelya <bdobrelia@mirantis.com> Thu, 23 Jan 2014 18:55:00 +0400

View File

@ -1 +0,0 @@
8

View File

@ -1,14 +0,0 @@
Source: network-checker
Section: unknown
Priority: net
Maintainer: Mirantis Product <product@mirantis.com>
Build-Depends: debhelper (>= 8.0.0), python-setuptools
X-Python-Version: 2.6, 2.7
Standards-Version: 3.9.2
Homepage: mirantis.com
Package: nailgun-net-check
Architecture: all
Depends: ${misc:Depends}, ${python:Depends}, python-pypcap, vlan, python-scapy, cliff-tablib, python-stevedore, python-daemonize, python-yaml, tcpdump, python-requests, python-six
Description: Nailgun network connectivity check tool
.

View File

@ -1,18 +0,0 @@
#!/usr/bin/make -f
DH_VERBOSE=1
topdir=$(shell pwd)
%:
dh $@ --with python2
override_dh_auto_install:
python setup.py install -O0 --single-version-externally-managed --install-layout=deb --root=$(topdir)/debian/nailgun-net-check
dh_auto_install
override_dh_auto_build:
dh_clean
python setup.py build
dh_auto_build
override_dh_auto_clean:
python setup.py clean
dh_auto_clean

View File

@ -1 +0,0 @@
3.0 (quilt)

View File

@ -1,13 +0,0 @@
# Copyright 2013 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.

View File

@ -1,152 +0,0 @@
# Copyright 2013 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 itertools
import logging
import time
from scapy import config as scapy_config
scapy_config.use_pcap = True
logging.getLogger('scapy.runtime').setLevel(logging.CRITICAL)
from dhcp_checker import utils
import pcap
from scapy import all as scapy
LOG = logging.getLogger(__name__)
def _get_dhcp_discover_message(iface):
dhcp_options = [("message-type", "discover"),
("param_req_list", utils.format_options(
[1, 2, 3, 4, 5, 6,
11, 12, 13, 15, 16, 17, 18, 22, 23,
28, 40, 41, 42, 43, 50, 51, 54, 58, 59, 60, 66, 67])),
"end"]
fam, hw = scapy.get_if_raw_hwaddr(iface)
dhcp_discover = (
scapy.Ether(src=hw, dst="ff:ff:ff:ff:ff:ff") /
scapy.IP(src="0.0.0.0", dst="255.255.255.255") /
scapy.UDP(sport=68, dport=67) /
scapy.BOOTP(chaddr=hw) /
scapy.DHCP(options=dhcp_options))
return dhcp_discover
@utils.single_format
def check_dhcp_on_eth(iface, timeout):
"""Check if there is roque dhcp server in network on given iface
@iface - name of the ethernet interface
@timeout - scapy timeout for waiting on response
>>> check_dhcp_on_eth('eth1')
"""
scapy.conf.iface = iface
scapy.conf.checkIPaddr = False
dhcp_discover = _get_dhcp_discover_message(iface)
ans, unans = scapy.srp(dhcp_discover, multi=True,
nofilter=1, timeout=timeout, verbose=0)
return ans
def check_dhcp(ifaces, timeout=5, repeat=2):
"""Given list of ifaces. Process them in separate processes
@ifaces - lsit of ifaces
@timeout - timeout for scapy to wait for response
@repeat - number of packets sended
>>> check_dhcp(['eth1', 'eth2'])
"""
config = {}
for iface in ifaces:
config[iface] = ()
return check_dhcp_with_vlans(config, timeout=timeout, repeat=repeat)
def send_dhcp_discover(iface):
dhcp_discover = _get_dhcp_discover_message(iface)
scapy.sendp(dhcp_discover, iface=iface, verbose=0)
def make_listeners(ifaces):
listeners = []
for iface in ifaces:
try:
listener = pcap.pcap(iface)
mac_filter = utils.create_mac_filter(iface)
# catch the answers only for this iface
listener.setfilter('((dst port 68) and {0})'.format(mac_filter))
listeners.append(listener)
except Exception:
LOG.warning(
'Spawning listener for {iface} failed.'.format(iface=iface))
return listeners
@utils.filter_duplicated_results
def check_dhcp_with_vlans(config, timeout=5, repeat=2):
"""Provide config of {iface: [vlans..]} pairs
@config - {'eth0': (100, 101), 'eth1': (100, 102)}
"""
# vifaces - list of pairs ('eth0', ['eth0.100', 'eth0.101'])
with utils.VlansContext(config) as vifaces:
ifaces, vlans = zip(*vifaces)
listeners = make_listeners(ifaces)
for _ in xrange(repeat):
for i in utils.filtered_ifaces(itertools.chain(ifaces, *vlans)):
send_dhcp_discover(i)
time.sleep(timeout)
for l in listeners:
for pkt in l.readpkts():
yield utils.format_answer(scapy.Ether(pkt[1]), l.name)
@utils.single_format
def check_dhcp_request(iface, server, range_start, range_end, timeout=5):
"""Provide interface, server endpoint and pool of ip adresses
Should be used after offer received
>>> check_dhcp_request('eth1','10.10.0.5','10.10.0.10','10.10.0.15')
"""
scapy.conf.iface = iface
scapy.conf.checkIPaddr = False
fam, hw = scapy.get_if_raw_hwaddr(iface)
ip_address = next(utils.pick_ip(range_start, range_end))
# note lxc dhcp server does not respond to unicast
dhcp_request = (scapy.Ether(src=hw, dst="ff:ff:ff:ff:ff:ff") /
scapy.IP(src="0.0.0.0", dst="255.255.255.255") /
scapy.UDP(sport=68, dport=67) /
scapy.BOOTP(chaddr=hw) /
scapy.DHCP(options=[("message-type", "request"),
("server_id", server),
("requested_addr", ip_address),
"end"]))
ans, unans = scapy.srp(dhcp_request, nofilter=1, multi=True,
timeout=timeout, verbose=0)
return ans

View File

@ -1,52 +0,0 @@
# Copyright 2013 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 logging
import os
import sys
# fixed in cmd2 >=0.6.6
os.environ['EDITOR'] = '/usr/bin/nano'
from cliff.commandmanager import CommandManager
from fuel_network_checker import base_app
class DhcpApp(base_app.BaseApp):
DEFAULT_VERBOSE_LEVEL = 0
LOG_FILENAME = '/var/log/dhcp_checker.log'
def __init__(self):
super(DhcpApp, self).__init__(
description='Dhcp check application',
version='0.1',
command_manager=CommandManager('dhcp.check'),
)
def configure_logging(self):
super(DhcpApp, self).configure_logging()
# set scapy logger level only to ERROR
# due to a lot of spam
runtime_logger = logging.getLogger('scapy.runtime')
runtime_logger.setLevel(logging.ERROR)
def main(argv=sys.argv[1:]):
myapp = DhcpApp()
return myapp.run(argv)
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))

View File

@ -1,111 +0,0 @@
# Copyright 2013 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 json
import logging
from cliff import command
from cliff import lister
from dhcp_checker import api
from dhcp_checker import utils
LOG = logging.getLogger(__name__)
class BaseCommand(command.Command):
"""Base command for all app"""
def get_parser(self, prog_name):
parser = super(BaseCommand, self).get_parser(prog_name)
parser.add_argument('--timeout', default=5, type=int,
help="Provide timeout for each network request")
parser.add_argument('--repeat', default=2, type=int,
help="Provide number of repeats for request")
return parser
class ListDhcpServers(lister.Lister, BaseCommand):
"""Show list of dhcp servers on ethernet interfaces"""
def get_parser(self, prog_name):
parser = super(ListDhcpServers, self).get_parser(prog_name)
parser.add_argument(
'--ifaces', metavar='I', nargs='+',
help='If no eth provided - will run against all except lo')
return parser
def take_action(self, parsed_args):
LOG.info('Starting dhcp discover for {0}'.format(parsed_args.ifaces))
res = list(api.check_dhcp(
parsed_args.ifaces,
timeout=parsed_args.timeout,
repeat=parsed_args.repeat))
# NOTE(dshulyak) unfortunately cliff doesnt allow to configure
# PrettyTable output, see link:
# https://github.com/dhellmann/cliff/blob/master/
# cliff/formatters/table.py#L34
# and in case i want always print empty table if nothing found
# it is not possible by configuration
if not res:
res = [{}]
return (utils.DHCP_OFFER_COLUMNS,
[utils.get_item_properties(item, utils.DHCP_OFFER_COLUMNS)
for item in res])
class ListDhcpAssignment(lister.Lister, BaseCommand):
"""Make dhcp request to servers and receive acknowledgement messages"""
def get_parser(self, prog_name):
parser = super(ListDhcpAssignment, self).get_parser(prog_name)
parser.add_argument('iface',
help='Ethernet interface name')
parser.add_argument('endpoint',
help='Endpoint of server or multicast group')
parser.add_argument('--range_start', dest='range_start',
help='Start of the range')
parser.add_argument('--range_end', dest='range_end', default=None,
help='Start of the range')
return parser
def take_action(self, parsed_args):
res = iter(api.check_dhcp_request(
parsed_args.iface,
parsed_args.endpoint,
parsed_args.range_start,
parsed_args.range_end, timeout=parsed_args.timeout))
first = res.next()
columns = first.keys()
return columns, [first.values()] + [item.values() for item in res]
class DhcpWithVlansCheck(lister.Lister, BaseCommand):
"""Provide iface with list of vlans to check
If no vlans created - they will be. After creation they won't be deleted.
"""
def get_parser(self, prog_name):
parser = super(DhcpWithVlansCheck, self).get_parser(prog_name)
parser.add_argument('config',
help='Ethernet interface name')
return parser
def take_action(self, parsed_args):
res = api.check_dhcp_with_vlans(json.loads(parsed_args.config),
timeout=parsed_args.timeout,
repeat=parsed_args.repeat)
first = res.next()
columns = first.keys()
return columns, [first.values()] + [item.values() for item in res]

View File

@ -1,13 +0,0 @@
# Copyright 2013 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.

View File

@ -1,119 +0,0 @@
# Copyright 2013 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.
"""
For this tests you need
vagrant up develop dhcp1 dhcp2
"""
import unittest
from dhcp_checker import api
from dhcp_checker import utils
class TestDhcpServers(unittest.TestCase):
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)
# 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.assertTrue(response[0]['server_ip'])
class TestDhcpUtils(unittest.TestCase):
def setUp(self):
self.iface_down = 'eth1'
utils.command_util('ifconfig', self.iface_down, 'down')
def test_check_network_up(self):
"""Verify that true would be returned on test network up"""
result = utils.check_network_up('eth0')
self.assertTrue(result)
def test_check_network_down(self):
"""Verify that false would be returned on test network down"""
self.assertFalse(utils.check_network_up(self.iface_down))
def tearDown(self):
utils.command_util('ifconfig', self.iface_down, 'up')
class TestDhcpWithNetworkDown(unittest.TestCase):
def setUp(self):
self.iface_up = 'eth0'
self.iface_down = 'eth2'
utils.command_util('ifconfig', self.iface_down, 'down')
def test_dhcp_server_on_eth2_down(self):
"""iface should be ifuped in case it's down and rolledback after"""
manager = utils.IfaceState(self.iface_down)
with manager as iface:
response = api.check_dhcp_on_eth(iface, 2)
self.assertEqual(len(response), 1)
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')
def test_dhcp_server_on_eth0_up(self):
"""Test verifies that if iface is up, it won't be touched"""
manager = utils.IfaceState(self.iface_up)
with manager as iface:
response = api.check_dhcp_on_eth(iface, 2)
self.assertEqual(len(response), 1)
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')
def test_dhcp_server_on_nonexistent_iface(self):
def test_check():
manager = utils.IfaceState('eth10')
with manager as iface:
api.check_dhcp_on_eth(iface, 2)
self.assertRaises(EnvironmentError, test_check)
def tearDown(self):
utils.command_util('ifconfig', self.iface_down, 'up')
class TestMainFunctions(unittest.TestCase):
def test_with_vlans(self):
config = {'eth1': (103, 105),
'eth2': range(106, 120)}
result = api.check_dhcp_with_vlans(config)
self.assertEqual(len(list(result)), 2)
def test_with_duplicated_with_repeat(self):
ifaces = ['eth1', 'eth2']
result = api.check_dhcp(ifaces, repeat=3)
self.assertEqual(len(list(result)), 2)
if __name__ == '__main__':
unittest.main()

View File

@ -1,13 +0,0 @@
# Copyright 2013 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.

View File

@ -1,100 +0,0 @@
# Copyright 2013 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
import unittest
from mock import patch
from scapy import all as scapy
from dhcp_checker import api
dhcp_options = [("message-type", "offer"), "end"]
request = (
scapy.Ether(),
scapy.Ether(src="", dst="ff:ff:ff:ff:ff:ff") /
scapy.IP(src="0.0.0.0", dst="255.255.255.255") /
scapy.UDP(sport=68, dport=67) /
scapy.BOOTP(chaddr="") /
scapy.DHCP(options=dhcp_options)
)
expected_response = {
'dport': 67,
'gateway': '172.18.194.2',
'iface': 'eth1',
'mac': '00:15:17:ee:0a:a8',
'message': 'offer',
'server_id': '172.18.208.44',
'server_ip': '172.18.194.2',
'yiaddr': '172.18.194.35'
}
class TestDhcpApi(unittest.TestCase):
def setUp(self):
directory_path = os.path.dirname(__file__)
self.scapy_data = list(scapy.rdpcap(os.path.join(directory_path,
'dhcp.pcap')))
self.dhcp_response = self.scapy_data[1:]
@patch('dhcp_checker.api.scapy.srp')
@patch('dhcp_checker.api.scapy.get_if_raw_hwaddr')
def test_check_dhcp_on_eth(self, raw_hwaddr, srp_mock):
raw_hwaddr.return_value = ('111', '222')
srp_mock.return_value = ([self.dhcp_response], [])
response = api.check_dhcp_on_eth('eth1', timeout=5)
self.assertEqual([expected_response], response)
@patch('dhcp_checker.api.scapy.srp')
@patch('dhcp_checker.api.scapy.get_if_raw_hwaddr')
def test_check_dhcp_on_eth_empty_response(self, raw_hwaddr, srp_mock):
raw_hwaddr.return_value = ('111', '222')
srp_mock.return_value = ([], [])
response = api.check_dhcp_on_eth('eth1', timeout=5)
self.assertEqual([], response)
@patch('dhcp_checker.api.send_dhcp_discover')
@patch('dhcp_checker.api.make_listeners')
def test_check_dhcp_with_multiple_ifaces(
self, make_listeners, send_discover):
api.check_dhcp(['eth1', 'eth2'], repeat=1)
make_listeners.assert_called_once_with(('eth2', 'eth1'))
self.assertEqual(send_discover.call_count, 2)
@patch('dhcp_checker.api.send_dhcp_discover')
@patch('dhcp_checker.api.make_listeners')
def test_check_dhcp_with_vlans(self, make_listeners, send_discover):
config_sample = {
'eth0': (100, 101),
'eth1': (100, 102)
}
api.check_dhcp_with_vlans(config_sample, timeout=1)
make_listeners.assert_called_once_with(('eth1', 'eth0'))
self.assertEqual(send_discover.call_count, 6)
@patch('dhcp_checker.api.time.sleep')
@patch('dhcp_checker.api.send_dhcp_discover')
@patch('dhcp_checker.api.make_listeners')
def test_check_dhcp_with_vlans_repeat_2(self, make_listeners,
send_discover, sleep_mock):
config_sample = {
'eth0': (),
}
api.check_dhcp_with_vlans(config_sample, timeout=1, repeat=3)
self.assertEqual(sleep_mock.call_count, 3)
make_listeners.assert_called_once_with(('eth0',))
self.assertEqual(send_discover.call_count, 3)

View File

@ -1,61 +0,0 @@
# Copyright 2013 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 json
from mock import patch
import unittest
from dhcp_checker import cli
expected_response = {
'dport': 67,
'gateway': '172.18.194.2',
'iface': 'eth1',
'mac': '00:15:17:ee:0a:a8',
'message': 'offer',
'server_id': '172.18.208.44',
'server_ip': '172.18.194.2',
'yiaddr': '172.18.194.35'
}
@patch('dhcp_checker.commands.api')
class TestCommandsInterface(unittest.TestCase):
def test_list_dhcp_servers(self, api):
api.check_dhcp.return_value = iter([expected_response])
command = cli.main(['discover', '--ifaces', 'eth0', 'eth1',
'--format', 'json'])
self.assertEqual(command, 0)
api.check_dhcp.assert_called_once_with(['eth0', 'eth1'],
repeat=2, timeout=5)
def test_list_dhcp_assignment(self, api):
api.check_dhcp_request.return_value = iter([expected_response])
command = cli.main(['request', 'eth1', '10.20.0.2',
'--range_start', '10.20.0.10',
'--range_end', '10.20.0.20'])
self.assertEqual(command, 0)
api.check_dhcp_request.assert_called_once_with(
'eth1', '10.20.0.2', '10.20.0.10', '10.20.0.20', timeout=5
)
def test_list_dhcp_vlans_info(self, api):
config_sample = {'eth1': ['100', '101'],
'eth2': range(103, 110)}
api.check_dhcp_with_vlans.return_value = iter([expected_response])
command = cli.main(['vlans', json.dumps(config_sample)])
self.assertEqual(command, 0)
api.check_dhcp_with_vlans.assert_called_once_with(
config_sample, repeat=2, timeout=5)

View File

@ -1,176 +0,0 @@
# Copyright 2013 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.
from mock import call
from mock import patch
import os
import unittest
from scapy import all as scapy
from dhcp_checker import utils
IP_LINK_SHOW_UP = (
"2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP>"
"mtu 1500 qdisc pfifo_fast state UP qlen 1000"
"link/ether 08:60:6e:6f:70:09 brd ff:ff:ff:ff:ff:ff"
)
IP_LINK_SHOW_DOES_NOT_EXIST = 'Device "eth2" does not exist.'
expected_response = {
'dport': 67,
'gateway': '172.18.194.2',
'iface': 'eth1',
'mac': '00:15:17:ee:0a:a8',
'message': 'offer',
'server_id': '172.18.208.44',
'server_ip': '172.18.194.2',
'yiaddr': '172.18.194.35'
}
class TestDhcpUtils(unittest.TestCase):
def test_command_utils_helper(self):
command = utils.command_util('echo', 'hello')
self.assertEqual(command.stdout.read(), 'hello\n')
@patch('dhcp_checker.utils.command_util')
def test_check_iface_state_up(self, command_util):
command_util().stdout.read.return_value = IP_LINK_SHOW_UP
self.assertEqual(utils._iface_state('eth0'), 'UP')
@patch('dhcp_checker.utils.command_util')
def test_check_network_up(self, command_util):
command_util().stdout.read.return_value = IP_LINK_SHOW_UP
self.assertTrue(utils.check_network_up('eth0'))
@patch('dhcp_checker.utils.command_util')
def test_check_iface_doesnot_exist(self, command_util):
command_util().stderr.read.return_value = IP_LINK_SHOW_DOES_NOT_EXIST
self.assertFalse(utils.check_iface_exist('eth2'))
@patch('dhcp_checker.utils.command_util')
def test_check_iface_exist(self, command_util):
command_util().stderr.read.return_value = ''
self.assertTrue(utils.check_iface_exist('eth0'))
def test_filter_duplicated_results(self):
test_data = [{'first': 'value'}, {'first': 'value'}]
@utils.filter_duplicated_results
def test_func(values):
return values
self.assertEqual(list(test_func(test_data)), [{'first': 'value'}])
def test_filter_duplicated_results_diff(self):
test_data = [{'first': 'value'}, {'second': 'value'}]
@utils.filter_duplicated_results
def test_func(values):
return values
self.assertEqual(list(test_func(test_data)), test_data)
class TestDhcpFormat(unittest.TestCase):
def setUp(self):
directory_path = os.path.dirname(__file__)
self.scapy_data = list(scapy.rdpcap(os.path.join(directory_path,
'dhcp.pcap')))
self.dhcp_response = self.scapy_data[1:]
def test_single_format_decorator(self):
"""Test modifying scapy response objects
Test verifies that single_format decorator contains logic to modify
scapy response object into dict with predefined fields
"""
@utils.single_format
def tobe_decorated(iface, timeout=5):
return [self.dhcp_response]
self.assertEqual(tobe_decorated('eth1'), [expected_response])
def test_order_preserver(self):
example = {'first': 'first', 'second': 'second'}
columns = ['second', 'first']
items = utils.get_item_properties(example, columns)
self.assertEqual(columns, items)
class TestMultiprocMap(unittest.TestCase):
"""Check if multiproc_map decorator works the same for different arg types
Test verifies that working with function decorated by multiproc_map
will work indifirently either args passed as tuple, or *args
"""
def test_multiproc_map_first_tuple(self):
@utils.multiproc_map
def test_multiproc(*args, **kwargs):
return args, kwargs
rargs, rkwargs = test_multiproc(('h', 'e'))
self.assertEqual(rargs, ('h', 'e'))
self.assertEqual(rkwargs, {})
def test_multiproc_mapn_normal(self):
@utils.multiproc_map
def test_multiproc(*args, **kwargs):
return args, kwargs
rargs, rkwargs = test_multiproc('h', 'e')
self.assertEqual(rargs, ('h', 'e'))
self.assertEqual(rkwargs, {})
@patch('dhcp_checker.utils._iface_state')
@patch('dhcp_checker.utils.command_util')
class TestIfaceStateHelper(unittest.TestCase):
def test_iface_is_up(self, command, iface_state):
iface_value = iter(('UP',) * 3)
iface_state.side_effect = lambda *args, **kwargs: next(iface_value)
with utils.IfaceState('eth1') as iface:
self.assertEqual(iface, 'eth1')
self.assertEqual(iface_state.call_count, 2)
self.assertEqual(command.call_count, 0)
def test_iface_is_down(self, command, iface_state):
iface_value = iter(('DOWN', 'UP', 'DOWN'))
iface_state.side_effect = lambda *args, **kwargs: next(iface_value)
with utils.IfaceState('eth1') as iface:
self.assertEqual(iface, 'eth1')
self.assertEqual(iface_state.call_count, 3)
self.assertEqual(command.call_count, 2)
self.assertEqual(command.call_args_list,
[call('ifconfig', 'eth1', 'up'),
call('ifconfig', 'eth1', 'down')])
def test_iface_cant_ifup(self, command, iface_state):
iface_value = iter(('DOWN',) * 10)
iface_state.side_effect = lambda *args, **kwargs: next(iface_value)
def test_raises():
with utils.IfaceState('eth1', retry=4) as iface:
self.assertEqual(iface, 'eth1')
self.assertRaises(EnvironmentError, test_raises)
self.assertEqual(command.call_count, 4)

View File

@ -1,236 +0,0 @@
# Copyright 2013 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 functools
import re
import subprocess
import sys
from scapy import all as scapy
DHCP_OFFER_COLUMNS = ('iface', 'mac', 'server_ip', 'server_id', 'gateway',
'dport', 'message', 'yiaddr')
def command_util(*command):
"""object with stderr and stdout"""
return subprocess.Popen(command, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
def _check_vconfig():
"""Check vconfig installed or not"""
return not command_util('which', 'vconfig').stderr.read()
def _iface_state(iface):
"""For a given iface return it's state
returns UP, DOWN, UNKNOWN
"""
state = command_util('ip', 'link', 'show', iface).stdout.read()
search_result = re.search(r'.*<(?P<state>.*)>.*', state)
if search_result:
state_list = search_result.groupdict().get('state', [])
if 'UP' in state_list:
return 'UP'
else:
return 'DOWN'
return 'UNKNOWN'
def check_network_up(iface):
return _iface_state(iface) == 'UP'
def check_iface_exist(iface):
"""Check provided interface exists"""
return not command_util("ip", "link", "show", iface).stderr.read()
def filtered_ifaces(ifaces):
for iface in ifaces:
if not check_iface_exist(iface):
sys.stderr.write('Iface {0} does not exist.'.format(iface))
else:
if not check_network_up(iface):
sys.stderr.write('Network for iface {0} is down.'.format(
iface))
else:
yield iface
def pick_ip(range_start, range_end):
"""Given start_range, end_range generate list of ips
>>> next(pick_ip('192.168.1.10','192.168.1.13'))
'192.168.1.10'
"""
split_address = lambda ip_address: \
[int(item) for item in ip_address.split('.')]
range_start = split_address(range_start)
range_end = split_address(range_end)
i = 0
# ipv4 subnet cant be longer that 4 items
while i < 4:
# 255 - end of subnet
if not range_start[i] == range_end[i] and range_start[i] < 255:
yield '.'.join([str(item) for item in range_start])
range_start[i] += 1
else:
i += 1
def get_item_properties(item, columns):
"""Get specified in columns properties, with preserved order.
Required for correct cli table generation
:param item: dict
:param columns: list with arbitrary keys
"""
properties = []
for key in columns:
properties.append(item.get(key, ''))
return properties
def format_options(options):
"""Util for serializing dhcp options
@options = [1,2,3]
>>> format_options([1, 2, 3])
'\x01\x02\x03'
"""
return "".join((chr(item) for item in options))
def _dhcp_options(dhcp_options):
"""Dhcp options returned by scapy is not in usable format
[('message-type', 2), ('server_id', '192.168.0.5'),
('name_server', '192.168.0.1', '192.168.0.2'), 'end']
"""
for option in dhcp_options:
if isinstance(option, (tuple, list)):
header = option[0]
if len(option[1:]) > 1:
yield (header, option)
else:
yield (header, option[1])
def format_answer(ans, iface):
dhcp_options = dict(_dhcp_options(ans[scapy.DHCP].options))
results = (
iface, ans[scapy.Ether].src, ans[scapy.IP].src,
dhcp_options['server_id'], ans[scapy.BOOTP].giaddr,
ans[scapy.UDP].sport,
scapy.DHCPTypes[dhcp_options['message-type']],
ans[scapy.BOOTP].yiaddr)
return dict(zip(DHCP_OFFER_COLUMNS, results))
def single_format(func):
"""Manage format of dhcp response"""
@functools.wraps(func)
def formatter(*args, **kwargs):
iface = args[0]
ans = func(*args, **kwargs)
# scapy stores all sequence of requests
# so ans[0][1] would be response to first request
return [format_answer(response[1], iface) for response in ans]
return formatter
def multiproc_map(func):
# multiproc map could not work with format *args
@functools.wraps(func)
def workaround(*args, **kwargs):
args = args[0] if isinstance(args[0], (tuple, list)) else args
return func(*args, **kwargs)
return workaround
def filter_duplicated_results(func):
# due to network infra on broadcast multiple duplicated results
# returned. This helper filter them out
@functools.wraps(func)
def wrapper(*args, **kwargs):
resp = func(*args, **kwargs)
return (dict(t) for t in set([tuple(d.items()) for d in resp]))
return wrapper
class VlansContext(object):
"""Contains all logic to manage vlans"""
def __init__(self, config):
"""Initialize VlansContext
@config - list or tuple of (iface, vlan) pairs
"""
self.config = config
def __enter__(self):
for iface, vlans in self.config.iteritems():
vifaces = []
for vlan in vlans:
if vlan > 0:
vifaces.append('{0}.{1}'.format(iface, vlan))
yield str(iface), vifaces
def __exit__(self, type, value, trace):
pass
class IfaceState(object):
"""Context manager to control state of iface while dhcp checker runs"""
def __init__(self, iface, rollback=True, retry=3):
self.rollback = rollback
self.retry = retry
self.iface = iface
self.pre_iface_state = _iface_state(iface)
self.iface_state = self.pre_iface_state
self.post_iface_state = ''
def iface_up(self):
while self.retry and self.iface_state != 'UP':
command_util('ifconfig', self.iface, 'up')
self.iface_state = _iface_state(self.iface)
self.retry -= 1
if self.iface_state != 'UP':
raise EnvironmentError(
'Tried my best to ifup iface {0}.'.format(self.iface))
def __enter__(self):
self.iface_up()
return self.iface
def __exit__(self, exc_type, exc_val, exc_tb):
if self.pre_iface_state != 'UP' and self.rollback:
command_util('ifconfig', self.iface, 'down')
self.post_iface_state = _iface_state(self.iface)
def create_mac_filter(iface):
"""tcpdump can not catch all 6 octets so it is splitted
See http://blog.jasonantman.com/2010/04/dhcp-debugging-and-handy-tcpdump-filters # noqa
"""
mac = scapy.get_if_hwaddr(iface).split(':')
filter1 = '(udp[36:2] = 0x{0})'.format(''.join(mac[:2]))
filter2 = '(udp[38:4] = 0x{0})'.format(''.join(mac[2:]))
return '{0} and {1}'.format(filter1, filter2)

View File

@ -1,43 +0,0 @@
# Copyright 2015 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 logging
from logging import handlers
from cliff.app import App
class BaseApp(App):
DEFAULT_VERBOSE_LEVEL = 0
LOG_FILENAME = '' # This needs to be redefined in child class
def configure_logging(self):
super(BaseApp, self).configure_logging()
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
formatter = logging.Formatter(
'%(asctime)s %(levelname)s (%(module)s) %(message)s',
"%Y-%m-%d %H:%M:%S")
stream_handler = logging.StreamHandler()
stream_handler.setLevel(logging.ERROR)
stream_handler.setFormatter(formatter)
file_handler = handlers.TimedRotatingFileHandler(
self.LOG_FILENAME)
file_handler.setLevel(logging.DEBUG)
file_handler.setFormatter(formatter)
logger.addHandler(stream_handler)
logger.addHandler(file_handler)

View File

@ -1,62 +0,0 @@
# 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.
from stevedore import driver
from network_checker import config
from network_checker import daemon
from network_checker import xmlrpc
class Api(object):
namespace = 'network_checker'
def __init__(self, verification, **kwargs):
self.verification = verification
self.server_config = config.get_config()[verification]
self.verification_config = dict(self.server_config['defaults'],
**kwargs)
def serve(self):
daemon.cleanup(self.server_config)
self.manager = driver.DriverManager(
self.namespace,
self.verification,
invoke_on_load=True,
invoke_kwds=self.verification_config)
self.driver = self.manager.driver
rpc_server = xmlrpc.get_server(self.server_config)
# TODO(dshulyak) verification api should know what methods to serve
rpc_server.register_function(self.driver.listen, 'listen')
rpc_server.register_function(self.driver.send, 'send')
rpc_server.register_function(self.driver.get_info, 'get_info')
rpc_server.register_function(self.driver.test, 'test')
return daemon.run_server(rpc_server, self.server_config)
def listen(self):
return xmlrpc.get_client(self.server_config).listen()
def send(self):
return xmlrpc.get_client(self.server_config).send()
def info(self):
return xmlrpc.get_client(self.server_config).get_info()
def clean(self):
return daemon.cleanup(self.server_config)
def test(self):
return xmlrpc.get_client(self.server_config).test()

View File

@ -1,62 +0,0 @@
# 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.
"""Fuel network checker
Available verifications: multicast
Example:
fuel-netcheck multicast serve listen send info clean
Multicast config options:
group: 225.0.0.250
port: 13310
ttl: 5
uid: '1001'
repeat: 3
iface: eth0
timeout: 10
"""
import argparse
import json
import textwrap
from network_checker import api
def parse_args():
parser = argparse.ArgumentParser(
description=textwrap.dedent(__doc__),
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument(
'verification',
help="Type of verification that should be started.")
parser.add_argument(
'actions', nargs='+',
help="List of actions to perform.")
# TODO(dshulyak) Add posibility to provide args like regular shell args
parser.add_argument(
'-c', '--config', default='{}',
help="User defined configuration in json format.")
return parser.parse_args()
def main():
args = parse_args()
user_data = json.loads(args.config)
api_instance = api.Api(args.verification, **user_data)
# Print only last response if user requires multiple sequantial actions
for action in args.actions:
result = getattr(api_instance, action)()
print(json.dumps(result))

View File

@ -1,30 +0,0 @@
# 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
import yaml
def _get_config_path():
if os.path.exists('/etc/network_checker/config.yaml'):
return '/etc/network_checker/config.yaml'
return os.path.join(
os.path.dirname(os.path.realpath(__file__)),
'config.yaml')
def get_config():
with open(_get_config_path()) as conf:
return yaml.load(conf.read())

View File

@ -1,13 +0,0 @@
---
multicast:
app: multicast
pidfile: /var/run/multicast.pid
unix: /var/run/multicast.sock
defaults:
group: 225.0.0.250
port: 13310
ttl: 5
uid: '1001'
repeat: 3
iface: eth0
timeout: 10

View File

@ -1,51 +0,0 @@
# 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 logging
import os
import daemonize
LOG = logging.getLogger(__name__)
def run_server(server, config):
daemon = daemonize.Daemonize(
app=config['app'],
pid=config['pidfile'],
action=server.serve_forever,
# keep open stdin, stdout, stderr and socket file
keep_fds=[0, 1, 2, server.fileno()])
try:
daemon.start()
# this is required to do some stuff after server is daemonized
except SystemExit as e:
if e.code is 0:
return True
raise
def cleanup(config):
if os.path.exists(config['unix']):
os.unlink(config['unix'])
if os.path.exists(config['pidfile']):
with open(config['pidfile'], 'r') as f:
pid = f.read().strip('\n')
try:
os.kill(int(pid), 9)
except OSError:
# it is ok if proc already stopped
pass
os.unlink(config['pidfile'])
return True

View File

@ -1,93 +0,0 @@
# 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 logging
import socket
import struct
import pcap
import scapy.all as scapy
LOG = logging.getLogger(__name__)
# it is not defined in every python version
SO_BINDTODEVICE = 25
class MulticastChecker(object):
def __init__(self, group='225.0.0.250', port='13100',
uid='999', iface='eth0',
ttl=1, repeat=1, timeout=3):
self.group = group
self.port = int(port)
self.ttl = ttl
self.uid = uid
self.repeat = repeat
self.timeout = timeout
self.receiver = None
self.messages = []
self.iface = iface
def send(self):
ttl_data = struct.pack('@i', self.ttl)
_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
_socket.setsockopt(socket.IPPROTO_IP, socket.IP_MULTICAST_TTL,
ttl_data)
_socket.setsockopt(
socket.SOL_SOCKET,
SO_BINDTODEVICE, self.iface)
for _ in xrange(self.repeat):
_socket.sendto(self.uid, (self.group, self.port))
return {'group': self.group,
'port': self.port,
'iface': self.iface,
'uid': self.uid}
def listen(self):
self._register_group()
self._start_listeners()
return {'group': self.group,
'port': self.port,
'iface': self.iface,
'uid': self.uid}
def _register_group(self):
self.receiver = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.receiver.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.receiver.setsockopt(
socket.SOL_SOCKET,
SO_BINDTODEVICE, self.iface)
self.receiver.bind(('', self.port))
group_packed = socket.inet_aton(self.group)
group_data = struct.pack('4sL', group_packed, socket.INADDR_ANY)
self.receiver.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP,
group_data)
def _start_listeners(self):
self.listener = pcap.pcap(self.iface)
udp_filter = 'udp and dst port {0}'.format(self.port)
self.listener.setfilter(udp_filter)
def get_info(self):
for sock, pack in self.listener.readpkts():
pack = scapy.Ether(pack)
data, _ = pack[scapy.UDP].extract_padding(pack[scapy.UDP].load)
self.messages.append(data.decode())
self.receiver.close()
return list(set(self.messages))
def test(self):
return {'test': 'test'}

View File

@ -1,13 +0,0 @@
# 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.

View File

@ -1,706 +0,0 @@
#!/usr/bin/env python
# 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.
#
# Generate and send Ethernet packets to specified interfaces.
# Collect data from interfaces.
# Analyse dumps for packets with special cookie in UDP payload.
#
import argparse
import json
import logging
import os
import re
import shutil
import signal
import socket
import subprocess
import sys
import time
import traceback
import logging.handlers
from scapy import config as scapy_config
scapy_config.logLevel = 40
scapy_config.use_pcap = True
import scapy.all as scapy
from scapy import error
from scapy.utils import PcapReader
from network_checker.net_check.utils import signal_timeout
class ActorFabric(object):
@classmethod
def getInstance(cls, config):
if config.get('action') not in ('listen', 'generate'):
raise Exception(
'Wrong config, you need define '
'valid action instead of {0}'.format(config.get('action')))
if config['action'] in ('listen',):
return Listener(config)
elif config['action'] in ('generate',):
return Sender(config)
class ActorException(Exception):
def __init__(self, logger, message='', level='error'):
getattr(logger, level, logger.error)(message)
super(ActorException, self).__init__(message)
class Actor(object):
def __init__(self, config=None):
self.config = {
'src_mac': None,
'src': '198.18.1.1',
'dst': '198.18.1.2',
'sport': 31337,
'dport': 31337,
'cookie': "Nailgun:",
'pcap_dir': "/var/run/pcap_dir/",
'duration': 5,
'repeat': 1,
'collect_timeout': 300,
}
if config:
self.config.update(config)
self.logger.debug("Running with config: %s", json.dumps(self.config))
self.iface_down_after = {}
self.viface_remove_after = {}
def _define_logger(self, filename=None,
appname='netprobe', level=logging.DEBUG):
logger = logging.getLogger(appname)
logger.setLevel(level)
syslog_formatter = logging.Formatter(
'{appname}: %(message)s'.format(appname=appname)
)
syslog_handler = logging.handlers.SysLogHandler('/dev/log')
syslog_handler.setFormatter(syslog_formatter)
logger.addHandler(syslog_handler)
# A syslog handler should be always. But a file handler is the option.
# If you don't want it you can keep 'filename' variable as None to skip
# this handler.
if filename:
file_formatter = logging.Formatter(
'%(asctime)s %(levelname)s %(name)s %(message)s'
)
file_handler = logging.FileHandler(filename)
file_handler.setFormatter(file_formatter)
logger.addHandler(file_handler)
return logger
def _execute(self, command, expected_exit_codes=(0,)):
self.logger.debug("Running command: %s" % " ".join(command))
env = os.environ
env["PATH"] = "/bin:/usr/bin:/sbin:/usr/sbin"
p = subprocess.Popen(command, shell=False,
env=env, stdout=subprocess.PIPE)
output, _ = p.communicate()
if p.returncode not in expected_exit_codes:
raise ActorException(
self.logger,
"Command exited with error: %s: %s" % (" ".join(command),
p.returncode)
)
return output.split('\n')
def _viface_by_iface_vid(self, iface, vid):
return (self._try_viface_create(iface, vid) or "%s.%d" % (iface, vid))
def _iface_name(self, iface, vid=None):
if vid:
return self._viface_by_iface_vid(iface, vid)
return iface
def _look_for_link(self, iface, vid=None):
viface = None
if vid:
viface = self._viface_by_iface_vid(iface, vid)
command = ['ip', 'link']
r = re.compile(ur"(\d+?):\s+((?P<viface>[^:@]+)@)?(?P<iface>[^:]+?):"
".+?(?P<state>UP|DOWN|UNKNOWN).*$")
for line in self._execute(command):
m = r.search(line)
if m:
md = m.groupdict()
if (iface == md.get('iface') and
viface == md.get('viface') and md.get('state')):
return (iface, viface, md.get('state'))
# If we are here we aren't able to say if iface with vid is up
raise ActorException(
self.logger,
"Cannot find interface %s with vid=%s" % (iface, vid)
)
def _try_iface_up(self, iface, vid=None):
if vid and not self._try_viface_create(iface, vid):
# if viface does not exist we raise exception
raise ActorException(
self.logger,
"Vlan %s on interface %s does not exist" % (str(vid), iface)
)
self.logger.debug("Checking if interface %s with vid %s is up",
iface, str(vid))
_, _, state = self._look_for_link(iface, vid)
return (state == 'UP')
def _iface_up(self, iface, vid=None):
"""Brings interface with vid up"""
if vid and not self._try_viface_create(iface, vid):
# if viface does not exist we raise exception
raise ActorException(
self.logger,
"Vlan %s on interface %s does not exist" % (str(vid), iface)
)
set_iface = self._iface_name(iface, vid)
self.logger.debug("Brining interface %s with vid %s up",
set_iface, str(vid))
self._execute([
"ip",
"link", "set",
"dev", set_iface,
"up"])
def _ensure_iface_up(self, iface, vid=None):
"""Ensures interface is with vid up."""
if not self._try_iface_up(iface, vid):
# if iface is not up we try to bring it up
self._iface_up(iface, vid)
if self._try_iface_up(iface, vid):
# if iface was down and we have brought it up
# we should mark it to be brought down after probing
self.iface_down_after[self._iface_name(iface, vid)] = True
else:
# if viface is still down we raise exception
raise ActorException(
self.logger,
"Can not bring interface %s with vid %s up" % (iface,
str(vid))
)
def _ensure_iface_down(self, iface, vid=None):
set_iface = self._iface_name(iface, vid)
if self.iface_down_after.get(set_iface, False):
# if iface with vid have been marked to be brought down
# after probing we try to bring it down
self.logger.debug("Brining down interface %s with vid %s",
iface, str(vid))
self._execute([
"ip",
"link", "set",
"dev", set_iface,
"down"])
self.iface_down_after.pop(set_iface)
def _try_viface_create(self, iface, vid):
"""Tries to find vlan interface on iface with VLAN_ID=vid and return it
:returns: name of vlan interface if it exists or None
"""
self.logger.debug("Checking if vlan %s on interface %s exists",
str(vid), iface)
with open("/proc/net/vlan/config", "r") as f:
for line in f:
m = re.search(ur'(.+?)\s+\|\s+(.+?)\s+\|\s+(.+?)\s*$', line)
if m and m.group(2) == str(vid) and m.group(3) == iface:
return m.group(1)
def _viface_create(self, iface, vid):
"""Creates VLAN interface with VLAN_ID=vid on interface iface
:returns: None
"""
self.logger.debug("Creating vlan %s on interface %s", str(vid), iface)
self._execute([
"ip",
"link", "add",
"link", iface,
"name", self._viface_by_iface_vid(iface, vid),
"type", "vlan",
"id", str(vid)])
def _ensure_viface_create(self, iface, vid):
"""Ensures that vlan interface exists.
If it does not already
exist, then we need it to be created. It also marks newly created
vlan interface to remove it after probing procedure.
"""
if not self._try_viface_create(iface, vid):
# if viface does not exist we try to create it
self._viface_create(iface, vid)
if self._try_viface_create(iface, vid):
# if viface had not existed and have been created
# we mark it to be removed after probing procedure
self.viface_remove_after[
self._viface_by_iface_vid(iface, vid)
] = True
else:
# if viface had not existed and still does not
# we raise exception
raise ActorException(
self.logger,
"Can not create vlan %d on interface %s" % (vid, iface)
)
def _ensure_viface_remove(self, iface, vid):
viface = self._viface_by_iface_vid(iface, vid)
if self.viface_remove_after.get(viface, False):
# if viface have been marked to be removed after probing
# we try to remove it
self.logger.debug("Removing vlan %s on interface %s",
str(vid), iface)
self._execute([
"ip",
"link", "del",
"dev", viface])
self.viface_remove_after.pop(viface)
def _parse_vlan_list(self, vlan_string):
self.logger.debug("Parsing vlan list: %s", vlan_string)
validate = lambda x: (x >= 0) and (x < 4095)
chunks = vlan_string.split(",")
vlan_list = []
for chunk in chunks:
delim = chunk.find("-")
try:
if delim > 0:
left = int(chunk[:delim])
right = int(chunk[delim + 1:])
if validate(left) and validate(right):
vlan_list.extend(xrange(left, right + 1))
else:
raise ValueError
else:
vlan = int(chunk)
if validate(vlan):
vlan_list.append(vlan)
else:
raise ValueError
except ValueError:
raise ActorException(self.logger, "Incorrect vlan: %s" % chunk)
self.logger.debug("Parsed vlans: %s", str(vlan_list))
return vlan_list
def _ensure_viface_create_and_up(self, iface, vid):
self._ensure_viface_create(iface, vid)
self._ensure_iface_up(iface, vid)
def _ensure_viface_down_and_remove(self, iface, vid):
self._ensure_iface_down(iface, vid)
self._ensure_viface_remove(iface, vid)
def _iface_vlan_iterator(self):
for iface, vlan_list in self.config['interfaces'].iteritems():
# Variables iface and vlan_list are getted from decoded JSON
# and json.dump convert all string data to Python unicode string.
# We use these variables in logging messages later.
# CentOS 6.4 uses Python 2.6 and logging module 0.5.0.5 which has
# a bug with converting unicode strings to message in
# SysLogHandler. So we need to convert all unicode to plain
# strings to avoid syslog message corruption.
for vlan in self._parse_vlan_list(str(vlan_list)):
yield (str(iface), vlan)
def _iface_iterator(self):
for iface in self.config['interfaces']:
yield iface
def _log_ifaces(self, prefix="Current interfaces"):
self.logger.debug("%s: ", prefix)
for line in self._execute(['ip', 'address']):
self.logger.debug(line.rstrip())
class Sender(Actor):
def __init__(self, config=None):
self.logger = self._define_logger('/var/log/netprobe_sender.log',
'netprobe_sender')
super(Sender, self).__init__(config)
self.logger.info("=== Starting Sender ===")
self._log_ifaces("Interfaces just before sending probing packages")
def run(self):
try:
self._run()
except Exception as e:
self.logger.error("An internal error occured: %s\n%s", str(e),
traceback.format_exc())
def _get_iface_mac(self, iface):
path = '/sys/class/net/{iface}/address'.format(iface=iface)
with open(path, 'r') as address:
return address.read().strip('\n')
def _run(self):
for iface, vlan in self._iface_vlan_iterator():
self._ensure_iface_up(iface)
self._send_packets()
self._log_ifaces("Interfaces just after sending probing packages")
for iface in self._iface_iterator():
self._ensure_iface_down(iface)
self._log_ifaces("Interfaces just after ensuring them down in sender")
self.logger.info("=== Sender Finished ===")
def _send_packets(self):
start_time = time.time()
while time.time() - start_time <= self.config['duration']:
for iface, vlan in self._iface_vlan_iterator():
self.logger.debug("Sending packets: iface=%s vlan=%s",
iface, str(vlan))
for _ in xrange(self.config['repeat']):
self._sendp(iface, vlan)
def _sendp(self, iface, vlan):
try:
data = str(''.join((self.config['cookie'], iface, ' ',
self.config['uid'])))
p = scapy.Ether(src=self._get_iface_mac(iface),
dst="ff:ff:ff:ff:ff:ff")
if vlan > 0:
p = p / scapy.Dot1Q(vlan=vlan)
p = p / scapy.IP(src=self.config['src'], dst=self.config['dst'])
p = p / scapy.UDP(sport=self.config['sport'],
dport=self.config['dport']) / data
scapy.sendp(p, iface=iface)
except socket.error as e:
self.logger.error("Socket error: %s, %s", e, iface)
class Listener(Actor):
def __init__(self, config=None):
self.logger = self._define_logger('/var/log/netprobe_listener.log',
'netprobe_listener')
super(Listener, self).__init__(config)
self.logger.info("=== Starting Listener ===")
self._log_ifaces("Interfaces just before starting listerning "
"for probing packages")
self.pidfile = self.addpid('/var/run/net_probe')
self.neighbours = {}
self._define_pcap_dir()
def addpid(self, piddir):
pid = os.getpid()
if not os.path.exists(piddir):
os.mkdir(piddir)
pidfile = os.path.join(piddir, str(pid))
with open(pidfile, 'w') as fo:
fo.write('')
return pidfile
def _define_pcap_dir(self):
if os.path.exists(self.config['pcap_dir']):
shutil.rmtree(self.config['pcap_dir'])
os.mkdir(self.config['pcap_dir'])
def run(self):
try:
self._run()
except Exception as e:
self.logger.error("An internal error occured: %s\n%s", str(e),
traceback.format_exc())
def _run(self):
sniffers = set()
listeners = []
for iface in self._iface_iterator():
self._ensure_iface_up(iface)
if iface not in sniffers:
listeners.append(self.get_probe_frames(iface))
listeners.append(self.get_probe_frames(iface, vlan=True))
sniffers.add(iface)
try:
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((self.config.get('ready_address', 'locahost'),
self.config.get('ready_port', 31338)))
except socket.error as e:
self.logger.error("Socket error: %s", e)
else:
self.logger.debug("Listener threads have been launched. "
"Reporting READY.")
msg = "READY"
total_sent = 0
while total_sent < len(msg):
sent = s.send(msg[total_sent:])
if sent == 0:
raise ActorException(
self.logger,
"Socket broken. Cannot send %s status." % msg
)
total_sent += sent
s.shutdown(socket.SHUT_RDWR)
s.close()
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
self.logger.debug("Interruption signal catched")
with signal_timeout(self.config['collect_timeout'], raise_exc=False):
for listener in listeners:
# terminate and flush pipes
listener.terminate()
out, err = listener.communicate()
if err and listener.returncode:
self.logger.error('Listerner: %s', err)
elif err:
self.logger.warning('Listener: %s', err)
self.logger.debug('Start reading dumped information.')
self.read_packets()
self._log_ifaces("Interfaces just before ensuring interfaces down")
for iface in self._iface_iterator():
self._ensure_iface_down(iface)
self._log_ifaces(
"Interfaces just after ensuring them down in listener")
with open(self.config['dump_file'], 'w') as fo:
fo.write(json.dumps(self.neighbours))
os.unlink(self.pidfile)
self.logger.info("=== Listener Finished ===")
def read_packets(self):
for iface in self._iface_iterator():
filenames = ['{0}.pcap'.format(iface),
'vlan_{0}.pcap'.format(iface)]
for filename in filenames:
self.read_pcap_file(iface, filename)
def read_pcap_file(self, iface, filename):
try:
pcap_file = os.path.join(self.config['pcap_dir'], filename)
for pkt in PcapReader(pcap_file):
self.fprn(pkt, iface)
except (IOError, error.Scapy_Exception) as exc:
self.logger.exception(
'Cant read pcap file %s, err: %s', pcap_file, str(exc))
def fprn(self, p, iface):
if scapy.Dot1Q in p:
vlan = p[scapy.Dot1Q].vlan
else:
vlan = 0
self.logger.debug("Catched packet: vlan=%s len=%s payload=%s",
str(vlan), p[scapy.UDP].len, p[scapy.UDP].payload)
received_msg, _ = p[scapy.UDP].extract_padding(p[scapy.UDP].load)
decoded_msg = received_msg.decode()
riface, uid = decoded_msg[len(self.config["cookie"]):].split(' ', 1)
self.neighbours[iface].setdefault(vlan, {})
if riface not in self.neighbours[iface][vlan].setdefault(uid, []):
self.neighbours[iface][vlan][uid].append(riface)
def get_probe_frames(self, iface, vlan=False):
if iface not in self.neighbours:
self.neighbours[iface] = {}
filter_string = 'udp and dst port {0}'.format(self.config['dport'])
filename = '{0}.pcap'.format(iface)
if vlan:
filter_string = '{0} {1}'.format('vlan and', filter_string)
filename = '{0}_{1}'.format('vlan', filename)
pcap_file = os.path.join(self.config['pcap_dir'], filename)
return subprocess.Popen(
['tcpdump', '-i', iface, '-w', pcap_file, filter_string],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
# -------------- main ---------------
def define_parser():
config_examples = """
Config file examples:
Capture frames config file example is:
{"action": "listen", "interfaces": {"eth0": "1-4094"},
"dump_file": "/var/tmp/net-probe-dump-eth0"}
Simple frame generation config file example is:
{"action": "generate", "uid": "aaa-bb-cccccc",
"interfaces": { "eth0": "1-4094"}}
Full frame generation config file example is:
{ "action": "generate",
"uid": "aaa-bb-cccccc", "cookie": "Some cookie",
"src_mac": "11:22:33:44:55:66",
"src": "10.0.0.1", "dst": "10.255.255.255",
"sport": 4056, "dport": 4057,
"interfaces": {
"eth0": "10, 15, 20, 201-210, 301-310, 1000-2000",
"eth1": "1-4094"
}
}
"""
parser = argparse.ArgumentParser(epilog=config_examples)
parser.add_argument(
'-c', '--config', dest='config', action='store', type=str,
help='config file', default=None
)
return parser
def define_subparsers(parser):
subparsers = parser.add_subparsers(
dest="action", help='actions'
)
listen_parser = subparsers.add_parser(
'listen', help='listen for probe packets'
)
listen_parser.add_argument(
'-i', '--interface', dest='interface', action='store', type=str,
help='interface to listen on', required=True
)
listen_parser.add_argument(
'-v', '--vlans', dest='vlan_list', action='store', type=str,
help='vlan list to send tagged packets ("100,200-300")', required=True
)
listen_parser.add_argument(
'-k', '--cookie', dest='cookie', action='store', type=str,
help='cookie string to insert into probe packets payload',
default='Nailgun:'
)
listen_parser.add_argument(
'-o', '--file', dest='dump_file', action='store', type=str,
help='file to dump captured packets', default=None
)
listen_parser.add_argument(
'-a', '--address', dest='ready_address', action='store', type=str,
help='address to report listener ready state', default='localhost'
)
listen_parser.add_argument(
'-p', '--port', dest='ready_port', action='store', type=int,
help='port to report listener ready state', default=31338
)
generate_parser = subparsers.add_parser(
'generate', help='generate and send probe packets'
)
generate_parser.add_argument(
'-i', '--interface', dest='interface', action='store', type=str,
help='interface to send packets from', required=True
)
generate_parser.add_argument(
'-v', '--vlans', dest='vlan_list', action='store', type=str,
help='vlan list to send tagged packets ("100,200-300")', required=True
)
generate_parser.add_argument(
'-k', '--cookie', dest='cookie', action='store', type=str,
help='cookie string to insert into probe packets payload',
default='Nailgun:'
)
generate_parser.add_argument(
'-u', '--uid', dest='uid', action='store', type=str,
help='uid to insert into probe packets payload', default='1'
)
generate_parser.add_argument(
'-d', '--duration', dest='duration', type=int, default=5,
help='Amount of time to generate network packets. In seconds',
)
generate_parser.add_argument(
'-r', '--repeat', dest='repeat', type=int, default=1,
help='Amount of packets sended in one iteration.',
)
def term_handler(signum, sigframe):
sys.exit()
def main():
signal.signal(signal.SIGTERM, term_handler)
parser = define_parser()
params, other_params = parser.parse_known_args()
config = {}
if params.config:
# if config file is set then we discard all other
# command line parameters
try:
if params.config == '-':
fo = sys.stdin
else:
fo = open(params.config, 'r')
config = json.load(fo)
fo.close()
except IOError:
print("Can not read config file %s" % params.config)
exit(1)
except ValueError as e:
print("Can not parse config file: %s" % str(e))
exit(1)
else:
define_subparsers(parser)
params, other_params = parser.parse_known_args()
if params.action == 'listen':
config['action'] = 'listen'
config['interfaces'] = {}
config['interfaces'][params.interface] = params.vlan_list
config['cookie'] = params.cookie
config['ready_address'] = params.ready_address
config['ready_port'] = params.ready_port
if params.dump_file:
config['dump_file'] = params.dump_file
else:
config['dump_file'] = "/var/tmp/net-probe-dump-%s" %\
config['interface']
elif params.action == 'generate':
config['action'] = 'generate'
config['interfaces'] = {}
config['interfaces'][params.interface] = params.vlan_list
config['uid'] = params.uid
config['cookie'] = params.cookie
config['duration'] = params.duration
config['repeat'] = params.repeat
actor = ActorFabric.getInstance(config)
actor.run()
if __name__ == "__main__":
main()

View File

@ -1,53 +0,0 @@
# Copyright 2015 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.
from contextlib import contextmanager
from functools import partial
import logging
import signal
log = logging.getLogger(__name__)
class TimeoutException(KeyboardInterrupt):
"""Exception should be raised if timeout is exceeded."""
def timeout_handler(timeout, signum, frame):
raise TimeoutException("Timeout {0} seconds exceeded".format(timeout))
@contextmanager
def signal_timeout(timeout, raise_exc=True):
"""Timeout handling using signals
:param timeout: timeout in seconds, integer
:param raise_exc: bool to control suppressing of exception
"""
handler = partial(timeout_handler, timeout)
signal.signal(signal.SIGALRM, handler)
signal.alarm(timeout)
try:
yield
except TimeoutException as exc:
if raise_exc:
raise
else:
log.warning(str(exc))
finally:
signal.alarm(0)
signal.signal(signal.SIGALRM, signal.SIG_DFL)

View File

@ -1,13 +0,0 @@
# 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.

View File

@ -1,27 +0,0 @@
# 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.
class SimpleChecker(object):
name = 'simple'
def send(self):
return 'ready'
def listen(self):
return 'ready'
def get_info(self):
return {}

View File

@ -1,83 +0,0 @@
# 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 json
import subprocess
import unittest
from network_checker.multicast import api as multicast_api
class TestMulticastVerification(unittest.TestCase):
def setUp(self):
self.config_node_112 = {"uid": "112", "group": "225.0.0.250",
"port": 8890}
self.config_node_113 = {"uid": "113", "group": "225.0.0.250",
"port": 8890}
self.mchecker_node_112 = multicast_api.MulticastChecker(
**self.config_node_112)
self.mchecker_node_113 = multicast_api.MulticastChecker(
**self.config_node_113)
def test_multicast_verification(self):
self.mchecker_node_112.listen()
self.mchecker_node_113.listen()
self.mchecker_node_112.send()
self.mchecker_node_113.send()
info_node_112 = self.mchecker_node_112.get_info()
info_node_113 = self.mchecker_node_113.get_info()
self.assertEqual(info_node_112, [u"113", u"112"])
self.assertEqual(info_node_113, [u"113", u"112"])
class TestSystemMulticastVerification(unittest.TestCase):
def shell_helper(self, args):
proc = subprocess.Popen(args, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
out, err = proc.communicate()
return json.loads(out)
def test_multicast_verification_with_detach(self):
init_args = ['fuel-netcheck', 'multicast', 'serve', 'listen']
listen_data = self.shell_helper(init_args)
self.assertIn('uid', listen_data)
args = ['fuel-netcheck', 'multicast', 'send', 'info']
info = self.shell_helper(args)
self.assertEqual([listen_data['uid']], info)
cleanup_args = ['fuel-netcheck', 'multicast', 'clean']
clean = self.shell_helper(cleanup_args)
self.assertTrue(clean)
def test_mutlicast_with_config(self):
config = {"uid": "112", "group": "225.0.0.250",
"port": 8890, "iface": "eth0"}
config_json = json.dumps(config)
init_args = ["fuel-netcheck", "multicast", "serve",
"--config", config_json]
self.shell_helper(init_args)
args = ['fuel-netcheck', 'multicast', 'listen', 'send', 'info']
info = self.shell_helper(args)
self.assertEqual([config['uid']], info)
cleanup_args = ['fuel-netcheck', 'multicast', 'clean']
clean = self.shell_helper(cleanup_args)
self.assertTrue(clean)

View File

@ -1,190 +0,0 @@
# 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 json
import multiprocessing
import os
import signal
import socket
import time
import unittest
import pcap
from scapy import all as scapy
from network_checker.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": {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"
}
self.config = config or default_config
self.start_socket()
listener = api.Listener(self.config)
self.listener = multiprocessing.Process(target=listener.run)
self.listener.start()
connection, address = self.ready_socket.accept()
request = connection.recv(1024)
self.assertEqual('READY', request.decode())
connection.close()
self.ready_socket.shutdown(socket.SHUT_RDWR)
self.ready_socket.close()
self.send_packets()
os.kill(self.listener.pid, signal.SIGINT)
self.listener.join()
def start_socket(self):
self.ready_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.ready_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.ready_socket.bind((self.config['ready_address'], 0))
self.config['ready_port'] = self.ready_socket.getsockname()[1]
self.ready_socket.listen(1)
self.ready_socket.settimeout(5)
def send_packets(self):
pass
def tearDown(self):
self.ready_socket.close()
self.listener.terminate()
if os.path.exists(self.config['dump_file']):
os.unlink(self.config['dump_file'])
class TestCaseListenerPcap(BaseListenerTestCase):
def send_packets(self):
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, {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:{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'],
dst="ff:ff:ff:ff:ff:ff")
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) / corrupted_data
for i in xrange(5):
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, {self.iface: {u'0': {u'2': [self.iface]}}})
class TestNetCheckSender(unittest.TestCase):
def setUp(self):
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": {self.iface: "0,100,101,102,106,107,108"},
"action": "listen",
"cookie": "Nailgun:", "dport": 31337, "sport": 31337,
"src_mac": None,
"dump_file": "/var/tmp/net-probe-dump",
"uid": "2"
}
def start_pcap_listener(self):
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)
def start_sender(self):
sender = api.Sender(self.config)
self.sender = multiprocessing.Process(target=sender.run)
self.sender.start()
@property
def pcap_packets(self):
for pkt in self.pcap_listener.readpkts():
yield pkt
for pkt in self.vlan_pcap_listener.readpkts():
yield pkt
@property
def received_vlans(self):
results = set()
for pkt in self.pcap_packets:
ether = scapy.Ether(pkt[1])
if scapy.Dot1Q in ether:
vlan = str(ether[scapy.Dot1Q].vlan)
else:
vlan = '0'
results.update([vlan])
return results
def test_sender(self):
self.start_pcap_listener()
self.start_sender()
time.sleep(3)
self.sender.join()
expected_vlans = set(self.config['interfaces'][self.iface].split(','))
self.assertEqual(expected_vlans, self.received_vlans)

View File

@ -1,68 +0,0 @@
# 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 httplib
import SimpleXMLRPCServer
import socket
import xmlrpclib
class UnixStreamHTTPConnection(httplib.HTTPConnection):
def connect(self):
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
self.sock.connect(self.host)
def getreply(self):
response = self.getresponse()
self.file = response.fp
return response.status, response.reason, response.msg
def getfile(self):
return self.file
class UnixStreamTransport(xmlrpclib.Transport, object):
def __init__(self, socket_path):
self.socket_path = socket_path
super(UnixStreamTransport, self).__init__()
def make_connection(self, host):
return UnixStreamHTTPConnection(self.socket_path)
class UnixStreamHandler(SimpleXMLRPCServer.SimpleXMLRPCRequestHandler):
# if True leads to calling TCP_NODELAY on AF_UNIX socket
# which results in Errno 95
disable_nagle_algorithm = False
class UnixXMLRPCServer(SimpleXMLRPCServer.SimpleXMLRPCServer):
address_family = socket.AF_UNIX
def get_client(config):
return xmlrpclib.Server(
'http://arg_unused',
transport=UnixStreamTransport(config['unix']))
def get_server(config):
return UnixXMLRPCServer(
config['unix'],
requestHandler=UnixStreamHandler,
logRequests=False)

View File

@ -1,10 +0,0 @@
argparse
cliff-tablib
scapy==2.2.0-dev
pypcap==1.1.1
stevedore
daemonize
pyyaml
requests
netifaces
six

View File

@ -1,21 +0,0 @@
#!/bin/bash
# Copyright 2015 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.
set -e
set -x
tox -v

View File

@ -1,53 +0,0 @@
# 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 setuptools
setuptools.setup(
name="nailgun-net-check",
version='8.0.0',
author="Mirantis Inc",
classifiers=[
"License :: OSI Approved :: Apache 2.0",
"Development Status :: 5 - Production/Stable",
"Environment :: Console",
"Operating System :: POSIX",
"Programming Language :: Python",
"Topic :: Software Development :: Testing"
],
include_package_data=True,
packages=setuptools.find_packages(),
entry_points={
'console_scripts': [
'net_probe.py = network_checker.net_check.api:main',
'fuel-netcheck = network_checker.cli:main',
'dhcpcheck = dhcp_checker.cli:main',
'urlaccesscheck = url_access_checker.cli:main',
],
'dhcp.check': [
'discover = dhcp_checker.commands:ListDhcpServers',
'request = dhcp_checker.commands:ListDhcpAssignment',
'vlans = dhcp_checker.commands:DhcpWithVlansCheck'
],
'network_checker': [
'multicast = network_checker.multicast.api:MulticastChecker',
'simple = network_checker.tests.simple:SimpleChecker'
],
'urlaccesscheck': [
'check = url_access_checker.commands:CheckUrls',
'with_setup = url_access_checker.commands:CheckUrlsWithSetup'
],
},
)

View File

@ -1,41 +0,0 @@
%define name nailgun-net-check
%{!?version: %define version 8.0.0}
%{!?release: %define release 1}
Name: %{name}
Summary: Network checking package for CentOS6.x
Version: %{version}
Release: %{release}
License: GPLv2
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
URL: http://github.com/Mirantis
Requires: vconfig
Requires: scapy
Requires: python-argparse
Requires: python-pypcap
Requires: python-cliff-tablib
Requires: python-stevedore
Requires: python-daemonize
Requires: python-yaml
Requires: tcpdump
Requires: python-requests
Requires: python-netifaces
%prep
%setup -cq -n %{name}-%{version}
%build
cd %{_builddir}/%{name}-%{version}/network_checker && python setup.py build
%install
cd %{_builddir}/%{name}-%{version}/network_checker && python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=%{_builddir}/%{name}-%{version}/network_checker/INSTALLED_FILES
%clean
rm -rf $RPM_BUILD_ROOT
%description
This is a network tool that helps to verify networks connectivity
between hosts in network.
%files -f %{_builddir}/%{name}-%{version}/network_checker/INSTALLED_FILES
%defattr(-,root,root)

View File

@ -1,6 +0,0 @@
-r requirements.txt
hacking==0.7
mock==1.0.1
pytest
unittest2
requests-mock

View File

@ -1,6 +0,0 @@
# Command line options here
DHCRELAYARGS=""
# DHCPv4 only
INTERFACES="eth1"
# DHCPv4 only
DHCPSERVERS="10.10.0.8"

View File

@ -1,12 +0,0 @@
default-lease-time 600;
max-lease-time 7200;
option subnet-mask 255.255.255.0;
option broadcast-address 192.168.0.255;
option routers 192.168.0.254;
option domain-name-servers 192.168.0.1, 192.168.0.2;
option domain-name "mydomain.org";
subnet 192.168.0.0 netmask 255.255.255.0 {
range 192.168.0.10 192.168.0.100;
range 192.168.0.150 192.168.0.200;
}

View File

@ -1,12 +0,0 @@
default-lease-time 600;
max-lease-time 7200;
option subnet-mask 255.255.255.0;
option broadcast-address 10.10.0.255;
option routers 10.10.0.254;
option domain-name-servers 10.10.0.1;
option domain-name "mydomain1.org";
option dhcp-server-identifier 10.10.0.8;
subnet 10.10.0.0 netmask 255.255.255.0 {
range 10.10.0.10 10.10.0.100;
}

View File

@ -1,37 +0,0 @@
[tox]
minversion = 1.6
skipsdist = True
envlist = py26,py27,pep8
[testenv]
usedevelop = True
install_command = pip install --allow-external -U {opts} {packages}
setenv = VIRTUAL_ENV={envdir}
deps = -r{toxinidir}/test-requirements.txt
commands = py.test {toxinidir}/url_access_checker/tests
[tox:jenkins]
downloadcache = ~/cache/pip
[testenv:pep8]
deps = hacking==0.10
usedevelop = False
commands =
flake8 {posargs:.}
[testenv:venv]
commands = {posargs:}
[testenv:devenv]
envdir = devenv
usedevelop = True
[flake8]
ignore = H234,H302,H802
exclude = .venv,.git,.tox,dist,doc,*lib/python*,*egg,build,tools,__init__.py,docs
show-pep8 = True
show-source = True
count = True
[hacking]
import_exceptions = testtools.matchers

View File

@ -1,112 +0,0 @@
# Copyright 2015 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 json
import os
import socket
import requests
from six.moves import urllib
import url_access_checker.errors as errors
def check_urls(urls, proxies=None, timeout=60):
"""Checks a set of urls to see if they are valid
Url is valid if
- it returns 200 upon requesting it with http (if it doesn't specify
protocol as "file")
- it is an existing file or directory (if the protocol used in url is
"file")
Arguments:
urls -- an iterable containing urls for testing
proxies -- proxy servers to use for the request
timeout -- the max time to wait for a response, default 60 seconds
"""
responses = map(lambda u: _get_response_tuple(
u, proxies=proxies, timeout=timeout), urls)
failed_responses = filter(lambda x: x[0], responses)
if failed_responses:
raise errors.UrlNotAvailable(json.dumps(
{'failed_urls': map(lambda r: r[1], failed_responses)}))
else:
return True
def _get_response_tuple(url, proxies=None, timeout=60):
"""Return a tuple which contains a result of url test
Arguments:
url -- a string containing url for testing, can be local file
proxies -- proxy servers to use for the request
timeout -- the max time to wait for a response, default 60 seconds
Result tuple content:
result[0] -- boolean value, True if the url is deemed failed
result[1] -- unchange url argument
"""
parsed = urllib.parse.urlparse(url)
if parsed.scheme == 'file':
return _get_file_existence_tuple(url)
elif parsed.scheme in ['http', 'https']:
return _get_http_response_tuple(url, proxies, timeout)
elif parsed.scheme == 'ftp':
return _get_ftp_response_tuple(url, timeout)
else:
raise errors.InvalidProtocol(url)
def _get_file_existence_tuple(url):
path = url[len('file://'):]
return (not os.path.exists(path), url)
def _get_http_response_tuple(url, proxies=None, timeout=60):
try:
# requests seems to correctly handle various corner cases:
# proxies=None or proxies={} mean 'use the default' rather than
# "don't use proxy". To force a direct connection one should pass
# proxies={'http': None}.
# Setting the timeout for requests.get(...) sets max request time. We
# want to set a value to prevent this process from hanging as the
# default timeout is None which can lead to bad things when processes
# never exit. LP#1478138
response = requests.get(url, proxies=proxies, timeout=timeout)
return (response.status_code != 200, url)
except (requests.exceptions.ConnectionError,
requests.exceptions.Timeout,
requests.exceptions.HTTPError,
requests.exceptions.ProxyError,
ValueError,
socket.timeout):
return (True, url)
def _get_ftp_response_tuple(url, timeout=60):
"""Return a tuple which contains a result of ftp url test
It will try to open ftp url as anonymous user and return (True, url) if
any errors occur, or return (False, url) otherwise.
"""
try:
# NOTE(mkwiek): requests don't have tested ftp adapter yet, so
# lower level urllib2 is used here
urllib.request.urlopen(url, timeout=timeout)
return (False, url)
except urllib.error.URLError:
return (True, url)

View File

@ -1,42 +0,0 @@
# Copyright 2015 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
import sys
# fixed in cmd2 >=0.6.6
os.environ['EDITOR'] = '/usr/bin/nano'
from cliff.commandmanager import CommandManager
from fuel_network_checker import base_app
class UrlAccessCheckApp(base_app.BaseApp):
LOG_FILENAME = '/var/log/url_access_checker.log'
def __init__(self):
super(UrlAccessCheckApp, self).__init__(
description='Url access check application',
version='0.1',
command_manager=CommandManager('urlaccesscheck'),
)
def main(argv=sys.argv[1:]):
myapp = UrlAccessCheckApp()
return myapp.run(argv)
if __name__ == '__main__':
sys.exit(main(sys.argv[1:]))

View File

@ -1,63 +0,0 @@
# Copyright 2015 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 logging
import sys
from cliff import command
import url_access_checker.api as api
import url_access_checker.errors as errors
from url_access_checker.network import manage_network
LOG = logging.getLogger(__name__)
class CheckUrls(command.Command):
"""Check if it is possible to retrieve urls."""
def get_parser(self, prog_name):
parser = super(CheckUrls, self).get_parser(prog_name)
parser.add_argument('urls', type=str, nargs='+',
help='List of urls to check')
parser.add_argument('--timeout', type=int, default=60,
help='Max time to wait for response, Default: 60')
return parser
def take_action(self, parsed_args):
LOG.info('Starting url access check for {0}'.format(parsed_args.urls))
try:
api.check_urls(parsed_args.urls, timeout=parsed_args.timeout)
except errors.UrlNotAvailable as e:
sys.stdout.write(str(e))
raise e
class CheckUrlsWithSetup(CheckUrls):
def get_parser(self, prog_name):
parser = super(CheckUrlsWithSetup, self).get_parser(
prog_name)
parser.add_argument('-i', type=str, help='Interface', required=True)
parser.add_argument('-a', type=str, help='Addr/Mask pair',
required=True)
parser.add_argument('-g', type=str, required=True,
help='Gateway to be used as default')
parser.add_argument('--vlan', type=int, help='Vlan tag')
return parser
def take_action(self, pa):
with manage_network(pa.i, pa.a, pa.g, pa.vlan):
return super(
CheckUrlsWithSetup, self).take_action(pa)

View File

@ -1,25 +0,0 @@
# Copyright 2015 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.
class UrlNotAvailable(Exception):
pass
class CommandFailed(Exception):
pass
class InvalidProtocol(Exception):
pass

View File

@ -1,207 +0,0 @@
# Copyright 2015 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.
from contextlib import contextmanager
from logging import getLogger
import netifaces
from url_access_checker.errors import CommandFailed
from url_access_checker.utils import execute
logger = getLogger(__name__)
def get_default_gateway():
"""Return ipaddress, interface pair for default gateway"""
gws = netifaces.gateways()
if 'default' in gws:
return gws['default'][netifaces.AF_INET]
return None, None
def check_ifaddress_present(iface, addr):
"""Check if required ipaddress already assigned to the iface"""
for ifaddress in netifaces.ifaddresses(iface).get(netifaces.AF_INET, []):
if ifaddress['addr'] in addr:
return True
return False
def check_exist(iface):
rc, _, err = execute(['ip', 'link', 'show', iface])
if rc == 1 and 'does not exist' in err:
return False
elif rc:
msg = 'ip link show {0} failed with {1}'.format(iface, err)
raise CommandFailed(msg)
return True
def check_up(iface):
rc, stdout, _ = execute(['ip', 'link', 'show', iface])
return 'UP' in stdout
def log_network_info(stage):
logger.info('Logging networking info at %s', stage)
stdout = execute(['ip', 'a'])[1]
logger.info('ip a: %s', stdout)
stdout = execute(['ip', 'ro'])[1]
logger.info('ip ro: %s', stdout)
class Eth(object):
def __init__(self, iface):
self.iface = iface
self.is_up = None
def setup(self):
self.is_up = check_up(self.iface)
if self.is_up is False:
rc, out, err = execute(['ip', 'link', 'set',
'dev', self.iface, 'up'])
if rc:
msg = 'Cannot up interface {0}. Err: {1}'.format(
self.iface, err)
raise CommandFailed(msg)
def teardown(self):
if self.is_up is False:
execute(['ip', 'link', 'set', 'dev', self.iface, 'down'])
class Vlan(Eth):
def __init__(self, iface, vlan):
self.parent = iface
self.vlan = str(vlan)
self.iface = '{0}.{1}'.format(iface, vlan)
self.is_present = None
self.is_up = None
def setup(self):
self.is_present = check_exist(self.iface)
if self.is_present is False:
rc, out, err = execute(
['ip', 'link', 'add',
'link', self.parent, 'name',
self.iface, 'type', 'vlan', 'id', self.vlan])
if rc:
msg = (
'Cannot create tagged interface {0}.'
' With parent {1}. Err: {2}'.format(
self.iface, self.parent, err))
raise CommandFailed(msg)
super(Vlan, self).setup()
def teardown(self):
super(Vlan, self).teardown()
if self.is_present is False:
execute(['ip', 'link', 'delete', self.iface])
class IP(object):
def __init__(self, iface, addr):
self.iface = iface
self.addr = addr
self.is_present = None
def setup(self):
self.is_present = check_ifaddress_present(self.iface, self.addr)
if self.is_present is False:
rc, out, err = execute(['ip', 'a', 'add', self.addr,
'dev', self.iface])
if rc:
msg = 'Cannot add address {0} to {1}. Err: {2}'.format(
self.addr, self.iface, err)
raise CommandFailed(msg)
def teardown(self):
if self.is_present is False:
execute(['ip', 'a', 'del', self.addr, 'dev', self.iface])
class Route(object):
def __init__(self, iface, gateway):
self.iface = iface
self.gateway = gateway
self.default_gateway = None
self.df_iface = None
def setup(self):
self.default_gateway, self.df_iface = get_default_gateway()
rc = None
if (self.default_gateway, self.df_iface) == (None, None):
rc, out, err = execute(
['ip', 'ro', 'add',
'default', 'via', self.gateway, 'dev', self.iface])
elif ((self.default_gateway, self.df_iface)
!= (self.gateway, self.iface)):
rc, out, err = execute(
['ip', 'ro', 'change',
'default', 'via', self.gateway, 'dev', self.iface])
if rc:
msg = ('Cannot add default gateway {0} on iface {1}.'
' Err: {2}'.format(self.gateway, self.iface, err))
raise CommandFailed(msg)
def teardown(self):
if (self.default_gateway, self.df_iface) == (None, None):
execute(['ip', 'ro', 'del',
'default', 'via', self.gateway, 'dev', self.iface])
elif ((self.default_gateway, self.df_iface)
!= (self.gateway, self.iface)):
execute(['ip', 'ro', 'change',
'default', 'via', self.default_gateway,
'dev', self.df_iface])
@contextmanager
def manage_network(iface, addr, gateway, vlan=None):
log_network_info('before setup')
actions = [Eth(iface)]
if vlan:
vlan_action = Vlan(iface, vlan)
actions.append(vlan_action)
iface = vlan_action.iface
actions.append(IP(iface, addr))
actions.append(Route(iface, gateway))
executed = []
try:
for a in actions:
a.setup()
executed.append(a)
log_network_info('after setup')
yield
except Exception:
logger.exception('Unexpected failure.')
raise
finally:
for a in reversed(executed):
a.teardown()
log_network_info('after teardown')

View File

@ -1,68 +0,0 @@
# Copyright 2015 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 unittest2
import mock
import requests_mock
from url_access_checker import api
from url_access_checker import errors
class TestApi(unittest2.TestCase):
def setUp(self):
self.urls = ['http://url{0}'.format(i) for i in range(10)]
self.paths = ['file:///tmp/test_api{0}'.format(i) for i in range(10)]
self.ftps = ['ftp://url{0}'.format(i) for i in range(10)]
@requests_mock.Mocker()
def test_check_urls(self, req_mocker):
for url in self.urls:
req_mocker.get(url, status_code=200)
check_result = api.check_urls(self.urls)
self.assertTrue(check_result)
@requests_mock.Mocker()
def test_check_urls_fail(self, req_mocker):
for url in self.urls:
req_mocker.get(url, status_code=404)
with self.assertRaises(errors.UrlNotAvailable):
api.check_urls(self.urls)
@mock.patch('os.path.exists')
def test_check_paths(self, mock_exists):
mock_exists.return_value = True
check_result = api.check_urls(self.paths)
self.assertTrue(check_result)
@mock.patch('os.path.exists')
def test_check_paths_fail(self, mock_exists):
mock_exists.return_value = False
with self.assertRaises(errors.UrlNotAvailable):
api.check_urls(self.paths)
@mock.patch('urllib2.urlopen')
def test_check_ftp(self, _):
check_result = api.check_urls(self.ftps, timeout=5)
self.assertTrue(check_result)
def test_check_ftp_fail(self):
with self.assertRaises(errors.UrlNotAvailable):
api.check_urls(self.paths)

View File

@ -1,47 +0,0 @@
# Copyright 2015 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 unittest
import mock
from url_access_checker import cli
class TestUrlCheckerCommands(unittest.TestCase):
def setUp(self):
self.urls = ['http://url{0}'.format(i) for i in range(10)]
@mock.patch('requests.get')
def test_check_urls_success(self, get_mock):
response_mock = mock.Mock()
response_mock.status_code = 200
get_mock.return_value = response_mock
exit_code = cli.main(['check'] + self.urls)
self.assertEqual(exit_code, 0)
@mock.patch('requests.get')
def test_check_urls_fail(self, get_mock):
response_mock = mock.Mock()
response_mock.status_code = 404
get_mock.return_value = response_mock
exit_code = cli.main(['check'] + self.urls)
self.assertEqual(exit_code, 1)
def test_check_urls_fail_on_requests_error(self):
exit_code = cli.main(['check'] + self.urls)
self.assertEqual(exit_code, 1)

View File

@ -1,114 +0,0 @@
# Copyright 2015 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 unittest
from mock import call
from mock import Mock
from mock import patch
import netifaces
from url_access_checker import cli
@patch('url_access_checker.network.execute')
@patch('url_access_checker.network.netifaces.gateways')
@patch('requests.get', Mock(status_code=200))
@patch('url_access_checker.network.check_up')
@patch('url_access_checker.network.check_exist')
@patch('url_access_checker.network.check_ifaddress_present')
class TestVerificationWithNetworkSetup(unittest.TestCase):
def assert_by_items(self, expected_items, received_items):
"""In case of failure will show difference only for failed item."""
for expected, executed in zip(expected_items, received_items):
self.assertEqual(expected, executed)
def test_verification_route(self, mifaddr, mexist, mup, mgat, mexecute):
mexecute.return_value = (0, '', '')
mup.return_value = True
mexist.return_value = True
mifaddr.return_value = False
default_gw, default_iface = '172.18.0.1', 'eth2'
mgat.return_value = {
'default': {netifaces.AF_INET: (default_gw, default_iface)}}
iface = 'eth1'
addr = '10.10.0.2/24'
gw = '10.10.0.1'
cmd = ['with', 'setup', '-i', iface,
'-a', addr, '-g', gw, 'test.url']
cli.main(cmd)
execute_stack = [
call(['ip', 'a']),
call(['ip', 'ro']),
call(['ip', 'a', 'add', addr, 'dev', iface]),
call(['ip', 'ro', 'change', 'default', 'via', gw, 'dev', iface]),
call(['ip', 'a']),
call(['ip', 'ro']),
call(['ip', 'ro', 'change', 'default', 'via', default_gw,
'dev', default_iface]),
call(['ip', 'a', 'del', addr, 'dev', iface]),
call(['ip', 'a']),
call(['ip', 'ro'])]
self.assert_by_items(mexecute.call_args_list, execute_stack)
def test_verification_vlan(self, mifaddr, mexist, mup, mgat, mexecute):
mexecute.return_value = (0, '', '')
mup.return_value = False
mexist.return_value = False
mifaddr.return_value = False
default_gw, default_iface = '172.18.0.1', 'eth2'
mgat.return_value = {
'default': {netifaces.AF_INET: (default_gw, default_iface)}}
iface = 'eth1'
addr = '10.10.0.2/24'
gw = '10.10.0.1'
vlan = '101'
tagged_iface = '{0}.{1}'.format(iface, vlan)
cmd = ['with', 'setup', '-i', iface,
'-a', addr, '-g', gw, '--vlan', vlan, 'test.url']
cli.main(cmd)
execute_stack = [
call(['ip', 'a']),
call(['ip', 'ro']),
call(['ip', 'link', 'set', 'dev', iface, 'up']),
call(['ip', 'link', 'add', 'link', 'eth1', 'name',
tagged_iface, 'type', 'vlan', 'id', vlan]),
call(['ip', 'link', 'set', 'dev', tagged_iface, 'up']),
call(['ip', 'a', 'add', addr, 'dev', tagged_iface]),
call(['ip', 'ro', 'change', 'default',
'via', gw, 'dev', tagged_iface]),
call(['ip', 'a']),
call(['ip', 'ro']),
call(['ip', 'ro', 'change', 'default', 'via',
default_gw, 'dev', default_iface]),
call(['ip', 'a', 'del', addr, 'dev', tagged_iface]),
call(['ip', 'link', 'set', 'dev', tagged_iface, 'down']),
call(['ip', 'link', 'delete', tagged_iface]),
call(['ip', 'link', 'set', 'dev', iface, 'down']),
call(['ip', 'a']),
call(['ip', 'ro'])]
self.assert_by_items(mexecute.call_args_list, execute_stack)

View File

@ -1,32 +0,0 @@
# Copyright 2015 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.
from logging import getLogger
import subprocess
logger = getLogger(__name__)
def execute(cmd):
logger.debug('Executing command %s', cmd)
command = subprocess.Popen(
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = command.communicate()
msg = 'Command {0} executed. RC {1}, stdout {2}, stderr {3}'.format(
cmd, command.returncode, stdout, stderr)
if command.returncode:
logger.error(msg)
else:
logger.debug(msg)
return command.returncode, stdout, stderr

View File

@ -399,7 +399,6 @@ function run_extensions_tests {
function run_flake8 {
local result=0
run_flake8_subproject nailgun && \
run_flake8_subproject network_checker && \
run_flake8_subproject fuel_upgrade_system/fuel_upgrade && \
run_flake8_subproject fuel_upgrade_system/fuel_package_updates && \
return $result

View File

@ -116,12 +116,10 @@ cd %{_builddir}/%{name}-%{version}/nailgun && %{_builddir}/%{name}-%{version}/na
[ -n %{_builddir} ] && rm -rf %{_builddir}/%{name}-%{version}/nailgun/static
mv %{_builddir}/%{name}-%{version}/nailgun/compressed_static %{_builddir}/%{name}-%{version}/nailgun/static
cd %{_builddir}/%{name}-%{version}/nailgun && python setup.py build
cd %{_builddir}/%{name}-%{version}/network_checker && python setup.py build
cd %{_builddir}/%{name}-%{version}/fuel_upgrade_system/fuel_package_updates && python setup.py build
%install
cd %{_builddir}/%{name}-%{version}/nailgun && python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=%{_builddir}/%{name}-%{version}/nailgun/INSTALLED_FILES
cd %{_builddir}/%{name}-%{version}/network_checker && python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=%{_builddir}/%{name}-%{version}/network_checker/INSTALLED_FILES
cd %{_builddir}/%{name}-%{version}/fuel_upgrade_system/fuel_package_updates && python setup.py install --single-version-externally-managed -O1 --root=$RPM_BUILD_ROOT --record=%{_builddir}/%{name}-%{version}/fuel_upgrade_system/fuel_package_updates/INSTALLED_FILES
mkdir -p %{buildroot}/opt/nailgun/bin
mkdir -p %{buildroot}/etc/cron.d
@ -155,35 +153,6 @@ This package currently installs just a single file openstack.yaml
%{_datadir}/fuel-openstack-metadata/*
%{_sysconfdir}/fuel_openstack_version
%package -n nailgun-net-check
Summary: Network checking package for CentOS6.x
Version: %{version}
Release: %{release}
License: GPLv2
BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-buildroot
URL: http://github.com/Mirantis
Requires: vconfig
Requires: scapy
Requires: python-argparse
Requires: python-pypcap
Requires: python-cliff-tablib
Requires: python-stevedore
Requires: python-daemonize
Requires: python-yaml
Requires: tcpdump
Requires: python-requests
Requires: python-netifaces
%description -n nailgun-net-check
This is a network tool that helps to verify networks connectivity
between hosts in network.
%files -n nailgun-net-check -f %{_builddir}/%{name}-%{version}/network_checker/INSTALLED_FILES
%defattr(-,root,root)
%package -n fencing-agent
Summary: Fencing agent
Version: %{version}