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

Commit 25e6029

Browse files
fix: implement ttl for enricher (#195)
* fix: implement ttl feature for enricher * fix: update unit tests * fix: refactor mib_string_handler * fix: refactor and update unit tests * fix: provide workaround for old enricher logic to work * fix: update enricher logic * fix: delete tests of removed functions * fix: fix test_hec_sender tests * fix: fix test_static_config_data tests * fix: fix test config * fix: change position of celery import * fix: delete comment * fix: add check if we already have job in decit
1 parent 9166792 commit 25e6029

18 files changed

+311
-286
lines changed

splunk_connect_for_snmp_poller/manager/poller.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,34 @@
2626
from splunk_connect_for_snmp_poller.manager.poller_utilities import (
2727
automatic_onetime_task,
2828
automatic_realtime_job,
29+
create_poller_enricher_entry_key,
2930
create_poller_scheduler_entry_key,
3031
parse_inventory_file,
3132
return_database_id,
3233
update_enricher_config,
34+
update_inventory_record,
3335
)
3436
from splunk_connect_for_snmp_poller.manager.profile_matching import (
3537
assign_profiles_to_device,
3638
extract_desc,
3739
get_profiles,
3840
)
41+
from splunk_connect_for_snmp_poller.manager.task_utilities import translate_list_to_oid
3942
from splunk_connect_for_snmp_poller.manager.tasks import snmp_polling
4043
from splunk_connect_for_snmp_poller.manager.validator.inventory_validator import (
4144
DYNAMIC_PROFILE,
4245
)
43-
from splunk_connect_for_snmp_poller.manager.variables import onetime_if_walk
46+
from splunk_connect_for_snmp_poller.manager.variables import (
47+
enricher_existing_varbinds,
48+
enricher_if_mib,
49+
enricher_oid_family,
50+
onetime_if_walk,
51+
)
4452
from splunk_connect_for_snmp_poller.mongo import WalkedHostsRepository
4553
from splunk_connect_for_snmp_poller.utilities import (
54+
OnetimeFlag,
4655
file_was_modified,
56+
multi_key_lookup,
4757
parse_config_file,
4858
)
4959

@@ -57,6 +67,7 @@ def __init__(self, args, server_config):
5767
self._inventory_mod_time = 0
5868
self._config_mod_time = 0
5969
self._jobs_map = {}
70+
self._enricher_jobs_map = {}
6071
self._dynamic_jobs = set()
6172
self._mongo = WalkedHostsRepository(self._server_config["mongo"])
6273
self._local_snmp_engine = SnmpEngine()
@@ -173,6 +184,7 @@ def __add_enricher_to_a_host(self, current_enricher, ir, new_host=False):
173184
logger.info("Add enricher to a host")
174185
old_enricher = {} if new_host else self._old_enricher
175186
if current_enricher != {}:
187+
self.process_jobs_for_enricher(current_enricher, ir)
176188
update_enricher_config(
177189
old_enricher,
178190
current_enricher,
@@ -190,6 +202,13 @@ def delete_all_dynamic_entries_per_host(self, host):
190202
del self._jobs_map[entry_key]
191203
self._dynamic_jobs.remove(entry_key)
192204

205+
def delete_all_enricher_entries_per_host(self, host):
206+
for entry_key in list(self._enricher_jobs_map.keys()):
207+
if entry_key.split("#")[0] == host:
208+
logger.debug("Removing job for %s", entry_key)
209+
schedule.cancel_job(self._enricher_jobs_map.get(entry_key))
210+
del self._enricher_jobs_map[entry_key]
211+
193212
def clean_job_inventory(self, inventory_entry_keys: set, inventory_hosts: set):
194213
for entry_key in list(self._jobs_map):
195214
if entry_key not in inventory_entry_keys:
@@ -203,6 +222,7 @@ def clean_job_inventory(self, inventory_entry_keys: set, inventory_hosts: set):
203222
str(inventory_hosts),
204223
)
205224
self._mongo.delete_host(db_host_id)
225+
self.delete_all_enricher_entries_per_host(db_host_id)
206226
self._mongo.delete_onetime_walk_result(db_host_id)
207227
del self._jobs_map[entry_key]
208228

@@ -223,6 +243,32 @@ def is_conf_changed(self, entry_key, ir, old_conf):
223243
or frequency != interval
224244
)
225245

