Управление АТС — Дьявол в деталях

в 9:17, , рубрики: атс, разработка, телефония, метки:

Примерно год назад мне пришлось столкнуться с новой для себя задачей — автоматизацией управления АТС. На первый взгляд, в ней нет ничего сложного: необходимо соединиться с управляющим портом АТС по TCP или Serial-интерфейсу, передать команду и проанализировать ответ. Как выяснилось в процессе работы, простота эта оказалась обманчивой.

В своей статье я хочу рассказать о сложностях, с которыми мне пришлось столкнуться в процессе работы. Вряд ли эта статья будет интересна широкому кругу читателей, но, возможно, специалисты, занимающиеся телефонией, найдут в ней что-то для себя полезное. В качестве иллюстративного материала я буду использовать примеры команд АТС Alcatel S12 и M200, поддерживаемых проектом в настоящее время.

1. Глоссарий

Для начала, следует определиться с терминологией.

1.1 Задания

Задание представляет собой набор действий, выполняемый атомарно. С точки зрения подсистемы управления оборудованием, задание состоит из набора услуг. Каждая услуга управляет доступом абонента к тому или иному сервису (например, можно выделить услуги доступа к междугородней и международной связи).

При выполнении задания, отдельные услуги могут включаться или отключаться. Кроме того, для ряда услуг, могут устанавливаться значения конфигурационных параметров (например таких, как время срабатывания будильника или значение PIN-кода). Булевские значения, определяющие состояние подключения или отключения той или иной услуги, также рассматриваются в качестве параметров.

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

1.2 Спецификации

Спецификации управляют работой специализированного ORM, предназначенного для интеграции приложения с подсистемой Order Management, управляющей выполнением заданий. Я не буду углубляться в эту тему, поскольку она не связана с вопросами специфики взаимодействия с АТС. Для понимания последующего изложения, достаточно знать, что этот слой предоставляет доступ на чтение и запись к именованным переменным, содержащим значения загруженные из БД.

Помимо доступа к скалярным значениям, предоставляется доступ к коллекциям кортежей. Так, например, открыв коллекцию, представляемую именем 'tk' (Тех. Карта), можно получить доступ к записям, содержащим данные тех.карт, таким как 'tk.phone' — телефон абонента или 'tk.ats_type' — тип АТС. В свою очередь, эти записи могут содержать ссылки на другие коллекции. Вложенность коллекций не ограничена.

Из всего сказанного выше, возможно не очень понятно, почему используется термин спецификация? Дело в том, что при разработке этого слоя, активно использовались такие понятия SID как Specification и Policy, что, с моей точки зрения, весьма упростило разработку. В общем, я настоятельно рекомендую ознакомиться с материалами TM Forum-а всем, у кого есть такая возможность.

1.3 Скрипты

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

Фрагмент скрипта первой версии

...
[002082]            platform:M-200; if (tk.attach.service = 'SET_HOLD') { 
[020820] <            text:settune %s enb_flash=on; var_list:tk.phone; 
[001041] >            is_error:1; regexp:(.+); var_list:error; 
[020821]              is_rollback:1; {
[001020] <              device_id:tk.ats_id; 
[020822] <              text:settune %s enb_flash=off; var_list:tk.phone; 
[001041] >              is_error:1; regexp:(.+); var_list:error; 
                      }
                    }
[003082]            platform:S-12; if (tk.attach.service = 'SET_HOLD') { 
[030820] <            text:MODIFY-SUBSCR:DN=K'%s,RECALL=ADD&ECTRF.; var_list:tk.phone; 
[001041] >            is_error:1; regexp:(.+); var_list:error; 
[030821]              is_rollback:1; {
[001020] <              device_id:tk.ats_id; 
[030822] <              text:MODIFY-SUBSCR:DN=K'%s,RECALL=REMOVE&ECTRF.; var_list:tk.phone; 
[001041] >              is_error:1; regexp:(.+); var_list:error; 
                      }
                    }
...

По приведенному выше фрагменту, можно видеть, что анализируя переменную tk.attach.service мы определяем, какая команда должна выполняться. Затем, в зависимости от платформы, путем подстановки в соответствующий шаблон команды номера телефона из переменной tk.phone, формируется команда для выполнения на оборудовании.

