Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Scan] snmp_lldp plugin added; lldp_connections postprocessing plugin added #1024

Merged
merged 2 commits into from Aug 18, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
12 changes: 12 additions & 0 deletions src/ralph/discovery/snmp.py
Expand Up @@ -63,6 +63,18 @@ def snmp_command(
return vars


def snmp_walk(
hostname, community, oid, snmp_version='2c', timeout=1, attempts=3,
priv_protocol=cmdgen.usmDESPrivProtocol
):
transport = cmdgen.UdpTransportTarget((hostname, 161), attempts, timeout)
data = user_data(community, snmp_version, priv_protocol=priv_protocol)
gen = cmdgen.CommandGenerator()
error, status, index, values = gen.nextCmd(data, transport, oid)
if not error:
return values


def snmp_bulk(
hostname, community, oid, snmp_version='2c', timeout=1, attempts=3
):
Expand Down
122 changes: 122 additions & 0 deletions src/ralph/scan/plugins/snmp_lldp.py
@@ -0,0 +1,122 @@
# -*- coding: utf-8 -*-

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

from django.conf import settings

from ralph.discovery.models import ConnectionType
from ralph.scan.errors import Error
from ralph.scan.plugins import get_base_result_template
from ralph.discovery.snmp import snmp_walk


SETTINGS = settings.SCAN_PLUGINS.get(__name__, {})


def _get_device_interfaces_map(ip_address, snmp_community, snmp_version):
items = snmp_walk(
ip_address,
snmp_community,
[int(chunk) for chunk in "1.0.8802.1.1.2.1.3.7.1.3".split(".")],
snmp_version
)
interfaces_map = {}
for item in items:
interfaces_map[unicode(item[0][0]).split(".")[-1]] = unicode(
item[0][1]
)
return interfaces_map


def _get_remote_mac_addresses_map(ip_address, snmp_community, snmp_version):
items = snmp_walk(
ip_address,
snmp_community,
[int(chunk) for chunk in "1.0.8802.1.1.2.1.4.1.1.5".split(".")],
snmp_version
)
mac_addresses_map = {}
for item in items:
mac_addresses_map[unicode(item[0][0]).split(".")[-2]] = "".join(
"{:0>2}".format(str(hex(ord(chunk)))[2:]) for chunk in item[0][1]
).upper()
return mac_addresses_map


def _get_remote_connected_ports_map(ip_address, snmp_community, snmp_version):
items = snmp_walk(
ip_address,
snmp_community,
[int(chunk) for chunk in "1.0.8802.1.1.2.1.4.1.1.7".split(".")],
snmp_version
)
ports_map = {}
for item in items:
ports_map[unicode(item[0][0]).split(".")[-2]] = unicode(item[0][1])
return ports_map


def _snmp_lldp(
ip_address, snmp_community, snmp_version, messages=[]
):
try:
interfaces_map = _get_device_interfaces_map(
ip_address, snmp_community, snmp_version
)
mac_addresses_map = _get_remote_mac_addresses_map(
ip_address, snmp_community, snmp_version
)
ports_map = _get_remote_connected_ports_map(
ip_address, snmp_community, snmp_version
)
except TypeError:
raise Error("No answer.")
except IndexError:
raise Error("Incorrect answer.")
connections = []
for item_id, mac_address in mac_addresses_map.iteritems():
details = {}
if interfaces_map.get(item_id):
details['outbound_port'] = interfaces_map.get(item_id)
if ports_map.get(item_id):
details['inbound_port'] = ports_map.get(item_id)
connection = {
'connection_type': ConnectionType.network.name,
'connected_device_mac_addresses': mac_address
}
if details:
connection['details'] = details
connections.append(connection)
return {
'system_ip_addresses': [ip_address],
'connections': connections
}


def scan_address(ip_address, **kwargs):
snmp_version = kwargs.get('snmp_version', '2c') or '2c'
if snmp_version == '3':
snmp_community = SETTINGS['snmp_v3_auth']
else:
snmp_community = str(kwargs['snmp_community'])
messages = []
result = get_base_result_template('snmp_macs', messages)
try:
info = _snmp_lldp(
ip_address,
snmp_community,
snmp_version,
messages,
)
except Error as e:
messages.append(unicode(e))
result['status'] = "error"
else:
result.update({
'status': 'success',
'device': info,
})
return result
109 changes: 109 additions & 0 deletions src/ralph/scan/postprocess/lldp_connections.py
@@ -0,0 +1,109 @@
# -*- coding: utf-8 -*-

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import logging

from django.db.models import Q

from ralph.discovery.models import (
Connection,
ConnectionType,
NetworkConnection,
)
from ralph.scan.automerger import select_data
from ralph.scan.data import (
append_merged_proposition,
connection_from_data,
find_devices,
get_device_data,
get_external_results_priorities,
merge_data,
)


logger = logging.getLogger("LLDP_CONNECTIONS")


def _network_connections_in_results(data):
for plugin_name, plugin_result in data.iteritems():
if plugin_result['status'] == 'error':
continue
if 'device' not in plugin_result:
continue
if 'connections' in plugin_result['device']:
for conn in plugin_result['device']['connections']:
if conn['connection_type'] == ConnectionType.network.name:
return True
return False


def _create_or_update_connection(device, connection_data):
if connection_data['connection_type'] != 'network':
return
connection = connection_from_data(device, connection_data)
connetion_details = connection_data.get('details', {})
if connetion_details:
outbound_port = connetion_details.get('outbound_port')
inbound_port = connetion_details.get('inbound_port')
try:
details = NetworkConnection.objects.get(connection=connection)
except NetworkConnection.DoesNotExist:
details = NetworkConnection(connection=connection)
if outbound_port:
details.outbound_port = outbound_port
if inbound_port:
details.inbound_port = inbound_port
details.save()
return connection


def _append_connections_to_device(device, data, external_priorities):
device_data = get_device_data(device)
full_data = merge_data(
data,
{
'database': {'device': device_data},
},
only_multiple=True,
)
append_merged_proposition(full_data, device, external_priorities)
selected_data = select_data(full_data, external_priorities)
parsed_connections = set()
for conn_data in selected_data.get('connections', []):
conn = _create_or_update_connection(device, conn_data)
if conn:
parsed_connections.add(conn.pk)
Connection.objects.filter(
Q(outbound=device),
Q(connection_type=ConnectionType.network),
~Q(pk__in=parsed_connections),
).delete()


def _lldp_connections(ip, data):
if not _network_connections_in_results(data):
return # No reason to run it...
external_priorities = get_external_results_priorities(data)
devices = find_devices(data)
if len(devices) == 0:
logger.warning(
'No device found for the IP address %s. '
'There are some connections.' % ip.address
)
return
elif len(devices) > 1:
logger.warning(
'Many devices found for the IP address %s. '
'There are some connections.' % ip.address
)
return
_append_connections_to_device(devices[0], data, external_priorities)


def run_job(ip, **kwargs):
data = kwargs.get('plugins_results', {})
_lldp_connections(ip, data)
145 changes: 145 additions & 0 deletions src/ralph/scan/tests/plugins/test_snmp_lldp.py
@@ -0,0 +1,145 @@
# -*- coding: utf-8 -*-

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals

import mock

from django.test import TestCase

from ralph.scan.plugins.snmp_lldp import (
_get_device_interfaces_map,
_get_remote_connected_ports_map,
_get_remote_mac_addresses_map,
_snmp_lldp,
)


class SnmpLldpPluginTest(TestCase):

def test_get_device_interfaces_map(self):
with mock.patch(
'ralph.scan.plugins.snmp_lldp.snmp_walk',
) as snmp_walk:
snmp_walk.side_effect = [
[
(("1.0.8802.1.1.2.1.3.7.1.3.1", "Gi0/1"), None),
(("1.0.8802.1.1.2.1.3.7.1.3.2", "Gi0/2"), None),
(("1.0.8802.1.1.2.1.3.7.1.3.3", "Gi0/3"), None),
(("1.0.8802.1.1.2.1.3.7.1.3.4", "Gi0/4"), None),
]
]
self.assertEqual(
_get_device_interfaces_map("10.10.10.10", "public", "2c"),
{
"1": "Gi0/1",
"2": "Gi0/2",
"3": "Gi0/3",
"4": "Gi0/4",
}
)

def test_get_remote_mac_addresses_map(self):
with mock.patch(
'ralph.scan.plugins.snmp_lldp.snmp_walk',
) as snmp_walk:
snmp_walk.side_effect = [
[
(
(
"1.0.8802.1.1.2.1.3.7.1.3.6.1",
['\xff', '\xfe', '\xfd', '\xfc', '\xfb', '\x02']
),
None
),
]
]
self.assertEqual(
_get_remote_mac_addresses_map("10.10.10.10", "public", "2c"),
{"6": "FFFEFDFCFB02"}
)

def test_get_remote_connected_ports_map(self):
with mock.patch(
'ralph.scan.plugins.snmp_lldp.snmp_walk',
) as snmp_walk:
snmp_walk.side_effect = [
[
(
(
"1.0.8802.1.1.2.1.4.1.1.7.0.33.1",
"Eth0"
),
None
),
(
(
"1.0.8802.1.1.2.1.4.1.1.7.0.42.2",
"Eth1"
),
None
),
]
]
self.assertEqual(
_get_remote_connected_ports_map("10.10.10.10", "public", "2c"),
{'33': 'Eth0', '42': 'Eth1'},
)

def test_snmp_lldp(self):
with mock.patch(
'ralph.scan.plugins.snmp_lldp._get_device_interfaces_map',
) as get_device_interfaces_map_mock, mock.patch(
'ralph.scan.plugins.snmp_lldp._get_remote_mac_addresses_map',
) as get_remote_mac_addresses_map_mock, mock.patch(
'ralph.scan.plugins.snmp_lldp._get_remote_connected_ports_map',
) as get_remote_connected_ports_map_mock:
get_device_interfaces_map_mock.return_value = {
"1": "Gi0/1",
"2": "Gi0/2",
"3": "Gi0/3",
"4": "Gi0/4",
"5": "Gi0/5",
"6": "Gi0/6",
"7": "Gi0/7",
"8": "Gi0/8",
}
get_remote_mac_addresses_map_mock.return_value = {
"2": "FFFEFDFCFB03",
"3": "FFFEFDFCFB04",
"9": "FFFEFDFCFBFF",
}
get_remote_connected_ports_map_mock.return_value = {
'2': 'Eth0',
'3': 'Eth1',
}
self.assertEqual(
_snmp_lldp("10.10.10.11", "public", "2c"),
{
'connections': [
{
'connected_device_mac_addresses': 'FFFEFDFCFBFF',
'connection_type': 'network',
},
{
'connected_device_mac_addresses': 'FFFEFDFCFB04',
'connection_type': 'network',
'details': {
'inbound_port': 'Eth1',
'outbound_port': 'Gi0/3'
}
},
{
'connected_device_mac_addresses': 'FFFEFDFCFB03',
'connection_type': 'network',
'details': {
'inbound_port': 'Eth0',
'outbound_port': 'Gi0/2'
}
}
],
'system_ip_addresses': ['10.10.10.11']
}
)
Empty file.