kelefa  
大千世界中,唯一缺乏的就是人类的注意力。
日历
<2006年12月>
262728293012
3456789
10111213141516
17181920212223
24252627282930
31123456
统计
  • 随笔 - 11
  • 文章 - 0
  • 评论 - 28
  • 引用 - 0

导航

常用链接

留言簿(2)

随笔分类

随笔档案

搜索

  •  

最新评论

阅读排行榜

评论排行榜

 
一般主动告警系统的告警信息采集主要有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/local
make clean all
make install

snmpget <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.comhttp://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;
    }

}
posted on 2006-12-27 09:50 杨杰荣 阅读(5388) 评论(7)  编辑  收藏 所属分类: 开源应用
评论:
  • # re: 网络设备主动告警系统之snmp告警的实现  suwu Posted @ 2007-01-18 12:56
    非常好,获益良多  回复  更多评论   

  • # re: 网络设备主动告警系统之snmp告警的实现  vito Posted @ 2007-05-21 23:01
    很好啊 非常好  回复  更多评论   

  • # re: 网络设备主动告警系统之snmp告警的实现[未登录]  king Posted @ 2007-05-25 23:52
    恩 获益匪浅  回复  更多评论   

  • # re: 网络设备主动告警系统之snmp告警的实现   Posted @ 2007-09-27 17:10
    真的太好了。受益匪浅啊
      回复  更多评论   

  • # re: 网络设备主动告警系统之snmp告警的实现  conrad Posted @ 2007-10-18 09:27
    狂顶,太好了,恩人啊!  回复  更多评论   

  • # re: 网络设备主动告警系统之snmp告警的实现  天地一沙欧 Posted @ 2007-11-07 15:32
    太好了
    网络设备所有的告警能实现吗?
    要是有能贴出来的话就更好了
    谢谢  回复  更多评论   

  • # re: 网络设备主动告警系统之snmp告警的实现  jia Posted @ 2008-10-13 13:10
    把下面这些代码也贴出来吧,否则不能运行啊
    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;


      回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航:
 
 
Copyright © 杨杰荣 Powered by: 博客园 模板提供:沪江博客