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

Commit

Permalink
impr: better exception handling for mib request
Browse files Browse the repository at this point in the history
  • Loading branch information
okuzhel committed Oct 7, 2021
1 parent 51d7678 commit a41fab9
Show file tree
Hide file tree
Showing 4 changed files with 81 additions and 84 deletions.
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,4 @@ repos:
args: [-ignore, 'property ".+" is not defined in object type']
language: script
types: ["yaml"]
files: ^.github/workflows/
files: ^.github/workflows/
71 changes: 47 additions & 24 deletions splunk_connect_for_snmp_poller/manager/mib_server_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,37 @@

import aiohttp
import backoff as backoff
import requests as requests
from aiohttp import ClientSession

from splunk_connect_for_snmp_poller.utilities import format_value_for_mib_server

logger = logging.getLogger(__name__)


class SharedException(Exception):
"""Raised when the input value is too large"""

def __init__(self, msg="Default Shared Exception occurred.", *args):
super().__init__(msg, *args)


async def get_translation(var_binds, mib_server_url, data_format):
"""
@param var_binds: var_binds object getting from SNMP agents
@param mib_server_url: URL of SNMP MIB server
@return: translated string
"""
# Construct the payload
payload = await prepare_payload(var_binds)

try:
return await get_url(mib_server_url, payload, data_format)
except requests.Timeout:
logger.exception("Time out occurred during call to MIB Server")
raise
except requests.ConnectionError:
logger.exception("Can not connect to MIB Server for url - %s", mib_server_url)
raise
except Exception:
logger.exception("Error getting translation from MIB Server")
raise


async def prepare_payload(var_binds):
payload = {}
var_binds_list = []
# *TODO*: Below differs a bit between poller and trap!