Поскольку различных команд было довольно много, скрипт получался большим (более 1000 строк) и малопонятным. В результате, при разработке второй версии, было принято решение отказаться от формирования отдельных команд в скрипте (переведя ее на более низкий уровень), передавая на выполнение вместо готовых команд коды услуг. Это решение весьма благотворно отразилось на размере скрипта. Вот как выглядит скрипт второй версии целиком:

Скрипт второй версии

[002000]    {
[200001]      var_list:retry_cnt = retry_cnt + 1, state = 1; 
[200002]      if (subtype = -1) { 
[200003]        foreach (devices) { 
[200004] <        device_id:devices.ats_id; device_ip:devices.ats_ip; device_password:devices.ats_password; device_port:devices.ats_tcp_port; device_protocol:devices.ats_type; 
[200005] <        device_id:devices.ats_id; 
[200006] <        text:ROLLBACK; 
[200007]          var_list:retry_cnt = 0; 
                }
              }
[200008]      if (subtype = 1) { 
[200009]        foreach (tk) { 
[200010]          if (tk.phone = '') { 
[200011]            var_list:tk.phone = tk.phone_old; 
                  }
[200012] <        device_id:tk.ats_id; device_protocol:tk.ats_type; device_ip:tk.ats_ip; device_port:tk.ats_tcp_port; device_password:tk.ats_password; 
[200013]          var_list:action = 0, is_dou_activated = 0; 
[200014]          target:tk.ats_type; foreach (tk.detach) { 
[200015] <          device_id:tk.ats_id; 
[200016]            var_list:activate_command = 1; 
[200017]            platform:S-12; if (tk.detach.service = 'C_INTRAAREAL' | tk.detach.service = 'C_INTERCITY' | tk.detach.service = 'C_INTERNATIONAL') { 
[200018]              if (is_dou_activated = 1) { 
[200019]                var_list:activate_command = 0; 
                      }
[200020]              var_list:is_dou_activated = 1; 
                    }
[200025]            if (activate_command = 1) { 
[200026] <            text:%s; var_list:tk.detach.service; 
[200027]              var_list:retry_cnt = 0; 
                    }
                  }
[200028]          var_list:action = 1, is_dou_activated = 0, is_cat_activated = 0; 
[200029]          target:tk.ats_type; foreach (tk.attach) { 
[200030] <          device_id:tk.ats_id; 
[200031]            var_list:activate_command = 1; 
[200032]            platform:S-12; if (tk.attach.service = 'C_INTRAAREAL' | tk.attach.service = 'C_INTERCITY' | tk.attach.service = 'C_INTERNATIONAL') { 
[200033]              if (is_dou_activated = 1) { 
[200034]                var_list:activate_command = 0; 
                      }
[200035]              var_list:is_dou_activated = 1; 
                    }
[200021]            if (tk.attach.service = 'CATEG_AON_0' | tk.attach.service = 'CATEG_AON_1' | tk.attach.service = 'CATEG_AON_2' | tk.attach.service = 'CATEG_AON_3' | tk.attach.service = 'CATEG_AON_4' | tk.attach.service = 'CATEG_AON_6' | tk.attach.service = 'CATEG_AON_7' | & tk.attach.service = 'CATEG_AON_8' | tk.attach.service = 'CATEG_AON_9') { 
[200022]              if (is_cat_activated = 1) { 
[200023]                var_list:activate_command = 0; 
                      }
[200024]              var_list:is_cat_activated = 1; 
                    }
[200036]            if (activate_command = 1) { 
[200037] <            text:%s; var_list:tk.attach.service; 
[200038]              var_list:retry_cnt = 0; 
                    }
                  }
                }
              }
            }

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

1.4 Адаптер

Адаптер представляет собой подключаемый модуль (plugin), реализующий логику работы с конкретным типом оборудования. В первой версии, адаптер получал от скрипта готовые команды и его роль сводилась к тому, чтобы передать эти команды на АТС, используя TCP или Serial-подключение и обработать полученный ответ. В текущей (второй) версии, логика формирования набора команд, при подключении или отключении той или иной услуги, перенесена в адаптер. Объяснение причин, по которым это было сделано, и составляет предмет этой статьи.

1.5 Синхронизация

