Протокол OSSI и Avaya Communication Manager

в 9:19, , рубрики: Avaya, python, Разработка систем связи

В данной статье я попытаюсь подробно рассказать об использовании протокола OSSI для взаимодействия с АТС Avaya Communication Manager. В открытом доступе очень мало информации по данной теме, а уж в русском сегменте все ограничивается поверхностной статьей на Хабре за 2013 год. Необходимо данную несправедливость устранять.

Теория

Протокол OSSI (Operations Support Systems Interface) используется в продуктах Avaya для взаимодействия различных дополнительных модулей с основным модулем АТС, в данном случае Communication Manager. Получить к нему доступ можно простым выбором корректного типа терминала во время подключения к серверу.

Основного внимания заслуживают два типа терминала: ossi и ossimt. Первый тип используется для непосредственной работы с CM, получением информации и внесением изменений в настройку АТС. Второй тип используется для сопоставления идентификатора поля, использующегося в первом типе, с фактическим его назначением. Это нужно, т.к. в разных версиях CM используются разные идентификаторы и заранее узнать что к чему нельзя.

Стандартный вывод терминала ossi:

image

Для данного типа характерно то, что набранные в терминале символы не отображаются и не удаляются.

Стандартный вывод терминала ossimt:

image

Взаимодействие по протоколу производится путем передачи строк определенного типа. Тип строки определяется указателем, подставляемым в начало строки. Список этих указателей:

  • c (command) — указатель строки содержащей исполняемую команду;
  • f (field) — указатель строки, содержащей идентификаторы полей;
  • d (data) — указатель строки, содержащей данные, в соответствии с полем;
  • e (error) — указатель строки, содержащей сообщение об ошибке;
  • t (terminate) — указатель окончания ввода/вывода информации

Ввод и вывод информации в терминале производится в следующем формате:

с<команда>[RETURN]
f<поле 1>[TAB]<поле 2>[TAB]<поле 3>[RETURN]
d<данные 1>[TAB]<данные 2>[TAB]<данные 3>[RETURN]
t[RETURN]

Т.е. каждая строка завершается символом перевода строки (нажатие Enter) а внутри строк типа field и data элементы разделяются символом табуляции (нажатие Tab).

Рассмотрим подробнее первые 3 типа строк:

c (command)

В данной строке должна содержаться команда для выполнения. В общем случае, команды идентичны командам, используемым при стандартном администрировании в терминале. Вызвать весь список доступных команд можно набрав:

chelp
t

f (field)

В данной строке перечисляются идентификаторы полей для введеной команды. Разделение между полями осуществляется символом табуляции. Данные идентификаторы записаны в HEX-формате и чтобы понять какое поле чему соответствует, необходимо обращаться к ossimt. Строк данного типа может быть несколько, в зависимости от количества информации передаваемой командой.

d (data)

В данной строке перечисляются данные, относящиеся соответствующим полям, указанным в строке предыдущего типа. Данные также разделяются табуляцией. Следует учитывать, что количество строк данного типа должно быть кратно количеству строк типа field.

Строки типа field и data в основном используются при выводе информации после ввода команды, но кроме этого их можно использовать при внесении изменений в систему (напр. change station XXXX), либо выводе команды, принимающей дополнительные параметры (напр. display alarms). Достаточно только после строки command добавить эти строки, указав в них соответствующие поля и данные, которые требуется изменить.

К примеру, набрав в терминале:

сcha st 1000
f8003ff00
dI.C. Wiener
t

Мы изменим для внутреннего номера 1000 отображаемое имя на I.C. Wiener.

Строго говоря, терминалов типа ossi несколько. Мне известно по крайней мере 3: ossi, ossi3, ossis. Особой разницы между ними нет. Из видимых особенностей только то, что ossis при выводе результата команды не возвращает строку с самой командой.

Практика

Все описанное, конечно, хорошо. Но как можно использовать данный протокол во благо? Ну, например, можно сделать некое подобие мониторинга. Рассмотрим для примера мониторинг медиа-шлюзов.

В этом нам поможет обычная команда status media-gateways и Python.

Шаг 1: подключиться и получить информацию.

import telnetlib

tn = telnetlib.Telnet('127.0.0.1', '5023') # Подключаемся по телнету на стандартный порт 5023
tn.read_until('login'.encode()) # Ждем прихода строки с вводом логина
tn.write('usernamen'.encode())
tn.read_until('Password'.encode()) # Ждем прихода строки с вводом пароля
tn.write('passwordn'.encode())
tn.read_until('Pin'.encode()) # Ждем прихода строки с вводом пин-кода
tn.write('pinn'.encode())
tn.read_until('Terminal'.encode()) # Ждем прихода строки с вводом типа терминала
tn.write('ossin'.encode())
tn.read_until('tn'.encode()) # Ждем прихода строки с идентификатором окончания вводавывода

