Обработчик управления устройством с синхронизацией интерфейсов и их индексов - версия 2
Материал из BiTel WiKi
Решение сделано на основе вот этого
Отличия:
1) Не нужна сторонняя библиотека у snmp4j, используется стандартная из поставки. Поэтому не нужны классы SnmpClient и SnmpDeviceManager из того решения. А класс IfaceSnmpDeviceManager немного изменен и переименован в IndexSnmpDeviceManager. Он наследуется от стандартного ru.bitel.bgbilling.modules.inet.dyn.device.snmp.SnmpDeviceManager(Описан тут: https://docs.bitel.ru/pages/viewpage.action?pageId=43385967) и используется та же самая конфигурация при получении uptime.
2) В случае если интерфейс в биллинге не найден, то создается интерфейс с номером, который считываться как group(1) из snmp.ifNameRegexpFilter=GigabitEthernet0/0/2\.(\d+). В старом решении просто присваивался (максимальный номер интерфейса) + 1.
3) На устройстве не нужно явно вбивать команду ifsync, во всплывающем меню уже есть пункт "сихронизировать интерфейсы". При перезагрузке устройства этого делать не надо - смотрим следующий пункт.
4) При перезагрузке устройства IfaceSnmpDeviceManager сам это определит и вызовет( метод onReboot).
Проверялось на сisco и juniper. Использовалось в схеме сбора статистики по snmp.
Содержание |
Описание
Класс - обработчик управления устройством по SNMP (DeviceManager) взамен стандартного.
- Умеет синхронизировать интерфейсы устройства и их snmp-индексы (нужны для учёта flow/snmp)
Как пользоваться
- Копируем себе в динамический код класс:
ru.dsi.bgbilling.modules.inet.dyn.device.snmp.IndexSnmpDeviceManager
- Компилируем динамический код
- Указываем в типе устройства модуля Inet "Обработчик управления устройством: ru.dsi.bgbilling.modules.inet.dyn.device.snmp.IndexSnmpDeviceManager"
- Настраиваем конфигурацию устройства/типа устройства/родительского устройства (по усмотрению) - см раздел "Конфигурация"
- Перечитываем конфигурацию устройств
- При обнаружении перезагрузки ( а access скорее всего сразу обнаружит это после перечитывания конфигурации) IndexSnmpDeviceManager сам сихронизует интерфейсы и индексы.
- Чтобы запустить вручную заходим в устройство с нашим обработчиком, кликаем правой кнопкой, выбираем "сихронизировать интерфейсы" смотрим результат
Принцип работы
Обработчик поддерживает команды:
- ifsync - синхронизация интерфейсов и их ifIndex на роутере и в биллинге:
- 1. Получаем по SNMP список соответствий: ifIndex->ifName интерфейсов на устройстве
- 2. Для каждого полученного интерфейса проверяем, есть ли в биллинге на устройсве интерфейс с таким именем?
- 2.1. Если нет, и он проходит через фильтр snmp.ifNameRegexpFilter - создаём с правильным ifIndex и категорией ip = snmp.ipCategory и номер интерфейса находим как group(1) у заданного snmp.ifNameRegexpFilter.
- 2.2. Если есть, то совпадает ли ifIndex?
- 2.2.1. Если да, то ничего не делаем, всё ок.
- 2.2.2. Если нет, то меняем ifIndex
- 3. Оставшиеся необработанными интерфейсы в BG: если их нет на устройстве, то помечаем как отключенный и устанавливаем ifIndex=0
Настройка
Параметры конфигурации устройства/типа устройства:
- для SnmpDeviceManager
#версия протокола (1 или 2 (для 2с)) snmp.version=1 #ip-адрес или hostname устройства. По-умолчанию берётся из поля "Хост" устройства в модуле Inet snmp.host= #порт SNMP устройства snmp.port=161 #SNMP community. По-умолчанию - secret устройства в Inet snmp.community= #OID для опроса uptime устройства. По умолчанию - 1.3.6.1.2.1.1.3.0 snmp.uptimeOid=
- для IndexSnmpDeviceManager (в дополнение к настройкам из SnmpDeviceManager):
#включает работу менеджера на этом устройстве(по умолчанию включено) snmp.enable=1 #oid для получения списка интерфейсов устройства snmp.ifNameOid=1.3.6.1.2.1.2.2.1.2 #regexp для фильтрации интерфейсов. Например, если нам в биллинге нужны только интерфейсы вида GigabitEthernet0/0/2.1122, #то указываем snmp.ifNameRegexpFilter=GigabitEthernet0/0/2\.(\d+) #Группировку тут надо обязательно указывать, подразумевается что в ней идет номер индекса, мы его обновляем на интерфейсе. snmp.ifNameRegexpFilter=(.*) #id категории ресурсов, которая будет указана для создаваемых интерфейсов snmp.ipCategory=0 #если 1, то меняем индексы не с текущего момента, а со времени старта устройства, если это произошло сегодня. #Предполагаем, что ifIndex могут меняться только при рестарте устройства. snmp.substractUptime=1
Код
- IndexSnmpDeviceManager
package ru.dsi.bgbilling.modules.inet.dyn.device.snmp; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.LongStream; import org.apache.log4j.Logger; import bitel.billing.common.TimeUtils; import ru.bitel.bgbilling.kernel.container.managed.ServerContext; import ru.bitel.bgbilling.modules.inet.access.manage.event.InetDeviceManageEvent; import ru.bitel.bgbilling.modules.inet.api.common.bean.InetDevice; import ru.bitel.bgbilling.modules.inet.api.common.bean.InetDeviceType; import ru.bitel.bgbilling.modules.inet.dyn.device.snmp.SnmpDeviceManager; import ru.bitel.bgbilling.server.util.Setup; import ru.bitel.common.ParameterMap; import ru.bitel.common.Preferences; import ru.bitel.common.Utils; import ru.bitel.common.worker.ThreadContext; import ru.bitel.oss.systems.inventory.resource.common.DeviceInterfaceService; import ru.bitel.oss.systems.inventory.resource.common.bean.Device; import ru.bitel.oss.systems.inventory.resource.common.bean.DeviceInterface; import ru.bitel.oss.systems.inventory.resource.common.bean.DeviceInterfaceIndex; import ru.bitel.oss.systems.inventory.resource.common.bean.DeviceType; import ru.bitel.oss.systems.inventory.resource.server.DeviceManager; import ru.bitel.oss.systems.inventory.resource.server.DeviceManagerMethod; import uk.co.westhawk.snmp.stack.AsnObjectId; /** * @author cromeshnic@gmail.com * patched by stark * * Класс для работы с интерфейсами их индексами по SNMP * <pre> * команды: * ifsync - синхронизация интерфейсов и их ifIndex на роутере и в биллинге: * 1. Получаем по SNMP список соответствий: ifIndex->ifName интерфейсов на устройстве * 2. Для каждого полученного интерфейса проверяем, * есть ли в биллинге на устройсве интерфейс с таким именем? * 2.1. Если нет, и он проходит через фильтр snmp.ifNameRegexpFilter - создаём с правильным ifIndex и категорией ip = snmp.ipCategory и номер интерфейса находим * как group(1) у заданного snmp.ifNameRegexpFilter. * 2.2. Если есть, то совпадает ли ifIndex? * 2.2.1. Если да, то ничего не делаем, всё ок. * 2.2.2. Если нет, то меняем ifIndex * 3. Оставшиеся необработанными интерфейсы в BG: если их нет на устройстве, то * помечаем как отключенный и устанавливаем ifIndex=0 * параметры: * snmp.ifNameOid=1.3.6.1.2.1.2.2.1.1 * - oid для получения списка интерфейсов устройства * snmp.ifNameRegexpFilter=.* * - regexp для фильтрации интерфейсов. Например, если нам в биллинге нужны только интерфейсы вида GigabitEthernet0/0/2.1122, * то указываем snmp.ifNameRegexpFilter=GigabitEthernet0/0/2\.(\d+) * Группировку тут надо обязательно указывать, подразумевается что в ней идет номер индекса, мы его обновляем на интерфейсе. * snmp.ipCategory=0 * - id категории ресурсов, которая будет указана для создаваемых интерфейсов * snmp.substractUptime=1 * - если 1, то меняем индексы не с текущего момента, а со времени старта устройства, если это произошло сегодня. * Предполагаем, что ifIndex могут меняться только при рестарте устройства. * snmp.enable=1 * - включает работу менеджера на этом устройстве * </pre> * @see SnmpDeviceManager */ public class IndexSnmpDeviceManager extends SnmpDeviceManager implements DeviceManager { protected static final Logger logger = Logger.getLogger( SnmpDeviceManager.class ); //TODO - резервирование/закрытие датой расформированных интерфейсов : http://forum.bitel.ru/viewtopic.php?p=71924#p71924 private long[] ifNameOid; /** * Фильтр имён интерфейсов * Если имя не соответствует регэкспу, то не заводим его в биллинге */ private Pattern ifaceNamePattern; private int ipCategory; private boolean subtractUptimeForIfIndex; protected int mid; protected int deviceId; protected boolean enabled;//Работаем? @Override public Object init(Setup setup, int moduleId, Device<?, ?> device, DeviceType deviceType, ParameterMap deviceConfig) { super.init(setup, moduleId, device, deviceType, deviceConfig); this.mid = moduleId; this.deviceId = device.getId(); //1.3.6.1.2.1.2.2.1.2.56 = STRING: "GigabitEthernet0/0/2.1100" this.ifNameOid = new AsnObjectId( ( deviceConfig.get( "snmp.ifNameOid", "1.3.6.1.2.1.31.1.1.1.1" ) ) ).getOid(); this.ifaceNamePattern = Pattern.compile( deviceConfig.get("snmp.ifNameRegexpFilter", ".*") ); //Категория IP, выставляемая новым интерфейсам this.ipCategory = deviceConfig.getInt("snmp.ipCategory", 0); //Вычитать ли из текущего времени uptime устройства при проставлении даты/времени для ifIndex? //(не дальше, чем за границу суток) this.subtractUptimeForIfIndex = deviceConfig.getBoolean("snmp.substractUptime", true); this.enabled = deviceConfig.getBoolean("snmp.enable", true); return null; } @DeviceManagerMethod(title = "Cинхронизировать индексы" ) public String ifsync() throws Exception { if(!this.enabled){ return "SNMP manager disabled on device"; } StringBuilder result = new StringBuilder(); ServerContext ctx = ThreadContext.get(); DeviceInterfaceService devicePortService = ctx.getService(DeviceInterfaceService.class, this.mid); //Интерфейсы устройства в биллинге Map<String, DeviceInterface> ifaces = new HashMap<String, DeviceInterface>(); int maxPortNumber = 0; for (DeviceInterface deviceInterface : devicePortService.devicePortList(this.deviceId, false)) { //Не дублируются ли интерфейсы по именам? if(ifaces.get(deviceInterface.getTitle())!=null){ logger.error("duplicate iface names on device " + this.deviceId + ": " + deviceInterface.getTitle() + " (ports:" + deviceInterface.getPort() + "," + ifaces.get(deviceInterface.getTitle()).getPort() + ")"); result.append("! duplicate bg ifaces: ") .append(deviceInterface.getTitle()) .append(" (ports:") .append(deviceInterface.getPort()) .append(",") .append(ifaces.get(deviceInterface.getTitle()).getPort()) .append(")\n"); } ifaces.put(deviceInterface.getTitle(), deviceInterface); //Определяем максимальный номер порта if(deviceInterface.getPort()>maxPortNumber){ maxPortNumber=deviceInterface.getPort(); } } //Интерфейсы, полученные с устройства final Map<String, Integer> ifaceTitleToIfIndexMap = new HashMap<String, Integer>(); logger.info( "ifNameOid =" + LongStream.of( ifNameOid ).boxed().collect( Collectors.toList()) ); this.snmpClient.walk(this.ifNameOid, String.class, ( o, v ) -> { //System.out.println( new AsnObjectId( o ) + " = " + v ); //logger.info ( "v=" + v ); ifaceTitleToIfIndexMap.put( v , (int)o[ o.length - 1 ] ); } ); Calendar now = Calendar.getInstance(); //Получаем uptime (в TimeTicks - 0.01 секунды) Long uptime = (long) this.uptime(); Date start; Calendar cal; if(uptime==null || !this.subtractUptimeForIfIndex){//нет данных оп uptime, либо мы их не должны использовать start = (Date)now.getTime().clone(); }else{ cal = (Calendar)now.clone(); cal.add(Calendar.SECOND, (int)(uptime/-1000) ); if(TimeUtils.daysDelta(now, cal)!=0){//Девайс был рестартован не сегодня start = (Date)now.getTime().clone(); //Меняем индексы с текущего момента logger.info("device "+deviceId+" started "+TimeUtils.formatDate(cal)); }else{ start = (Date)cal.getTime().clone(); } } cal = Calendar.getInstance(); cal.setTime(start); cal.add(Calendar.SECOND, -1); Date nowMinusSecond = cal.getTime(); DeviceInterface iface; DeviceInterfaceIndex deviceInterfaceIndex; Integer ifaceIndex; List<DeviceInterfaceIndex> indexList; int count = 0; //1. Перебираем все полученные интерфейсы for (Map.Entry<String, Integer> nameToIndex : ifaceTitleToIfIndexMap.entrySet()) { //1.1 Ищем интерфейс в биллинге по его имени String ifName = nameToIndex.getKey(); Integer ifIndexReal = nameToIndex.getValue(); iface = ifaces.get( ifName ); /*count ++; if ( count > 1000 ) { break; }*/ Matcher matcher = ifaceNamePattern.matcher( ifName ); boolean find = matcher.find(); if( iface == null ) { //1.1.1 Не нашли - заводим в // Удовлетворяет ли интерфейс фильтру? Если нет - не заводим его if( !find ) { logger.info("skip snmp iface "+ifName+" on deviceId="+this.deviceId+" due to regexp "+ this.ifaceNamePattern.pattern() ); continue; } logger.info ( "ifName=" + ifName ); int portNumber = Utils.parseInt( matcher.group( 1 ), 0 ); if ( portNumber == 0 ) { logger.info("portNumber is 0 for snmp iface "+ifName+" on deviceId="+this.deviceId+" due to regexp "+ this.ifaceNamePattern.pattern() ); continue; } iface = new DeviceInterface(); iface.setPort( portNumber );//increment port number (port number != ifindex here !!!) iface.setTitle(ifName); iface.setComment(""); iface.setStatus(1);//1 - Доступен, 0 - Зарезервирован iface.setDeviceId(this.deviceId); iface.setIpCategoryId(this.ipCategory); deviceInterfaceIndex = new DeviceInterfaceIndex(); deviceInterfaceIndex.setId(0);//id<=0 => new deviceInterfaceIndex.setPort( portNumber );//вроде бы не обязательно, но пусть будет deviceInterfaceIndex.setDeviceId(this.deviceId);//вроде бы не обязательно, но пусть будет deviceInterfaceIndex.setIndex( ifIndexReal ); deviceInterfaceIndex.setTimeFrom(now.getTime()); deviceInterfaceIndex.setTimeTo(null); iface.setIndexList( Collections.singletonList(deviceInterfaceIndex) ); devicePortService.devicePortUpdate( iface, false ); logger.info("adding new iface "+iface.getTitle()+" with ifIndex="+ifIndexReal+" from "+ TimeUtils.formatFullDate(now.getTime())); result.append("+ ") .append(iface.getTitle()) .append(" (") .append(ifIndexReal) .append(") from ") .append(TimeUtils.formatFullDate(now.getTime())) .append("\n"); }else {//1.1.2 Нашли такой интерфейс, проверяем, изменился ли у него ifindex //Если интерфейс при этом не удовлетворяет фильтру, то пишем об этом в лог if( !find ) { logger.warn("existing bg iface "+ifName+" on deviceId="+this.deviceId+" doesn't match regexp "+this.ifaceNamePattern.pattern() ); result.append("? ") .append(iface.getTitle()) .append(" - нестандартный интерфейс в BG\n"); } indexList = iface.getIndexList(); deviceInterfaceIndex = this.getCurrentIndex(now.getTime(), indexList); if( deviceInterfaceIndex!=null ) { ifaceIndex = deviceInterfaceIndex.getIndex(); }else //По-умолчанию, индекс интерфейса - это его порт { ifaceIndex = iface.getPort(); } if( !ifaceIndex.equals(ifIndexReal) ){//Индекс поменялся! Нужно обновить //Закрываем датой предыдущий ifIndex, если есть if( deviceInterfaceIndex!=null ) { deviceInterfaceIndex.setTimeTo(nowMinusSecond); } if(indexList==null) { indexList = new ArrayList<DeviceInterfaceIndex>(); iface.setIndexList(indexList); } deviceInterfaceIndex = new DeviceInterfaceIndex(); deviceInterfaceIndex.setId(0);//id<=0 => new deviceInterfaceIndex.setPort(iface.getPort());//вроде бы не обязательно, но пусть будет deviceInterfaceIndex.setDeviceId(iface.getDeviceId());//вроде бы не обязательно, но пусть будет deviceInterfaceIndex.setIndex( ifIndexReal ); deviceInterfaceIndex.setTimeFrom(start); deviceInterfaceIndex.setTimeTo(null); indexList.add(deviceInterfaceIndex); devicePortService.devicePortUpdate( iface, false ); logger.info("updating iface "+iface.getTitle()+": ifIndex="+ifIndexReal+" (was "+ifaceIndex+") from "+ TimeUtils.formatFullDate(start)); result.append("* ") .append(iface.getTitle()) .append(" (") .append(ifaceIndex) .append("->") .append(ifIndexReal) .append(") from ") .append(TimeUtils.formatFullDate(start)) .append("\n"); } } } logger.info ( "other" ); //2. Теперь перебираем интерфейсы в BG, чтобы обработать оставшиеся for(Map.Entry<String, DeviceInterface> ifaceEntry : ifaces.entrySet()) { iface = ifaceEntry.getValue();//iface в BG //2.1 Ищем индекс интерфейса в полученном по SNMP списке ifaceIndex = ifaceTitleToIfIndexMap.get(ifaceEntry.getKey()); if(ifaceIndex==null){//Есть в BG, но не получен с устройства //зануляем ifIndex, чтобы он гарантированно ни с кем не пересекался indexList = iface.getIndexList(); deviceInterfaceIndex = getCurrentIndex(now.getTime(), indexList); if(deviceInterfaceIndex==null){ ifaceIndex = iface.getPort(); }else{ ifaceIndex = deviceInterfaceIndex.getIndex(); } if(ifaceIndex!=0){ //Зануляем! if(deviceInterfaceIndex!=null){ deviceInterfaceIndex.setTimeTo(nowMinusSecond); } if(indexList==null){ indexList = new ArrayList<DeviceInterfaceIndex>(); iface.setIndexList(indexList); } deviceInterfaceIndex = new DeviceInterfaceIndex(); deviceInterfaceIndex.setId(0);//id<=0 => new deviceInterfaceIndex.setPort(iface.getPort());//вроде бы не обязательно, но пусть будет deviceInterfaceIndex.setDeviceId(iface.getDeviceId());//вроде бы не обязательно, но пусть будет deviceInterfaceIndex.setIndex(0); deviceInterfaceIndex.setTimeFrom(start); deviceInterfaceIndex.setTimeTo(null); indexList.add(deviceInterfaceIndex); iface.setComment("deleted"); devicePortService.devicePortUpdate( iface, false ); logger.info("updating iface "+iface.getTitle()+": ifIndex=0 (was "+ifaceIndex+") from "+ TimeUtils.formatFullDate(start)); result.append("- ") .append(iface.getTitle()) .append(" (") .append(ifaceIndex) .append("->0) from ") .append(TimeUtils.formatFullDate(start)) .append("\n"); } } } if(result.length()==0){ return "no changes"; } return result.toString(); } protected DeviceInterfaceIndex getCurrentIndex(Date now, List<DeviceInterfaceIndex> deviceInterfaceIndexList){ if(deviceInterfaceIndexList!=null){ for (DeviceInterfaceIndex deviceInterfaceIndex : deviceInterfaceIndexList) { if(TimeUtils.timeInRange(now, deviceInterfaceIndex.getTimeFrom(), deviceInterfaceIndex.getTimeTo())){ return deviceInterfaceIndex; } } } return null; } public static void main( String argv[] ) { try { logger.info( "hello" ); IndexSnmpDeviceManager man = new IndexSnmpDeviceManager(); int moduleId = 1; InetDevice device = new InetDevice(); Preferences deviceConfig = new Preferences(); /* //cisco int deviceId = 2; device.setId( deviceId ); device.setHost( "10.10.1.42" ); deviceConfig.set( "snmp.ifNameOid", "1.3.6.1.2.1.2.2.1.2" ); deviceConfig.set( "snmp.ifNameRegexpFilter", "GigabitEthernet0/0/2\\.(\\d+)" ); deviceConfig.set( "snmp.version", "2" ); deviceConfig.set( "snmp.host", "10.10.1.42" ); */ //juniper int deviceId = 3; device.setId( deviceId ); device.setHost( "10.10.1.100" ); deviceConfig.set( "snmp.ifNameOid", "1.3.6.1.2.1.2.2.1.2" ); deviceConfig.set( "snmp.ifNameRegexpFilter", "ae0\\.(\\d+)" ); deviceConfig.set( "snmp.version", "2" ); //deviceConfig.set( "snmp.host", "10.10.1.42" ); man.init( Setup.getSetup(), moduleId, device, new InetDeviceType(), deviceConfig ); man.connect(); man.ifsync(); //System.out.println ( "hello" ); } catch( Exception e ) { // TODO Auto-generated catch block e.printStackTrace(); } } /** * Метод вызывается при обнаружении перезагрузки устройства. * @param e * @return * @throws Exception */ public Object onReboot( InetDeviceManageEvent e ) throws Exception { logger.info( "onReboot" ); ifsync(); return null; } }
P.S.
- Проверка доступности устройства по SNMP:
# snmpwalk -c public -v 2c 10.10.1.42 1.3.6.1.2.1.2.2.1.2 iso.3.6.1.2.1.2.2.1.2.1 = STRING: "GigabitEthernet0/0/0" iso.3.6.1.2.1.2.2.1.2.2 = STRING: "GigabitEthernet0/0/1" iso.3.6.1.2.1.2.2.1.2.3 = STRING: "GigabitEthernet0/0/2" iso.3.6.1.2.1.2.2.1.2.4 = STRING: "GigabitEthernet0/0/3" iso.3.6.1.2.1.2.2.1.2.5 = STRING: "TenGigabitEthernet0/2/0" iso.3.6.1.2.1.2.2.1.2.6 = STRING: "GigabitEthernet0" iso.3.6.1.2.1.2.2.1.2.7 = STRING: "VoIP-Null0" iso.3.6.1.2.1.2.2.1.2.8 = STRING: "Null0" iso.3.6.1.2.1.2.2.1.2.9 = STRING: "Loopback0" iso.3.6.1.2.1.2.2.1.2.10 = STRING: "Loopback1" iso.3.6.1.2.1.2.2.1.2.11 = STRING: "Port-channel1" iso.3.6.1.2.1.2.2.1.2.12 = STRING: "Port-channel1.2" iso.3.6.1.2.1.2.2.1.2.13 = STRING: "Port-channel1.6" iso.3.6.1.2.1.2.2.1.2.14 = STRING: "Port-channel1.7" iso.3.6.1.2.1.2.2.1.2.15 = STRING: "Port-channel1.8" iso.3.6.1.2.1.2.2.1.2.16 = STRING: "Port-channel1.9" iso.3.6.1.2.1.2.2.1.2.17 = STRING: "Port-channel1.15" iso.3.6.1.2.1.2.2.1.2.18 = STRING: "Port-channel1.16" iso.3.6.1.2.1.2.2.1.2.19 = STRING: "Port-channel1.17" iso.3.6.1.2.1.2.2.1.2.20 = STRING: "Port-channel1.18" iso.3.6.1.2.1.2.2.1.2.21 = STRING: "Port-channel1.19" iso.3.6.1.2.1.2.2.1.2.22 = STRING: "Port-channel1.20" iso.3.6.1.2.1.2.2.1.2.23 = STRING: "Port-channel1.21" ...
--Cromeshnic 08:16, 23 декабря 2013 (UTC)
--stark 13:23, 10 октября 2017 (UTC)