246+
def process_jobs_for_enricher(self, enricher, ir):
247+
ifmib_structure = multi_key_lookup(
248+
enricher, (enricher_oid_family, enricher_if_mib, enricher_existing_varbinds)
249+
)
250+
for varbind in ifmib_structure:
251+
ifmib_attr, ttl, = (
252+
varbind["id"],
253+
varbind["ttl"],
254+
)
255+
ifmib_oid = translate_list_to_oid(["IF-MIB", ifmib_attr])
256+
db_host = return_database_id(ir.host)
257+
entry_key = create_poller_enricher_entry_key(db_host, ifmib_attr)
258+
if entry_key in self._enricher_jobs_map:
259+
return
260+
logger.debug("Adding configuration for enricher job %s", entry_key)
261+
new_ir = update_inventory_record(ir, ifmib_oid, ttl)
262+
job_reference = schedule.every(int(ttl)).seconds.do(
263+
snmp_polling,
264+
new_ir.to_json(),
265+
self._server_config,
266+
self.__get_splunk_indexes(),
267+
None,
268+
OnetimeFlag.ENRICHER_UPDATE_WALK.value,
269+
)
270+
self._enricher_jobs_map[entry_key] = job_reference
271+
226272
def process_new_job(self, entry_key, ir, profiles):
227273
acquired_profiles = profiles.get("profiles")
228274
if acquired_profiles is not None and ir.profile not in acquired_profiles:

splunk_connect_for_snmp_poller/manager/poller_utilities.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515
#
16+
import copy
1617
import csv
1718
import logging.config
1819
import threading
@@ -379,8 +380,19 @@ def create_poller_scheduler_entry_key(host, profile):
379380
return host + "#" + profile
380381

381382

383+
def create_poller_enricher_entry_key(host, ifmib_attribute):
384+
return host + "#" + ifmib_attribute
385+
386+
382387
def return_database_id(host):
383388
if "#" in host:
384389
host = host.split("#")[0]
385390
_host, _port = parse_port(host)
386391
return f"{_host}:{_port}"
392+
393+
394+
def update_inventory_record(original_ir, oid, ttl):
395+
ir = copy.deepcopy(original_ir)
396+
ir.profile = f"{oid}.*"
397+
ir.frequency_str = ttl
398+
return ir

splunk_connect_for_snmp_poller/manager/realtime/interface_mib.py

Lines changed: 28 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
# See the License for the specific language governing permissions and
1414
# limitations under the License.
1515
#
16+
from operator import itemgetter
1617

1718

