diff options
39 files changed, 2269 insertions, 638 deletions
@@ -1,35 +1,51 @@ -# Use an official Python runtime as a base image -FROM python:3.6 +# Use an official pypy runtime as a base image +FROM pypy:3 ENV INSROOT /opt/app ENV APPUSER snmptrap ENV APPDIR ${INSROOT}/${APPUSER} +# add group and user: ubuntu RUN useradd -d ${APPDIR} ${APPUSER} +# +# add group and user: ubuntu - for when DCAE platform evolves and runs as NON-ROOT!!! +# RUN addgroup -g 1000 -S ${APPUSER} && \ +# adduser -u 1000 -S ${APPUSER} -G ${APPUSER} WORKDIR ${APPDIR} -EXPOSE 162:162/udp +EXPOSE 162:6162/udp # Copy the current directory contents into the container at ${APPDIR} COPY ./snmptrap/ ./bin/ COPY ./etc/ ./etc/ COPY requirements.txt ./ -RUN pip install -r requirements.txt +# +# RUN pip install -r requirements.txt +RUN pip install --trusted-host files.pythonhosted.org -r requirements.txt + +RUN mkdir -p /etc \ + && mkdir -p /etc/apt +RUN apt-get update -y && apt-get install -y jq bc vim RUN mkdir -p ${APPDIR}/data \ && mkdir -p ${APPDIR}/logs \ && mkdir -p ${APPDIR}/tmp \ - && chown -R ${APPUSER}:${APPUSER} ${APPDIR} \ +# && chown -R ${APPUSER}:${APPUSER} ${APPDIR} \ && chmod a+w ${APPDIR}/data \ && chmod a+w ${APPDIR}/logs \ && chmod a+w ${APPDIR}/tmp \ && chmod 500 ${APPDIR}/etc \ - && chmod 500 ${APPDIR}/bin/snmptrapd.sh + && chmod 500 ${APPDIR}/bin/snmptrapd.sh \ + && chmod 500 ${APPDIR}/bin/scheduler.sh \ +# && ln -s /usr/bin/python3 /usr/bin/python \ + && rm ${APPDIR}/requirements.txt -USER ${APPUSER} +# run everything from here on as $APPUSER, NOT ROOT! +#USER ${APPUSER} +# map logs directory to external volume VOLUME ${APPDIR}/logs -# Run run_policy.sh when the container launches +# launch container CMD ["./bin/snmptrapd.sh", "start"] diff --git a/LICENSE.txt b/LICENSE.txt index 22d3915..fef50eb 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,7 +1,7 @@ ============LICENSE_START======================================================= org.onap.dcae ================================================================================ -Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. +Copyright (c) 2017-2020 AT&T Intellectual Property. All rights reserved. ================================================================================ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/etc/snmptrapd.json b/etc/snmptrapd.json index 8281a35..cfaebe3 100644 --- a/etc/snmptrapd.json +++ b/etc/snmptrapd.json @@ -1,6 +1,6 @@ { "snmptrapd": { - "version": "1.4.0", + "version": "2.0", "title": "ONAP SNMP Trap Receiver" }, "protocols": { @@ -11,12 +11,12 @@ "ipv6_port": 6162 }, "cache": { - "dns_cache_ttl_seconds": 60 + "dns_cache_ttl_seconds": 10800 }, "publisher": { - "http_milliseconds_timeout": 1500, - "http_retries": 3, - "http_milliseconds_between_retries": 750, + "http_milliseconds_timeout": 500, + "http_retries": 2, + "http_milliseconds_between_retries": 250, "http_primary_publisher": "true", "http_peer_publisher": "unavailable", "max_traps_between_publishes": 10, @@ -49,9 +49,38 @@ "eelf_debug": "debug.log", "eelf_audit": "audit.log", "eelf_metrics": "metrics.log", - "roll_frequency": "day", + "roll_frequency": "hour", "minimum_severity_to_log": 3 }, + "check_hb_traps": { + "trap_thr": 900, + "hb_thr": 900, + "hb_notify_oid": ".1.3.6.1.4.1.74.2.46.12.1.1" + }, + "trap_config": { + "sw_interval_in_seconds": 60, + "metric_log_notification_threshold_pct": 25, + "notify_oids": [ + { + "oid": ".1.3.6.1.4.1.9.0.1", + "sw_high_water_in_interval": 100, + "sw_low_water_in_interval": 5, + "category": "logonly" + }, + { + "oid": ".1.3.6.1.4.1.9.0.2", + "sw_high_water_in_interval": 200, + "sw_low_water_in_interval": 10, + "category": "logonly" + }, + { + "oid": ".1.3.6.1.4.1.9.0.3", + "sw_high_water_in_interval": 300, + "sw_low_water_in_interval": 15, + "category": "logonly" + } + ] + }, "snmpv3_config": { "usm_users": [ { diff --git a/etc/version.dat b/etc/version.dat new file mode 100644 index 0000000..c1f0994 --- /dev/null +++ b/etc/version.dat @@ -0,0 +1 @@ +ONAP snmptrap v2.0.3 (built Wed Nov 20 11:48:38 EST 2019) @@ -30,7 +30,7 @@ ECOMP is a trademark and service mark of AT&T Intellectual Property. <groupId>org.onap.dcaegen2.collectors</groupId> <artifactId>snmptrap</artifactId> <name>dcaegen2-collectors-snmptrap</name> - <version>1.4.0-SNAPSHOT</version> + <version>2.0.3-SNAPSHOT</version> <url>http://maven.apache.org</url> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> @@ -27,12 +27,12 @@ from setuptools import setup, find_packages setup( name = "snmptrap", description = "snmp trap receiver for ONAP docker image", - version = "1.4.0", + version = "2.0.3", packages=find_packages(), install_requires=[ - "pysnmp==4.4.2", - "requests==2.18.3", - "onap_dcae_cbs_docker_client==1.0.1" + "pysnmp==4.4.2", + "requests==2.18.3", + "onap_dcae_cbs_docker_client==2.1.0" ], author = "Dave L", author_email = "dl3158@att.com", diff --git a/snmptrap/__init__.py b/snmptrap/__init__.py index 1875bf6..8b4cd11 100644 --- a/snmptrap/__init__.py +++ b/snmptrap/__init__.py @@ -1,5 +1,5 @@ -# ================================================================================ -# Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. +# ============LICENSE_START======================================================= +# Copyright (c) 2017-2020 AT&T Intellectual Property. All rights reserved. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,9 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============LICENSE_END========================================================= -# -# ECOMP is a trademark and service mark of AT&T Intellectual Property. - # empty __init__.py so that pytest can add correct path to coverage report, -- per pytest # best practice guideline diff --git a/snmptrap/healthcheck.sh b/snmptrap/healthcheck.sh index 371c18a..8fe063b 100755 --- a/snmptrap/healthcheck.sh +++ b/snmptrap/healthcheck.sh @@ -1,11 +1,12 @@ -# ================================================================================ -# Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. +#!/bin/bash +# ============LICENSE_START======================================================= +# Copyright (c) 2018-2020 AT&T Intellectual Property. All rights reserved. # ================================================================================ # 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 +# 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, @@ -13,7 +14,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============LICENSE_END========================================================= -#!/bin/bash # Health check script for service change handler @@ -23,5 +23,3 @@ # run standard status command, exit with results /opt/app/snmptrap/bin/snmptrapd.sh status > /dev/null 2>&1 -ret=$? -exit ${ret} diff --git a/snmptrap/mod/__init__.py b/snmptrap/mod/__init__.py index 1875bf6..b0cfa95 100644 --- a/snmptrap/mod/__init__.py +++ b/snmptrap/mod/__init__.py @@ -1,5 +1,5 @@ -# ================================================================================ -# Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. +# ============LICENSE_START======================================================= +# Copyright (c) 2018-2020 AT&T Intellectual Property. All rights reserved. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,9 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============LICENSE_END========================================================= -# -# ECOMP is a trademark and service mark of AT&T Intellectual Property. - # empty __init__.py so that pytest can add correct path to coverage report, -- per pytest # best practice guideline diff --git a/snmptrap/mod/trapd_exit.py b/snmptrap/mod/trapd_exit.py index ef7a2ae..6d1ea45 100644 --- a/snmptrap/mod/trapd_exit.py +++ b/snmptrap/mod/trapd_exit.py @@ -1,7 +1,5 @@ # ============LICENSE_START======================================================= -# org.onap.dcae -# ================================================================================ -# Copyright (c) 2017-2018 AT&T Intellectual Property. All rights reserved. +# Copyright (c) 2017-2020 AT&T Intellectual Property. All rights reserved. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,9 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============LICENSE_END========================================================= -# -# ECOMP is a trademark and service mark of AT&T Intellectual Property. -# """ trapc_exit_snmptrapd is responsible for removing any existing runtime PID file, and exiting with the provided (param 1) exit code diff --git a/snmptrap/mod/trapd_get_cbs_config.py b/snmptrap/mod/trapd_get_cbs_config.py index 8bdff15..272aabe 100644 --- a/snmptrap/mod/trapd_get_cbs_config.py +++ b/snmptrap/mod/trapd_get_cbs_config.py @@ -1,7 +1,5 @@ # ============LICENSE_START======================================================= -# org.onap.dcae -# ================================================================================ -# Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. +# Copyright (c) 2018-2020 AT&T Intellectual Property. All rights reserved. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,9 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============LICENSE_END========================================================= -# -# ECOMP is a trademark and service mark of AT&T Intellectual Property. -# """ Look for CBS broker and return application config; if not present, look for env variable that specifies JSON equiv of CBS config (typically used for @@ -74,47 +69,52 @@ def get_cbs_config(): except Exception as e: msg = "CBS_SIM_JSON not defined - FATAL ERROR, exiting" stdout_logger(msg) - cleanup_and_exit(1,None) + cleanup_and_exit(1, None) if _cbs_sim_json_file == "None": msg = "CBS_SIM_JSON not defined - FATAL ERROR, exiting" stdout_logger(msg) - cleanup_and_exit(1,None) + cleanup_and_exit(1, None) else: msg = ("ONAP controller override specified via CBS_SIM_JSON: %s" % _cbs_sim_json_file) stdout_logger(msg) try: tds.c_config = json.load(open(_cbs_sim_json_file)) - msg = ("%s loaded and parsed successfully" % _cbs_sim_json_file) + msg = ("%s loaded and parsed successfully" % + _cbs_sim_json_file) stdout_logger(msg) except Exception as e: msg = "Unable to load CBS_SIM_JSON " + _cbs_sim_json_file + \ " (invalid json?) - FATAL ERROR, exiting" stdout_logger(msg) - cleanup_and_exit(1,None) + cleanup_and_exit(1, None) + + # display consul config returned, regardless of source + msg = "cbs config: %s" % json.dumps(tds.c_config) + stdout_logger(msg) # recalc timeout, set default if not present try: - tds.timeout_seconds = tds.c_config['publisher']['http_milliseconds_timeout'] / 1000.0 + tds.timeout_seconds = float(tds.c_config['publisher']['http_milliseconds_timeout'] / 1000.0) except Exception as e: - tds.timeout_seconds = 1.5 + tds.timeout_seconds = float(1.5) # recalc seconds_between_retries, set default if not present try: - tds.seconds_between_retries = tds.c_config['publisher']['http_milliseconds_between_retries'] / 1000.0 + tds.seconds_between_retries = float(tds.c_config['publisher']['http_milliseconds_between_retries'] / 1000.0) except Exception as e: - tds.seconds_between_retries = .750 + tds.seconds_between_retries = float(.750) # recalc min_severity_to_log, set default if not present try: - tds.minimum_severity_to_log = tds.c_config['files']['minimum_severity_to_log'] + tds.minimum_severity_to_log = int(tds.c_config['files']['minimum_severity_to_log']) except Exception as e: - tds.minimum_severity_to_log = 3 + tds.minimum_severity_to_log = int(3) try: - tds.publisher_retries = tds.c_config['publisher']['http_retries'] + tds.publisher_retries = int(tds.c_config['publisher']['http_retries']) except Exception as e: - tds.publisher_retries = 2 + tds.publisher_retries = int(2) return True diff --git a/snmptrap/mod/trapd_http_session.py b/snmptrap/mod/trapd_http_session.py index 3efca21..14abb21 100644 --- a/snmptrap/mod/trapd_http_session.py +++ b/snmptrap/mod/trapd_http_session.py @@ -1,7 +1,5 @@ # ============LICENSE_START======================================================= -# org.onap.dcae -# ================================================================================ -# Copyright (c) 2017-2018 AT&T Intellectual Property. All rights reserved. +# Copyright (c) 2017-2020 AT&T Intellectual Property. All rights reserved. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,9 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============LICENSE_END========================================================= -# -# ECOMP is a trademark and service mark of AT&T Intellectual Property. -# """ trapd_http_session establishes an http session for future use in publishing messages to the dmaap cluster. @@ -79,7 +74,6 @@ def close_session_obj(_loc_http_requ_session): none """ - # Close existing session if present. if _loc_http_requ_session is not None: try: @@ -87,7 +81,8 @@ def close_session_obj(_loc_http_requ_session): return True except Exception as e: msg = "Unable to close current http session - FATAL ERROR, exiting" - ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_FATAL, tds.CODE_GENERAL, msg) + ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_FATAL, + tds.CODE_GENERAL, msg) stdout_logger(msg) cleanup_and_exit(1, tds.pid_file_name) @@ -111,7 +106,6 @@ def reset_session_obj(_loc_http_requ_session): none """ - # close existing http_requ_session if present ret = close_session_obj(_loc_http_requ_session) diff --git a/snmptrap/mod/trapd_io.py b/snmptrap/mod/trapd_io.py index d079cbe..991bcbd 100644 --- a/snmptrap/mod/trapd_io.py +++ b/snmptrap/mod/trapd_io.py @@ -1,7 +1,5 @@ -# ============LICENSE_START=======================================================) -# org.onap.dcae -# ================================================================================ -# Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. +# ============LICENSE_START======================================================= +# Copyright (c) 2018-2020 AT&T Intellectual Property. All rights reserved. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,9 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============LICENSE_END========================================================= -# -# ECOMP is a trademark and service mark of AT&T Intellectual Property. -# """ """ @@ -180,7 +175,7 @@ def roll_file(_loc_file_name): _msg = ("ERROR: Unable to rename %s to %s" % (_loc_file_name, _loc_file_name_bak)) - ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_CRIT, + ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_ERROR, tds.CODE_GENERAL, _msg) return False @@ -231,6 +226,7 @@ def close_file(_loc_fd, _loc_filename): # is released for python via LOG-161 # # # # # # # # # # ## # # # # # # # + def ecomp_logger(_log_type, _sev, _error_code, _msg): """ Log to ecomp-style logfiles. Logs include: @@ -309,10 +305,10 @@ def ecomp_logger(_log_type, _sev, _error_code, _msg): # above were various attempts at setting time string found in other # libs; instead, let's keep it real: - t_out = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S,%f")[:-3] + t_out = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%f")[:-3] calling_fx = inspect.stack()[1][3] - # DLFM: this entire module is a hack to override concept of prog logging + # DLFM: this entire module is a xyz to override concept of prog logging # written across multiple files (???), making diagnostics IMPOSSIBLE! # Hoping to leverage ONAP logging libraries & standards when available @@ -331,14 +327,19 @@ def ecomp_logger(_log_type, _sev, _error_code, _msg): if _sev >= tds.minimum_severity_to_log: # log to appropriate eelf log (different files ??) if _log_type == tds.LOG_TYPE_ERROR: - _out_rec = ('%s|%s|%s|%s|%s|%s|%s|%s|%s' - % (calling_fx, "snmptrapd", unused, unused, unused, tds.SEV_TYPES[_sev], _error_code, unused, _msg)) + # _out_rec = ('%s|%s|%s|%s|%s|%s|%s|%s|%s' + # _out_rec = ('%s|%s|%s|%s|%s|%s|%s|%s|%s' + # % (calling_fx, "snmptrapd", unused, unused, unused, tds.SEV_TYPES[_sev], _error_code, unused, _msg)) + _out_rec = ('%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s' + % (unused, unused, calling_fx, unused, "snmptrapd", unused, unused, unused, unused, unused, unused, unused, tds.SEV_TYPES[_sev], unused, unused, unused, unused, unused, unused, unused, unused, unused, unused, unused, unused, unused, _msg)) try: - tds.eelf_error_fd.write('%s|%s\n' % (t_out, str(_out_rec))) + tds.eelf_error_fd.write('%s|%s|%s\n' % (t_out, t_out, str(_out_rec))) except Exception as e: stdout_logger(str(_out_rec)) elif _log_type == tds.LOG_TYPE_AUDIT: # log message in AUDIT format + # _out_rec = ('%s|%s|%s|%s|%s|%s|%s|%s|%s' + # % (calling_fx, "snmptrapd", unused, unused, unused, tds.SEV_TYPES[_sev], _error_code, unused, _msg)) _out_rec = ('%s|%s|%s|%s|%s|%s|%s|%s|%s' % (calling_fx, "snmptrapd", unused, unused, unused, tds.SEV_TYPES[_sev], _error_code, unused, _msg)) try: @@ -358,8 +359,8 @@ def ecomp_logger(_log_type, _sev, _error_code, _msg): # DLFM: too much I/O !!! # always write to debug; we need ONE logfile that has time-sequence full view !!! # log message in DEBUG format - _out_rec = ("%s|%s|%s|%s|%s|%s|%s|%s|%s" - % (calling_fx, "snmptrapd", unused, unused, unused, tds.SEV_TYPES[_sev], _error_code, unused, _msg)) + _out_rec = ("%s|%s|%s|%s|%s|%s|%s|%s|%s|%s" + % (unused, calling_fx, "snmptrapd", unused, unused, unused, tds.SEV_TYPES[_sev], _error_code, unused, _msg)) try: tds.eelf_debug_fd.write('%s|%s\n' % (t_out, str(_out_rec))) except Exception as e: diff --git a/snmptrap/mod/trapd_runtime_pid.py b/snmptrap/mod/trapd_runtime_pid.py index c6ef76e..74668f5 100644 --- a/snmptrap/mod/trapd_runtime_pid.py +++ b/snmptrap/mod/trapd_runtime_pid.py @@ -1,7 +1,5 @@ # ============LICENSE_START======================================================= -# org.onap.dcae -# ================================================================================ -# Copyright (c) 2017-2018 AT&T Intellectual Property. All rights reserved. +# Copyright (c) 2017-2020 AT&T Intellectual Property. All rights reserved. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,9 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============LICENSE_END========================================================= -# -# ECOMP is a trademark and service mark of AT&T Intellectual Property. -# """ trapd_runtime_pid maintains a 'PID file' (file that contains the PID of currently running trap receiver) diff --git a/snmptrap/mod/trapd_settings.py b/snmptrap/mod/trapd_settings.py index 308a2f2..41dc18f 100644 --- a/snmptrap/mod/trapd_settings.py +++ b/snmptrap/mod/trapd_settings.py @@ -1,7 +1,5 @@ -# ============LICENSE_START=======================================================) -# org.onap.dcae -# ================================================================================ -# Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. +# ============LICENSE_START======================================================= +# Copyright (c) 2018-2020 AT&T Intellectual Property. All rights reserved. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,9 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============LICENSE_END========================================================= -# -# ECOMP is a trademark and service mark of AT&T Intellectual Property. -# """ """ @@ -81,10 +76,12 @@ def init(): first_varbind = True global trap_dict trap_dict = {} - global all_traps_str - all_traps_str = "" + global all_traps_json_str + all_traps_json_str = "" global all_vb_json_str all_vb_json_str = "" + global all_vb_str + all_vb_str = "" global trap_uuids_in_buffer trap_uuids_in_buffer = "" # </trap and varbind dictionaries> @@ -138,8 +135,6 @@ def init(): global sw_count_dict sw_count_dict = {} - global sw_interval_in_seconds - sw_interval_in_seconds = 60 # </stormwatch > # <logging types and severities> @@ -161,14 +156,14 @@ def init(): global SEV_DETAILED global SEV_INFO global SEV_WARN - global SEV_CRIT + global SEV_ERROR global SEV_FATAL - SEV_TYPES = ["none", "DETAILED", "INFO", "WARN", "CRITICAL", "FATAL"] + SEV_TYPES = ["none", "DETAILED", "INFO", "WARN", "ERROR", "FATAL"] SEV_NONE = 0 SEV_DETAILED = 1 SEV_INFO = 2 SEV_WARN = 3 - SEV_CRIT = 4 + SEV_ERROR = 4 SEV_FATAL = 5 global CODE_GENERAL diff --git a/snmptrap/mod/trapd_snmpv3.py b/snmptrap/mod/trapd_snmpv3.py index 5c0382b..b421ae1 100644 --- a/snmptrap/mod/trapd_snmpv3.py +++ b/snmptrap/mod/trapd_snmpv3.py @@ -1,7 +1,5 @@ # ============LICENSE_START======================================================= -# org.onap.dcae -# ================================================================================ -# Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. +# Copyright (c) 2018-2020 AT&T Intellectual Property. All rights reserved. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,9 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============LICENSE_END========================================================= -# -# ECOMP is a trademark and service mark of AT&T Intellectual Property. -# """ module for snmpv3 support @@ -52,9 +47,9 @@ prog_name = os.path.basename(__file__) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # -def load_snmpv3_credentials (_py_config, _snmp_engine, _cbs_config): +def load_snmpv3_credentials(_py_config, _snmp_engine, _cbs_config): """ - Add V3 credentials from CBS config to receiver config + Add V3 credentials from CBS config to receiver config so traps will be recieved from specified engines/users :Parameters: _config: snmp entity config @@ -63,7 +58,7 @@ def load_snmpv3_credentials (_py_config, _snmp_engine, _cbs_config): # add V3 credentials from CBS json structure to running config try: - v3_users=_cbs_config["snmpv3_config"]["usm_users"] + v3_users = _cbs_config["snmpv3_config"]["usm_users"] except Exception as e: msg = ("No V3 users defined") ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) @@ -73,121 +68,126 @@ def load_snmpv3_credentials (_py_config, _snmp_engine, _cbs_config): # engineId try: - ctx_engine_id=v3_user['engineId'] + ctx_engine_id = v3_user['engineId'] except Exception as e: - ctx_engine_id=None + ctx_engine_id = None # user try: - userName=v3_user['user'] + userName = v3_user['user'] except Exception as e: - userName=None + userName = None # authorization # find options at -> site-packages/pysnmp/entity/config.py - # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # print("Checking auth for %s" % (userName)) # usmHMACMD5AuthProtocol try: - authKey=v3_user['usmHMACMD5AuthProtocol'] - authProtocol=config.usmHMACMD5AuthProtocol + authKey = v3_user['usmHMACMD5AuthProtocol'] + authProtocol = config.usmHMACMD5AuthProtocol except Exception as e: try: - authKey=v3_user['usmHMACSHAAuthProtocol'] - authProtocol=config.usmHMACSHAAuthProtocol + authKey = v3_user['usmHMACSHAAuthProtocol'] + authProtocol = config.usmHMACSHAAuthProtocol except Exception as e: try: - authKey=v3_user['usmHMAC128SHA224AuthProtocol'] - authProtocol=config.usmHMAC128SHA224AuthProtocol + authKey = v3_user['usmHMAC128SHA224AuthProtocol'] + authProtocol = config.usmHMAC128SHA224AuthProtocol except Exception as e: try: - authKey=v3_user['usmHMAC192SHA256AuthProtocol'] - authProtocol=config.usmHMAC192SHA256AuthProtocol + authKey = v3_user['usmHMAC192SHA256AuthProtocol'] + authProtocol = config.usmHMAC192SHA256AuthProtocol except Exception as e: try: - authKey=v3_user['usmHMAC256SHA384AuthProtocol'] - authProtocol=config.usmHMAC256SHA384AuthProtocol + authKey = v3_user['usmHMAC256SHA384AuthProtocol'] + authProtocol = config.usmHMAC256SHA384AuthProtocol except Exception as e: try: - authKey=v3_user['usmHMAC384SHA512AuthProtocol'] - authProtocol=config.usmHMAC384SHA512AuthProtocol + authKey = v3_user['usmHMAC384SHA512AuthProtocol'] + authProtocol = config.usmHMAC384SHA512AuthProtocol except Exception as e: try: - authKey=v3_user['usmNoAuthProtocol'] - authProtocol=config.usmNoAuthProtocol + authKey = v3_user['usmNoAuthProtocol'] + authProtocol = config.usmNoAuthProtocol except Exception as e: # FMDL: default to NoAuth, or error/skip entry? - msg = ("No auth specified for user %s ?" % (userName)) - authKey=None - authProtocol=config.usmNoAuthProtocol - ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) + msg = ( + "No auth specified for user %s ?" % (userName)) + authKey = None + authProtocol = config.usmNoAuthProtocol + ecomp_logger( + tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) # privacy # find options at -> site-packages/pysnmp/entity/config.py - # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # print("Checking priv for %s" % (userName)) # usm3DESEDEPriv try: - privKey=v3_user['usm3DESEDEPrivProtocol'] - privProtocol=config.usm3DESEDEPrivProtocol + privKey = v3_user['usm3DESEDEPrivProtocol'] + privProtocol = config.usm3DESEDEPrivProtocol except Exception as e: # usmAesCfb128Protocol try: - privKey=v3_user['usmAesCfb128Protocol'] - privProtocol=config.usmAesCfb128Protocol + privKey = v3_user['usmAesCfb128Protocol'] + privProtocol = config.usmAesCfb128Protocol except Exception as e: # usmAesCfb192Protocol try: - privKey=v3_user['usmAesCfb192Protocol'] - privProtocol=config.usmAesCfb192Protocol + privKey = v3_user['usmAesCfb192Protocol'] + privProtocol = config.usmAesCfb192Protocol except Exception as e: # usmAesBlumenthalCfb192Protocol try: - privKey=v3_user['usmAesBlumenthalCfb192Protocol'] - privProtocol=config.usmAesBlumenthalCfb192Protocol + privKey = v3_user['usmAesBlumenthalCfb192Protocol'] + privProtocol = config.usmAesBlumenthalCfb192Protocol except Exception as e: # usmAesCfb256Protocol try: - privKey=v3_user['usmAesCfb256Protocol'] - privProtocol=config.usmAesCfb256Protocol + privKey = v3_user['usmAesCfb256Protocol'] + privProtocol = config.usmAesCfb256Protocol except Exception as e: # usmAesBlumenthalCfb256Protocol try: - privKey=v3_user['usmAesBlumenthalCfb256Protocol'] - privProtocol=config.usmAesBlumenthalCfb256Protocol + privKey = v3_user['usmAesBlumenthalCfb256Protocol'] + privProtocol = config.usmAesBlumenthalCfb256Protocol except Exception as e: # usmDESPrivProtocol try: - privKey=v3_user['usmDESPrivProtocol'] - privProtocol=config.usmDESPrivProtocol + privKey = v3_user['usmDESPrivProtocol'] + privProtocol = config.usmDESPrivProtocol except Exception as e: # usmNoPrivProtocol try: - privKey=v3_user['usmNoPrivProtocol'] - privProtocol=config.usmNoPrivProtocol + privKey = v3_user['usmNoPrivProtocol'] + privProtocol = config.usmNoPrivProtocol except Exception as e: # FMDL: default to NoPriv, or error/skip entry? - msg = ("No priv specified for user %s" % (userName)) - ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) - privKey=None - privProtocol=config.usmNoPrivProtocol + msg = ( + "No priv specified for user %s" % (userName)) + ecomp_logger( + tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) + privKey = None + privProtocol = config.usmNoPrivProtocol # break # msg = ("userName: %s authKey: %s authProtocol: %s privKey: %s privProtocol: %s engineId: %s % (userName, authKey, authProtocol, privKey, privProtocol, ctx_engine_id)) - msg = ("userName: %s authKey: **** authProtocol: %s privKey: **** privProtocol: %s engineId: ****" % (userName, authProtocol, privProtocol)) + msg = ("userName: %s authKey: **** authProtocol: %s privKey: **** privProtocol: %s engineId: ****" % + (userName, authProtocol, privProtocol)) ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) # user: usr-md5-des, auth: MD5, priv DES, contextEngineId: 8000000001020304 # this USM entry is used for TRAP receiving purposes - # help(addV3User) returns -> + # help(addV3User) returns -> # addV3User(snmpEngine, userName, authProtocol=(1, 3, 6, 1, 6, 3, 10, 1, 1, 1), authKey=None, privProtocol=(1, 3, 6, 1, 6, 3, 10, 1, 2, 1), priv Key=None, securityEngineId=None, securityName=None, contextEngineId=None) - if ctx_engine_id is not None: + if ctx_engine_id is not None: config.addV3User( _snmp_engine, userName, authProtocol, authKey, diff --git a/snmptrap/mod/trapd_stats_settings.py b/snmptrap/mod/trapd_stats_settings.py new file mode 100644 index 0000000..df4da0e --- /dev/null +++ b/snmptrap/mod/trapd_stats_settings.py @@ -0,0 +1,44 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= +""" +""" + +__docformat__ = 'restructuredtext' + + +def init(): + + # <stats> + # + # oid_counter_dict + # key [<notify oid>] -> count + # + # agent_counter_dict + # key [<agent>] -> count + # + global oid_counter_dict + oid_counter_dict = {} + + global agent_counter_dict + agent_counter_dict = {} + + global total_notifications + total_notifications = 0 + + global metric_log_notification_threshold_pct + metric_log_notification_threshold_pct = 25 + + # </stats> diff --git a/snmptrap/mod/trapd_stormwatch.py b/snmptrap/mod/trapd_stormwatch.py new file mode 100644 index 0000000..623fe39 --- /dev/null +++ b/snmptrap/mod/trapd_stormwatch.py @@ -0,0 +1,513 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= +""" +trapd_stormwatch makes the decision on whether an +arriving SNMP trap is exceeding a pre-configured +threshold (storm), and if so it will return "False" +so the trap can be logged and immediately discarded +""" + +__docformat__ = 'restructuredtext' + +import sys +import os +import string +import time + +from trapd_io import stdout_logger, ecomp_logger +import trapd_settings as tds +import trapd_stats_settings as stats +import trapd_stormwatch_settings as sws +from trapd_exit import cleanup_and_exit + +prog_name = os.path.basename(__file__) + + +def sw_init(): + + # <Storm Watch> + # + # sw_storm_counter_dict + # key [<ip address>.<notify oid>] -> count + # + # sw_storm_active_dict + # key [<ip address>.<notify oid>] -> True (or False, but no key present + # means False) + # + # sw_config_oid_dict + # key [<notify oid>] -> "true" (key presence means it participates) + # + # sw_config_low_water_in_interval_dict + # key [<notify oid>] -> <int> value that stormActive turns False + # + # sw_config_high_water_in_interval_dict + # key [<notify oid>] -> <int> value that stormActive turns True + # + sws.sw_counter_dict = {} + sws.sw_storm_active_dict = {} + sws.sw_config_oid_dict = {} + sws.sw_config_low_water_in_interval_dict = {} + sws.sw_config_high_water_in_interval_dict = {} + sws.sw_interval_in_seconds = 60 + + +# # # # # # # # # # # # # +# fx: sw_storm_active +# - check if storm is active for agent/oid +# - returns True if yes, False if no +# # # # # # # # # # # # # +def sw_clear_dicts(): + """ + Clear all storm watch dictionaries + :Parameters: + :Exceptions: + none + :Keywords: + stormwatch count threshold + :Variables: + """ + try: + stats.oid_counter_dict.clear() + stats.agent_counter_dict.clear() + sws.sw_storm_active_dict.clear() + sws.sw_storm_counter_dict.clear() + sws.sw_config_oid_dict.clear() + sws.sw_config_low_water_in_interval_dict.clear() + sws.sw_config_high_water_in_interval_dict.clear() + sws.sw_config_category.clear() + return True + except Exception as e: + msg = "unable to reset stormwatch dictionaries - results will be indeterminate: %s" % ( + e) + ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_WARN, tds.CODE_GENERAL, msg) + return False + +# # # # # # # # # # # # # +# fx: sw_load_trap_config +# - load trap configurations from CBS response +# # # # # # # # # # # # # + + +def sw_load_trap_config(_config): + """ + Load trap configs into dictionary + :Parameters: + _config: trapd_config from CBS + :Exceptions: + """ + + # clear any dicts present from previous invocations + try: + sws.sw_storm_active_dict + ret = sw_clear_dicts() + msg = ("reset existing sws dictionaries to empty") + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) + except NameError: + msg = ("sws dictionaries not present - initializing") + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) + ret = sw_init() + + # set last storm analysis to now + sws.sw_last_stormwatch_dict_analysis = int(time.time()) + + # get metric % threshold for logging trap count by-agent to metric log + try: + stats.metric_log_notification_threshold_pct = int( + _config["trap_config"]["metric_log_notification_threshold_pct"]) + msg = ("metric_log_notification_threshold_pct value: %d" % + stats.metric_log_notification_threshold_pct) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) + except Exception as e: + msg = ( + "metric_log_notification_threshold_pct not present in config - default to 25") + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_WARN, tds.CODE_GENERAL, msg) + stats.metric_log_notification_threshold_pct = 25 + + # get stormwatch interval; default to 60 seconds + try: + sws.sw_interval_in_seconds = int( + _config["trap_config"]["sw_interval_in_seconds"]) + msg = ("sw_interval_in_seconds value: %d" % sws.sw_interval_in_seconds) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) + except Exception as e: + msg = ("sw_interval_in_seconds not present in config - default to 60 seconds") + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_WARN, tds.CODE_GENERAL, msg) + sws.sw_interval_in_seconds = 60 + + # add trap configs from CBS json structure to running config + try: + notify_oids = _config["trap_config"]["notify_oids"] + except Exception as e: + msg = ("no trap_config or notify_oids defined") + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_WARN, tds.CODE_GENERAL, msg) + return False + + trap_block_counter = 0 + for trap_block in notify_oids: + # oid + try: + _oid = trap_block['oid'] + except Exception as e: + msg = ( + "missing oid value in notify_oids - oid section of CBS config - using empty value, disregard entry") + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_WARN, + tds.CODE_GENERAL, msg) + _oid = None + + # sw_high_water_in_interval + try: + _sw_high_water_in_interval = int( + trap_block['sw_high_water_in_interval']) + except Exception as e: + msg = ( + "missing sw_high_water_in_interval value in notify_oids - oid section of CBS config - using empty value") + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_WARN, + tds.CODE_GENERAL, msg) + _sw_high_water_in_interval = None + + # sw_low_water_in_interval + try: + _sw_low_water_in_interval = int( + trap_block['sw_low_water_in_interval']) + except Exception as e: + msg = ( + "missing sw_low_water_in_interval value in notify_oids - oid section of CBS config - using empty value") + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_WARN, + tds.CODE_GENERAL, msg) + _sw_low_water_in_interval = None + + # category + try: + _category = trap_block['category'] + except Exception as e: + msg = ( + "missing category value in notify_oids - oid section of CBS config - using empty value") + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_WARN, + tds.CODE_GENERAL, msg) + _category = None + + if (_oid is not None and _category is not None and + _sw_low_water_in_interval is not None and _sw_high_water_in_interval is not None and + _sw_low_water_in_interval < _sw_high_water_in_interval): + # FMDL: Do we actually need sw_config_oid_dict? + msg = ("oid: %s sw_high_water_in_interval: %d sw_low_water_in_interval: %d category: %s" % ( + _oid, _sw_high_water_in_interval, _sw_low_water_in_interval, _category)) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, + tds.CODE_GENERAL, msg) + sws.sw_config_oid_dict[_oid] = True + sws.sw_config_low_water_in_interval_dict[_oid] = _sw_low_water_in_interval + sws.sw_config_high_water_in_interval_dict[_oid] = _sw_high_water_in_interval + sws.sw_config_category[_oid] = _category + trap_block_counter += 1 + else: + msg = ("Missing or incorrect value for stormwatch config entry %d: skipping: %s" % ( + trap_block_counter, trap_block)) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, + tds.CODE_GENERAL, msg) + + + return trap_block_counter + + +# # # # # # # # # # # # # +# fx: sw_log_metrics +# - log stats for any count in interval that is > sw_metric_log_notification_threshold_pct % of total arriving traps +# # # # # # # # # # # # # +def sw_log_metrics(): + """ + Log counts for agents that exceed sw_metric_log_notification_threshold_pct % of + total traps that arrived in interval + :Parameters: + :Exceptions: + none + :Keywords: + stormwatch metrics + :Variables: + """ + + msg = "total notifications: %d, interval in seconds: %d" % ( + stats.total_notifications, sws.sw_interval_in_seconds) + ecomp_logger(tds.LOG_TYPE_METRICS, tds.SEV_INFO, tds.CODE_GENERAL, msg) + + # print metrics for total traps and traps-per-second avg + # during sample interval + avg_traps_per_second = stats.total_notifications / 60 + msg = "total traps: %d, interval in seconds: %d, average traps-per-second: %d" % ( + stats.total_notifications, sws.sw_interval_in_seconds, avg_traps_per_second) + ecomp_logger(tds.LOG_TYPE_METRICS, tds.SEV_WARN, + tds.CODE_GENERAL, msg) + + # print metrics for any agent that represents more than stats.metric_log_notification_threshold_pct + # during sample interval + for k in stats.agent_counter_dict: + c = stats.agent_counter_dict[k] + p = c / stats.total_notifications * 100 + if p > stats.metric_log_notification_threshold_pct: + msg = "agent: %s, notifications: %d, interval in seconds: %d, percent of total traps: %d" % ( + k, c, sws.sw_interval_in_seconds, p) + ecomp_logger(tds.LOG_TYPE_METRICS, tds.SEV_WARN, + tds.CODE_GENERAL, msg) + +# # # # # # # # # # # # # +# fx: stats_increment_counters +# - increment dictionary counters that are accumulating +# - total traps by OID and agent for each sample interval +# # # # # # # # # # # # # + + +def stats_increment_counters(_loc_agent, _loc_oid): + """ + update counters tracking traps-per-interval by + OID and agent + :Parameters: + _loc_agent + agent address from trap PDU + _loc_oid + notify OID from trap PDU + :Exceptions: + none + :Keywords: + stormwatch stats metrics + :Variables: + """ + # increment oid occurances in window + msg = "increment metric counters for %s %s" % (_loc_agent, _loc_oid) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) + try: + stats.total_notifications += 1 + except Exception as e: + stats.total_notifications = 1 + + try: + stats.oid_counter_dict[_loc_oid] += 1 + except Exception as e: + stats.oid_counter_dict[_loc_oid] = 1 + + # increment agent occurances in window + try: + stats.agent_counter_dict[_loc_agent] += 1 + except Exception as e: + stats.agent_counter_dict[_loc_agent] = 1 + + +# # # # # # # # # # # # # +# fx: sw_storm_active +# - check if storm is active for agent/oid +# - returns True if yes, False if no +# # # # # # # # # # # # # +def sw_storm_active(_loc_agent, _loc_oid): + """ + Check if this event is currently in an + active storm state. + :Parameters: + _loc_agent + agent address from trap PDU + _loc_oid + notify OID from trap PDU + :Exceptions: + none + :Keywords: + stormwatch count threshold + :Variables: + """ + + # we have to come here for every arriving trap, so increment + # trap counter dictionaries while we are here + stats_increment_counters(_loc_agent, _loc_oid) + + # if we are at or above stormwatch interval, re-eval and re-set + elapsed_time = int(time.time()) - sws.sw_last_stormwatch_dict_analysis + if elapsed_time >= sws.sw_interval_in_seconds: + msg = "%d seconds has elapsed since stormwatch dictionary eval (%d second threshold) - check and reset counters " % ( + elapsed_time, sws.sw_interval_in_seconds) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) + sw_log_metrics() + sw_reset_counter_dict() + + # Note: If there's a sw_config_high_water_in_interval config value present + # that means it's participating in stormwatch, otherwise bail out + try: + _high_water_val = sws.sw_config_high_water_in_interval_dict[_loc_oid] + msg = "%s present in stormwatch config - high water value: %d" % ( + _loc_oid, _high_water_val) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) + except Exception as e: + return False + + # build stormwatch dict key by appending agent IP and trap oid + _dict_key = _loc_agent + " " + _loc_oid + + # increment traps encountered for agent/oid + sw_increment_counter(_dict_key) + + # first check if storm is active for _dict_key (early bail-out if so) + msg = "check if stormWatch is active for %s" % (_dict_key) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) + if sws.sw_storm_active_dict.get(_dict_key) is not None: + msg = "stormWatch is active for %s - return true" % (_dict_key) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) + return True + else: + msg = "no stormWatch active entry for %s - continue" % (_dict_key) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) + + # if we got this far, trap is in stormwatch configs, we've incremented + # counter in sw_storm_counter_dict - figure out if we are over limit + if sws.sw_storm_counter_dict[_dict_key] > _high_water_val: + _loc_agent = _dict_key.split()[0] + _loc_oid = _dict_key.split()[1] + msg = "STORM ACTIVE: received %d events (%s) from %s (greater than high water threshold: %d)" % ( + sws.sw_storm_counter_dict[_dict_key], _loc_oid, _loc_agent, _high_water_val) + ecomp_logger(tds.LOG_TYPE_AUDIT, tds.SEV_WARN, tds.CODE_GENERAL, msg) + try: + sws.sw_storm_active_dict[_dict_key] = True + except Exception as e: + msg = "ERROR setting %s in storm active state: %s " % ( + _dict_key, e) + ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_ERROR, + tds.CODE_GENERAL, msg) + return True + else: + return False + +# # # # # # # # # # # # # +# fx: sw_reset_counter_dict +# - reset counter dictionary on <interval> boundaries +# # # # # # # # # # # # # + + +def sw_reset_counter_dict(): + """ + Create new storm_active_dict based on quantities received during + last sample interval + :Parameters: + :Exceptions: + none + :Keywords: + stormwatch count threshold + :Variables: + """ + + # <stats> + # publish stats to MR... + try: + msg = "publish counts-by-oid from stats.oid_counter_dict" + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, + tds.CODE_GENERAL, msg) + msg = "publish count-by-agent from stats.agent_counter_dict" + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, + tds.CODE_GENERAL, msg) + except Exception as e: + msg = "unable to publish counts by oid and agent to MR: " % (e) + ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_WARN, tds.CODE_GENERAL, msg) + + # ...and now reset stats counters to start next interval + try: + stats.oid_counter_dict.clear() + stats.agent_counter_dict.clear() + stats.total_notifications = 0 + except Exception as e: + msg = "unable to reset counts by oid and agent dictionaries - stats will be INNACURATE: " % ( + e) + ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_WARN, tds.CODE_GENERAL, msg) + # </stats> + + # <storm watch active> + + # for k in sws.sw_storm_active_dict: + # File "./snmptrapd.py", line 642, in notif_receiver_cb + # if stormwatch.sw_storm_active(tds.trap_dict["agent address"], tds.trap_dict["notify OID"]): + # File "/opt/app/snmptrap/bin/mod/trapd_stormwatch.py", line 299, in sw_storm_active + # sw_reset_counter_dict() + # File "/opt/app/snmptrap/bin/mod/trapd_stormwatch.py", line 381, in sw_reset_counter_dict + # for k in sws.sw_storm_active_dict: + # RuntimeError: dictionary changed size during iteration + # FIXME: changed to "for k in list(sw_storm_active_dict)" as explained here: + # see https://stackoverflow.com/questions/20418851/delete-an-entry-from-a-dictionary-python + + for k in list(sws.sw_storm_active_dict): + _loc_agent = k.split()[0] + _loc_oid = k.split()[1] + + _high_water_val = sws.sw_config_high_water_in_interval_dict[_loc_oid] + + if sws.sw_storm_counter_dict[k] >= _high_water_val: + msg = "%s remaining in storm state, received %d events (GE to upper threshold: %d)" % ( + k, sws.sw_storm_counter_dict[k], _high_water_val) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, + tds.CODE_GENERAL, msg) + sws.sw_storm_counter_dict[k] = 0 + else: + _low_water_val = sws.sw_config_low_water_in_interval_dict[_loc_oid] + if sws.sw_storm_counter_dict[k] < _low_water_val: + try: + msg = "STORM OVER: received %d events (%s) from %s (less than low water threshold: %d)" % ( + sws.sw_storm_counter_dict[k], _loc_oid, _loc_agent, _low_water_val) + ecomp_logger(tds.LOG_TYPE_AUDIT, tds.SEV_WARN, + tds.CODE_GENERAL, msg) + del sws.sw_storm_active_dict[k] + sws.sw_storm_counter_dict[k] = 0 + except Exception as e: + msg = "unable to remove %s from storm active dictionary - TRAPS MAY BE DISCARDED UNINTENTIONALLY! Reason: %s " % ( + k, e) + ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_ERROR, + tds.CODE_GENERAL, msg) + else: + msg = "%s remaining in storm state, received %d events (GE to lower threshold: %d)" % ( + k, sws.sw_storm_counter_dict[k], _low_water_val) + ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_INFO, + tds.CODE_GENERAL, msg) + sws.sw_storm_counter_dict[k] = 0 + + sws.sw_last_stormwatch_dict_analysis = int(time.time()) + + return True + +# # # # # # # # # # # # # +# fx: sw_increment_counter +# - increment OID and agent trap counters +# based on arriving trap attributes +# # # # # # # # # # # # # + + +def sw_increment_counter(_dict_key): + """ + Add to appropriate counter based on arriving trap + agent and OID + :Parameters: + _dict_key + agent address from trap PDU and notify OID + trap PDU, separated by a space + :Exceptions: + none + :Keywords: + stormwatch count threshold + :Variables: + """ + + try: + sws.sw_storm_counter_dict[_dict_key] += 1 + msg = "stormwatch counter for %s now: %d" % ( + _dict_key, sws.sw_storm_counter_dict[_dict_key]) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, + tds.CODE_GENERAL, msg) + return True + except Exception as E: + msg = "first trap for %s - init stormwatch counter to 1" % (_dict_key) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, + tds.CODE_GENERAL, msg) + sws.sw_storm_counter_dict[_dict_key] = 1 + return True diff --git a/snmptrap/mod/trapd_stormwatch_settings.py b/snmptrap/mod/trapd_stormwatch_settings.py new file mode 100644 index 0000000..f2586f9 --- /dev/null +++ b/snmptrap/mod/trapd_stormwatch_settings.py @@ -0,0 +1,61 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= +""" +""" + +__docformat__ = 'restructuredtext' + + +def init(): + + # <Storm Watch> + # + # sw_storm_counter_dict + # key [<ip address>.<notify oid>] -> count + # + # sw_storm_active_dict + # key [<ip address>.<notify oid>] -> True (or False, but no key present + # means False) + # + # sw_config_oid_dict + # key [<notify oid>] -> "true" (key presence means it participates) + # + # sw_config_low_water_in_interval_dict + # key [<notify oid>] -> <int> value that stormActive turns False + # + # sw_config_high_water_in_interval_dict + # key [<notify oid>] -> <int> value that stormActive turns True + # + global sw_storm_counter_dict + sw_storm_counter_dict = {} + + global sw_storm_active_dict + sw_storm_active_dict = {} + + global sw_config_oid_dict + sw_config_oid_dict = {} + global sw_config_low_water_in_interval_dict + sw_config_low_water_in_interval_dict = {} + global sw_config_high_water_in_interval_dict + sw_config_high_water_in_interval_dict = {} + global sw_config_category + sw_config_category = {} + + global sw_interval_in_seconds + sw_interval_in_seconds = 60 + global sw_last_stormwatch_dict_analysis + sw_last_stormwatch_dict_analysis = 0 + # </Storm Watch> diff --git a/snmptrap/mod/trapd_vb_types.py b/snmptrap/mod/trapd_vb_types.py index 2d01a30..98d5d2c 100644 --- a/snmptrap/mod/trapd_vb_types.py +++ b/snmptrap/mod/trapd_vb_types.py @@ -1,7 +1,5 @@ # ============LICENSE_START======================================================= -# org.onap.dcae -# ================================================================================ -# Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. +# Copyright (c) 2018-2020 AT&T Intellectual Property. All rights reserved. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,9 +13,6 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============LICENSE_END========================================================= -# -# ECOMP is a trademark and service mark of AT&T Intellectual Property. -# """ module for converting varbind types from Net-SNMP to PYSNMP @@ -47,22 +42,24 @@ prog_name = os.path.basename(__file__) # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # _pysnmp_to_netsnmp_vb_type = { - 'Integer' : 'integer', - 'Gauge32' : 'unsigned', - 'Counter32' : 'counter32', - 'OctetString' : 'octet', - 'py_type_5' : 'hex', - 'py_type_6' : 'decimal', - 'Null' : 'null', - 'ObjectIdentifier' : 'oid', - 'TimeTicks' : 'timeticks', - 'IpAddress' : 'ipaddress', - 'Bits' : 'bits' - } + 'Integer32': 'integer', + 'Integer': 'integer', + 'Gauge32': 'unsigned', + 'Counter32': 'counter32', + 'OctetString': 'octet', + 'py_type_5': 'hex', + 'py_type_6': 'decimal', + 'Null': 'null', + 'ObjectIdentifier': 'oid', + 'TimeTicks': 'timeticks', + 'IpAddress': 'ipaddress', + 'Bits': 'bits' +} default_vb_type = "octet" -def pysnmp_to_netsnmp_varbind_convert (_pysnmp_vb_type): + +def pysnmp_to_netsnmp_varbind_convert(_pysnmp_vb_type): """ Convert pysnmp varbind types to Net-SNMP nomenclature to maintain backward compatibilty with existing solutions @@ -76,14 +73,11 @@ def pysnmp_to_netsnmp_varbind_convert (_pysnmp_vb_type): # lookup _pysnmp_vb_type in conversion dictionary try: - msg = ("checking for netsnmp equiv of varbind type: %s" \ - % _pysnmp_vb_type) - ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) _netsnmp_vb_type = _pysnmp_to_netsnmp_vb_type[_pysnmp_vb_type] return _netsnmp_vb_type except Exception as e: # if not found, return original pysnmp type - msg = ("%s not configured as pysnmp varbind type" \ - % _pysnmp_vb_type) + msg = ("%s not configured as pysnmp varbind type - returning %s" + % (_pysnmp_vb_type,default_vb_type)) ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) return default_vb_type diff --git a/snmptrap/scheduler.sh b/snmptrap/scheduler.sh new file mode 100755 index 0000000..bb2d083 --- /dev/null +++ b/snmptrap/scheduler.sh @@ -0,0 +1,186 @@ +#!/usr/bin/env bash +# ============LICENSE_START======================================================= +# Copyright (c) 2017-2020 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= +# +# + +script_name=`basename "$0"` + +# sleep_time=.1 # in seconds +sleep_time=1 # in seconds + +log_fd=/var/tmp/${script_name}.log +log_lines=0 +max_log_lines=4000 + +num_days_to_keep_logs=7 + +# find the best tool for the job +if [ `command -v pypy3` ] +then + PY_BINARY=pypy3 +else + if [ `command -v python3` ] + then + PY_BINARY=python3 + else + if [ `command -v python` ] + then + PY_BINARY=python + else + echo "ERROR: no pypy3 or python available in container - FATAL ERROR, exiting" + exit 1 + fi + fi +fi + +# logging utility +logFx() +{ +_fx=$1 +# skipping verbosity setting +_verbosity=$2 +_log_message=$3 + + echo "`date` `date +%s` ${_fx} ${_error_code} ${_log_message}" >> ${log_fd} + + # log_lines=`wc -l ${log_fd} | awk {'print $1'} | bc` + # log_lines=$((${log_lines}+1)) + + # if [ ${log_lines} -ge ${max_log_lines} ] + # then + # if [ -f ${log_fd} ] + # then + # mv -f ${log_fd} ${log_fd}.old + # fi + # fi +} + +run_10sec_jobs() +{ + # send heartbeat + ${PY_BINARY} /opt/app/snmptrap/bin/send_hb_trap localhost 6162 > /var/tmp/send_hb_trap.out 2>&1 & + rc=$? + logFx ${FUNCNAME[0]} 0 "send_hb_trap returned ${rc}" + + # other 10 second jobs below here + + return ${rc} +} + +run_minute_jobs() +{ +rc=0 + # add other minute jobs below here + + return ${rc} +} + +run_hourly_jobs() +{ + # no hourly jobs scheduled at this time + rc=0 + + # add other hourly jobs below here + + return ${rc} +} + +run_daily_jobs() +{ + rc=0 + + # remove old logs + for f in `find /opt/app/snmptrap/logs -type f -mtime +${num_days_to_keep_logs}` + do + logFx ${FUNCNAME[0]} 0 "removing $f" + rm $f + done + + # move scheduler log_fd to daily backup + mv -f ${log_fd} ${log_fd}.`date +%a` + + # add other daily jobs below here + + return ${rc} +} + + +# # # # # # # # # # # # # # +# main HCCCKK area +# # # # # # # # # # # # # # + +begin_minute=`date +%M | bc` + +# wait for minute to roll to new one +logFx ${FUNCNAME[0]} 0 "waiting for new minute..." +while [ ${begin_minute} -eq `date +%M | bc` ] +do + sleep .1 +done + +SECONDS=0 +logFx ${FUNCNAME[0]} 0 "scheduler synced to new minute" +last_minute=`date +%M` +last_hour=`date +%H` +last_day=`date +%j` + +logFx ${FUNCNAME[0]} 0 "entering endless loop" +while(true) +do + if [ $SECONDS -ge 10 ] + then + # run every 10 seconds jobs + logFx ${FUNCNAME[0]} 0 "$SECONDS seconds have elapsed - calling run_10sec_jobs" + run_10sec_jobs + # reset SECONDS + SECONDS=0 + + # check for minute change + current_minute=`date +%M | bc` + if [ ${current_minute} -ne ${last_minute} ] + then + # run every minute jobs + logFx ${FUNCNAME[0]} 0 "minute change from ${last_minute} to ${current_minute} - calling run_minute_jobs" + run_minute_jobs + # reset last_minute + last_minute=${current_minute} + + # check for hour change + current_hour=`date +%H | bc` + if [ ${current_hour} -ne ${last_hour} ] + then + # run every hour jobs + logFx ${FUNCNAME[0]} 0 "hour change from ${last_hour} to ${current_hour} - calling run_hourly_jobs" + run_hourly_jobs + # reset last_minute + last_hour=${current_hour} + + # check for day change + current_day=`date +%j | bc` + if [ ${current_day} -ne ${last_day} ] + then + # run every day jobs + logFx ${FUNCNAME[0]} 0 "day change from ${last_day} to ${current_day} - calling run_daily_jobs" + run_daily_jobs + # reset last_day + last_day=${current_day} + fi + fi + fi + fi + sleep ${sleep_time} +done diff --git a/snmptrap/send_hb_trap b/snmptrap/send_hb_trap new file mode 100755 index 0000000..efec02c --- /dev/null +++ b/snmptrap/send_hb_trap @@ -0,0 +1,65 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= +import time +from pysnmp.hlapi import * +from pysnmp.error import PySnmpError +import argparse +import os + +parser = argparse.ArgumentParser() +parser.add_argument("dest", help="trap receiver hostname or ip address") +parser.add_argument("port", help="trap receiver port number", type=int) +args = parser.parse_args() + +env_service_name = os.getenv('SERVICE_NAME') +env_service_tags = os.getenv('SERVICE_TAGS') + +if env_service_name is None or env_service_tags is None: + now = time.strftime("%Y-%m-%d %H:%M:%S %Z", time.localtime()) + print('%s Cannot get SERVICE_NAME and/or SERVICE_TAGS env vars' + % now) + if env_service_name is None: + env_service_name = 'SERVICE_NAME N/A' + if env_service_tags is None: + env_service_tags = 'SERVICE_TAGS N/A' + +try: + errorIndication, errorStatus, errorIndex, varBinds = next( + sendNotification( + SnmpEngine(), + CommunityData('public', mpModel=1), + UdpTransportTarget((args.dest, args.port)), + ContextData(), + 'trap', + NotificationType( + ObjectIdentity('.1.3.6.1.4.1.74.2.46.12.1.1') + ).addVarBinds( + ('.1.3.6.1.4.1.74.2.46.12.1.1.1', + OctetString('onap trapd heartbeat')), + ('.1.3.6.1.4.1.74.2.46.12.1.1.2', + OctetString(time.ctime())), + ('.1.3.6.1.4.1.74.2.46.12.1.1.3', + OctetString(env_service_name)), + ('.1.3.6.1.4.1.74.2.46.12.1.1.4', + OctetString(env_service_tags)) + ) + ) + ) + if errorIndication: + print(errorIndication) + +except PySnmpError as e: + print("Exception from sendNotification: %s" % e) diff --git a/snmptrap/snmptrapd.py b/snmptrap/snmptrapd.py index 8a824ef..5f46f34 100644 --- a/snmptrap/snmptrapd.py +++ b/snmptrap/snmptrapd.py @@ -1,7 +1,5 @@ -# ============LICENSE_START=======================================================) -# org.onap.dcae -# ================================================================================ -# Copyright (c) 2017-2018 AT&T Intellectual Property. All rights reserved. +# ============LICENSE_START======================================================= +# Copyright (c) 2017-2020 AT&T Intellectual Property. All rights reserved. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,11 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. # ============LICENSE_END========================================================= -# -# ECOMP is a trademark and service mark of AT&T Intellectual Property. -# """ -dcae_snmptrapd is responsible for SNMP trap receipt and publishing activities. +snmptrapd is responsible for SNMP trap receipt and publishing activities. It's behavior is controlled by CBS (config binding service) using a JSON construct obtained via a "get_config" call or (for testing/standalone purposes) a file specified using the env variable "CBS_SIM_JSON". @@ -28,7 +23,7 @@ As traps arrive they are decomposed and transformed into a JSON message which is published to a dmaap instance that has been defined by controller. :Parameters: - usage: snmptrapd.py + usage: snmptrapd.py [-v] :Keywords: onap dcae snmp trap publish dmaap """ @@ -67,15 +62,23 @@ from pysnmp.entity.rfc3413 import ntfrcv from pysnmp.proto.api import v2c from pysnmp import debug -# dcae_snmptrap +# snmptrap import trapd_settings as tds + from trapd_runtime_pid import save_pid, rm_pid from trapd_get_cbs_config import get_cbs_config from trapd_exit import cleanup_and_exit -from trapd_http_session import init_session_obj, close_session_obj, reset_session_obj +from trapd_http_session import init_session_obj, close_session_obj,\ + reset_session_obj from trapd_snmpv3 import load_snmpv3_credentials from trapd_vb_types import pysnmp_to_netsnmp_varbind_convert -from trapd_io import roll_all_logs, open_eelf_logs, roll_file, open_file, close_file, ecomp_logger, stdout_logger +from trapd_io import roll_all_logs, open_eelf_logs, roll_file, open_file,\ + close_file, ecomp_logger, stdout_logger + +import trapd_stormwatch_settings as sws +import trapd_stormwatch as stormwatch + +import trapd_stats_settings as stats prog_name = os.path.basename(__file__) verbose = False @@ -97,7 +100,7 @@ def usage_err(): """ print('Incorrect usage invoked. Correct usage:') - print(' %s' % prog_name) + print(' %s [-v]' % prog_name) cleanup_and_exit(1, "undefined") @@ -126,38 +129,83 @@ def load_all_configs(_signum, _frame): if int(_signum) != 0: msg = ("received signal %s at frame %s; re-reading configs" % (_signum, _frame)) - ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) - # re-request config (from broker, or local json file - # if broker not present) + # re-request config from broker: if not get_cbs_config(): msg = "Error (re)loading CBS config - FATAL ERROR, exiting" stdout_logger(msg) cleanup_and_exit(1, tds.pid_file_name) else: - current_runtime_config_file_name = tds.c_config['files']['runtime_base_dir'] + \ - "/tmp/current_config.json" + current_runtime_config_file_name = ( + tds.c_config['files']['runtime_base_dir'] + + "/tmp/current_config.json") if int(_signum) != 0: - msg = "updated config logged to : %s" % current_runtime_config_file_name + msg = "updated config logged to : %s" % \ + current_runtime_config_file_name else: - msg = "current config logged to : %s" % current_runtime_config_file_name - ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) + msg = "current config logged to : %s" % \ + current_runtime_config_file_name + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, + msg) - with open(current_runtime_config_file_name, 'w') as outfile: - json.dump(tds.c_config, outfile) + with open(current_runtime_config_file_name, 'w') as outfile: + json.dump(tds.c_config, outfile) # reset http session based on latest config tds.http_requ_session = reset_session_obj(tds.http_requ_session) - # FMDL: add with stormWatch # reload sw participating entries, reset counter dictionary - # sw.interval_in_seconds, sw.participant_oid_dict = load_sw_participant_dict(tds.c_config['trap_config']) - # sw.counter_dict = init_counter_dict() + traps_configured = stormwatch.sw_load_trap_config(tds.c_config) + msg = "encountered %d trap configurations in CBS/json config" % \ + traps_configured + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, + msg) - # if here, config re-read successfully + tds.last_minute = datetime.datetime.now().minute + tds.last_hour = datetime.datetime.now().hour + tds.last_day = datetime.datetime.now().day + + # if here, configs re-read successfully return True + +# # # # # # # # # # # # # +# fx: resolve_ip +# # # # # # # # # # # # # +def resolve_ip(_loc_ip_addr_str): + + try: + if int(tds.dns_cache_ip_expires[_loc_ip_addr_str] < int(time.time())): + raise Exception('cache expired for %s at %d - updating value' % + (_loc_ip_addr_str, + (tds.dns_cache_ip_expires[_loc_ip_addr_str]))) + else: + agent_fqdn = tds.dns_cache_ip_to_name[_loc_ip_addr_str] + + except Exception as e: + msg = "dns cache expired or missing for %s - refreshing" % \ + _loc_ip_addr_str + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, + tds.CODE_GENERAL, msg) + try: + agent_fqdn, alias, addresslist = socket.gethostbyaddr( + _loc_ip_addr_str) + except Exception as e: + agent_fqdn = _loc_ip_addr_str + + tds.dns_cache_ip_to_name[_loc_ip_addr_str] = agent_fqdn + tds.dns_cache_ip_expires[_loc_ip_addr_str] = ( + time.time() + int(tds.c_config['cache']['dns_cache_ttl_seconds'])) + msg = "cache for %s (%s) updated - set to expire at %d" % \ + (agent_fqdn, _loc_ip_addr_str, + tds.dns_cache_ip_expires[_loc_ip_addr_str]) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, + tds.CODE_GENERAL, msg) + + return agent_fqdn + # # # # # # # # # # # # # # fx: log_all_arriving_traps # # # # # # # # # # # # # @@ -185,29 +233,29 @@ def log_all_arriving_traps(): # always log arriving trap try: - # going for: - # 1520971776 Tue Mar 13 16:09:36 2018; 1520971776 2018-03-13 16:09:36 DCAE-COLLECTOR-UCSNMP 15209717760049 .1.3.6.1.4.1.2636.4.1.6 gfpmt5pcs10.oss.att.com 135.91.10.139 12.123.1.240 12.123.1.240 2 varbinds: [0] .1.3.6.1.2.1.1.3.0 {10} 1212058366 140 days, 6:49:43.66 [1] .1.3.6.1.6.3.1.1.4.1.0 {6} .1.3.6.1.4.1.2636.4.1.6 [2] .1.3.6.1.4.1.2636.3.1.15.1.1.2.4.0.0 {2} 2 [3] .1.3.6.1.4.1.2636.3.1.15.1.2.2.4.0.0 {2} 4 [4] .1.3.6.1.4.1.2636.3.1.15.1.3.2.4.0.0 {2} 0 [5] .1.3.6.1.4.1.2636.3.1.15.1.4.2.4.0.0 {2} 0 [6] .1.3.6.1.4.1.2636.3.1.15.1.5.2.4.0.0 {4} PEM 3 [7] .1.3.6.1.4.1.2636.3.1.15.1.6.2.4.0.0 {2} 7 [8] .1.3.6.1.4.1.2636.3.1.15.1.7.2.4.0.0 {2} 4 [9] .1.3.6.1.6.3.18.1.3.0 {7} 12.123.1.240 - - tds.arriving_traps_fd.write('%s %s; %s %s %s %s %s %s %s %s %s %s\n' % - (tds.trap_dict["time received"], - time.strftime( - "%a %b %d %H:%M:%S %Y", time.localtime(time.time())), - time.strftime("%a %b %d %H:%M:%S %Y", time.localtime( - tds.trap_dict["time received"])), - tds.trap_dict["trap category"], - tds.trap_dict["epoch_serno"], - tds.trap_dict["notify OID"], - tds.trap_dict["agent name"], - tds.trap_dict["agent address"], - tds.trap_dict["cambria.partition"], - tds.trap_dict["protocol version"], - tds.trap_dict["uuid"], - tds.all_vb_json_str)) + time_now=int(round(time.time(),0)) + arrived_epoch_time_int=int(tds.trap_dict["time received"]) + tds.arriving_traps_fd.write('%d %s; %s %s %s %s %s %s %s %s %s %s %s %s %s\n' % + (time_now, + datetime.datetime.fromtimestamp(time_now).strftime("%a %b %d %H:%M:%S %Y"), + tds.trap_dict["time received"], + datetime.datetime.fromtimestamp(arrived_epoch_time_int).strftime("%a %b %d %H:%M:%S %Y"), + tds.trap_dict["trap category"], + tds.trap_dict["epoch_serno"], + tds.trap_dict["notify OID"], + tds.trap_dict["agent name"], + tds.trap_dict["agent address"], + tds.trap_dict["pdu agent name"], + tds.trap_dict["pdu agent address"], + tds.trap_dict["cambria.partition"], + tds.trap_dict["protocol version"], + tds.trap_dict["uuid"], + tds.all_vb_str)) except Exception as e: msg = "Error writing to %s : %s - arriving trap %s NOT LOGGED" % ( tds.arriving_traps_filename, str(e), tds.trap_dict["uuid"]) - ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_CRIT, tds.CODE_GENERAL, msg) + ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_ERROR, tds.CODE_GENERAL, msg) # # # # # # # # # # # # # @@ -227,12 +275,12 @@ def log_published_messages(_post_data_enclosed): tds.json_traps_fd.write('%s\n' % _post_data_enclosed) msg = "successfully logged json for %s to %s" % ( tds.trap_dict["uuid"], tds.json_traps_filename) - ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) except Exception as e: msg = "Error writing to %s : %s - trap %s NOT LOGGED" % ( tds.json_traps_filename, str(e), tds.trap_dict["uuid"]) - ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_CRIT, tds.CODE_GENERAL, msg) + ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_ERROR, tds.CODE_GENERAL, msg) # # # # # # # # # # # # # @@ -255,19 +303,19 @@ def post_dmaap(): if tds.http_requ_session is None: msg = "tds.http_requ_session is None - getting new (%s)" % tds.http_requ_session - ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) tds.http_requ_session = init_session_obj() # if only 1 trap, ship as-is if tds.traps_since_last_publish == 1: - post_data_enclosed = tds.all_traps_str + post_data_enclosed = tds.all_traps_json_str else: # otherwise, add brackets around package - post_data_enclosed = '[' + tds.all_traps_str + ']' + post_data_enclosed = '[' + tds.all_traps_json_str + ']' msg = "post_data_enclosed: %s" % (post_data_enclosed) - ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, tds.CODE_GENERAL, msg) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) k = 0 dmaap_pub_success = False @@ -277,7 +325,7 @@ def post_dmaap(): if tds.c_config['streams_publishes']['sec_fault_unsecure']['aaf_username'] == "" or tds.c_config['streams_publishes']['sec_fault_unsecure']['aaf_username'] is None: msg = "%d trap(s) : %s - attempt %d (unsecure)" % ( tds.traps_since_last_publish, tds.trap_uuids_in_buffer, k) - ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) http_resp = tds.http_requ_session.post(tds.c_config['streams_publishes']['sec_fault_unsecure']['dmaap_info']['topic_url'], post_data_enclosed, headers=http_headers, @@ -285,7 +333,7 @@ def post_dmaap(): else: msg = "%d trap(s) : %s - attempt %d (secure)" % ( tds.traps_since_last_publish, tds.trap_uuids_in_buffer, k) - ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) http_resp = tds.http_requ_session.post(tds.c_config['streams_publishes']['sec_fault_unsecure']['dmaap_info']['topic_url'], post_data_enclosed, auth=(tds.c_config['streams_publishes']['sec_fault_unsecure']['aaf_username'], @@ -309,7 +357,7 @@ def post_dmaap(): tds.CODE_GENERAL, msg) except OSError as e: - msg = "OS exception while attempting to post %s attempt %s: (%s) %s %s" % ( + msg = "OS exception while attempting to post %s attempt %d: (%s) %s %s" % ( tds.trap_uuids_in_buffer, k, e.errno, e.strerror, str(e)) ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_WARN, tds.CODE_GENERAL, msg) @@ -322,10 +370,10 @@ def post_dmaap(): k += 1 - if k < tds.c_config['publisher']['http_retries']: + if k < int(tds.c_config['publisher']['http_retries']): msg = "sleeping %.4f seconds and retrying" % ( tds.seconds_between_retries) - ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, + ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_WARN, tds.CODE_GENERAL, msg) time.sleep(tds.seconds_between_retries) else: @@ -334,12 +382,12 @@ def post_dmaap(): if not dmaap_pub_success: msg = "ALL publish attempts failed for traps %s to URL %s "\ % (tds.trap_uuids_in_buffer, tds.c_config['streams_publishes']['sec_fault_unsecure']['dmaap_info']['topic_url']) - ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_CRIT, tds.CODE_GENERAL, msg) + ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_ERROR, tds.CODE_GENERAL, msg) # FMDL: This currently tries, then logs error and trashes buffer if all dmaap attempts fail. Better way? tds.traps_since_last_publish = 0 tds.trap_uuids_in_buffer = "" - tds.all_traps_str = "" + tds.all_traps_json_str = "" tds.first_trap = True # # # # # # # # # # # # # # # # # # # @@ -410,6 +458,7 @@ def snmp_engine_observer_cb(snmp_engine, execpoint, variables, cbCtx): tds.traps_in_epoch = 0 tds.last_epoch_second = epoch_second traps_in_epoch_04d = format(tds.traps_in_epoch, '04d') + tds.trap_dict['epoch_arrived'] = epoch_second tds.trap_dict['epoch_serno'] = int( (str(epoch_second) + str(traps_in_epoch_04d))) @@ -418,36 +467,22 @@ def snmp_engine_observer_cb(snmp_engine, execpoint, variables, cbCtx): # ip and hostname ip_addr_str = str(variables['transportAddress'][0]) + # set agent address and name to source of packet, OVERWRITE if + # .1.3.6.1.6.3.18.1.3.0 varbind encountered later in trap processing tds.trap_dict["agent address"] = ip_addr_str + tds.trap_dict["agent name"] = resolve_ip(ip_addr_str) + # set overridden/logical address and name to source of packet so we know + # original value if .1.3.6.1.6.3.18.1.3.0 shows up + # NOTE: This does NOT change ever, label may change to + # "overridden agent..." in the future for truth in nameing + tds.trap_dict["pdu agent address"] = tds.trap_dict["agent address"] + tds.trap_dict["pdu agent name"] = tds.trap_dict["agent name"] + + # log arrival now that we have agent addr + msg = 'trap from %s %s, assigned uuid: %s' % \ + (ip_addr_str, tds.trap_dict["agent name"], tds.trap_dict["uuid"]) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) - msg = 'snmp trap arrived from %s, assigned uuid: %s' % \ - (ip_addr_str, tds.trap_dict["uuid"]) - ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, tds.CODE_GENERAL, msg) - - try: - if int(tds.dns_cache_ip_expires[ip_addr_str] < int(time.time())): - raise Exception('cache expired for %s at %d - updating value' % - (ip_addr_str, (tds.dns_cache_ip_expires[ip_addr_str]))) - else: - tds.trap_dict["agent name"] = tds.dns_cache_ip_to_name[ip_addr_str] - except Exception as e: - msg = "dns cache expired or missing for %s - refreshing" % ip_addr_str - ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, - tds.CODE_GENERAL, msg) - try: - agent_fqdn, alias, addresslist = socket.gethostbyaddr(ip_addr_str) - except Exception as e: - agent_fqdn = ip_addr_str - - tds.trap_dict["agent name"] = agent_fqdn - - tds.dns_cache_ip_to_name[ip_addr_str] = agent_fqdn - tds.dns_cache_ip_expires[ip_addr_str] = ( - time.time() + tds.c_config['cache']['dns_cache_ttl_seconds']) - msg = "cache for %s (%s) updated - set to expire at %d" % \ - (agent_fqdn, ip_addr_str, tds.dns_cache_ip_expires[ip_addr_str]) - ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, - tds.CODE_GENERAL, msg) tds.trap_dict["cambria.partition"] = str(tds.trap_dict["agent name"]) # do not include cleartext community in pub @@ -491,6 +526,42 @@ def snmp_engine_observer_cb(snmp_engine, execpoint, variables, cbCtx): # # # # # # # # # # # # # # # # # # # # fx: request_observer for community string rewrite # # # # # # # # # # # # # # # # # # # + +def add_varbind_to_log_string(vb_idx, vb_oid, vb_type, vb_val): + """ + Called for each varbind, adds individual attributes of varbind instance to + all_vb_str for logging. + :Parameters: + vb_idx + index to specific varbind being processed + vb_oid + the varbind oid + vb_type + the varbind type + vb_val + the value of the varbind + :Exceptions: + none + :Keywords: + varbind extract log + :Variables: + """ + + if vb_idx == 0: + tds.all_vb_str = 'varbinds:' + + tds.all_vb_str = tds.all_vb_str + " [" + str(vb_idx) + "] " \ + + str(vb_oid) + " {" + vb_type + "} " + str(vb_val.prettyPrint()) + + # try: + # tds.all_vb_str = tds.all_vb_str + " [" + str(vb_idx) + "] " + vb_oid + " {" + vb_type + "} " + vb_val + # return 0 + # except Exception as e: + # msg = "unable to add varbind to log string: %s" % (str(e)) + # stdout_logger(msg) + # ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_WARN, tds.CODE_GENERAL, msg) + # return 1 + def add_varbind_to_json(vb_idx, vb_oid, vb_type, vb_val): """ Called for each varbind, adds individual attributes of varbind instance to @@ -509,6 +580,7 @@ def add_varbind_to_json(vb_idx, vb_oid, vb_type, vb_val): :Variables: """ + agent_override_oid = ".1.3.6.1.6.3.18.1.3.0" _individual_vb_dict = {} # if first varbind (sysUptime), always return immediately as @@ -516,10 +588,21 @@ def add_varbind_to_json(vb_idx, vb_oid, vb_type, vb_val): if vb_idx == 0: return 0 + _vb_oid = "." + str(vb_oid.prettyPrint()) + _vb_value = vb_val.prettyPrint() + _vb_type = pysnmp_to_netsnmp_varbind_convert(vb_type) + # if second varbind, use as notifyOID for all snmp versions if vb_idx == 1: - tds.trap_dict["notify OID"] = "." + str(vb_val.prettyPrint()) - tds.trap_dict["notify OID len"] = tds.trap_dict["notify OID"].count('.') + tds.trap_dict["notify OID"] = "." + _vb_value + tds.trap_dict["notify OID len"] = tds.trap_dict["notify OID"].count( + '.') + return 0 + + # if override varbind OID, use value as agent address + if _vb_oid == agent_override_oid: + tds.trap_dict["agent address"] = _vb_value + tds.trap_dict["agent name"] = resolve_ip(_vb_value) return 0 # for SNMPv1 traps, skip varbinds 2, 3 and 4: @@ -529,7 +612,7 @@ def add_varbind_to_json(vb_idx, vb_oid, vb_type, vb_val): if tds.trap_dict["protocol version"] == "v1": if vb_idx < 5: return 0 - + if tds.first_varbind: tds.all_vb_json_str = ', \"varbinds\": [' tds.first_varbind = False @@ -537,9 +620,9 @@ def add_varbind_to_json(vb_idx, vb_oid, vb_type, vb_val): tds.all_vb_json_str = tds.all_vb_json_str + " ," _individual_vb_dict.clear() - _individual_vb_dict['varbind_oid'] = "." + vb_oid.prettyPrint() - _individual_vb_dict['varbind_type'] = pysnmp_to_netsnmp_varbind_convert(vb_type) - _individual_vb_dict['varbind_value'] = vb_val.prettyPrint() + _individual_vb_dict['varbind_oid'] = _vb_oid + _individual_vb_dict['varbind_type'] = _vb_type + _individual_vb_dict['varbind_value'] = _vb_value _individual_vb_json_str = json.dumps(_individual_vb_dict) @@ -570,7 +653,7 @@ def notif_receiver_cb(snmp_engine, stateReference, contextEngineId, contextName, :Variables: """ msg = "processing varbinds for %s" % (tds.trap_dict["uuid"]) - ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, tds.CODE_GENERAL, msg) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) # help(snmp_engine) # print(snmp_engine) @@ -582,16 +665,20 @@ def notif_receiver_cb(snmp_engine, stateReference, contextEngineId, contextName, # print(key, val) # FMDL update reset location when batching publishes - pdu_varbinds = 0 + pdu_varbind_count = 0 payload_varbinds = 0 tds.all_vb_json_str = "" + tds.all_vb_str = " varbinds:" tds.first_varbind = True # iterate over varbinds, add to json struct for vb_oid, vb_val in varBinds: - varbinds_added = add_varbind_to_json(pdu_varbinds, vb_oid, vb_val.__class__.__name__, vb_val) + log_ret = add_varbind_to_log_string( + pdu_varbind_count, vb_oid, vb_val.__class__.__name__, vb_val) + varbinds_added = add_varbind_to_json( + pdu_varbind_count, vb_oid, vb_val.__class__.__name__, vb_val) payload_varbinds += varbinds_added - pdu_varbinds += 1 + pdu_varbind_count += 1 curr_trap_json_str = json.dumps(tds.trap_dict) # now have everything except varbinds in "curr_trap_json_str" @@ -611,7 +698,7 @@ def notif_receiver_cb(snmp_engine, stateReference, contextEngineId, contextName, curr_trap_json_str = curr_trap_json_str + '}' msg = "trap %s : %s" % (tds.trap_dict["uuid"], curr_trap_json_str) - ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, tds.CODE_GENERAL, msg) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) # always log arriving traps log_all_arriving_traps() @@ -620,29 +707,41 @@ def notif_receiver_cb(snmp_engine, stateReference, contextEngineId, contextName, tds.traps_since_last_publish += 1 milliseconds_since_last_publish = (time.time() - tds.last_pub_time) * 1000 - msg = "adding %s to buffer" % (tds.trap_dict["uuid"]) - ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, tds.CODE_GENERAL, msg) - if tds.first_trap: - tds.all_traps_str = curr_trap_json_str - tds.trap_uuids_in_buffer = tds.trap_dict["uuid"] - tds.first_trap = False + # only add to publish buffer if stormwatch is NOT active + if stormwatch.sw_storm_active(tds.trap_dict["agent address"], tds.trap_dict["notify OID"]): + msg = "stormwatch active - deflecting notification %s from %s" % ( + tds.trap_dict["notify OID"], tds.trap_dict["agent address"]) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, + tds.CODE_GENERAL, msg) else: - tds.trap_uuids_in_buffer = tds.trap_uuids_in_buffer + \ - ', ' + tds.trap_dict["uuid"] - tds.all_traps_str = tds.all_traps_str + ', ' + curr_trap_json_str + msg = "adding %s to buffer" % (tds.trap_dict["uuid"]) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, + tds.CODE_GENERAL, msg) + if tds.first_trap: + tds.all_traps_json_str = curr_trap_json_str + tds.trap_uuids_in_buffer = tds.trap_dict["uuid"] + tds.first_trap = False + else: + tds.trap_uuids_in_buffer = tds.trap_uuids_in_buffer + \ + ', ' + tds.trap_dict["uuid"] + tds.all_traps_json_str = tds.all_traps_json_str + ', ' + curr_trap_json_str - # publish to dmaap after last varbind is processed - if tds.traps_since_last_publish >= tds.c_config['publisher']['max_traps_between_publishes']: + if tds.traps_since_last_publish >= int(tds.c_config['publisher']['max_traps_between_publishes']): msg = "num traps since last publish (%d) exceeds threshold (%d) - publish traps" % ( - tds.traps_since_last_publish, tds.c_config['publisher']['max_traps_between_publishes']) - ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, + tds.traps_since_last_publish, int(tds.c_config['publisher']['max_traps_between_publishes'])) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) post_dmaap() - elif milliseconds_since_last_publish >= tds.c_config['publisher']['max_milliseconds_between_publishes']: + elif milliseconds_since_last_publish >= int(tds.c_config['publisher']['max_milliseconds_between_publishes']): msg = "num milliseconds since last publish (%.0f) exceeds threshold - publish traps" % milliseconds_since_last_publish - ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) post_dmaap() + else: + msg = "neither milliseconds_since_last_publish (%.0f) or traps_since_last_publish (%d) exceed threshold - continue" % ( + milliseconds_since_last_publish, tds.traps_since_last_publish) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, + tds.CODE_GENERAL, msg) # # # # # # # # # # # # # @@ -658,37 +757,39 @@ if __name__ == "__main__": help="verbose logging") parser.add_argument('-?', action="store_true", dest="usage_requested", help="show command line use") - + # parse args args = parser.parse_args() - + # set vars from args verbose = args.verbose usage_requested = args.usage_requested - + # if usage, just display and exit if usage_requested: usage_err() - + # init vars tds.init() - + sws.init() + stats.init() + # FMDL: add with stormWatch # init sw vars - # sw.init() - + stormwatch.sw_init() + # Set initial startup hour for rolling logfile tds.last_hour = datetime.datetime.now().hour - + # get config binding service (CBS) values (either broker, or json file override) load_all_configs(0, 0) msg = "%s : %s version %s starting" % ( prog_name, tds.c_config['snmptrapd']['title'], tds.c_config['snmptrapd']['version']) stdout_logger(msg) - + # open various ecomp logs open_eelf_logs() - + # bump up logging level if overridden at command line if verbose: msg = "WARNING: '-v' argument present. All diagnostic messages will be logged. This can slow things down, use only when needed." @@ -698,7 +799,6 @@ if __name__ == "__main__": # debug.setLogger(debug.Debug('dsp', 'msgproc')) debug.setLogger(debug.Debug('all')) - # name and open arriving trap log tds.arriving_traps_filename = tds.c_config['files']['runtime_base_dir'] + "/" + \ tds.c_config['files']['log_dir'] + "/" + \ @@ -707,7 +807,7 @@ if __name__ == "__main__": msg = ("arriving traps logged to: %s" % tds.arriving_traps_filename) stdout_logger(msg) ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) - + # name and open json trap log tds.json_traps_filename = tds.c_config['files']['runtime_base_dir'] + "/" + tds.c_config['files']['log_dir'] + "/" + "DMAAP_" + ( tds.c_config['streams_publishes']['sec_fault_unsecure']['dmaap_info']['topic_url'].split('/')[-1]) + ".json" @@ -715,38 +815,38 @@ if __name__ == "__main__": msg = ("published traps logged to: %s" % tds.json_traps_filename) stdout_logger(msg) ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) - + # setup signal handling for config reload signal.signal(signal.SIGUSR1, load_all_configs) - + # save current PID for future/external reference tds.pid_file_name = tds.c_config['files']['runtime_base_dir'] + \ '/' + tds.c_config['files']['pid_dir'] + '/' + prog_name + ".pid" msg = "Runtime PID file: %s" % tds.pid_file_name ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) rc = save_pid(tds.pid_file_name) - + # Get the event loop for this thread loop = asyncio.get_event_loop() - + # Create SNMP engine with autogenerated engineID pre-bound # to socket transport dispatcher snmp_engine = engine.SnmpEngine() - + # # # # # # # # # # # # # Transport setup # # # # # # # # # # # # - + # UDP over IPv4 try: ipv4_interface = tds.c_config['protocols']['ipv4_interface'] - ipv4_port = tds.c_config['protocols']['ipv4_port'] - + ipv4_port = int(tds.c_config['protocols']['ipv4_port']) + try: # FIXME: this doesn't appear to throw an exception even if # the userID is unable (perms) to bind to port # - # We may need to open raw port using other + # We may need to open raw port using other # means to confirm proper privileges (then # close it and reopen w/ pysnmp api) config.addTransport( @@ -756,23 +856,23 @@ if __name__ == "__main__": (ipv4_interface, ipv4_port)) ) except Exception as e: - msg = "Unable to bind to %s:%s - %s" % ( + msg = "Unable to bind to %s:%d - %s" % ( ipv4_interface, ipv4_port, str(e)) - ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_FATAL, tds.CODE_GENERAL, msg) + ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_FATAL, + tds.CODE_GENERAL, msg) stdout_logger(msg) cleanup_and_exit(1, tds.pid_file_name) - + except Exception as e: msg = "IPv4 interface and/or port not specified in config - not listening for IPv4 traps" stdout_logger(msg) ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_WARN, tds.CODE_GENERAL, msg) - - + # UDP over IPv6 try: ipv6_interface = tds.c_config['protocols']['ipv6_interface'] - ipv6_port = tds.c_config['protocols']['ipv6_port'] - + ipv6_port = int(tds.c_config['protocols']['ipv6_port']) + try: config.addTransport( snmp_engine, @@ -781,27 +881,27 @@ if __name__ == "__main__": (ipv6_interface, ipv6_port)) ) except Exception as e: - msg = "Unable to bind to %s:%s - %s" % ( + msg = "Unable to bind to %s:%d - %s" % ( ipv6_interface, ipv6_port, str(e)) stdout_logger(msg) - ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_FATAL, tds.CODE_GENERAL, msg) + ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_FATAL, + tds.CODE_GENERAL, msg) cleanup_and_exit(1, tds.pid_file_name) - + except Exception as e: msg = "IPv6 interface and/or port not specified in config - not listening for IPv6 traps" stdout_logger(msg) ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_WARN, tds.CODE_GENERAL, msg) - - + # # # # # # # # # # # # # SNMPv1/2c setup # # # # # # # # # # # # - + # SecurityName <-> CommunityName mapping # to restrict trap reception to only those with specific community # strings config.addV1System(snmp_engine, 'my-area', 'public') - + # register comm_string_rewrite_observer for message arrival snmp_engine.observer.registerObserver( comm_string_rewrite_observer, @@ -811,7 +911,8 @@ if __name__ == "__main__": # # # # # # # # # # # # # SNMPv3 setup # # # # # # # # # # # # - config, snmp_engine=load_snmpv3_credentials(config, snmp_engine, tds.c_config) + config, snmp_engine = load_snmpv3_credentials( + config, snmp_engine, tds.c_config) # register snmp_engine_observer_cb for message arrival snmp_engine.observer.registerObserver( @@ -819,12 +920,12 @@ if __name__ == "__main__": 'rfc3412.receiveMessage:request', 'rfc3412.returnResponsePdu', ) - + # Register SNMP Application at the SNMP engine ntfrcv.NotificationReceiver(snmp_engine, notif_receiver_cb) - + snmp_engine.transportDispatcher.jobStarted(1) # loop forever - + # Run I/O dispatcher which will receive traps try: snmp_engine.transportDispatcher.runDispatcher() diff --git a/snmptrap/snmptrapd.sh b/snmptrap/snmptrapd.sh index ab70c91..a10e049 100755 --- a/snmptrap/snmptrapd.sh +++ b/snmptrap/snmptrapd.sh @@ -1,47 +1,68 @@ #!/usr/bin/env bash # # ============LICENSE_START======================================================= -# org.onap.dcae -# ================================================================================ -# Copyright (c) 2017-2018 AT&T Intellectual Property. All rights reserved. +# Copyright (c) 2017-2020 AT&T Intellectual Property. All rights reserved. # ================================================================================ # 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. # ============LICENSE_END========================================================= -# -# ECOMP is a trademark and service mark of AT&T Intellectual Property. -# # basics current_cmd=`basename $0` current_module=`echo $current_cmd | cut -d"." -f1` -# FMDL:: need to pick up these values from json config, but it isn't -# present at startup -base_dir=/opt/app/snmptrap -pid_file=${base_dir}/tmp/${current_module}.py.pid +# get base_dir from current script invocation +bin_base_dir="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )" +base_dir=`dirname ${bin_base_dir}` + +snmptrapd_pid_file=${base_dir}/tmp/${current_module}.py.pid +scheduler_pid_file=${base_dir}/tmp/scheduler.sh.pid + start_dir=${base_dir}/bin +# global return code +# - required because functions ultimately call log_msg, which +# is to stdout, which conflicts with "echo <return_value>" +# in the functions themselves +g_return=0 + # include path to 3.6+ version of python that has required dependencies included export PATH=/opt/app/python-3.6.1/bin:$PATH # set location of SSL certificates -export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-bundle.crt +# export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-bundle.crt # open source/external world +export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt # otherwise... -# get to where we are supposed to be for startup -cd /opt/app/snmptrap/bin +# find the best tool for the job +if [ `command -v pypy3` ] +then + PY_BINARY=pypy3 +else + if [ `command -v python3` ] + then + PY_BINARY=python3 + else + if [ `command -v python` ] + then + PY_BINARY=python + else + echo "ERROR: no pypy3 or python available in container - FATAL ERROR, exiting" + exit 1 + fi + fi +fi # expand search for python modules to include ./mod in current/runtime dir -export PYTHONPATH=./mod:./:$PYTHONPATH +export PYTHONPATH=${bin_base_dir}/mod:$PYTHONPATH # PYTHONUNBUFFERED: # set PYTHONUNBUFFERED to a non-empty string to avoid output buffering; @@ -50,7 +71,10 @@ export PYTHONPATH=./mod:./:$PYTHONPATH # set location of config broker server overrride IF NEEDED # -export CBS_SIM_JSON=../etc/snmptrapd.json +export CBS_SIM_JSON=${base_dir}/etc/snmptrapd.json + +# misc +exit_after=1 # # # # # # # # # # # log_msg - log messages to stdout in standard manner @@ -59,7 +83,43 @@ log_msg() { msg=$* - printf "`date +%Y-%m-%dT%H:%M:%S,%N | cut -c1-23` ${msg}" + echo "`date +%Y-%m-%dT%H:%M:%S,%N | cut -c1-23` ${msg}" +} + +# +# start process +# +start_process() +{ +process_name=$1 +pid_file=$2 +exec_cmd=$3 + + # check if exec_cmd has a pid_file + if [ ! -r ${pid_file} ] + then + log_msg "Starting ${process_name}" + stdout_fd=${base_dir}/logs/${process_name}.out + if [ -f ${stdout_fd} ] + then + mv -f ${stdout_fd} ${stdout_fd}.bak + fi + ${exec_cmd} >> ${base_dir}/logs/${process_name}.out 2>&1 & + g_return=$? + echo $! > ${pid_file} + else + pid=$(cat ${pid_file}) + if ps -p ${pid} > /dev/null + then + g_return=$? + log_msg "${process_name} already running - PID ${pid}" + else + log_msg "PID file present, but no corresponding process. Starting ${process_name}" + ${exec_cmd} >> ${base_dir}/logs/${process_name}.out 2>&1 & + g_return=$? + echo $! > ${pid_file} + fi + fi } # # # # # # # # # # @@ -69,113 +129,142 @@ start_service() { # Hints for startup modifications: # _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ + + # handy for debug (e.g. docker logs output) + # log_msg "Runtime env present for ${current_module} placed in ${base_dir}/logs/${current_module}.out" + env >> ${base_dir}/logs/${current_module}.out + # standard startup? Use this: - cmd="python ./snmptrapd.py" + cmd="${PY_BINARY} ${base_dir}/bin/snmptrapd.py" # want tracing? Use this: - # cmd="python ./snmptrapd.py -v" + # cmd="${PY_BINARY} ./snmptrapd.py -v" # unbuffered io for logs? Use this: - # cmd="python -u ./snmptrapd.py" + # cmd="${PY_BINARY} -u ./snmptrapd.py" # fmdl: needs further research - # cmd="python -m trace --trackcalls ./snmptrapd.py" + # cmd="${PY_BINARY} -m trace --trackcalls ./snmptrapd.py" cd ${start_dir} - # check for process already running - if [ -r ${pid_file} ] + # + # scheduler + # + start_process scheduler ${scheduler_pid_file} "${bin_base_dir}/scheduler.sh" + if [ ${g_return} -ne 0 ] then - pid=$(cat ${pid_file}) - if ps -p ${pid} > /dev/null - then - printf "${current_module} already running - PID ${pid}\n" - return 0 - fi + log_msg "ERROR! Unable to start scheduler. Check logs for details." fi - # FMDL:: do this in snmptrapd.py at startup - # roll log if present at startup - # if [ -f ${LOGFILE} ] - # then - # mv -f ${LOGFILE} ${LOGFILE}.`date +%h-%m-%Y_%H:%M:%S` - # fi - - log_msg "Starting ${current_module}... " - eval ${cmd} - return_code=$? - - if [ ${return_code} -eq 0 ] + # + # snmptrapd + # + start_process ${current_module} ${snmptrapd_pid_file} "${cmd}" + if [ ${g_return} -ne 0 ] then - log_msg "Started.\n" - else - log_msg "\nERROR! Unable to start ${current_module}. Check logs for details.\n" + log_msg "ERROR! Unable to start ${current_module}. Check logs for details." fi - - return ${return_code} - } # # # # # # # # # # # Stop the service # # # # # # # # # # -stop_service() +stop_process() { +process_name=$1 +pid_file=$2 + if [ ! -r ${pid_file} ] then - log_msg "PID file ${pid_file} does not exist or not readable - unable to stop specific instance of ${current_module}.\n" - log_msg "Diagnose further at command line as needed.\n" - return_code=1 + log_msg "PID file ${pid_file} does not exist or not readable - unable to stop ${process_name}" + g_return=1 else pid=$(cat ${pid_file}) - log_msg "Stopping ${current_module} PID ${pid}...\n" - kill ${pid} - if [ $? -ne 0 ] + pgrep -f ${process_name} | grep "^${pid}" > /dev/null + loc_return=$? + if [ ${loc_return} -eq 0 ] then - log_msg "\nERROR while trying to terminate ${current_module} PID ${pid} (is it not running or owned by another userID?)" - log_msg "\nDiagnose further at command line as needed." - return_code=$? - if [ -w ${pid_file} ] + log_msg "Stopping ${process_name} PID ${pid}..." + kill ${pid} + g_return=$? + if [ ${g_return} -eq 0 ] then - rm -f ${pid_file} + log_msg "Stopped" + else + log_msg "ERROR while terminating ${process_name} PID ${pid} (is it not running or owned by another userID?)" fi else - log_msg "Stopped\n" - if [ -w ${pid_file} ] - then - rm -f ${pid_file} - fi - return_code=0 + log_msg "${process_name} PID ${pid} not present - skipping" + fi + + if [ -w ${pid_file} ] + then + rm -f ${pid_file} fi fi +} + +# # # # # # # # # # # # # # # +# stop all snmptrapd services +# # # # # # # # # # # # # # # +stop_service() +{ + # scheduler + # + stop_process scheduler ${scheduler_pid_file} - return ${return_code} + # snmptrapd + # + stop_process ${current_module} ${snmptrapd_pid_file} } # # # # # # # # # # # # # # # # Check status of the service # # # # # # # # # # # # # # # -status_service() +status_process() { +process_name=$1 +pid_file=$2 + if [ -r ${pid_file} ] then pid=$(cat ${pid_file}) - pgrep -f ${current_module}.py | grep "^${pid}" > /dev/null - return_code=$? + pgrep -f ${process_name} | grep "^${pid}" > /dev/null + loc_return=$? - if [ ${return_code} -eq 0 ] + if [ ${loc_return} -eq 0 ] then - log_msg "Status: ${current_module} running\n" - ps -f -p ${pid} -f | grep -v PID - return_code=0 + log_msg "- ${process_name} running, PID ${pid}" + # ps -f -p ${pid} -f | grep -v PID + g_return=0 else - log_msg "Status: ERROR! ${current_module} not running.\n" - return_code=1 + log_msg "ERROR! ${process_name} not running" + g_return=1 fi else - log_msg "PID file ${pid_file} does not exist or not readable - unable to check status of ${current_module}\n" - log_msg "Diagnose further at command line as needed.\n" - return 1 + log_msg "PID file ${pid_file} does not exist or not readable - unable to check status of ${process_name}" + g_return=1 fi +} - return ${return_code} +# +# +# +status_service() +{ + # scheduler + # + status_process scheduler ${scheduler_pid_file} + loc_return=${g_return} + + # snmptrapd + # + status_process ${current_module} ${snmptrapd_pid_file} + loc_return=$((loc_return+g_return)) + if [ ${loc_return} -ne 0 ] + then + log_msg "Overall Status: CRITICAL - Required process(es) missing!" + else + log_msg "Overall Status: Normal" + fi } # # # # # # # # # # # # # # # # # @@ -183,62 +272,95 @@ status_service() # # # # # # # # # # # # # # # # # reload_cfg() { - if [ -r ${pid_file} ] + # only needed for snmptrapd + if [ -r ${snmptrapd_pid_file} ] then - pid=$(cat ${pid_file}) + pid=$(cat ${snmptrapd_pid_file}) ps -p ${pid} > /dev/null 2>&1 - ret=$? - if [ ${ret} ] + loc_return=$? + if [ ${loc_return} ] then - log_msg "Signaling ${current_module} PID ${pid} to request/read updated configs...\n" + log_msg "Signaling ${current_module} PID ${pid} to request/read updated configs..." kill -USR1 ${pid} - return_code=$? - if [ ${return_code} -eq 0 ] + g_return=$? + if [ ${g_return} -eq 0 ] then - log_msg "...Signal complete.\n" + log_msg "...Signal complete." else - log_msg "\nERROR signaling ${current_module} - diagnose further at the command line.\n" + log_msg "ERROR signaling ${current_module} (do you have permissions to do this?)" fi else - log_msg "\nERROR: ${current_module} PID ${pid} does not appear to be running.\n" - return_code=1 + log_msg "ERROR: ${current_module} PID ${pid} does not appear to be running." fi else - log_msg "\nERROR: ${current_module} pid_file ${pid_file} does not exist - unable to signal for config re-read.\n" - return_code=1 + log_msg "ERROR: ${snmptrapd_pid_file} does not exist - unable to signal for config re-read." + g_return=1 fi +} + +# # # # # # # # # # # # # # # +# stop all snmptrapd services +# # # # # # # # # # # # # # # +version() +{ +exit_swt=$1 + +version_fd=${base_dir}/etc/version.dat +if [ -f ${version_fd} ] +then + version_string=`cat ${version_fd}` + log_msg "${version_string}" + ec=0 +else + log_msg "ERROR: unable to determine version" + ec=1 +fi + +if [ "${exit_swt}" == "${exit_after}" ] +then + exit ${ec} +fi - return ${return_code} } # # # # # # # # # # # # # # M A I N # # # # # # # # # # # # # + case "$1" in "start") + version start_service - exit $? + sleep 1 + status_service + wait ;; "stop") + version stop_service - exit $? ;; "restart") + version stop_service sleep 1 start_service - exit $? + status_service ;; "status") + version status_service - exit $? ;; "reloadCfg") + version reload_cfg - exit $? + ;; + "version") + version ${exit_after} ;; *) - printf "\nUsage: ${current_cmd} {start|stop|restart|status|rollLog|reloadCfg}\n" - exit 1 + echo "Usage: ${current_cmd} {start|stop|restart|status|reloadCfg|version}" + g_return=1 esac + +exit ${g_return} diff --git a/tests/__init__.py b/tests/__init__.py index 1875bf6..af0fbb2 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,5 +1,5 @@ # ================================================================================ -# Copyright (c) 2018 AT&T Intellectual Property. All rights reserved. +# Copyright (c) 2018-2020 AT&T Intellectual Property. All rights reserved. # ================================================================================ # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. diff --git a/tests/py-test-snmptrap.sh b/tests/py-test-snmptrap.sh new file mode 100755 index 0000000..b57eef6 --- /dev/null +++ b/tests/py-test-snmptrap.sh @@ -0,0 +1,26 @@ +#!/bin/env bash +# ============LICENSE_START======================================================= +# Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= + +# export CBS_SIM_JSON=/opt/app/snmptrap/etc/snmptrapd.json +export PYTHONPATH=/opt/app/snmptrap/bin:/opt/app/snmptrap/bin/mod:$PYTHONPATH + +# cd /opt/app/snmptrap +cd /opt/app + +py.test --cov-config /opt/app/snmptrap/tests/.coveragerc --cov=snmptrap /opt/app/snmptrap/tests/ +# py.test --cov=snmptrap /opt/app/snmptrap/tests/ +coverage report -m diff --git a/tests/test_snmptrapd.py b/tests/test_snmptrapd.py index 9dc92c0..cdd19a4 100644 --- a/tests/test_snmptrapd.py +++ b/tests/test_snmptrapd.py @@ -1,3 +1,19 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2018-2020 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= + import os import pytest import unittest @@ -5,6 +21,8 @@ import snmptrapd import datetime import trapd_settings as tds +import trapd_stormwatch_settings as sws +import trapd_stormwatch as sw import trapd_http_session import trapd_runtime_pid import trapd_io @@ -18,7 +36,7 @@ class test_snmptrapd(unittest.TestCase): Test the save_pid mod """ - pytest_json_data = "{ \"snmptrapd\": { \"version\": \"1.4.0\", \"title\": \"ONAP SNMP Trap Receiver\" }, \"protocols\": { \"transport\": \"udp\", \"ipv4_interface\": \"0.0.0.0\", \"ipv4_port\": 6162, \"ipv6_interface\": \"::1\", \"ipv6_port\": 6162 }, \"cache\": { \"dns_cache_ttl_seconds\": 60 }, \"publisher\": { \"http_timeout_milliseconds\": 1500, \"http_retries\": 3, \"http_milliseconds_between_retries\": 750, \"http_primary_publisher\": \"true\", \"http_peer_publisher\": \"unavailable\", \"max_traps_between_publishes\": 10, \"max_milliseconds_between_publishes\": 10000 }, \"streams_publishes\": { \"sec_fault_unsecure\": { \"type\": \"message_router\", \"aaf_password\": null, \"dmaap_info\": { \"location\": \"mtl5\", \"client_id\": null, \"client_role\": null, \"topic_url\": \"http://uebsb91kcdc.it.att.com:3904/events/ONAP-COLLECTOR-SNMPTRAP\" }, \"aaf_username\": null } }, \"files\": { \"runtime_base_dir\": \"/tmp/opt/app/snmptrap\", \"log_dir\": \"logs\", \"data_dir\": \"data\", \"pid_dir\": \"tmp\", \"arriving_traps_log\": \"snmptrapd_arriving_traps.log\", \"snmptrapd_diag\": \"snmptrapd_prog_diag.log\", \"traps_stats_log\": \"snmptrapd_stats.csv\", \"perm_status_file\": \"snmptrapd_status.log\", \"eelf_base_dir\": \"/tmp/opt/app/snmptrap/logs\", \"eelf_error\": \"error.log\", \"eelf_debug\": \"debug.log\", \"eelf_audit\": \"audit.log\", \"eelf_metrics\": \"metrics.log\", \"roll_frequency\": \"day\", \"minimum_severity_to_log\": 2 }, \"trap_config\": { \"sw_interval_in_seconds\": 60, \"notify_oids\": { \".1.3.6.1.4.1.9.0.1\": { \"sw_high_water_in_interval\": 102, \"sw_low_water_in_interval\": 7, \"category\": \"logonly\" }, \".1.3.6.1.4.1.9.0.2\": { \"sw_high_water_in_interval\": 101, \"sw_low_water_in_interval\": 7, \"category\": \"logonly\" }, \".1.3.6.1.4.1.9.0.3\": { \"sw_high_water_in_interval\": 102, \"sw_low_water_in_interval\": 7, \"category\": \"logonly\" }, \".1.3.6.1.4.1.9.0.4\": { \"sw_high_water_in_interval\": 10, \"sw_low_water_in_interval\": 3, \"category\": \"logonly\" } } }, \"snmpv3_config\": { \"usm_users\": [ { \"user\": \"usr-sha-aes256\", \"engineId\": \"8000000001020304\", \"usmHMACSHAAuth\": \"authkey1\", \"usmAesCfb256\": \"privkey1\" }, { \"user\": \"user1\", \"engineId\": \"8000000000000001\", \"usmHMACMD5Auth\": \"authkey1\", \"usmDESPriv\": \"privkey1\" }, { \"user\": \"user2\", \"engineId\": \"8000000000000002\", \"usmHMACSHAAuth\": \"authkey2\", \"usmAesCfb128\": \"privkey2\" }, { \"user\": \"user3\", \"engineId\": \"8000000000000003\", \"usmHMACSHAAuth\": \"authkey3\", \"usmAesCfb256\": \"privkey3\" } ] } }" + pytest_json_data = "{ \"snmptrapd\": { \"version\": \"2.0.3\", \"title\": \"ONAP SNMP Trap Receiver\" }, \"protocols\": { \"transport\": \"udp\", \"ipv4_interface\": \"0.0.0.0\", \"ipv4_port\": 6162, \"ipv6_interface\": \"::1\", \"ipv6_port\": 6162 }, \"cache\": { \"dns_cache_ttl_seconds\": 60 }, \"publisher\": { \"http_timeout_milliseconds\": 1500, \"http_retries\": 3, \"http_milliseconds_between_retries\": 750, \"http_primary_publisher\": \"true\", \"http_peer_publisher\": \"unavailable\", \"max_traps_between_publishes\": 10, \"max_milliseconds_between_publishes\": 10000 }, \"streams_publishes\": { \"sec_fault_unsecure\": { \"type\": \"message_router\", \"aaf_password\": null, \"dmaap_info\": { \"location\": \"mtl5\", \"client_id\": null, \"client_role\": null, \"topic_url\": \"http://uebsb91kcdc.it.att.com:3904/events/ONAP-COLLECTOR-SNMPTRAP\" }, \"aaf_username\": null } }, \"files\": { \"runtime_base_dir\": \"/tmp/opt/app/snmptrap\", \"log_dir\": \"logs\", \"data_dir\": \"data\", \"pid_dir\": \"tmp\", \"arriving_traps_log\": \"snmptrapd_arriving_traps.log\", \"snmptrapd_diag\": \"snmptrapd_prog_diag.log\", \"traps_stats_log\": \"snmptrapd_stats.csv\", \"perm_status_file\": \"snmptrapd_status.log\", \"eelf_base_dir\": \"/tmp/opt/app/snmptrap/logs\", \"eelf_error\": \"error.log\", \"eelf_debug\": \"debug.log\", \"eelf_audit\": \"audit.log\", \"eelf_metrics\": \"metrics.log\", \"roll_frequency\": \"day\", \"minimum_severity_to_log\": 2 }, \"trap_config\": { \"sw_interval_in_seconds\": 60, \"notify_oids\": { \".1.3.6.1.4.1.9.0.1\": { \"sw_high_water_in_interval\": 102, \"sw_low_water_in_interval\": 7, \"category\": \"logonly\" }, \".1.3.6.1.4.1.9.0.2\": { \"sw_high_water_in_interval\": 101, \"sw_low_water_in_interval\": 7, \"category\": \"logonly\" }, \".1.3.6.1.4.1.9.0.3\": { \"sw_high_water_in_interval\": 102, \"sw_low_water_in_interval\": 7, \"category\": \"logonly\" }, \".1.3.6.1.4.1.9.0.4\": { \"sw_high_water_in_interval\": 10, \"sw_low_water_in_interval\": 3, \"category\": \"logonly\" } } }, \"snmpv3_config\": { \"usm_users\": [ { \"user\": \"usr-sha-aes256\", \"engineId\": \"8000000001020304\", \"usmHMACSHAAuth\": \"authkey1\", \"usmAesCfb256\": \"privkey1\" }, { \"user\": \"user1\", \"engineId\": \"8000000000000001\", \"usmHMACMD5Auth\": \"authkey1\", \"usmDESPriv\": \"privkey1\" }, { \"user\": \"user2\", \"engineId\": \"8000000000000002\", \"usmHMACSHAAuth\": \"authkey2\", \"usmAesCfb128\": \"privkey2\" }, { \"user\": \"user3\", \"engineId\": \"8000000000000003\", \"usmHMACSHAAuth\": \"authkey3\", \"usmAesCfb256\": \"privkey3\" } ] } }" # create copy of snmptrapd.json for pytest pytest_json_config = "/tmp/opt/app/snmptrap/etc/snmptrapd.json" @@ -44,6 +62,7 @@ class test_snmptrapd(unittest.TestCase): # init vars tds.init() + sw.sw_init() # request load of CBS data os.environ.update(CBS_SIM_JSON='/tmp/opt/app/snmptrap/etc/snmptrapd.json') @@ -150,29 +169,5 @@ class test_snmptrapd(unittest.TestCase): result = errorIndication self.assertEqual(result, None) - def test_add_varbind_to_json(self): - - # init vars - tds.init() - tds.trap_dict["notify OID"] = ".1.2.3.4.5.6.7.8" - tds.trap_dict["protocol version"] = "v2c" - - # varbinds=[(ObjectName('1.3.6.1.2.1.1.3.0'), TimeTicks(0)), (ObjectName('1.3.6.1.6.3.1.1.4.1.0'), ObjectIdentifier('1.3.6.1.4.1.74.2.46.12.1.1')), (ObjectName('1.3.6.1.4.1.74.2.46.12.1.1.1'), OctetString(b'ucsnmp heartbeat - ignore')), (ObjectName('1.3.6.1.4.1.74.2.46.12.1.1.2'), OctetString(b'Thu Mar 21 15:46:58 2019'))] - - # vb=(ObjectName('1.3.6.1.4.1.74.2.46.12.1.1.1'), OctetString(b'ucsnmp heartbeat - ignore')) - - self.assertEqual(snmptrapd.add_varbind_to_json(0,ObjectIdentifier('.1.2.3.4'), 'OctetString', OctetString(b'Thu Mar 21 15:46:58 2019')), 0) - self.assertEqual(snmptrapd.add_varbind_to_json(1,ObjectIdentifier('.1.2.3.4'), 'OctetString', OctetString(b'Thu Mar 21 15:46:58 2019')), 0) - self.assertEqual(snmptrapd.add_varbind_to_json(2,ObjectIdentifier('.1.2.3.4'), 'OctetString', OctetString(b'Thu Mar 21 15:46:58 2019')), 1) - self.assertEqual(snmptrapd.add_varbind_to_json(3,ObjectIdentifier('.1.2.3.4'), 'OctetString', OctetString(b'Thu Mar 21 15:46:58 2019')), 1) - - # init vars - tds.init() - tds.trap_dict["notify OID"] = ".1.2.3.4.5.6.7.8" - tds.trap_dict["protocol version"] = "v1" - - self.assertEqual(snmptrapd.add_varbind_to_json(0,ObjectIdentifier('.1.2.3.4'), 'OctetString', OctetString(b'Thu Mar 21 15:46:58 2019')), 0) - self.assertEqual(snmptrapd.add_varbind_to_json(5,ObjectIdentifier('.1.2.3.4'), 'OctetString', OctetString(b'Thu Mar 21 15:46:58 2019')), 1) - if __name__ == '__main__': unittest.main() diff --git a/tests/test_snmptrapd_send_test_trap.py b/tests/test_snmptrapd_send_test_trap.py index 54d522e..0e23def 100755 --- a/tests/test_snmptrapd_send_test_trap.py +++ b/tests/test_snmptrapd_send_test_trap.py @@ -1,3 +1,19 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2018-2020 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= + from pysnmp.hlapi import * from pysnmp import debug diff --git a/tests/test_trapd_exit.py b/tests/test_trapd_exit.py index 594624f..3bbc386 100644 --- a/tests/test_trapd_exit.py +++ b/tests/test_trapd_exit.py @@ -1,3 +1,19 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2018-2020 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= + import pytest import unittest import trapd_exit diff --git a/tests/test_trapd_get_cbs_config.py b/tests/test_trapd_get_cbs_config.py index 44bf021..67bccfc 100644 --- a/tests/test_trapd_get_cbs_config.py +++ b/tests/test_trapd_get_cbs_config.py @@ -1,44 +1,77 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2019-2020 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= + import pytest import unittest import os +import sys from onap_dcae_cbs_docker_client.client import get_config from trapd_exit import cleanup_and_exit from trapd_io import stdout_logger, ecomp_logger import trapd_settings as tds import trapd_get_cbs_config - + +from pathlib import Path + +# # # # # # # # +# ENV setup +# # # # # # # # + +# required directory tree +try: + Path("/tmp/opt/app/snmptrap/logs").mkdir(parents=True, exist_ok=True) + Path("/tmp/opt/app/snmptrap/tmp").mkdir(parents=True, exist_ok=True) +except Exception as e: + print("Error while running %s : %s" % (os.path.basename(__file__), str(e.strerror))) + sys.exit(1) + +# env var for CBS_SIM_JSON +try: + os.environ['CBS_SIM_JSON'] = "/tmp/opt/app/snmptrap/etc/snmptrapd.json" +except Exception as e: + print("Error while running %s : %s" % (os.path.basename(__file__), str(e.strerror))) + sys.exit(1) + +pytest_json_data = "{ \"snmptrapd\": { \"version\": \"1.4.0\", \"title\": \"ONAP SNMP Trap Receiver\" }, \"protocols\": { \"transport\": \"udp\", \"ipv4_interface\": \"0.0.0.0\", \"ipv4_port\": 6162, \"ipv6_interface\": \"::1\", \"ipv6_port\": 6162 }, \"cache\": { \"dns_cache_ttl_seconds\": 60 }, \"publisher\": { \"http_timeout_milliseconds\": 1500, \"http_retries\": 3, \"http_milliseconds_between_retries\": 750, \"http_primary_publisher\": \"true\", \"http_peer_publisher\": \"unavailable\", \"max_traps_between_publishes\": 10, \"max_milliseconds_between_publishes\": 10000 }, \"streams_publishes\": { \"sec_fault_unsecure\": { \"type\": \"message_router\", \"aaf_password\": null, \"dmaap_info\": { \"location\": \"mtl5\", \"client_id\": null, \"client_role\": null, \"topic_url\": \"http://localhost:3904/events/ONAP-COLLECTOR-SNMPTRAP\" }, \"aaf_username\": null } }, \"files\": { \"runtime_base_dir\": \"/tmp/opt/app/snmptrap\", \"log_dir\": \"logs\", \"data_dir\": \"data\", \"pid_dir\": \"tmp\", \"arriving_traps_log\": \"snmptrapd_arriving_traps.log\", \"snmptrapd_diag\": \"snmptrapd_prog_diag.log\", \"traps_stats_log\": \"snmptrapd_stats.csv\", \"perm_status_file\": \"snmptrapd_status.log\", \"eelf_base_dir\": \"/tmp/opt/app/snmptrap/logs\", \"eelf_error\": \"error.log\", \"eelf_debug\": \"debug.log\", \"eelf_audit\": \"audit.log\", \"eelf_metrics\": \"metrics.log\", \"roll_frequency\": \"day\", \"minimum_severity_to_log\": 2 }, \"trap_config\": { \"sw_interval_in_seconds\": 60, \"notify_oids\": { \".1.3.6.1.4.1.9.0.1\": { \"sw_high_water_in_interval\": 102, \"sw_low_water_in_interval\": 7, \"category\": \"logonly\" }, \".1.3.6.1.4.1.9.0.2\": { \"sw_high_water_in_interval\": 101, \"sw_low_water_in_interval\": 7, \"category\": \"logonly\" }, \".1.3.6.1.4.1.9.0.3\": { \"sw_high_water_in_interval\": 102, \"sw_low_water_in_interval\": 7, \"category\": \"logonly\" }, \".1.3.6.1.4.1.9.0.4\": { \"sw_high_water_in_interval\": 10, \"sw_low_water_in_interval\": 3, \"category\": \"logonly\" } } }, \"snmpv3_config\": { \"usm_users\": [ { \"user\": \"usr-sha-aes256\", \"engineId\": \"8000000001020304\", \"usmHMACSHAAuth\": \"authkey1\", \"usmAesCfb256\": \"privkey1\" }, { \"user\": \"user1\", \"engineId\": \"8000000000000001\", \"usmHMACMD5Auth\": \"authkey1\", \"usmDESPriv\": \"privkey1\" }, { \"user\": \"user2\", \"engineId\": \"8000000000000002\", \"usmHMACSHAAuth\": \"authkey2\", \"usmAesCfb128\": \"privkey2\" }, { \"user\": \"user3\", \"engineId\": \"8000000000000003\", \"usmHMACSHAAuth\": \"authkey3\", \"usmAesCfb256\": \"privkey3\" } ] } }" + +# create snmptrapd.json for pytest +pytest_json_config = os.getenv('CBS_SIM_JSON') +with open(pytest_json_config, 'w') as outfile: + outfile.write(pytest_json_data) +outfile.close() + +# test class/methods class test_get_cbs_config(unittest.TestCase): """ Test the trapd_get_cbs_config mod """ - pytest_json_data = "{ \"snmptrapd\": { \"version\": \"1.4.0\", \"title\": \"ONAP SNMP Trap Receiver\" }, \"protocols\": { \"transport\": \"udp\", \"ipv4_interface\": \"0.0.0.0\", \"ipv4_port\": 6162, \"ipv6_interface\": \"::1\", \"ipv6_port\": 6162 }, \"cache\": { \"dns_cache_ttl_seconds\": 60 }, \"publisher\": { \"http_timeout_milliseconds\": 1500, \"http_retries\": 3, \"http_milliseconds_between_retries\": 750, \"http_primary_publisher\": \"true\", \"http_peer_publisher\": \"unavailable\", \"max_traps_between_publishes\": 10, \"max_milliseconds_between_publishes\": 10000 }, \"streams_publishes\": { \"sec_fault_unsecure\": { \"type\": \"message_router\", \"aaf_password\": null, \"dmaap_info\": { \"location\": \"mtl5\", \"client_id\": null, \"client_role\": null, \"topic_url\": \"http://localhost:3904/events/ONAP-COLLECTOR-SNMPTRAP\" }, \"aaf_username\": null } }, \"files\": { \"runtime_base_dir\": \"/tmp/opt/app/snmptrap\", \"log_dir\": \"logs\", \"data_dir\": \"data\", \"pid_dir\": \"tmp\", \"arriving_traps_log\": \"snmptrapd_arriving_traps.log\", \"snmptrapd_diag\": \"snmptrapd_prog_diag.log\", \"traps_stats_log\": \"snmptrapd_stats.csv\", \"perm_status_file\": \"snmptrapd_status.log\", \"eelf_base_dir\": \"/tmp/opt/app/snmptrap/logs\", \"eelf_error\": \"error.log\", \"eelf_debug\": \"debug.log\", \"eelf_audit\": \"audit.log\", \"eelf_metrics\": \"metrics.log\", \"roll_frequency\": \"day\", \"minimum_severity_to_log\": 2 }, \"trap_config\": { \"sw_interval_in_seconds\": 60, \"notify_oids\": { \".1.3.6.1.4.1.9.0.1\": { \"sw_high_water_in_interval\": 102, \"sw_low_water_in_interval\": 7, \"category\": \"logonly\" }, \".1.3.6.1.4.1.9.0.2\": { \"sw_high_water_in_interval\": 101, \"sw_low_water_in_interval\": 7, \"category\": \"logonly\" }, \".1.3.6.1.4.1.9.0.3\": { \"sw_high_water_in_interval\": 102, \"sw_low_water_in_interval\": 7, \"category\": \"logonly\" }, \".1.3.6.1.4.1.9.0.4\": { \"sw_high_water_in_interval\": 10, \"sw_low_water_in_interval\": 3, \"category\": \"logonly\" } } }, \"snmpv3_config\": { \"usm_users\": [ { \"user\": \"usr-sha-aes256\", \"engineId\": \"8000000001020304\", \"usmHMACSHAAuth\": \"authkey1\", \"usmAesCfb256\": \"privkey1\" }, { \"user\": \"user1\", \"engineId\": \"8000000000000001\", \"usmHMACMD5Auth\": \"authkey1\", \"usmDESPriv\": \"privkey1\" }, { \"user\": \"user2\", \"engineId\": \"8000000000000002\", \"usmHMACSHAAuth\": \"authkey2\", \"usmAesCfb128\": \"privkey2\" }, { \"user\": \"user3\", \"engineId\": \"8000000000000003\", \"usmHMACSHAAuth\": \"authkey3\", \"usmAesCfb256\": \"privkey3\" } ] } }" - - # create copy of snmptrapd.json for pytest - pytest_json_config = "/tmp/opt/app/snmptrap/etc/snmptrapd.json" - with open(pytest_json_config, 'w') as outfile: - outfile.write(pytest_json_data) - - - def test_cbs_env_present(self): + def test_cbs_fallback_env_present(self): """ - Test that CONSUL_HOST env variable exists but fails to - respond + Test that CBS fallback env variable exists and we can get config + from fallback env var """ - os.environ.update(CONSUL_HOST='nosuchhost') - # del os.environ['CBS_SIM_JSON'] - # result = trapd_get_cbs_config.get_cbs_config() - # print("result: %s" % result) + os.environ.update(CBS_SIM_JSON='/tmp/opt/app/snmptrap/etc/snmptrapd.json') + result = trapd_get_cbs_config.get_cbs_config() + print("result: %s" % result) # compare = str(result).startswith("{'snmptrap': ") - # self.assertEqual(compare, False) - - with pytest.raises(Exception) as pytest_wrapped_sys_exit: - result = trapd_get_cbs_config.get_cbs_config() - assert pytest_wrapped_sys_exit.type == SystemExit - # assert pytest_wrapped_sys_exit.value.code == 1 + # self.assertEqual(compare, True) + self.assertEqual(result, True) - def test_cbs_override_env_invalid(self): """ """ @@ -51,35 +84,32 @@ class test_get_cbs_config(unittest.TestCase): with pytest.raises(SystemExit) as pytest_wrapped_sys_exit: result = trapd_get_cbs_config.get_cbs_config() assert pytest_wrapped_sys_exit.type == SystemExit - assert pytest_wrapped_sys_exit.value.code == 1 - + # assert pytest_wrapped_sys_exit.value.code == 1 - def test_cbs_override_env_unset(self): + def test_cbs_env_present(self): """ + Test that CONSUL_HOST env variable exists but fails to + respond """ - os.environ.update(CBS_SIM_JSON='') - #result = trapd_get_cbs_config.get_cbs_config() - #print("result: %s" % result) + os.environ.update(CONSUL_HOST='localhost') + del os.environ['CBS_SIM_JSON'] + # result = trapd_get_cbs_config.get_cbs_config() + # print("result: %s" % result) # compare = str(result).startswith("{'snmptrap': ") # self.assertEqual(compare, False) - with pytest.raises(SystemExit) as pytest_wrapped_sys_exit: - result = trapd_get_cbs_config.get_cbs_config() - assert pytest_wrapped_sys_exit.type == SystemExit - assert pytest_wrapped_sys_exit.value.code == 1 - + with pytest.raises(SystemExit) as sys_exit: + trapd_get_cbs_config.get_cbs_config() + assert sys_exit.value.errno == errno.ECONNREFUSED - def test_cbs_fallback_env_present(self): + def test_cbs_override_env_undefined(self): """ - Test that CBS fallback env variable exists and we can get config - from fallback env var """ - os.environ.update(CBS_SIM_JSON='/tmp/opt/app/snmptrap/etc/snmptrapd.json') - result = trapd_get_cbs_config.get_cbs_config() - print("result: %s" % result) - # compare = str(result).startswith("{'snmptrap': ") - # self.assertEqual(compare, True) - self.assertEqual(result, True) + print("------>>> RUNNING test_no_cbs_override_env_var:") + del os.environ['CBS_SIM_JSON'] + + with pytest.raises(SystemExit) as pytest_wrapped_sys_exit: + assert trapd_get_cbs_config.get_cbs_config() == SystemExit if __name__ == '__main__': unittest.main() diff --git a/tests/test_trapd_http_session.py b/tests/test_trapd_http_session.py index a423be6..478b29d 100644 --- a/tests/test_trapd_http_session.py +++ b/tests/test_trapd_http_session.py @@ -1,3 +1,19 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2019-2020 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= + import pytest import unittest import trapd_http_session @@ -7,6 +23,14 @@ class test_init_session_obj(unittest.TestCase): Test the init_session_obj mod """ + def close_nonexisting_session(self): + """ + test close of existing http session + """ + sess="no session" + result = trapd_http_session.close_session_obj(sess) + self.assertEqual(result, True) + def init_session(self): """ test creation of http session @@ -32,12 +56,5 @@ class test_init_session_obj(unittest.TestCase): result = trapd_http_session.close_session_obj(sess) self.assertEqual(result, True) - def close_nonexistent_session(self): - """ - test close of non-existent http session - """ - result = trapd_http_session.close_session_obj(None) - self.assertEqual(result, True) - if __name__ == '__main__': unittest.main() diff --git a/tests/test_trapd_io.py b/tests/test_trapd_io.py index 0ec93d5..3e32493 100644 --- a/tests/test_trapd_io.py +++ b/tests/test_trapd_io.py @@ -1,3 +1,19 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2019-2020 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= + import os import pytest import unittest @@ -7,127 +23,176 @@ import json import trapd_settings as tds import trapd_runtime_pid import trapd_io +import sys +from pathlib import Path + class test_trapd_io(unittest.TestCase): """ Test the save_pid mod """ tds.c_config = json.loads("{ \"snmptrapd\": { \"version\": \"1.4.0\", \"title\": \"ONAP SNMP Trap Receiver\" }, \"protocols\": { \"transport\": \"udp\", \"ipv4_interface\": \"0.0.0.0\", \"ipv4_port\": 6162, \"ipv6_interface\": \"::1\", \"ipv6_port\": 6162 }, \"cache\": { \"dns_cache_ttl_seconds\": 60 }, \"publisher\": { \"http_timeout_milliseconds\": 1500, \"http_retries\": 3, \"http_milliseconds_between_retries\": 750, \"http_primary_publisher\": \"true\", \"http_peer_publisher\": \"unavailable\", \"max_traps_between_publishes\": 10, \"max_milliseconds_between_publishes\": 10000 }, \"streams_publishes\": { \"sec_fault_unsecure\": { \"type\": \"message_router\", \"aaf_password\": null, \"dmaap_info\": { \"location\": \"mtl5\", \"client_id\": null, \"client_role\": null, \"topic_url\": \"http://localhost:3904/events/ONAP-COLLECTOR-SNMPTRAP\" }, \"aaf_username\": null } }, \"files\": { \"runtime_base_dir\": \"/tmp/opt/app/snmptrap\", \"log_dir\": \"logs\", \"data_dir\": \"data\", \"pid_dir\": \"tmp\", \"arriving_traps_log\": \"snmptrapd_arriving_traps.log\", \"snmptrapd_diag\": \"snmptrapd_prog_diag.log\", \"traps_stats_log\": \"snmptrapd_stats.csv\", \"perm_status_file\": \"snmptrapd_status.log\", \"eelf_base_dir\": \"/tmp/opt/app/snmptrap/logs\", \"eelf_error\": \"error.log\", \"eelf_debug\": \"debug.log\", \"eelf_audit\": \"audit.log\", \"eelf_metrics\": \"metrics.log\", \"roll_frequency\": \"day\", \"minimum_severity_to_log\": 2 }, \"trap_config\": { \"sw_interval_in_seconds\": 60, \"notify_oids\": { \".1.3.6.1.4.1.9.0.1\": { \"sw_high_water_in_interval\": 102, \"sw_low_water_in_interval\": 7, \"category\": \"logonly\" }, \".1.3.6.1.4.1.9.0.2\": { \"sw_high_water_in_interval\": 101, \"sw_low_water_in_interval\": 7, \"category\": \"logonly\" }, \".1.3.6.1.4.1.9.0.3\": { \"sw_high_water_in_interval\": 102, \"sw_low_water_in_interval\": 7, \"category\": \"logonly\" }, \".1.3.6.1.4.1.9.0.4\": { \"sw_high_water_in_interval\": 10, \"sw_low_water_in_interval\": 3, \"category\": \"logonly\" } } }, \"snmpv3_config\": { \"usm_users\": [ { \"user\": \"usr-sha-aes256\", \"engineId\": \"8000000001020304\", \"usmHMACSHAAuth\": \"authkey1\", \"usmAesCfb256\": \"privkey1\" }, { \"user\": \"user1\", \"engineId\": \"8000000000000001\", \"usmHMACMD5Auth\": \"authkey1\", \"usmDESPriv\": \"privkey1\" }, { \"user\": \"user2\", \"engineId\": \"8000000000000002\", \"usmHMACSHAAuth\": \"authkey2\", \"usmAesCfb128\": \"privkey2\" }, { \"user\": \"user3\", \"engineId\": \"8000000000000003\", \"usmHMACSHAAuth\": \"authkey3\", \"usmAesCfb256\": \"privkey3\" } ] } }") - post_data_enclosed=json.loads("[{\"epoch_serno\": 15488041460000, \"uuid\": \"bd3a87a0-241c-11e9-ac41-0242ac11000f\", \"agent address\": \"127.0.0.1\", \"agent name\": \"localhost\", \"cambria.partition\": \"localhost\", \"community\": \"\", \"community len\": 0, \"protocol version\": \"v2c\", \"time received\": 1548804146, \"trap category\": \"com.att.dcae.dmaap.FTL.24256-DCAE-COLLECTOR-UCSNMP-v1\", \"notify OID\": \".1.3.6.1.4.1.74.2.46.12.1.1\", \"notify OID len\": 12, \"varbinds\": [{\"varbind_oid\": \".1.3.6.1.4.1.74.2.46.12.1.1.1\", \"varbind_type\": \"octet\", \"varbind_value\": \"ucsnmp heartbeat - ignore\"} ,{\"varbind_oid\": \".1.3.6.1.4.1.74.2.46.12.1.1.2\", \"varbind_type\": \"octet\", \"varbind_value\": \"Tue Jan 29 23:22:26 2019\"}]}, {\"epoch_serno\": 15488041560000, \"uuid\": \"c338a4b6-241c-11e9-ac41-0242ac11000f\", \"agent address\": \"127.0.0.1\", \"agent name\": \"localhost\", \"cambria.partition\": \"localhost\", \"community\": \"\", \"community len\": 0, \"protocol version\": \"v2c\", \"time received\": 1548804156, \"trap category\": \"com.att.dcae.dmaap.FTL.24256-DCAE-COLLECTOR-UCSNMP-v1\", \"notify OID\": \".1.3.6.1.4.1.74.2.46.12.1.1\", \"notify OID len\": 12, \"varbinds\": [{\"varbind_oid\": \".1.3.6.1.4.1.74.2.46.12.1.1.1\", \"varbind_type\": \"octet\", \"varbind_value\": \"ucsnmp heartbeat - ignore\"} ,{\"varbind_oid\": \".1.3.6.1.4.1.74.2.46.12.1.1.2\", \"varbind_type\": \"octet\", \"varbind_value\": \"Tue Jan 29 23:22:36 2019\"}]}]") - - -def test_open_eelf_error_file(): - """ - Test bad error file location - """ - - # open eelf error logs - tds.c_config['files.eelf_error']="/bad_dir/error.log" - - # try to open file in non-existent dir - with pytest.raises(SystemExit) as pytest_wrapped_exception: - result = trapd_io.open_eelf_logs() - assert pytest_wrapped_exception.type == SystemExit - -def test_open_eelf_debug_file(): - """ - Test bad debug file location - """ - - # open eelf debug logs - tds.c_config['files.eelf_debug']="/bad_dir/debug.log" - - # try to open file in non-existent dir - with pytest.raises(SystemExit) as pytest_wrapped_exception: - result = trapd_io.open_eelf_logs() - assert pytest_wrapped_exception.type == SystemExit -def test_open_eelf_audit_file(): - """ - Test bad audit file location - """ - - # open eelf debug logs - tds.c_config['files.eelf_audit']="/bad_dir/audit.log" - - # try to open file in non-existent dir - with pytest.raises(SystemExit) as pytest_wrapped_exception: - result = trapd_io.open_eelf_logs() - assert pytest_wrapped_exception.type == SystemExit - -def test_open_eelf_metrics_file(): - """ - Test bad metrics file location - """ - - # open eelf debug logs - tds.c_config['files.eelf_metrics']="/bad_dir/metrics.log" - - # try to open file in non-existent dir - with pytest.raises(SystemExit) as pytest_wrapped_exception: - result = trapd_io.open_eelf_logs() - assert pytest_wrapped_exception.type == SystemExit - -def test_roll_all_logs(): - """ - Test roll of logs when not open - """ - # try to roll logs when not open - with pytest.raises(SystemExit) as pytest_wrapped_exception: - result = trapd_io.roll_all_logs() - assert pytest_wrapped_exception.type == SystemExit - -def test_roll_file(): - """ - Test roll of individual file when not present - """ - - # try to roll logs when not open - assert trapd_io.roll_file("/file/not/present") == False - -def test_open_file_exists(): - """ - Test file open in directory present - """ - - # create copy of snmptrapd.json for pytest - test_file = "/tmp/snmptrap_pytest" - - # try to roll logs when not open - result = trapd_io.open_file(test_file) - assert str(result).startswith("<_io.TextIOWrapper name=") == True - - -def test_open_file_exists_does_not_exist(): - """ - Test file open in directory present - """ - - # create copy of snmptrapd.json for pytest - test_file = "/tmp/no_such_dir/snmptrap_pytest" - - # try to open file when dir not present - with pytest.raises(SystemExit) as pytest_wrapped_exception: + def test_roll_all_files_notopen(self): + """ + Test rolling files that aren't open + """ + + # try to roll logs when not open + with pytest.raises(SystemExit) as pytest_wrapped_exception: + result = trapd_io.roll_all_logs() + assert pytest_wrapped_exception.type == SystemExit + + def test_open_eelf_error_file(self): + """ + Test bad error file location + """ + + # open eelf error logs + tds.c_config['files.eelf_error']="/bad_dir/error.log" + + # try to open file in non-existent dir + with pytest.raises(SystemExit) as pytest_wrapped_exception: + result = trapd_io.open_eelf_logs() + assert pytest_wrapped_exception.type == SystemExit + + def test_open_eelf_debug_file(self): + """ + Test bad debug file location + """ + + # open eelf debug logs + tds.c_config['files.eelf_debug']="/bad_dir/debug.log" + + # try to open file in non-existent dir + with pytest.raises(SystemExit) as pytest_wrapped_exception: + result = trapd_io.open_eelf_logs() + assert pytest_wrapped_exception.type == SystemExit + + def test_open_eelf_audit_file(self): + """ + Test bad audit file location + """ + + # open eelf debug logs + tds.c_config['files.eelf_audit']="/bad_dir/audit.log" + + # try to open file in non-existent dir + with pytest.raises(SystemExit) as pytest_wrapped_exception: + result = trapd_io.open_eelf_logs() + assert pytest_wrapped_exception.type == SystemExit + + def test_open_eelf_metrics_file(self): + """ + Test bad metrics file location + """ + + # open eelf debug logs + tds.c_config['files.eelf_metrics']="/bad_dir/metrics.log" + + # try to open file in non-existent dir + with pytest.raises(SystemExit) as pytest_wrapped_exception: + result = trapd_io.open_eelf_logs() + assert pytest_wrapped_exception.type == SystemExit + + def test_roll_all_logs(self): + """ + Test roll of logs when not open + """ + + # try to roll logs when not open + with pytest.raises(SystemExit) as pytest_wrapped_exception: + result = trapd_io.roll_all_logs() + assert pytest_wrapped_exception.type == SystemExit + + def test_roll_file(self): + """ + Test roll of individual file when not present + """ + + # try to roll logs when not open + result = trapd_io.roll_file("/file/not/present") + self.assertEqual(result, False) + + def test_roll_file_no_write_perms(self): + """ + try to roll logs when not enough perms + """ + + no_perms_dir="/tmp/opt/app/snmptrap/no_perms" + no_perms_file="test.dat" + no_perms_fp= no_perms_dir + "/" + no_perms_file + + # required directory tree + try: + Path(no_perms_dir).mkdir(parents=True, exist_ok=True) + os.chmod(no_perms_dir,0o777) + except Exception as e: + print("Error while running %s : %s" % (os.path.basename(__file__), str(e.strerror))) + sys.exit(1) + + # create empty file + open(no_perms_fp,'a').close() + os.chmod(no_perms_dir,0o444) + + result = trapd_io.roll_file(no_perms_fp) + self.assertEqual(result, False) + + # try to roll file in dir with no write perms + with pytest.raises(SystemExit) as pytest_wrapped_exception: + result = trapd_io.open_file(no_perms_fp) + assert pytest_wrapped_exception.type == SystemExit + + + def test_open_file_exists(self): + """ + Test file open in directory present + """ + + # create copy of snmptrapd.json for pytest + test_file = "/tmp/snmptrap_pytest" + + # try to roll logs when not open result = trapd_io.open_file(test_file) - assert pytest_wrapped_exception.type == SystemExit - -def test_close_file_exists(): - """ - Test closing a file that's present - """ - - # create copy of snmptrapd.json for pytest - test_file_name = "/tmp/snmptrap_pytest" - test_file = trapd_io.open_file(test_file_name) - - # close active file - assert trapd_io.close_file(test_file, test_file_name) == True - -def test_close_file_does_not_exists(): - """ - Test closing non-existent file - """ - - # try to roll logs when not open - assert trapd_io.close_file(None, None) == False + compare = str(result).startswith("<_io.TextIOWrapper name=") + self.assertEqual(compare, True) + + def test_open_file_exists_does_not_exist(self): + """ + Test file open in directory present + """ + + # create copy of snmptrapd.json for pytest + test_file = "/tmp/no_such_dir/snmptrap_pytest" + + # try to open file when dir not present + with pytest.raises(SystemExit) as pytest_wrapped_exception: + result = trapd_io.open_file(test_file) + assert pytest_wrapped_exception.type == SystemExit + + def test_close_file_exists(self): + """ + Test closing a file that's present + """ + + # create copy of snmptrapd.json for pytest + test_file_name = "/tmp/snmptrap_pytest" + test_file = trapd_io.open_file(test_file_name) + + # close active file + result = trapd_io.close_file(test_file, test_file_name) + self.assertEqual(result, True) + + def test_close_file_does_not_exists(self): + """ + Test closing non-existent file + """ + + # try to roll logs when not open + result = trapd_io.close_file(None, None) + self.assertEqual(result, False) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_trapd_runtime_pid.py b/tests/test_trapd_runtime_pid.py index b9010e1..e22b6cb 100644 --- a/tests/test_trapd_runtime_pid.py +++ b/tests/test_trapd_runtime_pid.py @@ -1,3 +1,19 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2019-2020 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= + import pytest import unittest import trapd_runtime_pid diff --git a/tests/test_trapd_settings.py b/tests/test_trapd_settings.py index 17b20a8..9d5cee2 100644 --- a/tests/test_trapd_settings.py +++ b/tests/test_trapd_settings.py @@ -1,3 +1,19 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2019-2020 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= + import pytest import unittest import trapd_exit diff --git a/tests/test_trapd_snmpv3.py b/tests/test_trapd_snmpv3.py index 2a0ef12..478f479 100644 --- a/tests/test_trapd_snmpv3.py +++ b/tests/test_trapd_snmpv3.py @@ -1,3 +1,19 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2019-2020 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= + import pytest import json import unittest diff --git a/tests/test_trapd_stormwatch.py b/tests/test_trapd_stormwatch.py new file mode 100644 index 0000000..236157e --- /dev/null +++ b/tests/test_trapd_stormwatch.py @@ -0,0 +1,54 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= + +import pytest +import unittest +import trapd_exit + +import trapd_stormwatch as sw +import trapd_stormwatch_settings as sws +import trapd_stats_settings as stats + +class test_cleanup_and_exit(unittest.TestCase): + """ + Test for presense of required vars + """ + + + def test_increment_existing_counter(self): + """ + Test increment counter + """ + sw.sw_init() + stats.init() + + oid=".1.2.3.4.5.6" + sws.sw_config_oid_dict[oid] = True + sws.sw_config_low_water_in_interval_dict[oid] = 1 + sws.sw_config_high_water_in_interval_dict[oid] = 10 + + try: + sw.stats_increment_counters("192.168.1.1", ".1.2.3.4.5.6") + result = True + except: + result = False + + self.assertEqual(result, True) + + +if __name__ == '__main__': + # sws.init() + unittest.main() diff --git a/tests/test_trapd_stormwatch_settings.py b/tests/test_trapd_stormwatch_settings.py new file mode 100644 index 0000000..ba8ddb0 --- /dev/null +++ b/tests/test_trapd_stormwatch_settings.py @@ -0,0 +1,158 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2020 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= + +import pytest +import unittest +import trapd_exit + +pid_file="/tmp/test_pid_file" +pid_file_dne="/tmp/test_pid_file_NOT" + +import trapd_stormwatch_settings as sws + +class test_cleanup_and_exit(unittest.TestCase): + """ + Test for presense of required vars + """ + + + def test_nonexistent_dict(self): + """ + Test nosuch var + """ + sws.init() + try: + sws.no_such_var + result = True + except: + result = False + + self.assertEqual(result, False) + + def test_storm_counter_dict(self): + """ + Test storm_counter_dict + """ + sws.init() + try: + sws.sw_storm_counter_dict + result = True + except: + result = False + + self.assertEqual(result, True) + + def test_storm_active_dict(self): + """ + Test storm_active_dict + """ + + sws.init() + try: + sws.sw_storm_active_dict + result = True + except: + result = False + + self.assertEqual(result, True) + + def test_sw_config_oid_dict(self): + """ + Test sw_config_oid_dict + """ + + sws.init() + try: + sws.sw_config_oid_dict + result = True + except: + result = False + + self.assertEqual(result, True) + + def test_sw_config_low_water_in_interval_dict(self): + """ + Test low_water + """ + + sws.init() + try: + sws.sw_config_low_water_in_interval_dict + result = True + except: + result = False + + self.assertEqual(result, True) + + def test_sw_config_high_water_in_interval_dict(self): + """ + Test high water dict + """ + + sws.init() + try: + sws.sw_config_high_water_in_interval_dict + result = True + except: + result = False + + self.assertEqual(result, True) + + def test_sw_config_category(self): + """ + Test category + """ + + sws.init() + try: + sws.sw_config_category + result = True + except: + result = False + + self.assertEqual(result, True) + + def test_sw_interval_in_seconds(self): + """ + Test sw_interval + """ + + sws.init() + try: + str(sws.sw_interval_in_seconds).isnumeric() + result = True + except: + result = False + + self.assertEqual(result, True) + + def test_sw_last_stormwatch_dict_analysis(self): + """ + Test last_stormwatch_dict_analysis + """ + + sws.init() + try: + str(sws.sw_last_stormwatch_dict_analysis).isnumeric() + result = True + except: + result = False + + self.assertEqual(result, True) + +if __name__ == '__main__': + # sws.init() + unittest.main() diff --git a/tests/test_trapd_vb_types.py b/tests/test_trapd_vb_types.py index 67c9233..1cf75ab 100644 --- a/tests/test_trapd_vb_types.py +++ b/tests/test_trapd_vb_types.py @@ -1,3 +1,19 @@ +# ============LICENSE_START======================================================= +# Copyright (c) 2019-2020 AT&T Intellectual Property. All rights reserved. +# ================================================================================ +# 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. +# ============LICENSE_END========================================================= + import pytest import json import unittest @@ -16,25 +32,111 @@ class test_trapd_vb_types(unittest.TestCase): Test snmpv3 module """ - def trapd_vb_type_conversion_valid(self): + good_varbind_types = ["Integer", "Unsigned32", "Counter32", "OctetString", "ObjectIdentifier", "TimeTicks", "IpAddress"] + + def trapd_vb_type_conversion_integer(self): """ - Test that pysnmp varbind types convert to netsnmp + Test that pysnmp varbind types Integer converts """ - tds.c_config = json.loads("{ \"snmptrapd\": { \"version\": \"1.4.0\", \"title\": \"ONAP SNMP Trap Receiver\" }, \"protocols\": { \"transport\": \"udp\", \"ipv4_interface\": \"0.0.0.0\", \"ipv4_port\": 6162, \"ipv6_interface\": \"::1\", \"ipv6_port\": 6162 }, \"cache\": { \"dns_cache_ttl_seconds\": 60 }, \"publisher\": { \"http_timeout_milliseconds\": 1500, \"http_retries\": 3, \"http_milliseconds_between_retries\": 750, \"http_primary_publisher\": \"true\", \"http_peer_publisher\": \"unavailable\", \"max_traps_between_publishes\": 10, \"max_milliseconds_between_publishes\": 10000 }, \"streams_publishes\": { \"sec_fault_unsecure\": { \"type\": \"message_router\", \"aaf_password\": null, \"dmaap_info\": { \"location\": \"mtl5\", \"client_id\": null, \"client_role\": null, \"topic_url\": \"http://localhost:3904/events/ONAP-COLLECTOR-SNMPTRAP\" }, \"aaf_username\": null } }, \"files\": { \"runtime_base_dir\": \"/tmp/opt/app/snmptrap\", \"log_dir\": \"logs\", \"data_dir\": \"data\", \"pid_dir\": \"tmp\", \"arriving_traps_log\": \"snmptrapd_arriving_traps.log\", \"snmptrapd_diag\": \"snmptrapd_prog_diag.log\", \"traps_stats_log\": \"snmptrapd_stats.csv\", \"perm_status_file\": \"snmptrapd_status.log\", \"eelf_base_dir\": \"/tmp/opt/app/snmptrap/logs\", \"eelf_error\": \"error.log\", \"eelf_debug\": \"debug.log\", \"eelf_audit\": \"audit.log\", \"eelf_metrics\": \"metrics.log\", \"roll_frequency\": \"day\", \"minimum_severity_to_log\": 2 }, \"trap_config\": { \"sw_interval_in_seconds\": 60, \"notify_oids\": { \".1.3.6.1.4.1.9.0.1\": { \"sw_high_water_in_interval\": 102, \"sw_low_water_in_interval\": 7, \"category\": \"logonly\" }, \".1.3.6.1.4.1.9.0.2\": { \"sw_high_water_in_interval\": 101, \"sw_low_water_in_interval\": 7, \"category\": \"logonly\" }, \".1.3.6.1.4.1.9.0.3\": { \"sw_high_water_in_interval\": 102, \"sw_low_water_in_interval\": 7, \"category\": \"logonly\" }, \".1.3.6.1.4.1.9.0.4\": { \"sw_high_water_in_interval\": 10, \"sw_low_water_in_interval\": 3, \"category\": \"logonly\" } } } }") + result = trapd_vb_types.pysnmp_to_netsnmp_varbind_convert("Integer32") + self.assertEqual(result, "integer") + + def trapd_vb_type_conversion_integer(self): + """ + Test that pysnmp varbind types Integer converts + """ - good_varbind_types = ["Integer", "Unsigned32", "Counter32", "OctetString", "ObjectIdentifier", "TimeTicks", "IpAddress"] result = trapd_vb_types.pysnmp_to_netsnmp_varbind_convert("Integer") self.assertEqual(result, "integer") - def trapd_vb_type_conversion_invalid(self): + def trapd_vb_type_conversion_integer(self): + """ + Test that pysnmp varbind types Integer converts + """ + + result = trapd_vb_types.pysnmp_to_netsnmp_varbind_convert("Gauge32") + self.assertEqual(result, "unsigned") + + def trapd_vb_type_conversion_integer(self): + """ + Test that pysnmp varbind types Integer converts + """ + + result = trapd_vb_types.pysnmp_to_netsnmp_varbind_convert("Counter32") + self.assertEqual(result, "counter32") + + def trapd_vb_type_conversion_integer(self): + """ + Test that pysnmp varbind types convert accurately + """ + + result = trapd_vb_types.pysnmp_to_netsnmp_varbind_convert("OctetString") + self.assertEqual(result, "octet") + + def trapd_vb_type_conversion_integer(self): + """ + Test that pysnmp varbind types convert accurately + """ + + result = trapd_vb_types.pysnmp_to_netsnmp_varbind_convert("py_type_5") + self.assertEqual(result, "hex") + + def trapd_vb_type_conversion_integer(self): """ - Test that pysnmp varbind types convert to netsnmp + Test that pysnmp varbind types convert accurately """ - tds.c_config = json.loads("{ \"snmptrapd\": { \"version\": \"1.4.0\", \"title\": \"ONAP SNMP Trap Receiver\" }, \"protocols\": { \"transport\": \"udp\", \"ipv4_interface\": \"0.0.0.0\", \"ipv4_port\": 6162, \"ipv6_interface\": \"::1\", \"ipv6_port\": 6162 }, \"cache\": { \"dns_cache_ttl_seconds\": 60 }, \"publisher\": { \"http_timeout_milliseconds\": 1500, \"http_retries\": 3, \"http_milliseconds_between_retries\": 750, \"http_primary_publisher\": \"true\", \"http_peer_publisher\": \"unavailable\", \"max_traps_between_publishes\": 10, \"max_milliseconds_between_publishes\": 10000 }, \"streams_publishes\": { \"sec_fault_unsecure\": { \"type\": \"message_router\", \"aaf_password\": null, \"dmaap_info\": { \"location\": \"mtl5\", \"client_id\": null, \"client_role\": null, \"topic_url\": \"http://localhost:3904/events/ONAP-COLLECTOR-SNMPTRAP\" }, \"aaf_username\": null } }, \"files\": { \"runtime_base_dir\": \"/tmp/opt/app/snmptrap\", \"log_dir\": \"logs\", \"data_dir\": \"data\", \"pid_dir\": \"tmp\", \"arriving_traps_log\": \"snmptrapd_arriving_traps.log\", \"snmptrapd_diag\": \"snmptrapd_prog_diag.log\", \"traps_stats_log\": \"snmptrapd_stats.csv\", \"perm_status_file\": \"snmptrapd_status.log\", \"eelf_base_dir\": \"/tmp/opt/app/snmptrap/logs\", \"eelf_error\": \"error.log\", \"eelf_debug\": \"debug.log\", \"eelf_audit\": \"audit.log\", \"eelf_metrics\": \"metrics.log\", \"roll_frequency\": \"day\", \"minimum_severity_to_log\": 2 }, \"trap_config\": { \"sw_interval_in_seconds\": 60, \"notify_oids\": { \".1.3.6.1.4.1.9.0.1\": { \"sw_high_water_in_interval\": 102, \"sw_low_water_in_interval\": 7, \"category\": \"logonly\" }, \".1.3.6.1.4.1.9.0.2\": { \"sw_high_water_in_interval\": 101, \"sw_low_water_in_interval\": 7, \"category\": \"logonly\" }, \".1.3.6.1.4.1.9.0.3\": { \"sw_high_water_in_interval\": 102, \"sw_low_water_in_interval\": 7, \"category\": \"logonly\" }, \".1.3.6.1.4.1.9.0.4\": { \"sw_high_water_in_interval\": 10, \"sw_low_water_in_interval\": 3, \"category\": \"logonly\" } } } }") + result = trapd_vb_types.pysnmp_to_netsnmp_varbind_convert("py_type_6") + self.assertEqual(result, "decimal") + + def trapd_vb_type_conversion_integer(self): + """ + Test that pysnmp varbind types convert accurately + """ + + result = trapd_vb_types.pysnmp_to_netsnmp_varbind_convert("Null") + self.assertEqual(result, "null") + + def trapd_vb_type_conversion_integer(self): + """ + Test that pysnmp varbind types convert accurately + """ + + result = trapd_vb_types.pysnmp_to_netsnmp_varbind_convert("ObjectIdentifier") + self.assertEqual(result, "oid") + + def trapd_vb_type_conversion_integer(self): + """ + Test that pysnmp varbind types convert accurately + """ + + result = trapd_vb_types.pysnmp_to_netsnmp_varbind_convert("TimeTicks") + self.assertEqual(result, "timeticks") + + def trapd_vb_type_conversion_integer(self): + """ + Test that pysnmp varbind types convert accurately + """ + + result = trapd_vb_types.pysnmp_to_netsnmp_varbind_convert("IpAddress") + self.assertEqual(result, "ipaddress") + + def trapd_vb_type_conversion_integer(self): + """ + Test that pysnmp varbind types convert accurately + """ + + result = trapd_vb_types.pysnmp_to_netsnmp_varbind_convert("Bits") + self.assertEqual(result, "bits") + + def trapd_vb_type_conversion_invalid(self): + """ + Test that pysnmp varbind types convert accurately + """ result = trapd_vb_types.pysnmp_to_netsnmp_varbind_convert("noSuchVarbindType") + # should return default of octet if not defined self.assertEqual(result, "octet") diff --git a/version.properties b/version.properties index f352992..c8a67b0 100644 --- a/version.properties +++ b/version.properties @@ -1,6 +1,6 @@ -major=1 -minor=4 -patch=1 +major=2 +minor=0 +patch=3 base_version=${major}.${minor}.${patch} release_version=${base_version} snapshot_version=${base_version}-SNAPSHOT |