CRL
Новичок
Проектирование API ISP-биллинга для районной сети.
Здравствуйте.
Столкнулся с проблемой проектирования API биллинга в смысле службы ISP районной сети.
Целевая сеть - это 90-110 машин, на уровне доступа - неуправляемое оборудование.
API должно с одинаковым успехом использоваться и web-мордой, и в CLI.
Проект предполагается обёрткой для iptables + iproute + dhcpd + ip-sentinel.
Не имея опыта проектирования подобных систем, придерживался такой схемы:
1) Описал ряд интерфейсов для базовых классов:
- Интерфейс взаимодействия с хранилищами
- Интерфейс взаимодействия с журналами
- Интерфейс, описывающий механизм проверки входных данных (счёл необходимым в этом случае ввести интерфейс потому, что входные данные могут проверятся, как минимум, 2 способами: средствами РНР и средствами ОС)
2) Создал необходимые реализации интерфейсов
- Класс для MySQL-хранилища
- Класс для работы с текстовыми журналами
- Класс проверки входных данных (ip, mac) средствами РНР
3) Логически разделил всю систему на отдельные модули, каждый из которых отвечает за работу с одним внешним компонентом: iptables, iproute и др. Каждый модуль имеет собственный конфиг.
После этого получилась примерно следующая картина:
- объект класса-модуля читает свой конфиг и выполняет инициализацию необходимых ему объектов служебных классов
- объеты служеных классов сохранятются в виде приватных свойств класса-модуля и используются в публичных методах
- объект главного класса биллинга создаёт объекты классов-модулей и сохраняет их в виде публичных свойств; доступ извне API к её функционалу осуществляется через эти публичные свойства
- каждый публичный метод любого объекта класса-модуля может вернуть следующие значения: FALSE, TRUE или небулевый результат(массив, строка), экземпляр объекта-исключения
- отлов исключений поручается приложению, использующему API
В черновом варианте реализовал всё это дело для двух модулей: GateMod(iptables) и SpeedMod(iproute). Попробовал на тестовой сети из 4 компов - работает. Но при этом у меня почему-то возникает чувство, что я "сам себя где-то н@#бал, а где - не пойму." Отсюда и вопрос: кто сталкивался с проектированием подобных вещей - как такое должно выглядеть? будет ли описанное жизнеспособно? И еще один вопрос - иерархия исключений. Сейчас никакой иерархии нет - исключение выбрасывается в случае фатала во внешних компонентах и в случае нарушения целостности последовательности исполнения команд внешними компонентами. Т.е., уровень критичности ошибок сильно задран и слабо раеализован функционал восстановления. Как быть? Создавать собственное исключение для каждого модуля? Создавать класс, реализующий функционал восстановления?
Для наглядности приведу пример.
Один из интерфейсов:
Он реализуется:
Класс-модуль:
Основной класс биллинга:
-~{}~ 07.04.09 13:21:
Если не касаться идеологической правильности подхода к построению таких систем, то основная непонятка сейчас сводится к следующему.
Серьёзную проблему представляют ошибки, связанные с нарушением целостности последовательности исполнения внешних команд. Например, метод gate_edit_user() модуля GateMod редактирует уже существующего пользователя подсистемы шлюза. Для простоты будем считать, что редактирование - это просто замена одного ip на другой, и внутри метода происходит последовательный вызов внешнего приложения
(iptables) сначала для удаления юзера с исходным ip, а потом для добавления юзера с конечным ip. И вот если удаления пользователя проходит успешно, а добавление крашится - возникает ошибка нарушения последовательности исполнения. В данном случае, самая главная задача - откат шлюза до состояния, предшествующего началу операции. У меня сейчас это реализовано так: любой метод, вносящий изменения в настройки шлюза, предварительно делает бэкап состояния (приватный метод модуля шлюза gate_save()). В ходе выполнения целостность последовательности проверяется на каждом этапе, если она нарушатся - выполняется откат (приватный метод шлюза gate_rollback()), после этого выбрасывается исключение, которое отлавливается уже за пределами API и носит чисто информационный характер, потому как в его обработке нет никакого кода восстановления. Вот в связи с этим и вопросы - правильно ли держать такие операции, как создание бэкапа и откат, внутри самого класса модуля? Или это должен быть какой-то другой класс? Где выбрасывать исключения? Где их ловить и обрабатывать?
Здравствуйте.
Столкнулся с проблемой проектирования API биллинга в смысле службы ISP районной сети.
Целевая сеть - это 90-110 машин, на уровне доступа - неуправляемое оборудование.
API должно с одинаковым успехом использоваться и web-мордой, и в CLI.
Проект предполагается обёрткой для iptables + iproute + dhcpd + ip-sentinel.
Не имея опыта проектирования подобных систем, придерживался такой схемы:
1) Описал ряд интерфейсов для базовых классов:
- Интерфейс взаимодействия с хранилищами
- Интерфейс взаимодействия с журналами
- Интерфейс, описывающий механизм проверки входных данных (счёл необходимым в этом случае ввести интерфейс потому, что входные данные могут проверятся, как минимум, 2 способами: средствами РНР и средствами ОС)
2) Создал необходимые реализации интерфейсов
- Класс для MySQL-хранилища
- Класс для работы с текстовыми журналами
- Класс проверки входных данных (ip, mac) средствами РНР
3) Логически разделил всю систему на отдельные модули, каждый из которых отвечает за работу с одним внешним компонентом: iptables, iproute и др. Каждый модуль имеет собственный конфиг.
После этого получилась примерно следующая картина:
- объект класса-модуля читает свой конфиг и выполняет инициализацию необходимых ему объектов служебных классов
- объеты служеных классов сохранятются в виде приватных свойств класса-модуля и используются в публичных методах
- объект главного класса биллинга создаёт объекты классов-модулей и сохраняет их в виде публичных свойств; доступ извне API к её функционалу осуществляется через эти публичные свойства
- каждый публичный метод любого объекта класса-модуля может вернуть следующие значения: FALSE, TRUE или небулевый результат(массив, строка), экземпляр объекта-исключения
- отлов исключений поручается приложению, использующему API
В черновом варианте реализовал всё это дело для двух модулей: GateMod(iptables) и SpeedMod(iproute). Попробовал на тестовой сети из 4 компов - работает. Но при этом у меня почему-то возникает чувство, что я "сам себя где-то н@#бал, а где - не пойму." Отсюда и вопрос: кто сталкивался с проектированием подобных вещей - как такое должно выглядеть? будет ли описанное жизнеспособно? И еще один вопрос - иерархия исключений. Сейчас никакой иерархии нет - исключение выбрасывается в случае фатала во внешних компонентах и в случае нарушения целостности последовательности исполнения команд внешними компонентами. Т.е., уровень критичности ошибок сильно задран и слабо раеализован функционал восстановления. Как быть? Создавать собственное исключение для каждого модуля? Создавать класс, реализующий функционал восстановления?
Для наглядности приведу пример.
Один из интерфейсов:
PHP:
<?php
interface iStorage
{
public function open($params);
public function close();
public function exec($query_tpl, $query_params);
public function res_count($query_tpl, $query_params);
public function res_row_format($query_tpl, $query_params);
public function res_assoc_format($query_tpl, $query_params);
}
?>
PHP:
<?php
...
class MySQLStorage implements iStorage
{
private $db_marker;
private function sql_wrap($query_tpl, $query_params)
{
...
}
public function __destruct()
{
$this->close();
}
public function open($params)
{
...
$this->db_marker = $db_marker;
return true;
}
public function exec($query_tpl, $query_params)
{
$query = $this->sql_wrap($query_tpl, $query_params);
...
}
...
?>
PHP:
<?php
...
// Модуль шлюза
class GateMod
{
// Настройки
private $DB_config;
private $log_config;
private $gate_config;
private $DB;
private $gate_log;
private function gate_rollback()
{
$restore_comand = "iptables-restore ".$this->gate_config["rules_dir"]."/".$this->gate_config["main_rules"]." 2> /dev/null";
exec($restore_comand, $box, $retval);
if($retval != 0)
{
return false;
}
return true;
}
private function gate_save()
{
$save_comand = "iptables-save >".$this->gate_config["rules_dir"]."/".$this->gate_config["main_rules"]." 2> /dev/null";
exec($save_comand, $box, $retval);
if($retval != 0)
{
return false;
}
return true;
}
public function __construct()
{
// Подключение файла конфигурации
require_once "GateMod.config.php";
$this->DB_config = $storage;
$this->log_config = $log;
$this->gate_config = $gate;
$this->common_log = new TextLog;
$this->common_log->open($log["common"]);
$DBFactory = new StorageFactory;
$this->DB = $DBFactory->get_instance($storage["type"]);
if($this->DB === false)
{
throw new Exception("Не удалось загрузить драйвер хранилища ".$storage["type"].".");
}
$this->DB->open($this->DB_config);
}
// Корректное закрытие ресурсов
public function __destruct()
{
$this->DB->close();
$this->common_log->close();
}
/// МЕТОДЫ ШЛЮЗА ///
public function gate_add_user($ip)
{
...
}
public function gate_del_user($ip)
{
...
}
...
?>
PHP:
<?php
...
class Billing
{
public $gate;
public $shaper;
...
public function __construct()
{
...
$this->gate = new GateMod;
$this->shaper = new SpeedMod;
...
}
...
}
?>
Если не касаться идеологической правильности подхода к построению таких систем, то основная непонятка сейчас сводится к следующему.
Серьёзную проблему представляют ошибки, связанные с нарушением целостности последовательности исполнения внешних команд. Например, метод gate_edit_user() модуля GateMod редактирует уже существующего пользователя подсистемы шлюза. Для простоты будем считать, что редактирование - это просто замена одного ip на другой, и внутри метода происходит последовательный вызов внешнего приложения
(iptables) сначала для удаления юзера с исходным ip, а потом для добавления юзера с конечным ip. И вот если удаления пользователя проходит успешно, а добавление крашится - возникает ошибка нарушения последовательности исполнения. В данном случае, самая главная задача - откат шлюза до состояния, предшествующего началу операции. У меня сейчас это реализовано так: любой метод, вносящий изменения в настройки шлюза, предварительно делает бэкап состояния (приватный метод модуля шлюза gate_save()). В ходе выполнения целостность последовательности проверяется на каждом этапе, если она нарушатся - выполняется откат (приватный метод шлюза gate_rollback()), после этого выбрасывается исключение, которое отлавливается уже за пределами API и носит чисто информационный характер, потому как в его обработке нет никакого кода восстановления. Вот в связи с этим и вопросы - правильно ли держать такие операции, как создание бэкапа и откат, внутри самого класса модуля? Или это должен быть какой-то другой класс? Где выбрасывать исключения? Где их ловить и обрабатывать?