for name, val in var_binds:
var_bind = {
"oid": str(name),
Expand All @@ -54,28 +60,45 @@ async def get_translation(var_binds, mib_server_url, data_format):
var_binds_list.append(var_bind)
payload["var_binds"] = var_binds_list
payload = json.dumps(payload)
return payload


# Send the POST request to mib server
@backoff.on_exception(backoff.expo, aiohttp.ClientError, max_tries=3)
async def get_url(mib_server_url, payload, data_format):
headers = {"Content-type": "application/json"}
endpoint = "translation"
translation_url = os.path.join(mib_server_url.strip("/"), endpoint)
logger.debug(f"[-] translation_url: {translation_url}")

try:
return await get_url(translation_url, headers, payload, data_format)
except Exception as e:
logger.error(f"Error getting translation from MIB Server: {e}")
raise SharedException(f"Error getting translation from MIB Server: {e}")
logger.debug("[-] translation_url: %s", translation_url)


@backoff.on_exception(backoff.expo, aiohttp.ClientError, max_tries=3)
async def get_url(url, headers, payload, data_format):
async with ClientSession(raise_for_status=True) as session:
resp = await session.post(
url,
translation_url,
headers=headers,
data=payload,
params={"data_format": data_format},
timeout=1,
timeout=5,
)
return await resp.text()


# 1.3.6.1.2.1.2.2.1.4.1|Integer|16436|16436|True
# 1.3.6.1.2.1.1.6.0|DisplayString|San Francisco, California, United States|San Francisco, California, United States|True
# 1.3.6.1.2.1.2.2.1.6.2|OctetString|<null>ybù@|0x00127962f940|False
# 1.3.6.1.2.1.1.9.1.2.7|ObjectIdentity|1.3.6.1.2.1.50|SNMPv2-SMI::mib-2.50|False
# 1.3.6.1.2.1.6.13.1.4.195.218.254.105.51684.194.67.10.226.22|IpAddress|ÂCâ|194.67.10.226|False
# 1.3.6.1.2.1.25.3.2.1.6.1025|Counter32|0|0|True
# 1.3.6.1.2.1.31.1.1.1.15.2|Gauge32|100|100|True
# 1.3.6.1.2.1.1.3.0|TimeTicks|148271768|148271768|True
# 1.3.6.1.4.1.2021.10.1.6.1|Opaque|Ÿx>ë…|0x9f78043eeb851f|False
# 1.3.6.1.2.1.31.1.1.1.10.1|Counter64|453477588|453477588|True
#
# As you can see, for most types str(value) == value.prettyPrint(), however:
# - for Opaque, IpAddress, and OctetString we need to use prettyPrint(), otherwise the data is rubbish
# - any other type should use str() before sending data to MIB-server


def format_value_for_mib_server(value, value_type):
if value_type in ("OctetString", "IpAddress", "Opaque"):
return value.prettyPrint()
else:
return str(value)
71 changes: 33 additions & 38 deletions splunk_connect_for_snmp_poller/manager/task_utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,41 +83,12 @@ async def get_translated_string(mib_server_url, varBinds, return_multimetric=Fal
@return result: formated string ready to be sent to Splunk HEC
@return is_metric: boolean, metric data flag
"""
logger.debug(f"I got these var binds: {varBinds}")
# Get Original varbinds as backup in case the mib-server is unreachable
try:
for name, val in varBinds:
# Original oid
# TODO Discuss: should we return the original oid
# if the mib server is unreachable
# should we format it align with the format of the translated one
# result = "{} = {}".format(name.prettyPrint(), val.prettyPrint())

# check if this is metric data
is_metric = is_metric_data(val.prettyPrint())
if is_metric:
result = {
# Prefix the metric for ux in analytics workspace
# Splunk uses . rather than :: for hierarchy.
# if the metric name contains a . replace with _
"metric_name": f'sc4snmp.{name.prettyPrint().replace(".", "_").replace("::", ".")}',
"_value": val.prettyPrint(),
}
result = json.dumps(result)
else:
result = '{oid}="{value}"'.format(
oid=name.prettyPrint(), value=val.prettyPrint()
)
logger.debug("Our result is - %s", result)
except Exception as e:
logger.error(
f"Exception occurred while logging varBinds name & value. Exception: {e}"
)
logger.debug(f"Getting translation for the following varBinds: {varBinds}")
is_metric, result = await result_without_translation(varBinds)

# Override the varBinds string with translated varBinds string
try:
data_format = _get_data_format(is_metric, return_multimetric)
logger.debug(f"result before translated -- is_metric={is_metric}\n{result}")
result = await get_translation(varBinds, mib_server_url, data_format)
if data_format == "MULTIMETRIC":
result = json.loads(result)["metric"]
Expand All @@ -132,21 +103,45 @@ async def get_translated_string(mib_server_url, varBinds, return_multimetric=Fal
is_metric = False
data_format = _get_data_format(is_metric, return_multimetric)
result = await get_translation(varBinds, mib_server_url, data_format)
except Exception as e:
logger.error(f"Could not perform translation. Exception: {e}")
except Exception:
logger.exception("Could not perform translation. Returning original varBinds")
logger.debug(f"final result -- metric: {is_metric}\n{result}")
return result, is_metric


async def result_without_translation(var_binds):
# Get Original varbinds as backup in case the mib-server is unreachable
for name, val in var_binds:
# Original oid
# TODO Discuss: should we return the original oid
# if the mib server is unreachable
# should we format it align with the format of the translated one
# result = "{} = {}".format(name.prettyPrint(), val.prettyPrint())

# check if this is metric data
is_metric = is_metric_data(val.prettyPrint())
if is_metric:
result = {
# Prefix the metric for ux in analytics workspace
# Splunk uses . rather than :: for hierarchy.
# if the metric name contains a . replace with _
"metric_name": f'sc4snmp.{name.prettyPrint().replace(".", "_").replace("::", ".")}',
"_value": val.prettyPrint(),
}
result = json.dumps(result)
else:
result = '{oid}="{value}"'.format(
oid=name.prettyPrint(), value=val.prettyPrint()
)
logger.debug("Our result is_metric - %s and string - %s", is_metric, result)
return is_metric, result


def _get_data_format(is_metric: bool, return_multimetric: bool):
# if is_metric is true, return_multimetric doesn't matter
if is_metric:
return "METRIC"
else:
if return_multimetric:
return "MULTIMETRIC"
else:
return "TEXT"
return "MULTIMETRIC" if return_multimetric else "TEXT"


def mib_string_handler(mib_list: list) -> VarbindCollection:
Expand Down
21 changes: 0 additions & 21 deletions splunk_connect_for_snmp_poller/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,27 +119,6 @@ def parse_config_file(config_file_path):
return server_config


# 1.3.6.1.2.1.2.2.1.4.1|Integer|16436|16436|True
# 1.3.6.1.2.1.1.6.0|DisplayString|San Francisco, California, United States|San Francisco, California, United States|True
# 1.3.6.1.2.1.2.2.1.6.2|OctetString|<null>ybù@|0x00127962f940|False
# 1.3.6.1.2.1.1.9.1.2.7|ObjectIdentity|1.3.6.1.2.1.50|SNMPv2-SMI::mib-2.50|False
# 1.3.6.1.2.1.6.13.1.4.195.218.254.105.51684.194.67.10.226.22|IpAddress|ÂCâ|194.67.10.226|False
# 1.3.6.1.2.1.25.3.2.1.6.1025|Counter32|0|0|True
# 1.3.6.1.2.1.31.1.1.1.15.2|Gauge32|100|100|True
# 1.3.6.1.2.1.1.3.0|TimeTicks|148271768|148271768|True
# 1.3.6.1.4.1.2021.10.1.6.1|Opaque|Ÿx>ë…|0x9f78043eeb851f|False
# 1.3.6.1.2.1.31.1.1.1.10.1|Counter64|453477588|453477588|True
#
# As you can see, for most types str(value) == value.prettyPrint(), however:
# - for Opaque, IpAddress, and OctetString we need to use prettyPrint(), otherwise the data is rubbish
# - any other type should use str() before sending data to MIB-server
def format_value_for_mib_server(value, value_type):
if value_type in ("OctetString", "IpAddress", "Opaque"):
return value.prettyPrint()
else:
return str(value)


def file_was_modified(file_path, last_mod_time):
if os.stat(file_path, follow_symlinks=True).st_mtime > last_mod_time:
logger.info(f"[-] Change in {file_path} detected, reloading")
Expand Down

0 comments on commit a41fab9

Please sign in to comment.