Синхронизацией мы будем называть процесс получения текущих настроек абонентов с оборудования. Проблема рассогласования данных о состоянии настроек абонентов на АТС по отношению к БД является фундаментальной для данного класса задач. Эта проблема актуальна не только для телефонии. В предыдущем активационном проекте, связанном с управлением оборудованием cisco он стоял не менее остро. Можно сказать, что эта статья рассказывает о том как, используя синхронизацию, сделать процесс активации более «умным».

2. Уровень задания

Очевидно, что для управления различными типами АТС используются совершенно непохожие друг на друга системы команд, так, например, для отключения исходящей связи в АТС M-200 используется команда:

settune XXXXXXX cmn_outcome=off

а в Alcatel S12:

MODIFY-SUBSCR:DN=K'XXXXXXX,OCB=REMOVE&TOTALBAR.

Но подобные различия в синтаксисе команд, далеко не единственная сложность. Разберемся в этом вопросе подробнее.

2.1 Отображение услуг на команды

Первое, с чем мы сталкиваемся, это то, что возможности, по активации различных услуг, предоставляемые разными АТС также различны. Разумеется, есть некоторый общий набор услуг, поддерживаемых большинством типов АТС, таких как управление исходящей связью, установка будильника, управление автодозвоном и т.п.

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

Одна услуга задания может отображаться на несколько различных услуг поддерживаемых АТС. Например, M-200 поддерживает следующие команды для включения ПИН-кода:

settune XXXXXXX dvo_pincode=on
settune XXXXXXX dvo_pincodetwo=on

Первая включает использование ПИН-кода на междугороднюю и международную связь, а вторая на всю исходящую связь. В нашем случае, одним из требований Заказчика было то, чтобы обе эти услуги, на уровне задания, отображались в одну общую услугу 'PINCODE'. То какая именно услуга будет выполняться, определяется значением дополнительных переменных. Для Alcatel S12 реализована всего одна команда включения ПИН-кода:

MODIFY-SUBSCR:DN=K'XXXXXXX,PASSWORD=ADD&"YYYY",SUBCTRL=ADD&OCBVAR.

Помимо того, что отсутствует разделение этой услуги на два типа, можно заметить, что эта команда не только включает использование ПИН-кода, но и устанавливает его значение. Разумеется, M-200 позволяет выполнить, например такую команду:

settune XXXXXXX dvo_pincode=on pincode=YYYY

Но, в процессе внедрения первой версии активационного модуля в промышленную эксплуатацию, выяснилось, что эти команды более удобно активировать раздельно. Кроме того, выяснилось, что, для того, чтобы ПИН-код работал, необходимо активировать еще одну команду. Таким образом, для M-200 команда установки ПИН-кода должна привести к активации трех команд в заданном порядке:

settune XXXXXXX enb_pincode=on
settune XXXXXXX dvo_pincode=on
settune XXXXXXX pincode=YYYY

Более того, команда enb_pincode, управляющая возможностью использования ПИН-кода, может выполняться отдельно (добавляя таким образом еще один вариант отображения услуги 'PINCODE'). В этом случае, ПИН-код может быть установлен самим абонентом. Это приводит к довольно не тривиальной проблеме, которую я рассмотрю ниже, в разделе «Проблемы связанные с синхронизацией».

2.2 Ограничение исходящей связи

АТС различных типов могут различаться не только набором поддерживаемых услуг, но и тем, как эти услуги реализованы. Так для M-200 доступом к внутризоновой, междугородней и международной связи можно управлять раздельно:

settune XXXXXXX cmn_82xxx=on
settune XXXXXXX cmn_8xxx=on
settune XXXXXXX cmn_10xxx=on

Для Alcatel S12 аналогичные команды выглядят так:

MODIFY-SUBSCR:DN=K'XXXXXXX,OCB=MODIFY&PERM&5.
MODIFY-SUBSCR:DN=K'XXXXXXX,OCB=MODIFY&PERM&4.
MODIFY-SUBSCR:DN=K'XXXXXXX,OCB=REMOVE.

Можно заметить, что эти команды управляют состоянием всего одной настройки (при включении междугородней связи, ограничения просто снимаются). Это приводит к следующей проблеме:

  • Для M-200 услуги включения внутризоновой, междугородней и международной должны активироваться независимо (и взаимооднозначно отображаются на соответствующие услуги задания)
  • При активации на Alcatel S12 должна активироваться лишь одна из включаемых услуг, наименее ограничивающая доступ