1819
# See http://www.net-snmp.org/docs/mibs/interfaces.html for additional implementation details
@@ -24,6 +25,7 @@ def extract_if_mib_only(translated_walk_result):
2425
InterfaceMib.METRIC_NAME_KEY,
2526
InterfaceMib.METRIC_VALUE_KEY,
2627
InterfaceMib.METRIC_TYPE_KEY,
28+
InterfaceMib.METRIC_PARSED_INDEX_KEY,
2729
)
2830
)
2931
and translation[InterfaceMib.METRIC_NAME_KEY].startswith(
@@ -37,67 +39,51 @@ class InterfaceMib:
3739
METRIC_NAME_KEY = "metric_name"
3840
METRIC_VALUE_KEY = "_value"
3941
METRIC_TYPE_KEY = "metric_type"
42+
METRIC_PARSED_INDEX_KEY = "parsed_index"
43+
METRIC_IF_INDEX_KEY = "ifIndex"
4044
IF_MIB_METRIC_PREFIX = "sc4snmp.IF-MIB."
41-
NON_METRIC_IDENTIFIER = "ifDescr"
4245
IF_MIB_DATA_MONGO_IDENTIFIER = "IF-MIB"
43-
IF_MIB_IF_NUMBER = "sc4snmp.IF-MIB.ifNumber_0"
44-
IF_MIB_IF_INDEX_BASE = "sc4snmp.IF-MIB.ifIndex_"
45-
IF_MIB_IF_DESCR_BASE = "sc4snmp.IF-MIB.ifDescr_"
4646

4747
def __init__(self, if_mib_metric_walk_data):
4848
self._if_mib_walk_data = extract_if_mib_only(if_mib_metric_walk_data)
4949
self._full_dictionary = self.__build_in_memory_dictionary()
50-
self._network_interfaces = self.__extract_number_of_network_interfaces()
51-
self._network_indexes = self.__extract_interface_indexes()
52-
self._network_interface_names = self.__extract_interface_names()
5350

5451
def unprocessed_if_mib_data(self):
5552
return self._if_mib_walk_data
5653

57-
def network_interfaces(self):
58-
return self._network_interfaces
59-
60-
def network_indexes(self):
61-
return self._network_indexes
62-
63-
def network_interface_names(self):
64-
return self._network_interface_names
65-
66-
def has_consistent_data(self):
67-
return self.network_interfaces() == len(self.network_indexes()) and len(
68-
self.network_indexes()
69-
) == len(self.network_interface_names())
70-
7154
def __build_in_memory_dictionary(self):
7255
all_keys = {}
7356
for mib in self.unprocessed_if_mib_data():
74-
all_keys[mib[InterfaceMib.METRIC_NAME_KEY]] = {
75-
InterfaceMib.METRIC_VALUE_KEY: mib[InterfaceMib.METRIC_VALUE_KEY],
76-
InterfaceMib.METRIC_TYPE_KEY: mib[InterfaceMib.METRIC_TYPE_KEY],
77-
}
57+
if mib[InterfaceMib.METRIC_NAME_KEY] not in all_keys:
58+
all_keys[mib[InterfaceMib.METRIC_NAME_KEY]] = []
59+
all_keys[mib[InterfaceMib.METRIC_NAME_KEY]].append(
60+
{
61+
InterfaceMib.METRIC_VALUE_KEY: mib[InterfaceMib.METRIC_VALUE_KEY],
62+
InterfaceMib.METRIC_TYPE_KEY: mib[InterfaceMib.METRIC_TYPE_KEY],
63+
InterfaceMib.METRIC_PARSED_INDEX_KEY: self.__get_ifindex(
64+
mib[InterfaceMib.METRIC_PARSED_INDEX_KEY]
65+
),
66+
}
67+
)
68+
sorted(
69+
all_keys[mib[InterfaceMib.METRIC_NAME_KEY]],
70+
key=itemgetter(InterfaceMib.METRIC_PARSED_INDEX_KEY),
71+
)
7872
return all_keys
7973

80-
def __extract_number_of_network_interfaces(self):
81-
if InterfaceMib.IF_MIB_IF_NUMBER in self._full_dictionary:
82-
if_number = self._full_dictionary[InterfaceMib.IF_MIB_IF_NUMBER]
83-
return int(if_number[InterfaceMib.METRIC_VALUE_KEY])
84-
return 0
74+
def __get_ifindex(self, ifindex_dictionary):
75+
return ifindex_dictionary[InterfaceMib.METRIC_IF_INDEX_KEY]
76+
77+
def _return_number_of_interfaces(self):
78+
for value_list in self._full_dictionary.values():
79+
return len(value_list)
8580

8681
def __extract_single_field_as_list(self, base_mib_metric_name):
8782
all_indexes = []
88-
for index in range(0, self.network_interfaces()):
89-
current = base_mib_metric_name + str(index + 1)
90-
if current in self._full_dictionary:
91-
all_indexes.append(
92-
self._full_dictionary[current][InterfaceMib.METRIC_VALUE_KEY]
93-
)
83+
if base_mib_metric_name in self._full_dictionary:
84+
for el in self._full_dictionary[base_mib_metric_name]:
85+
all_indexes.append(el[InterfaceMib.METRIC_VALUE_KEY])
9486
return all_indexes
9587

96-
def __extract_interface_indexes(self):
97-
return self.__extract_single_field_as_list(InterfaceMib.IF_MIB_IF_INDEX_BASE)
98-
99-
def __extract_interface_names(self):
100-
return self.__extract_single_field_as_list(InterfaceMib.IF_MIB_IF_DESCR_BASE)
101-
10288
def extract_custom_field(self, snmp_field_name):
10389
return self.__extract_single_field_as_list(snmp_field_name)

splunk_connect_for_snmp_poller/manager/realtime/oid_constant.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,4 @@ class OidConstant:
2020
# see https://www.alvestrand.no/objectid/1.3.6.1.html for a better understanding
2121
UNIVERSAL_BASE_OID = "1.3.6.1.*"
2222
IF_MIB = "1.3.6.1.2.1.2.*"
23+
IF_MIB_PREFIX = "1.3.6.1.2.1.2"

splunk_connect_for_snmp_poller/manager/static/interface_mib_utililities.py

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -58,28 +58,26 @@ def extract_network_interface_data_from_existing_config(config_as_dict):
5858
result = []
5959
if splunk_dimensions:
6060
for splunk_dimension in splunk_dimensions:
61-
for key in splunk_dimension.keys():
62-
result.append(
63-
{
64-
"oid_name": f"{InterfaceMib.IF_MIB_METRIC_PREFIX}{key}_",
65-
"splunk_dimension_name": splunk_dimension[key],
66-
}
67-
)
61+
result.append(
62+
{
63+
"oid_name": f"{InterfaceMib.IF_MIB_METRIC_PREFIX}{splunk_dimension['id']}",
64+
"splunk_dimension_name": splunk_dimension["name"],
65+
}
66+
)
6867
logger.debug(f"IF-MIB additional attributes for Splunk: {result}")
6968
return result
7069

7170

7271
def extract_network_interface_data_from_walk(config_as_dict, if_mib_metric_walk_data):
7372
result = []
7473
network_data = InterfaceMib(if_mib_metric_walk_data)
75-
if network_data.has_consistent_data():
76-
enricher_fields = extract_network_interface_data_from_existing_config(
77-
config_as_dict
78-
)
79-
for data in enricher_fields:
80-
splunk_dimension = data["splunk_dimension_name"]
81-
current_result = network_data.extract_custom_field(data["oid_name"])
82-
if current_result:
83-
result.append({f"{splunk_dimension}": current_result})
74+
enricher_fields = extract_network_interface_data_from_existing_config(
75+
config_as_dict
76+
)
77+
for data in enricher_fields:
78+
splunk_dimension = data["splunk_dimension_name"]
79+
current_result = network_data.extract_custom_field(data["oid_name"])
80+
if current_result:
81+
result.append({f"{splunk_dimension}": current_result})
8482

8583
return result

splunk_connect_for_snmp_poller/manager/static/mib_enricher.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,13 @@ def extract_current_index_from_metric(parsed_index):
2828
return None
2929

3030

31-
def extract_dimension_name_and_value(dimension, index):
32-
all_keys = dimension.keys()
33-
if len(all_keys) == 1 and index is not None:
34-
dimension_name = [key for key in all_keys][0]
35-
dimension_values = dimension[dimension_name]
36-
# We need to enrich only table data. Static values like IF-MIB::ifNumber.0 won't be enriched (it doesn't
37-
# make sense for those)
31+
def extract_dimension_name_and_value(dimension_key, dimension_dict, index):
32+
dimension_values = dimension_dict[dimension_key]
33+
# We need to enrich only table data. Static values like IF-MIB::ifNumber.0 won't be enriched (it doesn't
34+
# make sense for those)
35+
if index is not None:
3836
if 0 <= index < len(dimension_values):
39-
return dimension_name, dimension_values[index]
37+
return dimension_key, dimension_values[index]
4038
return None, None
4139

4240

@@ -67,7 +65,9 @@ def __enrich_if_mib_existing(self, metric_name, parsed_index):
6765
(
6866
dimension_name,
6967
dimension_value,
70-
) = extract_dimension_name_and_value(dimension, index)
68+
) = extract_dimension_name_and_value(
69+
dimension, if_mib_record, index
70+
)
7171
if dimension_name:
7272
result.append({dimension_name: dimension_value})
7373
return result

