Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Alert manager addition to dolphin framework #8

Merged
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Empty file.
34 changes: 34 additions & 0 deletions dolphin/alert_manager/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Copyright 2020 The SODA Authors.
#
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2020 The SODA Authors.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any part of the code which is taken from some other place? If yes, then maybe it's not a good idea to change the copyright..We need to be careful here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copyright updated. Does not contain copied code

# 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.


# Default values for trap receiver ip address and port
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there are different groups of constants, it's better to make them class constants. For example, all MIB constants in class MIB and all Trap constants in class Trap. Advantage is the code readability and flow.
access them like MIB.MIB_DIR_PATH

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not moved to class currently

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason?

DEF_TRAP_RECV_ADDR = '0.0.0.0'
DEF_TRAP_RECV_PORT = 162
TRAP_RECEIVER_CLASS = 'dolphin.alert_manager.trap_receiver.TrapReceiver'

# Temporary snmp community and user configurations
SNMP_COMMUNITY_STR='public'
SNMP_USM_USER='test1'
SNMP_V3_AUTHKEY='abcd123456'
SNMP_V3_PRIVKEY='abcd123456'
SNMP_V3_AUTH_PROTOCOL='usmHMACMD5AuthProtocol'
SNMP_V3_PRIV_PROTOCOL='usmDESPrivProtocol'
SNMP_ENGINE_ID='800000d30300000e112245'

# Temporary mib lod dir. This mechanism to be changed later
SNMP_MIB_PATH = '/usr/local/lib/python3.6/dist-packages/pysnmp/smi/mibs'

# SNMP dispatcher job id (static identifier)
SNMP_DISPATCHER_JOB_ID = 1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why dispatcher job id is static here?

135 changes: 135 additions & 0 deletions dolphin/alert_manager/trap_receiver.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Copyright 2020 The SODA Authors.
#
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated

# 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.

from oslo_log import log

from pysnmp.entity import engine, config
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should pysnmp be added to requirements.txt, and what kind of license it used?

from pysnmp.carrier.asyncore.dgram import udp
from pysnmp.entity.rfc3413 import ntfrcv
from pysnmp.proto.api import v2c
from pysnmp.smi import builder, view, rfc1902, error

from dolphin.alert_manager import constants

LOG = log.getLogger(__name__)

# Currently static mib file list is loaded, logic to be changed to load all mib file
MIB_LOAD_LIST = ['SNMPv2-MIB','IF_MIB']

class TrapReceiver(object):
"""Trap listening and processing functions"""

def __init__(self, trap_receiver_address, trap_receiver_port,
snmp_mib_path, mib_view_controller=None, snmp_engine=None):
self.mib_view_controller = mib_view_controller
self.snmp_engine = snmp_engine
self.trap_receiver_address = trap_receiver_address
self.trap_receiver_port = trap_receiver_port
self.snmp_mib_path = snmp_mib_path

def _mib_builder(self):
"""Loads given set of mib files from given path."""
mib_builder = builder.MibBuilder()
try:
self.mib_view_controller = view.MibViewController(mib_builder)

# set mib path to mib_builder object and load mibs
mib_path = builder.DirMibSource(self.snmp_mib_path),
mib_builder.setMibSources(*mib_path)
if len(MIB_LOAD_LIST) > 0:
mib_builder.loadModules(*MIB_LOAD_LIST)
except error.MibNotFoundError:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this exception need to be throw out to the upper level?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, updated

LOG.error("Mib load failed.")

def _add_transport(self):
"""Configures the transport parameters for the snmp engine."""
try:
config.addTransport(
self.snmp_engine,
udp.domainName,
udp.UdpTransport().openServerMode((self.trap_receiver_address, int(self.trap_receiver_port)))
)
except Exception:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here..

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, updated

LOG.error("Port binding failed the provided port is in use.")

def _cb_fun(self, state_reference, context_engine_id, context_name,
var_binds, cb_ctx):
"""Callback function to process the incoming trap."""
exec_context = self.snmp_engine.observer.getExecutionContext('rfc3412.receiveMessage:request')
LOG.info(
'#Notification from %s \n#ContextEngineId: "%s" \n#ContextName: "%s" \n#SNMPVER "%s" \n#SecurityName "%s"' % (
'@'.join([str(x) for x in exec_context['transportAddress']]), context_engine_id.prettyPrint(),
context_name.prettyPrint(), exec_context['securityModel'], exec_context['securityName']))
for oid, val in var_binds:
output = rfc1902.ObjectType(rfc1902.ObjectIdentity(oid), val).resolveWithMib(
self.mib_view_controller).prettyPrint()
LOG.info(output)