Это означает, что если нам требуется включить на Alcatel S12 внутризоновую и междугороднюю связь, следует активировать только команду включения междугородней связи. Если впоследствии будет активирована команда включения внутризоновой связи, междугородняя связь для абонента будет отключена! Одновременная активация внутризоновой и международной связи, при отключенной междугородней, невозможна (в отличии от M-200).

Это хороший пример функционала, реализованного на уровне скрипта активации. Посмотрим, как это может быть реализовано:

[200028]          var_list:action = 1, is_dou_activated = 0, is_cat_activated = 0; 
[200029]          target:tk.ats_type; foreach (tk.attach) { 
[200031]            var_list:activate_command = 1; 
[200032]            platform:S-12; if (tk.attach.service = 'C_INTRAAREAL' | 
                                       tk.attach.service = 'C_INTERCITY' | 
                                       tk.attach.service = 'C_INTERNATIONAL') { 
[200033]              if (is_dou_activated = 1) { 
[200034]                var_list:activate_command = 0; 
                      }
[200035]              var_list:is_dou_activated = 1; 
                    }
[200036]            if (activate_command = 1) { 
[200037] <            text:%s; var_list:tk.attach.service; 
[200038]              var_list:retry_cnt = 0; 
                    }
                  }

Нас спасают переменные. Активировав первую команду, управляющую исходящей связью, из коллекции tk.attach, мы устанавливаем флаг is_dou_activated, запрещающий активацию последующих команд. Причем, этот код работает только для Alcatel S12.

Единственное что нам требуется, чтобы этот подход работал, это то, чтобы команды приходили в правильном порядке: сначала включение международной связи (если оно есть), затем междугородней и внутризоновой. К счастью, спецификации позволяют сортировать выборки
коллекций по любому критерию. Требуется только добавить в таблицу услуг поля приоритета при выполнении подключения и отключения.

3. Проблемы связанные с синхронизацией

Как я уже говорил выше, возможность получения актуальных настроек с оборудования — одно из главных требований для выполнения хоть сколь нибудь корректной активации. Посмотрим, почему это так?

3.1 «Интеллектуальная» активация

Самое первое дополнительное требование, которое мы получили от Заказчика, при внедрении первой версии продукта, заключалось в том, что не следовало повторно активировать настройки ранее активированные на оборудовании. Это действительно важно по двум причинам:

  1. Активация команд на оборудовании — процесс не быстрый. Среднее время выполнения одной команды на Alcatel S12 ~10 секунд (M-200 работает значительно быстрее (~1 сек), поскольку возвращает гораздо меньший объем данных). Добавим к этому то, что соединение с АТС очень не стабильно и мы можем потерять его в течение этих самых 10 секунд. Если при каждом повторном выполнении задания (а мы должны выполнить все команды составляющие задание, чтобы обеспечить атомарность его выполнения) мы будем повторно активировать все команды с самого начала, то, в самом худшем случае, мы будем активировать эти команды снова и снова и никогда не доберемся до конца задания
  2. В некоторых случаях (на Alcatel S12) повторная активация команды может приводить к ошибке. В этом случае, мы не можем подсмотреть состояние настроек в БД, поскольку, возможно, активация для этого абонента проводится в первый раз. В этом случае, единственное правильное решение — получить состояние настроек непосредственно с оборудования, до выполнения команды активации

Все это означает, что перед тем как активировать какую-либо команду, мы должны получить с АТС текущие значения настроек, влияющих на выполнение этой команды. В том случае если настройки уже находятся в требуемом состоянии, мы можем не выполнять активируемую команду и двигаться дальше (если активируемая команда не может быть выполнена из-за конфликтующих настроек, мы также можем не выполнять команду, а сразу инициировать исключение). Этот подход прекрасно работает для M-200 поскольку:

  1. Все настройки абонента возвращаются в ответ на выполнение всего одной команды gettune
  2. Активация на M-200 выполняется относительно быстро и соединение стабильно. Мы можем себе позволить выполнение одной дополнительной команды в начале каждой активации