# Как только идентификатор пришел, можно слать команду
tn.write('csta media-gn'.encode()) # Строка типа command
tn.write('tn'.encode()) # Строка типа terminate
output = tn.read_until('tn'.encode()) # Записываем всю информацию, пока не придет terminate.
output = output.decode('utf-8') # Конвертируем пришедшие байты в строку

Теперь у нас есть информация в виде строки, с которой мы можем творить всякое.

output

'ncsta media-gnf6c02ff00t6c08ff00t6c0aff00t6c0cff00t6c03ff00nf6c09ff00t6c0bff00t6c04ff00t6c0fff01t6c0fff02nf6c0fff03t6c0fff04t6c0fff05t6c0fff06t6c0fff07nf6c0fff08t6c10ff09t6c10ff0at6c10ff0bt6c10ff0cnf6c10ff0dt6c10ff0et6c10ff0ft6c10ff10t6c11ff11nf6c11ff12t6c11ff13t6c11ff14t6c11ff15t6c11ff16nf6c11ff17t6c11ff18t6c12ff19t6c12ff1at6c12ff1bnf6c12ff1ct6c12ff1dt6c12ff1et6c12ff1ft6c12ff20nf6c13ff21t6c13ff22t6c13ff23t6c13ff24t6c13ff25nf6c13ff26t6c13ff27t6c13ff28nd0t0t0t01t0nd0t26t40t2  0| 0| 3|upt5  0| 0| 2|upnd8  0| 0| 3|upt9  0| 0| 5|upt10  0| 0| 3|upt11  0| 0| 3|upt12  0| 0| 1|upnd13  0| 0| 0|upt14  0| 0| 1|upt15  0| 0| 0|upt16  0| 0| 3|upt180| 0| 0|upnd19  0| 0| 0|upt21  0| 0| 0|upt22  0| 0| 0|upt23  0| 0| 4|upt24  0| 0| 1|upnd25  0| 0| 1|upt26  0| 0| 0|upt27  0| 0| 0|upt28  0| 0| 1|upt29  0| 0| 1|upnd30  0| 0| 1|upt33  0| 0| 1|upt34  0| 0| 5|upt37  0| 0| 1|uptndttttndttttndttntn'

Шаг 2: парсим полученную информацию.

Имеющуюся информацию нам нужно разделить в зависимости от обозначенных выше типов строк. Т.к. количество строк одного типа может быть несколько, нам необходимо учесть их последовательность.

fields = {} # словарь для хранения полей
data = {} # словарь для хранения данных
lines = output.split('n') # разделяем построчно
for line in lines: # проходимся по строкам и заносим в соответствующий словарь
    if line.startswith('d'): # строка типа data
        data.update({
            len(data): line[1:] # Ключом словаря делаем номер строки
	})
    elif line.startswith('f'): # строка типа field
        fields.update({
	    len(fields): line[1:]
	})
    elif line.startswith('t'): # строка terminate
	break
    else: # остальные типа строк нам не интересны
	pass
parse = {
    'fields': fields,
    'data': data,
}

В результате этих действий получим такую переменную parse:

parse

{
    'fields': {
        0: '6c02ff00t6c08ff00t6c0aff00t6c0cff00t6c03ff00', 
        1: '6c09ff00t6c0bff00t6c04ff00t6c0fff01t6c0fff02', 
        2: '6c0fff03t6c0fff04t6c0fff05t6c0fff06t6c0fff07', 
        3: '6c0fff08t6c10ff09t6c10ff0at6c10ff0bt6c10ff0c', 
        4: '6c10ff0dt6c10ff0et6c10ff0ft6c10ff10t6c11ff11', 
        5: '6c11ff12t6c11ff13t6c11ff14t6c11ff15t6c11ff16', 
        6: '6c11ff17t6c11ff18t6c12ff19t6c12ff1at6c12ff1b', 
        7: '6c12ff1ct6c12ff1dt6c12ff1et6c12ff1ft6c12ff20', 
        8: '6c13ff21t6c13ff22t6c13ff23t6c13ff24t6c13ff25', 
        9: '6c13ff26t6c13ff27t6c13ff28'
    },
    'data': {
        0: '0t0t0t01t0', 
        1: '0t26t40t2  0| 0| 3|upt5  0| 0| 2|up', 
        2: '8  0| 0| 3|upt9  0|0| 5|upt10  0| 0| 3|upt11  0| 0| 3|upt12  0| 0| 1|up', 
        3: '13  0| 0| 0|upt14  0| 0| 1|upt15  0| 0| 0|upt16  0| 0| 3|upt18  0| 0| 0|up', 
        4: '19  0| 0| 0|upt21  0| 0| 0|upt22  0| 0| 0|upt23  0| 0| 4|upt24  0| 0| 1|up', 
        5: '25  0| 0| 1|upt26  0| 0| 0|upt27  0| 0| 0|upt28  0| 0| 1|upt29  0| 0|1|up', 
        6: '30  0| 0| 1|upt33  0| 0| 1|upt34  0| 0| 5|upt37  0| 0| 1|upt', 
        7: 'tttt', 
        8: 'tttt', 
        9: 'tt'
    }
}

