Skip to content
This repository was archived by the owner on Dec 17, 2021. It is now read-only.

Commit 7699591

Browse files
authored
feat: extract IF-MIB data from an SNMPWALK (#96)
* feat: initial support for parsing IF-MIB data - added a basic support for extracting from an SNMP WALK: - the total number of network interfaces, - all indexes associated to each network interface - all interface names available - some unit-test added In this way, we have the basic information required for sending additional data to Splunk. * feat: initial support for parsing IF-MIB data (ADDON-39612) - added a basic support for extracting from an SNMP WALK: - the total number of network interfaces, - all indexes associated to each network interface - all interface names available - some unit-test added In this way, we have the basic information required for sending additional data to Splunk. * fix: lint (ADDON-39612)
1 parent f3081c1 commit 7699591

File tree

7 files changed

+275
-1
lines changed

7 files changed

+275
-1
lines changed

pyproject.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,4 +45,6 @@ sc4snmp-poller = "splunk_connect_for_snmp_poller.snmp_poller_server:main"
4545
requires = ["poetry>=0.12"]
4646
build-backend = "poetry.masonry.api"
4747

48-
48+
[tool.pytest.ini_options]
49+
log_cli = true
50+
log_cli_level = "INFO"
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
#
2+
# Copyright 2021 Splunk Inc.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
# See http://www.net-snmp.org/docs/mibs/interfaces.html for additional implementation details
18+
def extract_if_mib_only(translated_walk_result):
19+
return filter(
20+
lambda translation: all(
21+
key in translation
22+
for key in (
23+
InterfaceMib.METRIC_NAME_KEY,
24+
InterfaceMib.METRIC_VALUE_KEY,
25+
InterfaceMib.METRIC_TYPE_KEY,
26+
)
27+
)
28+
and translation[InterfaceMib.METRIC_NAME_KEY].startswith(
29+
InterfaceMib.IF_MIB_METRIC_SUFFIX
30+
),
31+
translated_walk_result,
32+
)
33+
34+
35+
class InterfaceMib:
36+
METRIC_NAME_KEY = "metric_name"
37+
METRIC_VALUE_KEY = "_value"
38+
METRIC_TYPE_KEY = "metric_type"
39+
IF_MIB_METRIC_SUFFIX = "sc4snmp.IF-MIB."
40+
IF_MIB_IF_NUMBER = "sc4snmp.IF-MIB.ifNumber_0"
41+
IF_MIB_IF_INDEX_BASE = "sc4snmp.IF-MIB.ifIndex_"
42+
IF_MIB_IF_DESCR_BASE = "sc4snmp.IF-MIB.ifDescr_"
43+
44+
def __init__(self, if_mib_walk_data):
45+
self._if_mib_walk_data = extract_if_mib_only(if_mib_walk_data)
46+
self._full_dictionary = self.__build_in_memory_dictionary()
47+
self._network_interfaces = self.__extract_number_of_network_interfaces()
48+
self._network_indexes = self.__extract_interface_indexes()
49+
self._network_interface_names = self.__extract_interface_names()
50+
51+
def unprocessed_if_mib_data(self):
52+
return self._if_mib_walk_data
53+
54+
def network_interfaces(self):
55+
return self._network_interfaces
56+
57+
def network_indexes(self):
58+
return self._network_indexes
59+
60+
def network_interface_names(self):
61+
return self._network_interface_names
62+
63+
def has_consistent_data(self):
64+
return self.network_interfaces() == len(self.network_indexes()) and len(
65+
self.network_indexes()
66+
) == len(self.network_interface_names())
67+
68+
def __build_in_memory_dictionary(self):
69+
all_keys = dict()
70+
for mib in self.unprocessed_if_mib_data():
71+
all_keys[mib[InterfaceMib.METRIC_NAME_KEY]] = {
72+
InterfaceMib.METRIC_VALUE_KEY: mib[InterfaceMib.METRIC_VALUE_KEY],
73+
InterfaceMib.METRIC_TYPE_KEY: mib[InterfaceMib.METRIC_TYPE_KEY],
74+
}
75+
return all_keys
76+
77+
def __extract_number_of_network_interfaces(self):
78+
if InterfaceMib.IF_MIB_IF_NUMBER in self._full_dictionary:
79+
if_number = self._full_dictionary[InterfaceMib.IF_MIB_IF_NUMBER]
80+
return int(if_number[InterfaceMib.METRIC_VALUE_KEY])
81+
else:
82+
return 0
83+
84+
def __extract_single_field_as_list(self, base_mib_metric_name):
85+
all_indexes = []
86+
for index in range(0, self.network_interfaces()):
87+
current = base_mib_metric_name + str(index + 1)
88+
if current in self._full_dictionary:
89+
all_indexes.append(
90+
self._full_dictionary[current][InterfaceMib.METRIC_VALUE_KEY]
91+
)
92+
return all_indexes
93+
94+
def __extract_interface_indexes(self):
95+
return self.__extract_single_field_as_list(InterfaceMib.IF_MIB_IF_INDEX_BASE)
96+
97+
def __extract_interface_names(self):
98+
return self.__extract_single_field_as_list(InterfaceMib.IF_MIB_IF_DESCR_BASE)

tests/mib_walk_data/if_mib_walk.json

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
[
2+
{"metric_name": "sc4snmp.IF-MIB.ifNumber_0", "_value": "2", "metric_type": "Integer"},
3+
{"metric_name": "sc4snmp.IF-MIB.ifIndex_1", "_value": "1", "metric_type": "Integer"},
4+
{"metric_name": "sc4snmp.IF-MIB.ifIndex_2", "_value": "2", "metric_type": "Integer"},
5+
{"metric_name": "sc4snmp.IF-MIB.ifDescr_1", "_value": "lo", "metric_type": "OctetString"},
6+
{"metric_name": "sc4snmp.IF-MIB.ifDescr_2", "_value": "eth0", "metric_type": "OctetString"},
7+
{"metric_name": "sc4snmp.IF-MIB.ifType_1", "_value": "softwareLoopback", "metric_type": "Integer"},
8+
{"metric_name": "sc4snmp.IF-MIB.ifType_2", "_value": "ethernetCsmacd", "metric_type": "Integer"},
9+
{"metric_name": "sc4snmp.IF-MIB.ifMtu_1", "_value": "16436", "metric_type": "Integer"},
10+
{"metric_name": "sc4snmp.IF-MIB.ifMtu_2", "_value": "1500", "metric_type": "Integer"},
11+
{"metric_name": "sc4snmp.IF-MIB.ifSpeed_1", "_value": "10000000", "metric_type": "Gauge32"},
12+
{"metric_name": "sc4snmp.IF-MIB.ifSpeed_2", "_value": "100000000", "metric_type": "Gauge32"},
13+
{"metric_name": "sc4snmp.IF-MIB.ifPhysAddress_1", "_value": "", "metric_type": "OctetString"},
14+
{"metric_name": "sc4snmp.IF-MIB.ifPhysAddress_2", "_value": "12:79:62:f9:40", "metric_type": "OctetString"},
15+
{"metric_name": "sc4snmp.IF-MIB.ifAdminStatus_1", "_value": "up", "metric_type": "Integer"},
16+
{"metric_name": "sc4snmp.IF-MIB.ifAdminStatus_2", "_value": "up", "metric_type": "Integer"},
17+
{"metric_name": "sc4snmp.IF-MIB.ifOperStatus_1", "_value": "up", "metric_type": "Integer"},
18+
{"metric_name": "sc4snmp.IF-MIB.ifOperStatus_2", "_value": "up", "metric_type": "Integer"},
19+
{"metric_name": "sc4snmp.IF-MIB.ifLastChange_1", "_value": "124000016", "metric_type": "TimeTicks"},
20+
{"metric_name": "sc4snmp.IF-MIB.ifLastChange_2", "_value": "124000016", "metric_type": "TimeTicks"},
21+
{"metric_name": "sc4snmp.IF-MIB.ifInOctets_1", "_value": "572123778", "metric_type": "Counter32"},
22+
{"metric_name": "sc4snmp.IF-MIB.ifInOctets_2", "_value": "858185699", "metric_type": "Counter32"},
23+
{"metric_name": "sc4snmp.IF-MIB.ifInUcastPkts_1", "_value": "51491148", "metric_type": "Counter32"},
24+
{"metric_name": "sc4snmp.IF-MIB.ifInUcastPkts_2", "_value": "108703537", "metric_type": "Counter32"},
25+
{"metric_name": "sc4snmp.IF-MIB.ifInNUcastPkts_1", "_value": "2860619", "metric_type": "Counter32"},
26+
{"metric_name": "sc4snmp.IF-MIB.ifInNUcastPkts_2", "_value": "5721239", "metric_type": "Counter32"},
27+
{"metric_name": "sc4snmp.IF-MIB.ifInDiscards_1", "_value": "174351", "metric_type": "Counter32"},
28+
{"metric_name": "sc4snmp.IF-MIB.ifInDiscards_2", "_value": "174351", "metric_type": "Counter32"},
29+
{"metric_name": "sc4snmp.IF-MIB.ifInErrors_1", "_value": "174349", "metric_type": "Counter32"},
30+
{"metric_name": "sc4snmp.IF-MIB.ifInErrors_2", "_value": "174349", "metric_type": "Counter32"},
31+
{"metric_name": "sc4snmp.IF-MIB.ifInUnknownProtos_1", "_value": "286058", "metric_type": "Counter32"},
32+
{"metric_name": "sc4snmp.IF-MIB.ifInUnknownProtos_2", "_value": "286054", "metric_type": "Counter32"},
33+
{"metric_name": "sc4snmp.IF-MIB.ifOutOctets_1", "_value": "257455802", "metric_type": "Counter32"},
34+
{"metric_name": "sc4snmp.IF-MIB.ifOutOctets_2", "_value": "286062024", "metric_type": "Counter32"},
35+
{"metric_name": "sc4snmp.IF-MIB.ifOutUcastPkts_1", "_value": "14303102", "metric_type": "Counter32"},
36+
{"metric_name": "sc4snmp.IF-MIB.ifOutUcastPkts_2", "_value": "25745585", "metric_type": "Counter32"},
37+
{"metric_name": "sc4snmp.IF-MIB.ifOutNUcastPkts_1", "_value": "2860608", "metric_type": "Counter32"},
38+
{"metric_name": "sc4snmp.IF-MIB.ifOutNUcastPkts_2", "_value": "2860562", "metric_type": "Counter32"},
39+
{"metric_name": "sc4snmp.IF-MIB.ifOutDiscards_1", "_value": "174348", "metric_type": "Counter32"},
40+
{"metric_name": "sc4snmp.IF-MIB.ifOutDiscards_2", "_value": "174348", "metric_type": "Counter32"},
41+
{"metric_name": "sc4snmp.IF-MIB.ifOutErrors_1", "_value": "174347", "metric_type": "Counter32"},
42+
{"metric_name": "sc4snmp.IF-MIB.ifOutErrors_2", "_value": "174351", "metric_type": "Counter32"},
43+
{"metric_name": "sc4snmp.IF-MIB.ifOutQLen_1", "_value": "4294967295", "metric_type": "Gauge32"},
44+
{"metric_name": "sc4snmp.IF-MIB.ifOutQLen_2", "_value": "4294967295", "metric_type": "Gauge32"},
45+
{"metric_name": "sc4snmp.FAKE_DATA_1", "_value": "4294967295", "metric_type": "Gauge32"}
46+
]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[
2+
{"metric_name": "sc4snmp.IF-MIB.ifNumber_0", "_value": "2", "metric_type": "Integer"},
3+
{"metric_name": "sc4snmp.IF-MIB.ifIndex_1", "_value": "1", "metric_type": "Integer"},
4+
{"metric_name": "sc4snmp.IF-MIB.ifDescr_1", "_value": "lo", "metric_type": "OctetString"},
5+
{"metric_name": "sc4snmp.IF-MIB.ifDescr_2", "_value": "eth0", "metric_type": "OctetString"}
6+
]
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[
2+
{"metric_name": "sc4snmp.IF-MIB.ifNumber_0", "_value": "2", "metric_type": "Integer"},
3+
{"metric_name": "sc4snmp.IF-MIB.ifIndex_1", "_value": "1", "metric_type": "Integer"},
4+
{"metric_name": "sc4snmp.IF-MIB.ifIndex_2", "_value": "2", "metric_type": "Integer"},
5+
{"metric_name": "sc4snmp.IF-MIB.ifDescr_1", "_value": "lo", "metric_type": "OctetString"}
6+
]

tests/test_interface_mib.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#
2+
# Copyright 2021 Splunk Inc.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
import logging
18+
import os
19+
from unittest import TestCase
20+
21+
from splunk_connect_for_snmp_poller.manager.realtime.interface_mib import (
22+
InterfaceMib,
23+
)
24+
from tests.test_utils import load_test_data
25+
26+
logger = logging.getLogger(__name__)
27+
28+
29+
def file_data_path(data_file_name):
30+
current_dir = os.getcwd()
31+
relative_data_file_path = os.path.join("mib_walk_data", data_file_name)
32+
if current_dir.endswith("tests"):
33+
file_path = os.path.join(current_dir, relative_data_file_path)
34+
else:
35+
file_path = os.path.join(current_dir, "tests", relative_data_file_path)
36+
return file_path
37+
38+
39+
class TestInterfaceMib(TestCase):
40+
def test_loaded_walk_data_for_if_mib(self):
41+
file_path = file_data_path("if_mib_walk.json")
42+
if_mibs = load_test_data(file_path)
43+
self.assertIsNotNone(if_mibs)
44+
45+
mibs = InterfaceMib(if_mibs)
46+
47+
logger.info(f"Total number of network interfaces: {mibs.network_interfaces()}")
48+
self.assertGreater(mibs.network_interfaces(), 0)
49+
50+
interface_names = mibs.network_interface_names()
51+
logger.info(f"Interfaces: {interface_names}")
52+
self.assertIsNotNone(interface_names)
53+
self.assertEqual(interface_names, ["lo", "eth0"])
54+
55+
inteface_indexes = mibs.network_indexes()
56+
logger.info(f"Interfaces: {inteface_indexes}")
57+
self.assertIsNotNone(inteface_indexes)
58+
self.assertEqual(inteface_indexes, ["1", "2"])
59+
60+
self.assertTrue(mibs.has_consistent_data())
61+
62+
def test_walk_data_with_wrong_number_of_interfaces(self):
63+
for invalid_data_file in [
64+
"if_mib_walk_invalid_networks.json",
65+
"if_mib_walk_invalid_indexes.json",
66+
]:
67+
file_path = file_data_path(invalid_data_file)
68+
if_mibs = load_test_data(file_path)
69+
self.assertIsNotNone(if_mibs)
70+
71+
mibs = InterfaceMib(if_mibs)
72+
self.assertFalse(mibs.has_consistent_data())
73+
totals = mibs.network_interfaces()
74+
indexes = len(mibs.network_indexes())
75+
names = len(mibs.network_interface_names())
76+
logger.info(
77+
f"Inconsistent data for {invalid_data_file} totals = {totals}, indexes = {indexes}, names = {names}"
78+
)

tests/test_utils.py

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
#
2+
# Copyright 2021 Splunk Inc.
3+
#
4+
# Licensed under the Apache License, Version 2.0 (the "License");
5+
# you may not use this file except in compliance with the License.
6+
# You may obtain a copy of the License at
7+
#
8+
# http://www.apache.org/licenses/LICENSE-2.0
9+
#
10+
# Unless required by applicable law or agreed to in writing, software
11+
# distributed under the License is distributed on an "AS IS" BASIS,
12+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
#
16+
17+
import json
18+
import os
19+
20+
21+
def load_test_data(data_file_path):
22+
if data_file_path and os.path.exists(data_file_path):
23+
with open(data_file_path, "r") as sample_simulator_walk_data:
24+
walk_data = json.load(sample_simulator_walk_data)
25+
return walk_data
26+
return None
27+
28+
29+
def fake_walk_handler(simulator_ifmib_walk_data):
30+
for translated_metric in simulator_ifmib_walk_data:
31+
yield translated_metric
32+
33+
34+
if __name__ == "__main__":
35+
file_path = "mib_walk_data/if_mib_walk.json"
36+
37+
for metric in fake_walk_handler(load_test_data(file_path)):
38+
print(f"{metric}")

0 commit comments

Comments
 (0)