Для Alcatel S12 все не так радужно. Для чтения настроек придется выполнять различные команды и все они будут выполняться медленно. Таким образом, без хранения состояния настроек в БД не обойтись. При выполнении активации, мы сможем получить значения настроек из БД и, только в случае если их там нет, сформировать необходимые команды для получения недостающих значений с оборудования.

Разумеется, это не идеальное решение, поскольку при ручном выполнении команд, в обход подсистемы активации, данные в БД окажутся рассинхронизированы, но это меньшее из зол, которое мы можем себе позволить. Кроме того, по завершении активации команды, актуальные значения настроек станут нам известны (поскольку команда успешно их изменила) и мы сможем обновить рассинхронизированные данные в БД.

При использовании M-200 также имеется проблема. В некоторых случаях tune-сервер, используемый для взаимодействия с АТС, о котором я буду рассказывать ниже, начинает работать некорректно. На все команды settune он отвечает Ok, хотя данные на АТС не изменяются! Вызов gettune, в этом случае, приводит к ошибке.

Это означает, что для M-200 мы должны вызывать gettune не только до, но и после активации и, в случае получения ошибки, должны инициировать повторное выполнение задания. Как только tune-сервер начнет работать корректно, мы сможем активировать те команды, активация которых фактически не прошла на предыдущем выполнении задания. То что было успешно активировано, повторно активироваться не будет.

3.2 Зависимые услуги

Как я уже рассказывал выше, на АТС M-200, включение услуги 'PINCODE' (в зависимости от значений переменных спецификации) может привести к активации одной из трех последовательностей команд:

settune XXXXXXX enb_pincode=on

settune XXXXXXX enb_pincode=on
settune XXXXXXX dvo_pincode=on
settune XXXXXXX pincode=YYYY

settune XXXXXXX enb_pincode=on
settune XXXXXXX dvo_pincodetwo=on
settune XXXXXXX pincode=YYYY

Настройка enb_pincode включается во всех трех случаях. Легко представить себе возможную ошибку, возникающую при наивной реализации активации этих команд:

  1. Активируется услуга, включающая настройку enb_pincode, позволяющую абоненту устанавливать ПИН-код самостоятельно
  2. Активируется услуга, устанавливающая ПИН-код на исходящую связь (в рамках которой enb_pincode активируется повторно)
  3. Абонент отказывается от услуги enb_pincode

В результате этой последовательности действий, настройка enb_pincode оказывается отключенной и ПИН-код, установленный на втором шаге перестает работать! Что можно сделать чтобы этого не произошло?

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

Да, абонент сможет пользоваться услугой enb_pincode, от которой фактически отказался, но установленные ПИН-коды будут работать, без каких либо претензий со стороны абонента. Подобных зависимостей в системе команд M200 не очень много, но все они связаны с важными услугами (такими например, как установка будильника или включение переадресации). Учет этих зависимостей важен, с точки зрения корректной активации.

3.3 Реализация отката

Не любое задание может быть выполнено успешно. Активация команды может завершиться ошибкой, по причине некорректных данных (номер абонента не обслуживается данной АТС, некорректное время при установке будильника и т.п.), либо при конфликте активируемых услуг (такое довольно часто бывает на Alcatel S12). Подобные ошибки будем называть невосстановимыми.

Поскольку задание должно быть выполнено атомарно, при возникновении невосстановимой ошибки, нам придется «откатить» все команды, успешно активированные ранее, в рамках того-же задания. Первое, что приходит в голову, это запомнить все команды, выполнить которые удалось и, при возникновении ошибки, выполнить обратные им команды. Реализацию этого подхода можно видеть в старой версии скрипта:

...
[002391]            platform:M-200; if (tk.detach.service = 'PINCODE' & 
                                        tk.detach.act_mode = 0 & 
                                        tk.detach.act_level = 2) { 
[023910] <            text:settune %s dvo_pincode=off; var_list:tk.phone; 
[001041] >            is_error:1; regexp:(.+); var_list:error; 
[001610] <            text:settune %s pincode=; var_list:tk.phone; 
[001041] >            is_error:1; regexp:(.+); var_list:error; 
[022010] <            text:settune %s enb_pincode=off; var_list:tk.phone; 
[001041] >            is_error:1; regexp:(.+); var_list:error; 
[023911]              is_rollback:1; if (tk.detach.value != '') { 
[001020] <              device_id:tk.ats_id; 
[023912] <              text:settune %s dvo_pincode=on; var_list:tk.phone; 
[001041] >              is_error:1; regexp:(.+); var_list:error; 
[001603] <              text:settune %s pincode=%s; var_list:tk.phone, tk.detach.value; 
[001041] >              is_error:1; regexp:(.+); var_list:error; 
                      }
                    }