splunk_connect_for_snmp_poller/manager/task_utilities.py

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -182,28 +182,15 @@ def mib_string_handler(mib_list: list) -> VarbindCollection:
182182
if not mib_list:
183183
return VarbindCollection(get=[], bulk=[])
184184
get_list, bulk_list = [], []
185-
mibBuilder = builder.MibBuilder()
186-
mibViewController = view.MibViewController(mibBuilder)
187-
config = {"sources": [os.environ["MIBS_FILES_URL"]]}
188-
compiler.addMibCompiler(mibBuilder, **config)
189185
for mib_string in mib_list:
190186
try:
191-
if len(mib_string) == 3:
192-
# convert mib string to oid
193-
oid = ObjectIdentity(
194-
mib_string[0], mib_string[1], mib_string[2]
195-
).resolveWithMib(mibViewController)
196-
logger.debug(f"[-] oid: {oid}")
187+
oid = translate_list_to_oid(mib_string)
188+
logger.debug(f"[-] oid: {oid}")
189+
mib_string_length = len(mib_string)
190+
if mib_string_length == 3:
197191
get_list.append(ObjectType(oid))
198-
199-
elif len(mib_string) == 2:
200-
# convert mib string to oid
201-
oid = ObjectIdentity(mib_string[0], mib_string[1]).resolveWithMib(
202-
mibViewController
203-
)
204-
logger.debug(f"[-] oid: {oid}")
192+
elif mib_string_length < 3:
205193
bulk_list.append(ObjectType(oid))
206-
207194
else:
208195
raise Exception(
209196
f"Invalid mib string - {mib_string}."
@@ -217,6 +204,15 @@ def mib_string_handler(mib_list: list) -> VarbindCollection:
217204
return VarbindCollection(get=get_list, bulk=bulk_list)
218205

219206

207+
def translate_list_to_oid(mib_string):
208+
mibBuilder = builder.MibBuilder()
209+
mibViewController = view.MibViewController(mibBuilder)
210+
config = {"sources": [os.environ["MIBS_FILES_URL"]]}
211+
compiler.addMibCompiler(mibBuilder, **config)
212+
oid = ObjectIdentity(*mib_string).resolveWithMib(mibViewController)
213+
return oid
214+
215+
220216
def snmp_get_handler(
221217
mongo_connection,
222218
enricher_presence,

0 commit comments

Comments
 (0)