本站首页    管理页面    写新日志    退出


«August 2025»
12
3456789
10111213141516
17181920212223
24252627282930
31


公告
 本博客在此声明所有文章均为转摘,只做资料收集使用。

我的分类(专题)

日志更新

最新评论

留言板

链接

Blog信息
blog名称:
日志总数:1304
评论数量:2242
留言数量:5
访问次数:7593682
建立时间:2006年5月29日




[网站架构]网络设备主动告警系统之snmp告警的实现
软件技术

lhwork 发表于 2006/12/28 8:59:05

一般主动告警系统的告警信息采集主要有5种方法:  1. 在告警服务器ping各种设备, 判断设备是否存活和掉包率 2. 接收设备发过来的系统日志(syslog), 并通过相应的规则库(正则表达式)匹配判断是否需要告警 3. 接收设备发过来的snmp Trap信息, 进行判断告警 4. 提取网管系统的告警信息 5. 通过snmp协议, 取回相应oid的值, 进行判断告警  什么是snmp:  Simple Network Management Protocol (SNMP)提供了一些"简单"的操作, 允许你更容易的监控和管理网络设备, 例如路由器,交换机,服务器,打印机等等. 通过snmp你可以监控很多信息, 例如端口流量, 路由器里面的温度, cpu使用率等等. 学习snmp其实并不是特别简单, 请通过别的资料学习更多的方面, 特别是mib,oid之类的概念. 推荐学习Essential SNMP, 2nd Edition这本书.  如何收集数据:  如果安装了NET-SNMP, 可以从http://net-snmp.sourceforge.net/获取NET-SNMP的RPM包以及源代码。下载解压后su -cd ucd-snmp-4.2.3./configure --prefix=/usr  <-- 缺省是/usr/localmake clean allmake installsnmpget <target> public system.sysDescr.0应该可以看到一个关于系统的简短描述,类似这样:system.sysDescr.0 = Sun SNMP Agent, Ultra-60上述命令中的public可以理解为SNMP agent的口令,术语叫做"community string"。许多网络设备、操作系统都用"public"做为缺省"community string",潜在带来安全问题。应该修改这个缺省"community string"。上述命令还可以写成:snmpget <target> public .1.3.6.1.2.1.1.1.0"system.sysDescr.0"只是".1.3.6.1.2.1.1.1.0"的另一种表述方式,最终还是要转换成数字形式的OID(对象标识符)。snmpget返回一个值, 类型可以是数值或者字符串等, 还有一个snmpwalk的操作, 大概就是返回一个数组的结果.本系统使用java语言实现, 在网上下载了一个开源的snmp实现, 假设有以下工具类:public class Poller{    public Poller( String host, String community, int version )        throws IOException    {        //     }        public String get( String oid )            throws IOException    {        //         return null;    }            public Map<String, String> walk( String base, int startIndex,            int indexCount )    {        //         return null;    }            public void close()    {    }        public static void main( String[] args )    {        Poller poller = new Poller(); // 该ip对应的设备是cisco-6509                // 1. cpu告警        String cpuStr = poller.get( "1.3.6.1.4.1.9.9.109.1.1.1.1.5.9" ); // cisco-6509的CPU使用率        long cpu = Long.parseLong( valueStr );                if ( cpu > 85 )        {            System.out.println( "告警! cisco-6509的CPU使用率超过85%" ) ;        }                // 2. 板卡告警        String statusStr = poller.get( "1.3.6.1.4.1.9.5.1.3.1.1.10.1" ); // cisco-6509的第一个板卡状态        long status = Long.parseLong( statusStr );                if ( value != 2 && value != 1 ) // 1:未知 2:normal 3:minorFault 4:majorFault        {            System.out.println( "告警! cisco-6509的第一个板卡状态不正常" ) ;        }                // 3. 流量告警        String octetStr = poller.get( "ifHCInOctets.10" ); // cisco-6509的第10个接口的输入流量, 单位Byte        long value = Long.parseLong( octetStr );        long time = System.currentTimeMillis()/1000;        long lastValue = getLastValue(); // 从数据库或文件取上次的流量值        long lastTime = getLastTime(); // 从数据库或文件取上次采集的时间                if ( (value-lastValue)/(time-lastTime)*8>800000000 ) // 一般流量单位是 bit/s, 所以要乘以8        {            System.out.println( "告警! cisco-6509的第10个接口的输入流量超过800M" ) ;        }                        poller.close();            }}在上面的main函数, 我们已经基本可以实现snmp的告警功能了, 可是这样相当不灵活, 全部都是硬编码, 每添加一个新的snmp告警都要新加代码模块 经过分析, 大部分的snmp采集告警都是这样的过程:  1. 取得某设备的对象ID(oid) 2. 通过snmp协议得到该oid相应的值, 赋值给value这个变量 3. 取当前的时间(秒), 赋值给time这个变量 4. 取上次采集的值和时间, 分别赋值给lastValue, lastValue 5. 根据该oid返回值代表含义, 构造一个表达式, 这个表达式只能包括value, time, lastValue, lastTime这4个变量, 有时不必全部用上, 而且该表达式应回一个布尔类型的值, 如果为真则需要告警 6. 保存value, time为lastValue, lastTime, 用来在下次采集判断时使用  这个时候就比较清楚了, 如果有一种动态语言或动态脚本在java环境里能运行就能够比较灵活的实现snmp告警了, 不需要硬编码所有的告警情况, 只需要在ui界面添加修改告警表达式就ok了 经过在http://www.open-open.com或http://java-source.net上搜索, 发现BeanShell这个项目, 官方网站是http://www.beanshell.org/  Beanshell 是用Java写成的,一个小型的、免费的、可以下载的、嵌入式的Java源代码解释器,具有对象脚本语言特性。BeanShell执行标准Java语句和表达式,另外包括一些脚本命令和语法。它将脚本化对象看作简单闭包方法(simple method closure)来支持,就如同在Perl和JavaScript中的一样。以下是用BeanShell改写的snmp告警模块:package com.kelefa.warnlet.job;import java.io.IOException;import java.util.Date;import org.apache.log4j.Logger;import org.hibernate.HibernateException;import org.hibernate.classic.Session;import bsh.EvalError;import bsh.Interpreter;import com.kelefa.common.util.HibernateUtil;import com.kelefa.warnlet.dao.WarningDAO;import com.kelefa.warnlet.interpreter.SimpleInterpreter;import com.kelefa.warnlet.snmp.Poller;import com.kelefa.warnlet.vo.Device;import com.kelefa.warnlet.vo.SnmpObject;import com.kelefa.warnlet.vo.Warning;public class SnmpTask        implements Runnable{    private final static Logger log = Logger.getLogger( SnmpTask.class );    private SnmpObject snmpObject;    private WarningDAO warningDAO;    private static final String BSH = "bsh://";    public SnmpTask( SnmpObject snmpObject, WarningDAO warningDAO )    {        this.snmpObject = snmpObject;        this.warningDAO = warningDAO;    }    public void run()    {        log.debug( "----snmpObject.id=" + snmpObject.getId() );        try        {            Session session = HibernateUtil.currentSession();            HibernateUtil.beginTransaction();            session.refresh( snmpObject );            doSnmpTask();            HibernateUtil.commitTransaction();        }        catch ( Exception ex )        {            HibernateUtil.rollbackTransaction();            log.warn( ex.getMessage() );        }        finally        {            HibernateUtil.closeSession();        }        log.debug( "++++snmpObject.id=" + snmpObject.getId() );    }    /** *//**     * 执行snmp任务, 包括:      * 1. 用snmp协议取相应oid的值, 如果网络异常或oid设置错误则直接结束      * 2. 如果返回的字符串不是数字则直接结束     * 3. 用BSH运算告警表达式, 表达式错误结束      * 4. 告警表达式返回真, 进行告警      * 5. 更新最后时间,值     *      */    private void doSnmpTask()    {        Device device = snmpObject.getDevice();        String valueStr;        try        {            valueStr = snmpget( device.getIp(), device.getCommunity(), device                    .getSnmpVersion(), snmpObject.getOid() );        }        catch ( IOException e )        { // 1. 如果网络异常或oid设置错误则直接结束            log.warn( e.getMessage() );            return;        }        if ( valueStr == null || valueStr.trim().length() == 0 )            return;        Long value = null;        try        {            value = Long.valueOf( valueStr );        }        catch ( NumberFormatException ex )        {// 2. 如果返回的字符串不是数字则直接结束            log.warn( "NumberFormatException: " + ex.getMessage() + "\t"                    + device.getCommunity() + "@" + device.getIp() + ": "                    + snmpObject.getOid() );            return;        }        Date now = new Date();        Long time = new Long( (now.getTime() + 500) / 1000 );        if ( snmpObject.getLastValue() > 0 && snmpObject.getLastTime() > 0 )        { // 第一次不执行bsh脚本            Long lastValue = new Long( snmpObject.getLastValue() );            Long lastTime = new Long( snmpObject.getLastTime() );            boolean doWarn = false;            try            { // 3. 用BSH运算告警表达式                doWarn = evalExpr( value, time, lastValue, lastTime );            }            catch ( EvalError ex )            {                log.warn( ex.getMessage(), ex );                updateSnmpObject( value, time );                return;            }            if ( log.isDebugEnabled() )            {                logResult( time, value, lastValue, lastTime, doWarn );            }            if ( doWarn )            { // 4. 告警表达式返回真, 进行告警                Warning warning = newWarning( now, time, value, lastValue, lastTime );                try                {                    warningDAO.insertWarning( warning );                }                catch ( Exception ex )                {                    throw new HibernateException( ex.getMessage() );                }            }        }        // 5. 更新最后时间,值        updateSnmpObject( value, time );    }    /** *//**     * 更新监控对象的最后的执行时间(lastTime)以及最新值(lastValue)     *      * @param value     * @param time     */    private void updateSnmpObject( Long value, Long time )    {        snmpObject.setLastTime( time.longValue() );        snmpObject.setLastValue( value.longValue() );    }    /** *//**     * 执行动态bsh表达式, 并返回该表达式的结果值     *      * @param value     * @param time     * @param lastValue     * @param lastTime     * @return     * @throws EvalError     */    private boolean evalExpr( Long value, Long time, Long lastValue, Long lastTime )            throws EvalError    {        Interpreter bsh = new Interpreter();        bsh.set( "value", value );        bsh.set( "time", time );        bsh.set( "lastValue", lastValue );        bsh.set( "lastTime", lastTime );        // 执行bsh脚本,返回true则需要告警        Boolean doWarn = (Boolean) bsh.eval( snmpObject.getWarnExpr() );        return doWarn.booleanValue();    }    /** *//**     * 通过snmpget或snmpwalk命令取snmpObject的oid对应的值, oid可能是单独的oid例如 1.3.6.1.4.5,     * 也可能是包括sum, count, max, min, avg等函数的表达式. 如果是单独的oid, 返回snmpget相应的值即可;     * 如果是复合函数, 用snmpwalk, 再进行运算, 返回最后结果值     *      * @param device     *          ip, community, version从这个对象取     * @return     * @throws IOException     */    public static String snmpget( final String ip, final String community,            final int snmpversion, final String oid )            throws IOException    {        String valueStr = null;        Poller poller = null;        try        {            poller = new Poller( ip, community, snmpversion, 100 );            log.debug( "pollering " + oid );            if ( oid.indexOf( '(' ) == -1 )            {// 单独一个oid                valueStr = poller.get( oid );                if ( log.isDebugEnabled() )                    log.debug( "snmpget(" + oid + ")=" + valueStr );            }            else            {// 包括sum, count, max, min, avg等函数的表达式, 例如:                // sum(ippoolSize)*100/sum(ippoolUse)                SimpleInterpreter si = new SimpleInterpreter( poller, oid );                Long result = si.interprete();                if ( log.isDebugEnabled() )                    log.debug( oid + "=" + result );                if ( result != null )                    valueStr = result.toString();            }        }        finally        {            if ( poller != null )                poller.close();        }        return valueStr;    }    private Warning newWarning( Date now, Long time, Long value, Long lastValue,            Long lastTime )    {        Warning warning = new Warning();        warning.setDeviceID( snmpObject.getDeviceID() );        warning.setWarnType( snmpObject.getWarnType() );        warning.setWarnLevel( snmpObject.getWarnLevel() );        warning.setPrimarykey( snmpObject.getOid() );        String sms = snmpObject.getWarnSms();        sms = getBshWarnMsg( sms, value, lastValue, time, lastTime );        if ( sms == null || sms.trim().length() == 0 )            warning.setWarnSms( snmpObject.getWarnType() );        else            warning.setWarnSms( sms.trim() );        String email = snmpObject.getWarnEmail();        email = getBshWarnMsg( email, value, lastValue, time, lastTime );        if ( email == null || email.trim().length() == 0 )            warning.setWarnEmail( snmpObject.getWarnType() );        else            warning.setWarnEmail( email.trim() );        warning.setWarnTTS( snmpObject.getWarnTTS() );        warning.setFirstTime( now );        warning.setLastTime( now );        warning.setSuggestion( snmpObject.getSuggestion() );        return warning;    }    private void logResult( Long time, Long value, Long lastValue, Long lastTime,            boolean doWarn )    {        StringBuffer buf = new StringBuffer();        buf.append( "OID=" ).append( snmpObject.getOid() );        buf.append( ",time=" ).append( time );        buf.append( ",value=" ).append( value );        buf.append( ",lastTime=" ).append( snmpObject.getLastTime() );        buf.append( ",lastValue=" ).append( snmpObject.getLastValue() );        buf.append( "\n\t" ).append( snmpObject.getWarnExpr() ).append( "=" )                .append( doWarn );        if ( snmpObject.getWarnExpr().indexOf( "(value-lastValue)/(time-lastTime)" ) > -1 )        {            buf.append( "\n\t(value-lastValue)/(time-lastTime)=" ).append(                    ((value - lastValue) / (time - lastTime)) );        }        log.debug( buf.toString() );    }    /** *//**     * 如果参数是以"bsh://"开头则通过BSH计算一个字符串表达式,返回最后结果; 否则直接返回。     * 表达式参数包括value,lastValue,time,lastTime,例如:     * bsh://"端口45流量大于800M:"+((value-lastValue)/(time-lastTime)*8/1000000)+"M"     *      * @param msgExpr     *          字符串表达式     * @return String     */    private static String getBshWarnMsg( String msgExpr, Long value,            Long lastValue, Long time, Long lastTime )    {        if ( msgExpr == null || !msgExpr.startsWith( BSH ) )            return msgExpr;        msgExpr = msgExpr.substring( BSH.length() );        try        {            Interpreter bsh = new Interpreter();            bsh.set( "value", value );            bsh.set( "time", time );            bsh.set( "lastValue", lastValue );            bsh.set( "lastTime", lastTime );            // 执行bsh脚本,返回实际的告警信息            msgExpr = (String) bsh.eval( msgExpr );        }        catch ( EvalError ex )        {            log.warn( ex.getMessage() );        }        return msgExpr;    }}


阅读全文(2242) | 回复(0) | 编辑 | 精华
 



发表评论:
昵称:
密码:
主页:
标题:
验证码:  (不区分大小写,请仔细填写,输错需重写评论内容!)



站点首页 | 联系我们 | 博客注册 | 博客登陆

Sponsored By W3CHINA
W3CHINA Blog 0.8 Processed in 0.352 second(s), page refreshed 144761250 times.
《全国人大常委会关于维护互联网安全的决定》  《计算机信息网络国际联网安全保护管理办法》
苏ICP备05006046号