...

Здесь, немедленно после успешного отключения услуги 'PINCODE' выполняется сохранение обратных команд, для подключения услуги, в стеке команд отката. Помимо излишней многословности, этот подход имеет ряд других недостатков.

  1. Корректная реализация такого подхода весьма трудоемка. Дело в том, что для выполнения обратных команд, требуются значения переменных на момент активации прямой команды, но часть этих переменных находятся в коллекциях, просматриваемых оператором foreach. Это означает, что при формировании обратной команды, требуется сохранить «снимки» состояния этих коллекций (и вложенных в них подколлекций) и при откате использовать уже их, а не исходные спецификации. Кроме того, такие команды как сброс ПИН-кода, требуют для обратной команды значение ПИН-кода, для того, чтобы его можно было восстановить, и нам приходится добавлять это значение в спецификацию, хотя прямая команда его и не использует
  2. Запуск команд отката после возникновения ошибки активации может привести к ошибке на Alcatel S12. Например, в случае некорректного задания какого либо из параметров, АТС запрашивает корректное значение и ожидает получить его, а не команду отката. Получение команды, в этом контексте приводит к ошибке, в результате чего, команда отката не выполняется. В разделе «Валидация параметров» я разберу этот вопрос подробнее.
  3. Этот подход не работает! На самом деле, нам неоткуда взять корректные значения параметров не только для команды установки ПИН-кода, но и для команд включения enb_pincode и dvo_pincode. Если мы отключали их при выполнении задания, это не означает, что до активации задания они уже не были выключены. В этом случае, включение этих настроек, в процессе отката, будет ошибкой

Таким образом, единственное место, из которого мы можем получить корректные значения настроек, на момент начала активации — это оборудование или БД, при условии, что данные в ней синхронизированы с состоянием АТС.

Для того, чтобы закрыть этот вопрос, осталось упомянуть еще об одном моменте. Дело в том, что активационное задание может выполнять активацию команд на нескольких АТС (например при переносе абонента с одной АТС на другую).

Настройки доступа к АТС хранятся в коллекции tk и может оказаться так, что успешно отработав команды на одной АТС, мы получили ошибку при выполнении команды на другой. В открытой записи коллекции tk, на этот момент, уже нет параметров доступа к АТС, на которой должен выполняться откат. Самый простой способ справиться с этой проблемой — после инициации отката, вызвать рестарт активации задания. В этом случае, все коллекции будут открыты заново и «услуга» 'ROLLBACK' будет выполнена для всех АТС, участвующих в активации:

...
[200002]      if (subtype = -1) { 
[200003]        foreach (tk) { 
[200015] <        device_id:tk.ats_id; 
[200006] <        text:ROLLBACK; 
                  ...
                }
              }
...

3.4 Возобновляемая активация

Помимо невосстановимых ошибок, рассмотренных в предыдущем разделе, возможны ошибки, после возникновения которых активация может быть продолжена. Например если TCP-соединение с АТС было разорвано в процессе активации, откат выполнять не требуется. В этом случае, активацию достаточно повторить, пропуская выполнение команд, активированных ранее. Вот как выглядит иерархия исключений, возбуждаемых в процессе активации:

image

Исключения, подразумевающие необходимость возобновления активации, наследуются от RetryRequiredException. RollbackRequiredException дополнительно инициирует откат активации. Незапланированные ошибки инициирует RuntimeAeException, завершая работу сервиса активации с соответствующей диагностикой. Возобновление активации в связи с потерей соединения с АТС (TcpConnectionLostException) может возникнуть в двух принципиально различных случаях:

  1. Потеря соединения с АТС в процессе активации
  2. Невозможность первичного подключения к АТС