Шаг 3: сопоставляем поля и данные.

Наконец, нам нужно сопоставить идентификатор поля конкретному значению из данных.

result = {} # Сюда будем сохранять результат
for i in range(len(parse['fields'])): # считаем количество строк и проходимся по ним
    fids = parse['fields'][i].split('t') # разделяем строку на элементы
    data = parse['data'][i].split('t')
    for i in range(len(fids)):
        result.update({
            fids[i]: data[i] # сопоставляем поле соответствующим данным
        })

В итоге получаем словарь, в котором в качестве ключа стоит идентификатор поля, а в качестве значения — данные, соответствующие этому полю.

result

{
    '6c10ff0e': '21  0| 0| 0|up', 
    '6c11ff16': '29  0| 0| 1|up', 
    '6c13ff22': '', 
    '6c0fff01': '2  0| 0| 3|up', 
    '6c10ff0c': '18  0| 0| 0|up', 
    '6c11ff15': '28  0| 0| 1|up', 
    '6c10ff0d': '19  0| 0| 0|up', 
    '6c12ff20': '', 
    '6c10ff09': '14  0| 0| 1|up', 
    '6c0fff03': '8  0| 0| 3|up', 
    '6c10ff0f': '22  0| 0| 0|up', 
    '6c11ff14': '27  0| 0| 0|up', 
    '6c04ff00': '40', 
    '6c13ff26': '', 
    '6c10ff0b': '16  0| 0| 3|up', 
    '6c10ff0a': '15  0| 0| 0|up', 
    '6c0fff08': '13  0| 0| 0|up', 
    '6c13ff25': '', 
    '6c0cff00': '01', 
    '6c12ff1f': '', 
    '6c11ff18': '33  0| 0| 1|up', 
    '6c13ff27': '', 
    '6c11ff12': '25  0| 0| 1|up', 
    '6c0fff06': '11  0| 0| 3|up', 
    '6c0bff00': '26', 
    '6c03ff00': '0', 
    '6c11ff11': '24  0| 0| 1|up', 
    '6c0aff00': '0', 
    '6c10ff10': '23  0| 0| 4|up', 
    '6c13ff28': '', 
    '6c0fff07': '12  0| 0| 1|up', 
    '6c12ff1b': '', 
    '6c02ff00': '0', 
    '6c0fff05': '10  0| 0| 3|up', 
    '6c13ff23': '', 
    '6c12ff1e': '', 
    '6c08ff00': '0', 
    '6c12ff1d': '', 
    '6c12ff1a': '37  0| 0| 1|up', 
    '6c11ff13': '26  0| 0| 0|up', 
    '6c12ff1c': '', '6c13ff24': '', 
    '6c13ff21': '', 
    '6c0fff02': '5  0| 0| 2|up', 
    '6c09ff00': '0', 
    '6c12ff19': '34  0| 0| 5|up', 
    '6c0fff04': '9  0| 0| 5|up', 
    '6c11ff17': '30  0| 0| 1|up'
}

Финал: PROFIT

Итак, у нас есть словарь, в котором есть вся информация по статусу медиа-шлюзов. Нам остается только выяснить какой идентификатор что обозначает. Делается это, как мы помним, с помощью ossimt. К примеру, полям major alarms, minor alarms, warnings соответствуют идентификаторы 6c02ff00, 6c03ff00, 6c04ff00. Ищем их в нашем словаре и понимаем что у нас нет ни одной серьезной ошибки и «всего» 40 предупреждений. Жить можно.

Немного поработав с полученными данными, можно получить вполне годный мониторинг медиа-шлюзов доступный прямо из терминала. К примеру, мы можем получить такую картину:

image

А если не повезет, то и такую:

image

Для удобства работы, разработал небольшой класс для работы с данным протоколом, посмотреть можно тут.

Автор: iskhomutov

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js