def _snmp_v2v3_config(self):
"""Configures snmp v2 and v3 user parameters."""
community_str = constants.SNMP_COMMUNITY_STR
config.addV1System(self.snmp_engine, community_str, community_str)
auth_priv_protocols = {
'usmHMACMD5AuthProtocol': config.usmHMACMD5AuthProtocol,
'usmHMACSHAAuthProtocol': config.usmHMACSHAAuthProtocol,
'usmAesCfb128Protocol': config.usmAesCfb128Protocol,
'usmAesCfb256Protocol': config.usmAesCfb256Protocol,
'usmAesCfb192Protocol': config.usmAesCfb192Protocol,
'usmDESPrivProtocol': config.usmDESPrivProtocol,
'usmNoAuthProtocol': config.usmNoAuthProtocol,
'usmNoPrivProtocol': config.usmNoPrivProtocol
}
config.addV3User(
self.snmp_engine, userName=constants.SNMP_USM_USER,
authKey=constants.SNMP_V3_AUTHKEY, privKey=constants.SNMP_V3_PRIVKEY,
authProtocol=auth_priv_protocols.get(
constants.SNMP_V3_AUTH_PROTOCOL, config.usmNoAuthProtocol),
privProtocol=auth_priv_protocols.get(
constants.SNMP_V3_PRIV_PROTOCOL, config.usmNoPrivProtocol),
securityEngineId=v2c.OctetString(
hexValue=constants.SNMP_ENGINE_ID))

return

def start(self):
"""Starts the snmp trap receiver with necessary prerequisites."""
snmp_engine = engine.SnmpEngine()
self.snmp_engine = snmp_engine

# Load all the mibs and do snmp config
self._mib_builder()

self._snmp_v2v3_config()

# Register callback for notification receiver
ntfrcv.NotificationReceiver(snmp_engine, self._cb_fun)

# Add transport info(ip, port) and start the listener
self._add_transport()

snmp_engine.transportDispatcher.jobStarted(constants.SNMP_DISPATCHER_JOB_ID)
try:
LOG.info("Starting trap receiver.")
snmp_engine.transportDispatcher.runDispatcher()

except Exception:
LOG.error("Failed to start trap listener.")
snmp_engine.transportDispatcher.closeDispatcher()

def stop(self):
"""Brings down the snmp trap receiver."""
# Go ahead with shutdown, ignore if any errors happening during the process as it is shutdown
if self.snmp_engine:
self.snmp_engine.transportDispatcher.closeDispatcher()
LOG.info("Trap receiver stopped.")
5 changes: 4 additions & 1 deletion dolphin/cmd/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ def main():
task_server = service.Service.create(binary='dolphin-task', coordination=True)
launcher.launch_service(api_server, workers=api_server.workers or 1)
launcher.launch_service(task_server)
# TODO: add snmp server and launch it

#Launch alert manager service
alert_manager = service.AlertMngrService()
launcher.launch_service(alert_manager)

launcher.wait()

Expand Down
4 changes: 4 additions & 0 deletions dolphin/common/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from oslo_log import log
from oslo_middleware import cors
from oslo_utils import netutils
from dolphin.alert_manager import constants


CONF = cfg.CONF
Expand Down Expand Up @@ -77,6 +78,9 @@
cfg.StrOpt('dolphin_task_topic',
default='dolphin-task',
help='The topic task manager nodes listen on.'),
cfg.StrOpt('trap_receiver_class',
default=constants.TRAP_RECEIVER_CLASS,
help='Full class name for the trap receiver.'),
]

CONF.register_opts(global_opts)
Expand Down
41 changes: 41 additions & 0 deletions dolphin/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from dolphin import exception
from dolphin import rpc
from dolphin import coordination
from dolphin.alert_manager import constants

LOG = log.getLogger(__name__)

Expand Down Expand Up @@ -63,6 +64,15 @@
default=False,
help='Wraps the socket in a SSL context if True is set. '
'A certificate file and key file must be specified.'),
cfg.HostAddressOpt('trap_receiver_address',
default=constants.DEF_TRAP_RECV_ADDR,
help='IP address at which trap receiver listens.'),
cfg.PortOpt('trap_receiver_port',
default=constants.DEF_TRAP_RECV_PORT,
help='Port at which trap receiver listens.'),
cfg.StrOpt('snmp_mib_path',
default=constants.SNMP_MIB_PATH,
help='Path at which mib files to be loaded are placed.'),
]

CONF = cfg.CONF
Expand Down Expand Up @@ -208,6 +218,37 @@ def periodic_tasks(self, raise_on_error=False):
ctxt = context.get_admin_context()
self.manager.periodic_tasks(ctxt, raise_on_error=raise_on_error)

class AlertMngrService(service.Service):
"""Service object for triggering trap receiver functionalities.
"""

def __init__(self, trap_receiver_address=None,
trap_receiver_port=None, snmp_mib_path=None, trap_receiver_class=None):
super(AlertMngrService, self).__init__()

if not trap_receiver_address:
trap_receiver_address = CONF.trap_receiver_address
if not trap_receiver_port:
trap_receiver_port = CONF.trap_receiver_port
if not snmp_mib_path:
snmp_mib_path = CONF.snmp_mib_path
if not trap_receiver_class:
trap_receiver_class = CONF.trap_receiver_class
manager_class = importutils.import_class(trap_receiver_class)
self.manager = manager_class(trap_receiver_address,
trap_receiver_port, snmp_mib_path)

def start(self):
"""Trigger trap receiver creation"""
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these comments or doc strings? usually doc strings are put under """

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doc strings for each functions

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok

self.manager.start()

def kill(self):
"""Destroy the service object in the datastore."""
self.stop()

def stop(self):
"""Calls the shutdown flow of the service."""
self.manager.stop()

class WSGIService(service.ServiceBase):
"""Provides ability to launch API from a 'paste' configuration."""
Expand Down