В первом случае, вмешательства со стороны администратора не требуется. Поскольку АТС, в принципе, доступна, повторив активацию несколько раз (с задержкой выполнения на несколько минут), мы рано или поздно, выполним задание.

Во втором случае, ошибка, например, может быть вызвана тем, параметры доступа к АТС заданы неверно. В этом случае, должно быть выполнено несколько попыток подключения, после чего, должно быть сформировано сообщение об ошибке. Эта логика реализуется скриптом:

[002000]    {
[200001]      var_list:retry_cnt = retry_cnt + 1, state = 1; 
              ...
[200008]      if (subtype = 1) { 
[200009]        foreach (tk) { 
                  ...
[200014]          target:tk.ats_type; foreach (tk.detach) { 
[200015] <          device_id:tk.ats_id; 
                    ...
[200025]            if (activate_command = 1) { 
[200026] <            text:%s; var_list:tk.detach.service; 
[200027]              var_list:retry_cnt = 0; 
                    }
                  }
...
            }

В самом начале скрипта, мы увеличиваем счетчик попыток (если переменная не определена спецификацией, она автоматически создается с нулевым значением), а после успешного выполнения любой команды, сбрасываем его в 0. Превышение значением счетчика максимального числа попыток подключения отслеживается спецификацией и формирует исключение, с соответствующей диагностикой.

4. Валидация параметров

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

10.0.5.130:4001> MODIFY-SUBSCR:DN=K'8553377684,ALMCALL=ACTIVATE&06&00&00.
10.0.5.130:4001>     SEQ=1306.130403 9002
10.0.5.130:4001>     COM=4294
10.0.5.130:4001>     JOB SUBMITTED
10.0.5.130:4001>     
10.0.5.130:4001>      
10.0.5.130:4001>     ARGUMENT SEMANTIC ERROR : ALMCALL  ARG 0004
10.0.5.130:4001>     ERROR CODE = 0008
10.0.5.130:4001>     <
10.0.5.130:4001< MODIFY-SUBSCR:DN=K'8553377684,ALMCALL=ACTIVATE&06&00&00.
10.0.5.130:4001> MODIFY-SUBSCR:DN=K'8553377684,SUBCTRL=REMOVE&CWTG.
10.0.5.130:4001>     PARAMETER NOT EXISTENT ; MODIFY-S
10.0.5.130:4001>     <

Разумеется, можно анализировать паттерны таких ошибок и автоматически формировать дополнительные команды, переводящие АТС в состояние ожидания очередной команды, но это существенно усложнит алгоритм взаимодействия с АТС. Гораздо проще таких ошибок не допускать. Сделать это довольно легко. Например, для валидации значения количества срабатываний будильника, нулевое значение которого привело к ошибке в этом примере, достаточно использовать следующее регулярное выражение:

^([1-9])$

Разумеется, лучше всего выполнять эту проверку еще в Order Management, не допуская создания заданий с некорректными значениями параметров. Но, независимо от того, реализована такая проверка или нет, имеет смысл проверить значения параметров непосредственно перед передачей команды на АТС. Это поможет избежать лишних ошибок и позволит упростить логику взаимодействия с АТС.

5. Доступ к АТС

Особенности доступа к АТС также следует учитывать при проектировании. Общим моментом и для Alcatel S12 и для M-200 (с использованием tune) является то, что сессия управления не создается для каждого подключения к АТС. Фактически, мы каждый раз подключаемся к одной и той-же «сессии» (это является некоторым упрощением, но, для подсистемы активации, выглядит именно так). Из этого факта имеются два важных следствия:

  1. Подключаясь к АТС мы можем получить часть вывода оставшегося от предыдущего подключения. Возможны очень неприятные сценарии. Например, при подключении к Alcatel S12 может потребоваться авторизация. Мы определяем необходимость выполнения авторизации по наличию в выводе паттерна 'PASSWORD' и готовимся передать пароль. Если в этот момент теряется соединение с АТС, она продолжает ожидать ввода пароля, но, при очередном подключении к ней, не выводит ничего. Соответственно, нужно быть готовым к тому, что при вводе очередной команды, будет получено сообщение 'INVALID PASSWORD'. В этом случае, придется передать пароль и, по завершении авторизации, повторить команду, вызвавшую сообщение об ошибке
  2. Второй момент очевиден. Необходимо следить за тем, чтобы не возникало одновременных попыток подключения к одной и той-же АТС. При этом, на различных АТС, активация, разумеется, может выполняться параллельно (в целях улучшения производительности). В совокупности с тем фактом, что в одном активационном задании может быть задействовано несколько АТС, это несколько усложняет архитектуру сервера активации

Следует также учитывать, ряд особенностей Alcatel S12. Так, в ее выводе могут присутствовать непечатные символы (0x05, 0x17), а при вводе команд не используются символы перевода строки и возврата каретки. Строка команды, передаваемая на Alcatel S12 должна завершаться символом '.' или ';' (пароль, передаваемый при авторизации, также должен завершаться символом ';'). Старший бит в каждом байте, передаваемом на АТС может использоваться для контроля четности (это настраивается на АТС). На текст передаваемый с АТС эти настройки не распространяются.

В настоящее время, для подключения как к Alcatel S12, так и к M-200 используется Telnet-подобное TCP-подключение, но это решение не является единственно возможным. Ниже кратко рассмотрены другие варианты подключения к этим АТС.

5.1 Serial vs TCP

Как я уже говорил ранее, на момент начала проекта я не обладал каким-либо опытом работы с Alcatel S12. Тот факт, что с этим типом АТС (во всяком случае, в комплектации используемой Заказчиком) придется работать по Serial-соединению, с самого начала, рассматривался как один из самых значительных рисков проекта. Это может показаться странным, но я одновременно был прав и ошибался.

Разумеется, при использовании таких библиотек как RXTX или jSSC (о которой я узнал из статьи на Хабре) само по себе подключение к Serial-порту из Java SE (используемой в проекте) не является проблемой, но здесь нужно учитывать два момента:

  1. В настоящее время, уже довольно сложно найти материнскую плату с честным Serial-портом. В то же время, различные USB-Serial конвертеры (как правило китайские) проявили себя не лучшим образом. Мы сожгли парочку при попытке подключения к АТС, перед тем как поняли, что ими лучше не пользоваться. Кроме того, часто такой виртуальный Serial-порт вел себя не совсем так как «железный»
  2. То что управляющий компьютер должен находиться на расстоянии не превышающем максимальную длину RS-232 кабеля от АТС, является серьезным ограничением. Разумеется, нам хотелось управлять всеми АТС с одного сервера (особенно учитывая тот факт, что активационное задание могло обращаться к нескольким различным АТС), а АТС находились в разных городах!

Решение было найдено в процессе общения с Заказчиком, имевшим больший опыт в части взаимодействия с АТС. Разумеется, телефонистам тоже хотелось управлять всеми АТС из одного места и они давно и успешно этим занимались, используя коммуникационное оборудование MOXA, позволяющее «прокидывать» Serial-подключение через Intranet. К сожалению, при попытке соединения с виртуальным портом, создаваемым MOXA на клиентской стороне возникли проблемы.

Мы потратили довольно много времени, пытаясь решить эту проблему, пока не выяснили, что некоторые модели MOXA позволяют использовать TCP-подключение. Этот подход и был использован. В работе нам очень сильно помог монитор Serial-порта и сниффер.

5.2 Tune vs TCP

При подключении к M-200 было меньше проблем, поскольку она изначально поддерживала подключение по TCP. Первоначально планировалось использовать непосредственный доступ к «абонентской карте». Я не буду загромождать статью дампами TCP-сессий, скажу только, что настройки абонентов в M-200 хранятся в битовой карте. Протокол управления АТС позволяет (используя TCP) читать эту битовую карту целиком и записывать ее обратно, после внесения необходимых изменений.

Часть полей «абонентской карты» нам удалось расшифровать (документацию найти не удалось), но, в конечном итоге, было принято решение, использовать для управления АТС tune-сервер как более документированный метод. Кроме того, работа в режиме ввода текстовых команд была удобнее.

Заключение

В этой статье я постарался описать (и как-то классифицировать) сложности, возникшие в реальном активационном проекте. Несмотря на ярко-выраженную направленность на оборудование традиционной телефонии, материал может быть полезен при работе над автоматизацией управления любым оборудованием (например коммутаторами cisco). Надеюсь, что эта статья будет кому-то полезна.

Автор: GlukKazan

Источник

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


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