diff options
author | Ladue, David (dl3158) <dl3158@att.com> | 2018-03-14 14:01:36 -0400 |
---|---|---|
committer | Ladue, David (dl3158) <dl3158@att.com> | 2018-03-18 02:42:23 -0400 |
commit | abb283c5a3b72eb2d91e6adb4eda68184f040f35 (patch) | |
tree | 4765c8d6cb54a2ddb2052c0955425604bc265230 /bin | |
parent | a6f91efda578965710630154b173cf1ee9b64727 (diff) |
copyright and controller ENV changes
Change-Id: Ic12aa439a03f19c7a06a536368a8d2a1f26855aa
Issue-ID: DCAEGEN2-271
Signed-off-by: Ladue, David (dl3158) <dl3158@att.com>
Diffstat (limited to 'bin')
-rw-r--r-- | bin/mod/trapd_exit.py | 63 | ||||
-rw-r--r-- | bin/mod/trapd_file_utils.py | 220 | ||||
-rw-r--r-- | bin/mod/trapd_get_cbs_config.py | 116 | ||||
-rw-r--r-- | bin/mod/trapd_http_session.py | 61 | ||||
-rw-r--r-- | bin/mod/trapd_logging.py | 199 | ||||
-rw-r--r-- | bin/mod/trapd_runtime_pid.py | 94 | ||||
-rw-r--r-- | bin/mod/trapd_settings.py | 168 | ||||
-rw-r--r-- | bin/snmptrapd.py | 766 | ||||
-rwxr-xr-x | bin/snmptrapd.sh | 52 |
9 files changed, 1739 insertions, 0 deletions
diff --git a/bin/mod/trapd_exit.py b/bin/mod/trapd_exit.py new file mode 100644 index 0000000..a7ffc8a --- /dev/null +++ b/bin/mod/trapd_exit.py @@ -0,0 +1,63 @@ +# ============LICENSE_START======================================================= +# org.onap.dcae +# ================================================================================ +# Copyright (c) 2017-2018 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. +# +""" +trapc_exit_snmptrapd is responsible for removing any existing runtime PID +file, and exiting with the provided (param 1) exit code +""" + +__docformat__ = 'restructuredtext' + +import sys +import os +import string +from trapd_runtime_pid import save_pid, rm_pid + +prog_name = os.path.basename(__file__) + + +# # # # # # # # # # # # # +# fx: cleanup_and_exit +# - remove pid file +# - exit with supplied return code +# # # # # # # # # # # # # +def cleanup_and_exit(_loc_exit_code, _pid_file_name): + """ + Remove existing PID file, and exit with provided exit code + :Parameters: + _loc_exit_code + value to return to calling shell upon exit + _pid_file_name + name of file that contains current process ID (for + removal) + :Exceptions: + none + :Keywords: + runtime PID exit + :Variables: + _num_params + number of parameters passed to module + """ + + _num_params = len(locals()) + + if _num_params == 2: + rc = rm_pid(_pid_file_name) + sys.exit(_loc_exit_code) diff --git a/bin/mod/trapd_file_utils.py b/bin/mod/trapd_file_utils.py new file mode 100644 index 0000000..2da099b --- /dev/null +++ b/bin/mod/trapd_file_utils.py @@ -0,0 +1,220 @@ +# ============LICENSE_START=======================================================) +# org.onap.dcae +# ================================================================================ +# Copyright (c) 2018 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. +# +""" +""" + +__docformat__ = 'restructuredtext' + +# basics +import datetime +import errno +import inspect +import json +import logging +import logging.handlers +import os +import sys +import string +import time +import traceback +import unicodedata + +# dcae_snmptrap +import trapd_settings as tds +from trapd_logging import ecomp_logger, stdout_logger +from trapd_exit import cleanup_and_exit + +prog_name = os.path.basename(__file__) + + +# # # # # # # # # # # # # # # # # # # +# fx: roll_all_logs -> roll all logs to timestamped backup +# # # # # # # # # # ## # # # # # # # + + +def roll_all_logs(): + """ + roll all active logs to timestamped version, open new one + based on frequency defined in files.roll_frequency + """ + + # first roll all the eelf files + # NOTE: this will go away when onap logging is standardized/available + try: + # open various ecomp logs - if any fails, exit + for fd in [tds.eelf_error_fd, tds.eelf_debug_fd, tds.eelf_audit_fd, \ + tds.eelf_metrics_fd, tds.arriving_traps_fd, tds.json_traps_fd]: + fd.close() + + roll_file(tds.eelf_error_file_name) + roll_file(tds.eelf_debug_file_name) + roll_file(tds.eelf_audit_file_name) + roll_file(tds.eelf_metrics_file_name) + + except Exception as e: + msg = "Error closing logs: " + str(e) + stdout_logger(msg) + cleanup_and_exit(1, tds.pid_file_name) + + reopened_successfully = open_eelf_logs() + if not reopened_successfully: + msg = "Error re-opening EELF logs during roll-over to timestamped versions - EXITING" + stdout_logger(msg) + cleanup_and_exit(1, tds.pid_file_name) + + # json log + roll_file(tds.json_traps_filename) + + try: + tds.json_traps_fd = open_file(tds.json_traps_filename) + except Exception as e: + msg = ("Error opening json_log %s : %s" % (json_traps_filename, str(e))) + stdout_logger(msg) + cleanup_and_exit(1, tds.pid_file_name) + + # arriving trap log + roll_file(tds.arriving_traps_filename) + + try: + tds.arriving_traps_fd = open_file(tds.arriving_traps_filename) + except Exception as e: + msg = ("Error opening arriving traps %s : %s" % (arriving_traps_filename, str(e))) + stdout_logger(msg) + cleanup_and_exit(1, tds.pid_file_name) + + + +# # # # # # # # # # # # # # # # # # # +# fx: setup_ecomp_logs -> log in eelf format until standard +# is released for python via LOG-161 +# # # # # # # # # # ## # # # # # # # + + +def open_eelf_logs(): + """ + open various (multiple ???) logs + """ + + try: + # open various ecomp logs - if any fails, exit + + tds.eelf_error_file_name = (tds.c_config['files.eelf_base_dir'] + "/" + tds.c_config['files.eelf_error']) + tds.eelf_error_fd = open_file(tds.eelf_error_file_name) + + except Exception as e: + msg = "Error opening eelf error log : " + str(e) + stdout_logger(msg) + cleanup_and_exit(1, tds.pid_file_name) + + + try: + tds.eelf_debug_file_name = (tds.c_config['files.eelf_base_dir'] + "/" + tds.c_config['files.eelf_debug']) + tds.eelf_debug_fd = open_file(tds.eelf_debug_file_name) + + except Exception as e: + msg = "Error opening eelf debug log : " + str(e) + stdout_logger(msg) + cleanup_and_exit(1, tds.pid_file_name) + + try: + tds.eelf_audit_file_name = (tds.c_config['files.eelf_base_dir'] + "/" + tds.c_config['files.eelf_audit']) + tds.eelf_audit_fd = open_file(tds.eelf_audit_file_name) + except Exception as e: + msg = "Error opening eelf audit log : " + str(e) + stdout_logger(msg) + cleanup_and_exit(1, tds.pid_file_name) + + try: + tds.eelf_metrics_file_name = (tds.c_config['files.eelf_base_dir'] + "/" + tds.c_config['files.eelf_metrics']) + tds.eelf_metrics_fd = open_file(tds.eelf_metrics_file_name) + except Exception as e: + msg = "Error opening eelf metric log : " + str(e) + stdout_logger(msg) + cleanup_and_exit(1, tds.pid_file_name) + + return True + +# # # # # # # # # # # # # # # # # # # +# fx: roll_log_file -> move provided filename to timestamped version +# # # # # # # # # # ## # # # # # # # + + +def roll_file(_loc_file_name): + """ + move active file to timestamped archive + """ + + _file_name_suffix = "%s" % (datetime.datetime.fromtimestamp(time.time()). + fromtimestamp(time.time()). + strftime('%Y-%m-%dT%H:%M:%S')) + + _loc_file_name_bak = _loc_file_name + '.' + _file_name_suffix + + # roll existing file if present + if os.path.isfile(_loc_file_name): + try: + os.rename(_loc_file_name, _loc_file_name_bak) + except: + _msg = ("ERROR: Unable to rename %s to %s" + % (_loc_file_name, + _loc_file_name_bak)) + ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_CRIT, tds.CODE_GENERAL, _msg) + + +# # # # # # # # # # # # # +# fx: open_log_file +# # # # # # # # # # # # # + + +def open_file(_loc_file_name): + """ + open _loc_file_name, return file handle + """ + + + try: + # open append mode just in case so nothing is lost, but should be + # non-existent file + _loc_fd = open(_loc_file_name, 'a') + return _loc_fd + except Exception as e: + msg = "Error opening " + _loc_file_name + " append mode - " + str(e) + stdout_logger(msg) + cleanup_and_exit(1, tds.pid_file_name) + + +# # # # # # # # # # # # # +# fx: close_file +# # # # # # # # # # # # # + """ + close _loc_file_name, return True with success, False otherwise + """ + + +def close_file(_loc_fd, _loc_filename): + + try: + _loc_fd.close() + return True + except Exception as e: + msg = "Error closing %s : %s - results indeterminate" % (_loc_filename, str(e)) + ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_FATAL, tds.CODE_GENERAL, msg) + return False diff --git a/bin/mod/trapd_get_cbs_config.py b/bin/mod/trapd_get_cbs_config.py new file mode 100644 index 0000000..775e0b2 --- /dev/null +++ b/bin/mod/trapd_get_cbs_config.py @@ -0,0 +1,116 @@ +# ============LICENSE_START======================================================= +# org.onap.dcae +# ================================================================================ +# Copyright (c) 2018 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. +# +""" +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 +testing purposes) +""" + +__docformat__ = 'restructuredtext' + +import json +import os +import sys +import string +import time +import traceback +import collections + +import trapd_settings as tds +from onap_dcae_cbs_docker_client.client import get_config +from trapd_exit import cleanup_and_exit +from trapd_logging import stdout_logger + +prog_name = os.path.basename(__file__) + + +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # +# function: trapd_get_config_sim +# # # # # # # # # # # # # # # # # # # # # # # # # # # # # # + + +def get_cbs_config(): + """ + Get config values from CBS or JSON file (fallback) + :Parameters: + none + :Exceptions: + """ + + tds.c_config = {} + + # See if we are in a config binding service (CBS) /controller environment + try: + tds.c_config = get_config() + if tds.c_config == {}: + msg = "Unable to fetch CBS config or it is erroneously empty - trying override/simulator config" + stdout_logger(msg) + + # if no CBS present, default to JSON config specified via CBS_SIM_JSON env var + except Exception as e: + msg = "ONAP controller not present, trying json config override via CBS_SIM_JSON env variable" + stdout_logger(msg) + + try: + _cbs_sim_json_file = os.getenv("CBS_SIM_JSON", "None") + except Exception as e: + msg = "CBS_SIM_JSON not defined - FATAL ERROR, exiting" + stdout_logger(msg) + cleanup_and_exit(1, pid_file_name) + + if _cbs_sim_json_file == "None": + msg = "CBS_SIM_JSON not defined - FATAL ERROR, exiting" + stdout_logger(msg) + cleanup_and_exit(1, pid_file_name) + 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)) + 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, tds.pid_file_name) + + # recalc timeout, set default if not present + try: + tds.timeout_seconds = tds.c_config['publisher.http_timeout_milliseconds'] / 1000.0 + except: + tds.timeout_seconds = 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 + except: + tds.seconds_between_retries = .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'] + except: + tds.minimum_severity_to_log = 3 + + try: + tds.publisher_retries = tds.c_config['publisher.http_retries'] + except: + tds.publisher_retries = 3 + + return True diff --git a/bin/mod/trapd_http_session.py b/bin/mod/trapd_http_session.py new file mode 100644 index 0000000..2e0b77e --- /dev/null +++ b/bin/mod/trapd_http_session.py @@ -0,0 +1,61 @@ +# ============LICENSE_START======================================================= +# org.onap.dcae +# ================================================================================ +# Copyright (c) 2017-2018 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. +# +""" +trapd_http_session establishes an http session for future use in publishing +messages to the dmaap cluster. +""" + +__docformat__ = 'restructuredtext' + +import os +import requests +import traceback + +# snmptrapd +import trapd_settings + +prog_name = os.path.basename(__file__) + + +# # # # # # # # # # # # # +# fx: init_session_obj +# # # # # # # # # # # # # +def init_session_obj(): + """ + Initializes and returns a http request session object for later use + :Parameters: + none + :Exceptions: + session object creation + this function will throw an exception if unable to create + a new session object + :Keywords: + http request session + :Variables: + none + """ + + try: + _loc_session = requests.Session() + except Exception as e: + return None + + return _loc_session diff --git a/bin/mod/trapd_logging.py b/bin/mod/trapd_logging.py new file mode 100644 index 0000000..435f03e --- /dev/null +++ b/bin/mod/trapd_logging.py @@ -0,0 +1,199 @@ +# ============LICENSE_START=======================================================) +# org.onap.dcae +# ================================================================================ +# Copyright (c) 2018 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. +# +""" +""" + +__docformat__ = 'restructuredtext' + +# basics +import datetime +import errno +import inspect +import json +import logging +import logging.handlers +import os +import sys +import string +import time +import traceback +import unicodedata + +import trapd_settings as tds + +prog_name = os.path.basename(__file__) + + +# # # # # # # # # # # # # # # # # # # +# fx: ecomp_logger -> log in eelf format until standard +# is released for python via LOG-161 +# # # # # # # # # # ## # # # # # # # + +def ecomp_logger(_log_type, _sev, _error_code, _msg): + """ + Log to ecomp-style logfiles. Logs include: + + Note: this will be updated when https://jira.onap.org/browse/LOG-161 + is closed/available; until then, we resort to a generic format with + valuable info in "extra=" field (?) + + :Parameters: + _msg - + :Exceptions: + none + :Keywords: + eelf logging + :Log Styles: + + :error.log: + + if CommonLogger.verbose: print("using CommonLogger.ErrorFile") + self._logger.log(50, '%s|%s|%s|%s|%s|%s|%s|%s|%s|%s' \ + % (requestID, threadID, serviceName, partnerName, targetEntity, targetServiceName, + errorCategory, errorCode, errorDescription, detailMessage)) + + error.log example: + + 2018-02-20T07:21:34,007+00:00||MainThread|snmp_log_monitor||||FATAL|900||Tue Feb 20 07:21:11 UTC 2018 CRITICAL: [a0cae74e-160e-11e8-8f9f-0242ac110002] ALL publish attempts failed to DMAPP server: dcae-mrtr-zltcrdm5bdce1.1dff83.rdm5b.tci.att.com, topic: DCAE-COLLECTOR-UCSNMP, 339 trap(s) not published in epoch_serno range: 15191112530000 - 15191112620010 + + :debug.log: + + if CommonLogger.verbose: print("using CommonLogger.DebugFile") + self._logger.log(50, '%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s' \ + % (requestID, threadID, serverName, serviceName, instanceUUID, upperLogLevel, + severity, serverIPAddress, server, IPAddress, className, timer, detailMessage)) + + debug.log example: + + none available + + :audit.log: + + if CommonLogger.verbose: print("using CommonLogger.AuditFile") + endAuditTime, endAuditMsec = self._getTime() + if self._begTime is not None: + d = {'begtime': self._begTime, 'begmsecs': self._begMsec, 'endtime': endAuditTime, + 'endmsecs': endAuditMsec} + else: + d = {'begtime': endAuditTime, 'begmsecs': endAuditMsec, 'endtime': endAuditTime, + 'endmsecs': endAuditMsec} + + self._logger.log(50, '%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%s' \ + % (requestID, serviceInstanceID, threadID, serverName, serviceName, partnerName, + statusCode, responseCode, responseDescription, instanceUUID, upperLogLevel, + severity, serverIPAddress, timer, server, IPAddress, className, unused, + processKey, customField1, customField2, customField3, customField4, + detailMessage), extra=d) + + + :metrics.log: + + self._logger.log(50,'%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' \ + % (requestID, serviceInstanceID, threadID, serverName, serviceName, partnerName, + targetEntity, targetServiceName, statusCode, responseCode, responseDescription, + instanceUUID, upperLogLevel, severity, serverIPAddress, timer, server, + IPAddress, + className, unused, processKey, targetVirtualEntity, customField1, customField2, + customField3, customField4, detailMessage), extra=d) + + metrics.log example: + + none available + + + """ + + unused = "" + + # ct = time.time() + # lt = time.localtime(ct) + # t_hman = time.strftime(DateFmt, lt) + # t_ms = (ct - int(ct)) * 1000 + # 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] + calling_fx = inspect.stack()[1][3] + + # FIXME: this entire module is a hack to override concept of prog logging + # written across multiple files (???), making diagnostics IMPOSSIBLE! + # Hoping to leverage ONAP logging libraries & standards when available + + # catch invalid log type + if _log_type < 1 or _log_type > 5: + msg = ("INVALID log type: %s " % _log_type ) + _out_rec = ("%s|%s|%s|%s|%s|%s|%s|%s|%s|%s" \ + % ((t_out, calling_fx, "snmptrapd", unused, unused, unused, tds.SEV_TYPES[_sev], _error_code, unused, (msg + _msg)))) + tds.eelf_error_fd.write('%s\n' % str(_out_rec)) + return False + + 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|%s' \ + % ((t_out, calling_fx, "snmptrapd", unused, unused, unused, tds.SEV_TYPES[_sev], _error_code, unused, _msg))) + tds.eelf_error_fd.write('%s\n' % 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|%s' \ + % ((t_out, calling_fx, "snmptrapd", unused, unused, unused, tds.SEV_TYPES[_sev], _error_code, unused, _msg))) + tds.eelf_audit_fd.write('%s\n' % str(_out_rec)) + elif _log_type == tds.LOG_TYPE_METRICS: + # log message in METRICS format + _out_rec = ('%s|%s|%s|%s|%s|%s|%s|%s|%s|%s' \ + % ((t_out, calling_fx, "snmptrapd", unused, unused, unused, tds.SEV_TYPES[_sev], _error_code, unused, _msg))) + tds.eelf_metrics_fd.write('%s\n' % str(_out_rec)) + + # DEBUG *AND* others - there *MUST BE* a single time-sequenced log for diagnostics! + # FIXME: too much I/O !!! + # always write to debug; we need ONE logfile that has time-sequence full view !!! + # if (_log_type == tds.LOG_TYPE_DEBUG and _sev >= tds.current_min_sev_log_level) or (_log_type != tds.LOG_TYPE_DEBUG): + + # log message in DEBUG format + _out_rec = ("%s|%s|%s|%s|%s|%s|%s|%s|%s|%s" \ + % ((t_out, calling_fx, "snmptrapd", unused, unused, unused, tds.SEV_TYPES[_sev], _error_code, unused, _msg))) + tds.eelf_debug_fd.write('%s\n' % str(_out_rec)) + + return True + +# # # # # # # # # # # # # +# fx: stdout_logger +# # # # # # # # # # # # # + + +def stdout_logger(_msg): + """ + Log info/errors to stdout. This is done: + - for critical runtime issues + + :Parameters: + _msg + message to print + :Exceptions: + none + :Keywords: + log stdout + :Variables: + """ + + t_out = datetime.datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S,%f")[:-3] + # calling_fx = inspect.stack()[1][3] + + print('%s %s' % ( t_out, _msg)) diff --git a/bin/mod/trapd_runtime_pid.py b/bin/mod/trapd_runtime_pid.py new file mode 100644 index 0000000..c6ef76e --- /dev/null +++ b/bin/mod/trapd_runtime_pid.py @@ -0,0 +1,94 @@ +# ============LICENSE_START======================================================= +# org.onap.dcae +# ================================================================================ +# Copyright (c) 2017-2018 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. +# +""" +trapd_runtime_pid maintains a 'PID file' (file that contains the +PID of currently running trap receiver) +""" + +__docformat__ = 'restructuredtext' + +import logging +import os +import string +import time +import traceback + +prog_name = os.path.basename(__file__) + + +# # # # # # # # # # # # # +# fx: save_pid - save PID of running process +# # # # # # # # # # # # # +def save_pid(_pid_file_name): + """ + Save the current process ID in a file for external + access. + :Parameters: + none + :Exceptions: + file open + this function will catch exception of unable to + open/create _pid_file_name + :Keywords: + pid /var/run + """ + + try: + pid_fd = open(_pid_file_name, 'w') + pid_fd.write('%d' % os.getpid()) + pid_fd.close() + except IOError: + print("IOError saving PID file %s :" % _pid_file_name) + return False + # except: + # print("Error saving PID file %s :" % _pid_file_name) + # return False + else: + # print("Runtime PID file: %s" % _pid_file_name) + return True + + +# # # # # # # # # # # # # +# fx: rm_pid - remove PID of running process +# # # # # # # # # # # # # +def rm_pid(_pid_file_name): + """ + Remove the current process ID file before exiting. + :Parameters: + none + :Exceptions: + file open + this function will catch exception of unable to find or remove + _pid_file_name + :Keywords: + pid /var/run + """ + + try: + if os.path.isfile(_pid_file_name): + os.remove(_pid_file_name) + return True + else: + return False + + except IOError: + print("Error removing Runtime PID file: %s" % _pid_file_name) + return False diff --git a/bin/mod/trapd_settings.py b/bin/mod/trapd_settings.py new file mode 100644 index 0000000..735b68c --- /dev/null +++ b/bin/mod/trapd_settings.py @@ -0,0 +1,168 @@ +# ============LICENSE_START=======================================================) +# org.onap.dcae +# ================================================================================ +# Copyright (c) 2018 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. +# +""" +""" + +__docformat__ = 'restructuredtext' + +def init(): + + # <CONSUL config cache> + # consul config or simulated via json file + global c_config + c_config = None + # </CONSUL config cache> + + # <DNS cache> + # + # dns_cache_ip_to_name + # key [ip address] -> fqdn + # dns_cache_ip_expires + # key [ip address] -> epoch time this entry expires and must be reloaded + global dns_cache_ip_to_name + dns_cache_ip_to_name = {} + global dns_cache_ip_expires + dns_cache_ip_expires = {} + # </DNS cache> + + # <EELF logs> + global eelf_error_file_name + eelf_error_file_name = "" + global eelf_error_fd + eelf_error_fd = None + + global eelf_debug_file_name + eelf_debug_file_name = "" + global eelf_debug_fd + eelf_debug_fd = None + + global eelf_audit_file_name + eelf_audit_file_name = "" + global eelf_audit_fd + eelf_audit_fd = None + + global eelf_metrics_file_name + eelf_metrics_file_name = "" + global eelf_metrics_fd + eelf_metrics_fd = None + + global last_minute + last_minute = 0 + global last_hour + last_hour = 0 + global last_day + last_day = 0 + # </EELF logs> + + # <trap dictionary and corresponding strings for publish + global first_trap + first_trap = True + global first_varbind + first_varbind = True + global trap_dict + trap_dict = {} + global all_traps_str + all_traps_str = "" + global all_vb_json_str + all_vb_json_str = "" + global trap_uuids_in_buffer + trap_uuids_in_buffer = "" + # </trap and varbind dictionaries> + + # <publish timers and counters> + global traps_in_minute + traps_in_minute = 0 + global last_epoch_second + last_epoch_second = 0 + global traps_since_last_publish + traps_since_last_publish = 0 + global last_pub_time + last_pub_time = 0 + global milliseconds_since_last_publish + milliseconds_since_last_publish = 0 + global timeout_seconds + timeout_seconds = 1.5 + global seconds_between_retries + seconds_between_retries = 2 + global publisher_retries + publisher_retries = 2 + # </publish timers and counters> + + # <publish http request session (persistent as much as possible)> + global http_requ_session + http_requ_session = None + # </publish http request session> + + # <json log of traps published> + global json_traps_filename + json_log_filename = "" + global json_traps_fd + json_fd = None + # </json log of traps published> + + # <log of arriving traps > + global arriving_traps_filename + arriving_traps_filename = "" + global arriving_traps_fd + arriving_traps_fd = None + # <log of arriving traps > + + # <runtime PID> + global pid_file_name + pid_file_name = "" + + # <logging types and severities> + global LOG_TYPES + global LOG_TYPE_NONE + global LOG_TYPE_ERROR + global LOG_TYPE_DEBUG + global LOG_TYPE_AUDIT + global LOG_TYPE_METRICS + LOG_TYPES = ["none", "ERROR", "DEBUG", "AUDIT", "METRICS"] + LOG_TYPE_NONE = 0 + LOG_TYPE_ERROR = 1 + LOG_TYPE_DEBUG = 2 + LOG_TYPE_AUDIT = 3 + LOG_TYPE_METRICS = 4 + + global SEV_TYPES + global SEV_NONE + global SEV_DETAILED + global SEV_INFO + global SEV_WARN + global SEV_CRIT + global SEV_FATAL + SEV_TYPES = ["none", "DETAILED", "INFO", "WARN", "CRITICAL", "FATAL"] + SEV_NONE = 0 + SEV_DETAILED = 1 + SEV_INFO = 2 + SEV_WARN = 3 + SEV_CRIT = 4 + SEV_FATAL = 5 + + global CODE_GENERAL + CODE_GENERAL="100" + + global minimum_severity_to_log + minimum_severity_to_log=3 + + + # </logging types and severities> diff --git a/bin/snmptrapd.py b/bin/snmptrapd.py new file mode 100644 index 0000000..dde4e39 --- /dev/null +++ b/bin/snmptrapd.py @@ -0,0 +1,766 @@ +# ============LICENSE_START=======================================================) +# org.onap.dcae +# ================================================================================ +# Copyright (c) 2017-2018 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. +# +""" +dcae_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". + +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 [-v] +:Keywords: + onap dcae snmp trap publish dmaap +""" + +__docformat__ = 'restructuredtext' + +# basics +import argparse +import array +import asyncio +from collections import Counter +import datetime +import errno +import inspect +import json +import logging +import logging.handlers +import os +import pprint +import requests +import re +import sys +import signal +import string +import socket +import time +import traceback +import unicodedata +import uuid as uuid_mod + +# pysnmp +from pysnmp.entity import engine, config +from pysnmp.carrier.asyncio.dgram import udp, udp6 +# from pysnmp.carrier.asyncore.dgram import udp +from pysnmp.entity.rfc3413 import ntfrcv +from pysnmp.proto.api import v2c + +# dcae_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 + +from trapd_file_utils import roll_all_logs, open_eelf_logs, roll_file, open_file, close_file +from trapd_logging import ecomp_logger, stdout_logger + +prog_name = os.path.basename(__file__) +verbose = False + +# # # # # # # # # # # +# fx: usage_err +# # # # # # # # # # # + + +def usage_err(): + """ + Notify of incorrect (argument) usage + :Parameters: + none + :Exceptions: + none + :Keywords: + usage args + """ + + print('Incorrect usage invoked. Correct usage:') + print(' %s [-v]' % prog_name) + cleanup_and_exit(1, "undefined") + + +# # # # # # # # # # # # # # # # # # # +# fx: load_all_configs +# # # # # # # # # # ## # # # # # # # + + +def load_all_configs(_signum, _frame): + """ + Calls individual functions to read various config files required. This + function is called directly (e.g. at startup) and is also registered + with signal handling (e.g. kill -sigusr1 <pid>) + + :Parameters: + signum and frame (only present when called via signal to running process) + :Exceptions: + none + :Keywords: + config files + :Variables: + yaml_conf_file + rs + """ + + 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, tds.CODE_GENERAL, msg) + + # Initialize dmaap requests session object. Close existing session + # if applicable. + if tds.http_requ_session is not None: + tds.http_requ_session.close() + + tds.http_requ_session = init_session_obj() + if tds.http_requ_session == None: + msg = "Unable to create new http session - FATAL ERROR, exiting" + ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_FATAL, tds.CODE_GENERAL, msg) + stdout_logger(msg) + cleanup_and_exit(1, tds.pid_file_name) + + # re-request config from config binding service + # (either broker, or json file override) + 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) + + +# # # # # # # # # # # # # +# fx: log_all_arriving_traps +# # # # # # # # # # # # # + + +def log_all_arriving_traps(): + + + # roll logs as needed/defined in files.roll_frequency + if tds.c_config['files.roll_frequency'] == "minute": + curr_minute = datetime.datetime.now().minute + if curr_minute != tds.last_minute: + roll_all_logs() + tds.last_minute = curr_minute + elif tds.c_config['files.roll_frequency'] == "hour": + curr_hour = datetime.datetime.now().hour + if curr_hour != tds.last_hour: + roll_all_logs() + tds.last_hour = curr_hour + else: + # otherwise, assume daily roll + curr_day = datetime.datetime.now().day + if curr_day != tds.last_day: + roll_all_logs() + tds.last_day = curr_day + + # now log latest 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 %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["sysUptime"], + tds.trap_dict["uuid"], + tds.all_vb_json_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) + + +# # # # # # # # # # # # # +# fx: log_published_messages +# # # # # # # # # # # # # + + +def log_published_messages(_post_data_enclosed): + + # FIXME: should keep data dictionary of Fd's open, and reference those vs. + # repeatedly opening append-mode + + msg = "adding trap UUID %s to json log" % tds.trap_dict["uuid"] + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) + + try: + 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, 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) + + +# # # # # # # # # # # # # +# fx: post_dmaap +# # # # # # # # # # # # # + + +def post_dmaap(): + """ + Publish trap daata in json format to dmaap + :Parameters: + :Exceptions: + none + :Keywords: + http post dmaap json message + :Variables: + """ + + http_headers = {"Content-type": "application/json"} + + 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, 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 + else: + # otherwise, add brackets around package + post_data_enclosed = '[' + tds.all_traps_str + ']' + + msg = "post_data_enclosed: %s" % (post_data_enclosed) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, tds.CODE_GENERAL, msg) + + k = 0 + dmaap_pub_success = False + + while not dmaap_pub_success and k < (int(tds.c_config['publisher.http_retries'])): + try: + if tds.c_config['streams_publishes']['sec_fault_unsecure']['aaf_username'] == "" or tds.c_config['streams_publishes']['sec_fault_unsecure']['aaf_username'] == 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, 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, + timeout=tds.timeout_seconds) + 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, 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'], + tds.c_config['streams_publishes']['sec_fault_unsecure']['aaf_password']), + headers=http_headers, + timeout=tds.timeout_seconds) + + if http_resp.status_code == requests.codes.ok: + # msg = "%d trap(s) : %s successfully published - response from %s: %d %s" % (traps_since_last_publish, trap_uuids_in_buffer, ((c_config['streams_publishes']['sec_fault_unsecure']['dmaap_info']['topic_url']).split('/')[2][:-5]) ,http_resp.status_code, http_resp.text) + msg = "%d trap(s) successfully published: %s" % (tds.traps_since_last_publish, tds.trap_uuids_in_buffer) + ecomp_logger(tds.LOG_TYPE_METRICS, tds.SEV_INFO, tds.CODE_GENERAL, msg) + log_published_messages(post_data_enclosed) + tds.last_pub_time = time.time() + dmaap_pub_success = True + break + else: + msg = "Trap(s) %s publish attempt %d returned non-normal: %d %s" % (tds.trap_uuids_in_buffer, k, http_resp.status_code, http_resp.text) + ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_WARN, tds.CODE_GENERAL, msg) + + except OSError as e: + msg = "OS exception while attempting to post %s attempt %s: (%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) + + except requests.exceptions.RequestException as e: + msg = "Requests exception while attempting to post %s attempt %d: (%d) %s" % (tds.trap_uuids_in_buffer, int(k), int(e.errno), str(e.strerror)) + ecomp_logger(tds.LOG_TYPE_ERROR, tds.SEV_WARN, tds.CODE_GENERAL, msg) + + k += 1 + + if k < 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, tds.CODE_GENERAL, msg) + time.sleep(tds.seconds_between_retries) + else: + break + + 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) + + # FIXME: 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.first_trap = True + +# # # # # # # # # # # # # # # # # # # +# fx: request_observer for community string rewrite +# # # # # # # # # # # # # # # # # # # +def comm_string_rewrite_observer(snmpEngine, execpoint, variables, cbCtx): + + # match ALL community strings + if re.match('.*', str(variables['communityName'])): + # msg = "Rewriting communityName '%s' from %s into 'public'" % (variables['communityName'], ':'.join([str(x) for x in + # variables['transportInformation'][1]])) + # ecomp_logger(eelf_debug_fd, eelf_debug_fd, tds.LOG_TYPE_DEBUG, tds.SEV_INFO, tds.CODE_GENERAL, msg) + variables['communityName'] = variables['communityName'].clone('public') + +# # # # # # # # # # # # # # # # # # # +# fx: snmp_engine_observer_cb +# callback for when trap is received +# # # # # # # # # # # # # # # # # # # + + +def snmp_engine_observer_cb(snmp_engine, execpoint, variables, cbCtx): + """ + Decompose trap attributes and load in dictionary which is later used to + create json string for publishing to dmaap. + :Parameters: + snmp_engine + snmp engine created to listen for arriving traps + execpoint + point in code that snmp_engine_observer_cb was invoked + variables + trap attributes + cbCtx + callback context + :Exceptions: + none + :Keywords: + UEB non-AAF legacy http post + :Variables: + """ + + # init dictionary on new trap + tds.trap_dict = {} + + # assign uuid to trap + tds.trap_dict["uuid"] = str(uuid_mod.uuid1()) + + # ip and hostname + ip_addr_str = str(variables['transportAddress'][0]) + tds.trap_dict["agent address"] = ip_addr_str + + 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: + 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: + 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"]) + tds.trap_dict["community"] = "" # do not include cleartext community in pub + tds.trap_dict["community len"] = 0 + + # FIXME.CHECK_WITH_DOWNSTREAM_CONSUMERS: get rid of round for millisecond val + # epoch_second = int(round(time.time())) + epoch_msecond = time.time() + epoch_second = int(round(epoch_msecond)) + if epoch_second == tds.last_epoch_second: + tds.traps_in_epoch += 1 + else: + 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_serno'] = int( + (str(epoch_second) + str(traps_in_epoch_04d))) + + snmp_version = variables['securityModel'] + if snmp_version == 1: + tds.trap_dict["protocol version"] = "v1" + else: + if snmp_version == 2: + tds.trap_dict["protocol version"] = "v2c" + else: + if snmp_version == 3: + tds.trap_dict["protocol version"] = "v3" + else: + tds.trap_dict["protocol version"] = "unknown" + + if snmp_version == 3: + tds.trap_dict["protocol version"] = "v3" + tds.trap_dict["security level"] = str(variables['securityLevel']) + tds.trap_dict["context name"] = str(variables['contextName'].prettyPrint()) + tds.trap_dict["security name"] = str(variables['securityName']) + tds.trap_dict["security engine"] = str( + variables['contextEngineId'].prettyPrint()) + tds.trap_dict['time received'] = epoch_msecond + tds.trap_dict['trap category'] = (tds.c_config['streams_publishes']['sec_fault_unsecure']['dmaap_info']['topic_url']).split('/')[-1] + + +# # # # # # # # # # # # # # # # # # # +# fx: request_observer for community string rewrite +# # # # # # # # # # # # # # # # # # # +def add_varbind_to_json(vb_idx, vb_oid, vb_type, vb_val): + """ + Called for each varbind, adds individual attributes of varbind instance to + vb_json_str. vb_json_str will be added to curr_trap_json_str prior to publish. + :Parameters: + vb_idx + index to specific varbind being processed + vb_oid + the varbind oid + vb_val + the value of the varbind + :Exceptions: + none + :Keywords: + varbind extract json + :Variables: + """ + + _individual_vb_dict = {} + + if tds.trap_dict["protocol version"] == "v2c": + # if v2c and first 2 varbinds, special handling required - e.g. put + # in trap_dict, not vb_json_str + if vb_idx == 0: + tds.trap_dict["sysUptime"] = str(vb_val.prettyPrint()) + return True + else: + 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('.') + 1) + return True + if tds.first_varbind: + tds.all_vb_json_str = ', \"varbinds\": [' + tds.first_varbind = False + else: + # all_vb_json_str = ''.join([all_vb_json_str, ' ,']) + # all_vb_json_str = "%s ," % all_vb_json_str + 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'] = vb_type + _individual_vb_dict['varbind_value'] = vb_val.prettyPrint() + + _individual_vb_json_str = json.dumps(_individual_vb_dict) + + # all_vb_json_str = "%s%s" % (all_vb_json_str, individual_vb_json_str) + # all_vb_json_str = ''.join([all_vb_json_str, individual_vb_json_str]) + tds.all_vb_json_str = tds.all_vb_json_str + _individual_vb_json_str + return True + + +# Callback function for receiving notifications +# noinspection PyUnusedLocal,PyUnusedLocal,PyUnusedLocal +def notif_receiver_cb(snmp_engine, stateReference, contextEngineId, contextName, + varBinds, cbCtx): + """ + Callback executed when trap arrives + :Parameters: + snmp_engine + snmp engine created to listen for arriving traps + stateReference + contextEngineId + contextName + varBinds + trap varbinds - why we are here + cbCtx + callback context + :Exceptions: + none + :Keywords: + callback trap arrival + :Variables: + """ + + msg = "processing varbinds for %s" % (tds.trap_dict["uuid"]) + ecomp_logger(tds.LOG_TYPE_DEBUG, tds.SEV_DETAILED, tds.CODE_GENERAL, msg) + + # FIXME update reset location when batching publishes + vb_idx = 0 + + # For reference: + # + # print('\nvarBinds ==> %s' % (varBinds)) + # + # varBinds ==> [(ObjectName('1.3.6.1.2.1.1.3.0'), TimeTicks(1243175676)), + # (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'Fri Aug 11 17:46:01 EDT 2017'))] + # + + tds.all_vb_json_str = "" + vb_idx = 0 + tds.first_varbind = True + + # iterate over varbinds, add to json struct + for vb_oid, vb_val in varBinds: + add_varbind_to_json(vb_idx, vb_oid, vb_val.__class__.__name__, vb_val) + vb_idx += 1 + + # FIXME: DL back out first 2 varbinds for v2c notifs prior to publishing varbind count + # trap_dict["varbind count"] = vb_idx + curr_trap_json_str = json.dumps(tds.trap_dict) + # now have everything except varbinds in "curr_trap_json_str" + + # if varbinds present - which will almost always be the case - add all_vb_json_str to trap_json_message + if vb_idx != 0: + # close out vb array + # all_vb_json_str += "]" + # all_vb_json_str = ''.join([all_vb_json_str, ']']) + tds.all_vb_json_str = tds.all_vb_json_str + ']' + + # remove last close bracket from curr_trap_json_str + curr_trap_json_str = curr_trap_json_str[:-1] + + # add vb_json_str to payload + # curr_trap_json_str += all_vb_json_str + # curr_trap_json_str = ''.join([curr_trap_json_str, all_vb_json_str]) + curr_trap_json_str = curr_trap_json_str + tds.all_vb_json_str + + # add last close brace back in + # curr_trap_json_str += "}" + # curr_trap_json_str = ''.join([curr_trap_json_str, '}']) + 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) + + # now have a complete json message for this trap in "curr_trap_json_str" + 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 + 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 + + # always log arriving traps + log_all_arriving_traps() + + # publish to dmaap after last varbind is processed + if tds.traps_since_last_publish >= 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.CODE_GENERAL, msg) + post_dmaap() + elif milliseconds_since_last_publish >= 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, tds.CODE_GENERAL, msg) + post_dmaap() + + +# # # # # # # # # # # # # +# Main MAIN Main MAIN +# # # # # # # # # # # # # +# parse command line args +parser = argparse.ArgumentParser(description='Post SNMP traps ' + 'to message bus') +parser.add_argument('-v', action="store_true", dest="verbose", + 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() + +# 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['snmptrap.title'], tds.c_config['snmptrap.version']) +stdout_logger(msg) + +# Avoid this unless needed for testing; it prints sensitive data to log +# +# msg = "Running config: " +# stdout_logger(msg) +# msg = json.dumps(c_config, sort_keys=False, indent=4) +# 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 messages will be logged. This can slow things down, use only when needed." + tds.minimum_severity_to_log=0 + stdout_logger(msg) + +# name and open arriving trap log +tds.arriving_traps_filename = tds.c_config['files.runtime_base_dir'] + "/" + tds.c_config['files.log_dir'] + "/" + (tds.c_config['files.arriving_traps_log']) +tds.arriving_traps_fd = open_file(tds.arriving_traps_filename) +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" +tds.json_traps_fd = open_file(tds.json_traps_filename) +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 autogenernated engineID pre-bound +# to socket transport dispatcher +snmp_engine = engine.SnmpEngine() + +# # # # # # # # # # # # +# Transport setup +# # # # # # # # # # # # + +# UDP over IPv4 +# FIXME: add check for presense of ipv4_interface prior to attempting add OR just put entire thing in try/except clause +try: + ipv4_interface = tds.c_config['protocols.ipv4_interface'] + ipv4_port = tds.c_config['protocols.ipv4_port'] + + try: + config.addTransport( + snmp_engine, + udp.domainName + (1,), + udp.UdpTransport().openServerMode( + (ipv4_interface, ipv4_port)) + ) + except Exception as e: + msg = "Unable to bind to %s:%s - %s" % (ipv4_interface, ipv4_port, str(e)) + 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 IPv4, second listening interface/port example if you don't want to listen on all +# config.addTransport( +# snmp_engine, +# udp.domainName + (2,), +# udp.UdpTransport().openServerMode(('127.0.0.1', 2162)) +# ) + + +# UDP over IPv6 +# FIXME: add check for presense of ipv6_interface prior to attempting add OR just put entire thing in try/except clause +try: + ipv6_interface = tds.c_config['protocols.ipv6_interface'] + ipv6_port = tds.c_config['protocols.ipv6_port'] + + try: + config.addTransport( + snmp_engine, + udp6.domainName, + udp6.Udp6Transport().openServerMode( + (ipv6_interface, ipv6_port)) + ) + except Exception as e: + msg = "Unable to bind to %s:%s - %s" % (ipv6_interface,ipv6_port, str(e)) + stdout_logger(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, + 'rfc2576.processIncomingMsg:writable' +) + +# register snmp_engine_observer_cb for message arrival +snmp_engine.observer.registerObserver( + snmp_engine_observer_cb, + '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() +except: + snmp_engine.observer.unregisterObserver() + snmp_engine.transportDispatcher.closeDispatcher() + cleanup_and_exit(1, tds.pid_file_name) diff --git a/bin/snmptrapd.sh b/bin/snmptrapd.sh new file mode 100755 index 0000000..52f3913 --- /dev/null +++ b/bin/snmptrapd.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env bash +# +# ============LICENSE_START======================================================= +# org.onap.dcae +# ================================================================================ +# Copyright (c) 2017-2018 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. +# + + +# get to where we are supposed to be for startup +cd /opt/app/snmptrap/bin + +# include path to 3.6+ version of python that has required dependencies included +export PATH=/opt/app/python-3.6.1/bin:$PATH + +# expand search for python modules to include ./mod in runtime dir +export PYTHONPATH=./mod:./:$PYTHONPATH + +# set location of SSL certificates +export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-bundle.crt + +# PYTHONUNBUFFERED: +# set PYTHONUNBUFFERED to a non-empty string to avoid output buffering; +# comment out for runtime environments/better performance! +export PYTHONUNBUFFERED="True" + +# set location of config broker server overrride IF NEEDED +# +export CBS_SIM_JSON=../etc/snmptrapd.json + +# want tracing? Use this: +# python -m trace --trackcalls snmptrapd.py -v +# want verbose logging? Use this: +# python snmptrapd.py -v +# standard startup? Use this: +# python snmptrapd.py +python snmptrapd.py -v |