Skip to content

Commit

Permalink
Merge pull request #1 from sodafoundation/master
Browse files Browse the repository at this point in the history
Update from main branch
  • Loading branch information
jiangyutan authored Aug 28, 2020
2 parents 707fabb + d464175 commit a440023
Show file tree
Hide file tree
Showing 50 changed files with 1,244 additions and 715 deletions.
7 changes: 2 additions & 5 deletions delfin/alert_manager/alert_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from delfin import context
from delfin import db
from delfin import exception
from delfin.common import alert_util
from delfin.drivers import api as driver_manager
from delfin.exporter import base_exporter

Expand All @@ -40,11 +41,7 @@ def process_alert_info(self, alert):
alert['storage_id'],
alert)
# Fill storage specific info
alert_model['storage_id'] = storage['id']
alert_model['storage_name'] = storage['name']
alert_model['vendor'] = storage['vendor']
alert_model['model'] = storage['model']
alert_model['serial_number'] = storage['serial_number']
alert_util.fill_storage_attributes(alert_model, storage)
except Exception as e:
LOG.error(e)
raise exception.InvalidResults(
Expand Down
6 changes: 6 additions & 0 deletions delfin/alert_manager/rpcapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,9 @@ def sync_snmp_config(self, ctxt, snmp_config_to_del, snmp_config_to_add):
'sync_snmp_config',
snmp_config_to_del=snmp_config_to_del,
snmp_config_to_add=snmp_config_to_add)

def check_snmp_config(self, ctxt, snmp_config):
call_context = self.client.prepare(version='1.0')
return call_context.cast(ctxt,
'check_snmp_config',
snmp_config=snmp_config)
183 changes: 183 additions & 0 deletions delfin/alert_manager/snmp_validator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
# Copyright 2020 The SODA Authors.
#
# 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.
import binascii
from datetime import datetime

import six
from oslo_config import cfg
from oslo_log import log
from pyasn1.type.univ import OctetString
from pysnmp.entity.rfc3413.oneliner import cmdgen

from delfin import cryptor
from delfin import db
from delfin import exception
from delfin.common import constants
from delfin.exporter import base_exporter

CONF = cfg.CONF

LOG = log.getLogger(__name__)


class SNMPValidator(object):
def __init__(self):
self.exporter = base_exporter.AlertExporterManager()

def validate(self, ctxt, alert_source):
alert_source = dict(alert_source)
engine_id = alert_source.get('engine_id')
try:
alert_source = self.validate_connectivity(alert_source)

# If protocol is snmpv3, the snmp_validator will update
# engine id if engine id is empty. Therefore, engine id
# should be saved in database.
if not engine_id and alert_source.get('engine_id'):
db.access_info_update(ctxt,
alert_source.get('storage_id'),
alert_source)

except exception.SNMPConnectionFailed:
self._handle_validation_error(ctxt, alert_source.get('storage_id'))
except Exception as e:
msg = six.text_type(e)
LOG.error("Failed to check snmp config. Reason: %s", msg)

@staticmethod
def validate_connectivity(alert_source):
# Fill optional parameters with default values if not set in input
if not alert_source.get('port'):
alert_source['port'] = constants.DEFAULT_SNMP_CONNECT_PORT

if not alert_source.get('context_name'):
alert_source['context_name'] = None

if not alert_source.get('retry_num'):
alert_source['retry_num'] = constants.DEFAULT_SNMP_RETRY_NUM

if not alert_source.get('expiration'):
alert_source['expiration'] = constants.DEFAULT_SNMP_EXPIRATION_TIME

if CONF.snmp_validation_enabled is False:
return alert_source

cmd_gen = cmdgen.CommandGenerator()

# Register engine observer to get engineId,
# Code reference from: http://snmplabs.com/pysnmp/
observer_context = {}
cmd_gen.snmpEngine.observer.registerObserver(
lambda e, p, v, c: c.update(
securityEngineId=v['securityEngineId']),
'rfc3412.prepareDataElements:internal',
cbCtx=observer_context
)

version = alert_source.get('version')

# Connect to alert source through snmp get to check the configuration
try:
if version.lower() == 'snmpv3':
auth_key = cryptor.decode(alert_source['auth_key'])
privacy_key = cryptor.decode(alert_source['privacy_key'])
auth_protocol = None
privacy_protocol = None
if alert_source['auth_protocol']:
auth_protocol = constants.AUTH_PROTOCOL_MAP.get(
alert_source['auth_protocol'].lower())
if alert_source['privacy_protocol']:
privacy_protocol = constants.PRIVACY_PROTOCOL_MAP.get(
alert_source['privacy_protocol'].lower())

engine_id = alert_source.get('engine_id')
if engine_id:
engine_id = OctetString.fromHexString(engine_id)
error_indication, __, __, __ = cmd_gen.getCmd(
cmdgen.UsmUserData(alert_source['username'],
authKey=auth_key,
privKey=privacy_key,
authProtocol=auth_protocol,
privProtocol=privacy_protocol,
securityEngineId=engine_id),
cmdgen.UdpTransportTarget((alert_source['host'],
alert_source['port']),
timeout=alert_source[
'expiration'],
retries=alert_source[
'retry_num']),
constants.SNMP_QUERY_OID,
)

if 'securityEngineId' in observer_context:
engine_id = observer_context.get('securityEngineId')
alert_source['engine_id'] = binascii.hexlify(
engine_id.asOctets()).decode()
else:
community_string = cryptor.decode(
alert_source['community_string'])
error_indication, __, __, __ = cmd_gen.getCmd(
cmdgen.CommunityData(
community_string,
contextName=alert_source['context_name']),
cmdgen.UdpTransportTarget((alert_source['host'],
alert_source['port']),
timeout=alert_source[
'expiration'],
retries=alert_source[
'retry_num']),
constants.SNMP_QUERY_OID,
)

if not error_indication:
return alert_source

# Prepare exception with error_indication
msg = six.text_type(error_indication)
except Exception as e:
msg = six.text_type(e)

# Since validation occur error, raise exception
LOG.error("Configuration validation failed with alert source for "
"reason: %s." % msg)
raise exception.SNMPConnectionFailed(msg)

def _handle_validation_error(self, ctxt, storage_id):
try:
storage = db.storage_get(ctxt, storage_id)
alert = {
'storage_id': storage['id'],
'storage_name': storage['name'],
'vendor': storage['vendor'],
'model': storage['model'],
'serial_number': storage['serial_number'],
'alert_id': constants.INTERNAL_ALERT_ID,
'sequence_number': 0,
'alert_name': 'SNMP connect failed',
'category': constants.Category.FAULT,
'severity': constants.Severity.MAJOR,
'type': constants.EventType.COMMUNICATIONS_ALARM,
'location': 'NetworkEntity=%s' % storage['name'],
'description': "SNMP connection to the storage failed. "
"SNMP traps from storage will not be received.",
'recovery_advice': "1. The network connection is abnormal. "
"2. SNMP authentication parameters "
"are invalid.",
'occur_time': int(datetime.utcnow().timestamp()) * 1000,
}
self.exporter.dispatch(ctxt, alert)
except Exception as e:
msg = six.text_type(e)
LOG.error("Exception occurred when handling validation "
"error: %s ." % msg)
Loading

0 comments on commit a440023

Please sign in to comment.