Создание обработчика услуг
Материал из ISPWiki
Для взаимодействия BILLnamager с панелью управления сервером, в случае отсутствия готовой реализации, можно создавать собственные обработчики открытия услуг. Такой обработчик будет служить модулем взаимодействия между BILLmanager и панелью управления. Обработчик должен располагаться в /usr/local/ispmgr/sbin/ и иметь название вида cp*. Обработчик запускается с использованием механизма LongTask[1] и принимает данные от BILLmanager через параметры командной строки.
Содержание |
Требования к модулю
Создаваемый обработчик должен уметь взаимодействовать с BILLmanager и реализовывать следующие предъявляемые требования:
- В связи с тем, что BILLmanager запускает модуль через механизм LongTask по окончанию выполнения своей работы обработчик должен передать BILLmanager статус завершения своей работы. Подробнее об этом описано в статье LongTask.
- В обработчике должна быть реализована логика реакции на команды BILLmanager (check, getconfig, open, setparam - обязательно, остальное опционально).
Передаваемые команды
В ходе работы BILLmanager запускает модуль с различными параметра, реализуя логику работы пользователя с сервером. В зависимости от необходимости выполнить на сервере то или иное действия BILLmanager запускает модуль со следующими параметрами:
- check IP_ADDR USERNAME PASSWD, где check - управляющая команда, IP_ADDR - адрес для взаимодействия с панелью управления (адрес доступа в API функциям), USERNAME - имя пользователя для подключения к панели управления, PASSWD - пароль пользователя для подключения к панели управления.
- Команда, а так же параметры IP_ADDR и USERNAME передаются в качестве параметров командной строки, PASSWD необходимо считывать через стандартный поток ввода. В случае успешного подключения с полученными параметрами модуль должен выводить в стандартный поток вывода "OK\n" (без кавычек), в противном случае работа модуля завершается.
- getconfig SRV_ID, где getconfig - управляющая команда, SRV_ID - уникальный идентификатор сервера в базе данных[2].
- В результате работы модуль должен получить с сервера информацию о доступных тарифных планах и передать их на сервер BILLmanager в следующем формате: 'Preset <имя тарифа>', информация по каждому тарифу должна храниться в отдельной строке, так же можно передать любую другую необходимую вам информацию. Данная информация будет отображена на вкладке конфигурации в свойствах сервера.
- Для передачи информации на сервер BILLmanager используется вызов функции server.setconfig и передаются параметры elid со значением идентификатора сервера, config содержащий передаваемую информацию о сервере.
- open ITEM_ID, где open - управляющая команда, ITEM_ID - уникальный идентификатор заказа в базе данных[2].
- В результате работы модуля на сервере должен быт открыт хостинг с параметрами хранимыми в базе данных.
- После открытия хостинга на сервере, для изменения его статуса в BILLmanager необходимо в BILLmanager выполнить функцию <тип услуги>.open со следующими параметрами sok со значением 'ok' в случае успешного открытия услуги на сервере, elid со значение ITEM_ID для обработанной услуги и другими необходимыми параметрами.
- Для услуги vhost передаются так же password со значением пароля доступа к аккаунту на сервере, ip со значением любого из добавленных на сервер ip адресов.
- Для услуги rhost передаются так же password со значением пароля доступа к аккаунту на сервере.
- Для услуги vds передаются так же password со значением пароля доступа к аккаунту на сервере, ip со значением ip адреса доступа к серверу и дополнительно slavens со значением вторичных серверов имен.
- Для услуги rvds передаются так же password со значением пароля доступа к аккаунту на сервере.
- Для услуги dns передаются так же password со значением пароля доступа к аккаунту на сервере, ip со значением ip адреса доступа к серверу.
- В хоте открытия услуги на сервере так же может понадобится вызов дополнительный функций BILLmanager, например изменить имя пользователя или список IP адресов. Подробнее о том как это можно сделать сказано в описание BILLmanager API.
- setparam ITEM_ID, где setparam - управляющая команда, ITEM_ID - уникальный идентификатор заказа в базе данных[2]. Данная операция выполняется при изменение тарифного плана заказа или при изменение состава деталей (составляющих заказа, например заказанного дискового пространства, количества оперативной памяти и т.п.).
- suspend ITEM_ID, где suspend - управляющая команда, ITEM_ID - уникальный идентификатор заказа в базе данных[2]. Данная операция выполняется при необходимости приостановить работу хостинга.
- В случае успешной остановки работы услуги на сервере необходимо вызвать функцию BILLmanager item.setstatus с параметрами elid со значение ITEM_ID услуги, status со значением '3'.
- resume ITEM_ID, где resume - управляющая команда, ITEM_ID - уникальный идентификатор заказа в базе данных[2]. Данная операция выполняется при необходимости возобновить работу хостинга.
- В случае успешного возобновления работы услуги на сервере необходимо вызвать функцию BILLmanager item.setstatus с параметрами elid со значение ITEM_ID услуги, status со значением '2'.
- delete ITEM_ID, где delete - управляющая команда, ITEM_ID - уникальный идентификатор заказа в базе данных[2]. Данная операция выполняется при необходимости удалить хостинг с сервера.
- В случае успешного удаления услуги на сервере необходимо вызвать функцию BILLmanager item.setstatus с параметрами elid со значение ITEM_ID услуги, status со значением '4'. А так же удалить IP адреса привязанные к аккаунту из базы данных и в случае необходимости произвести другие действия.
- getstat SRV_ID, где getstat - управляющая команда, SRV_ID - уникальный идентификатор сервера в базе данных[2]. Команда передается модулю по расписанию для получения статистики по использованию ресурсов сервера клиентом.
- Статистика добавляется в базу данных в таблицу itemstat.
- По завершения выполнения операции необходимо выполнить в BILLmanager функцию billserver с параметрами elid со значением SRV_ID и date со значением даты за которую собрана статистика.
- getloginurl ITEM_ID, где getloginurl - управляющая команда, ITEM_ID - уникальный идентификатор заказа в базе данных[2]. По данной команде должен производится редирект на сервер управления хостингом, для этого в стандартный вывод передается строка location='<URL перехода>'.
- newip ITEM_ID DOMAIN COUNT, где newip - управляющая команда, ITEM_ID - уникальный идентификатор заказа в базе данных[2], DOMAIN - имя домена, для которого производится добавление IP адресов, COUNT - количество адресов для добавления. Операция выполняется при необходимости добавить некоторое количество IP адресов клиенту.
- Для каждого добавленного IP адреса вызывается функция BILLmanager item.ip.done с параметрами sok со значением 'ok', elid со значением ITEM_ID и ip со значением добавленного IP адреса.
- delip ITEM_ID IP, где delip - управляющая команда, ITEM_ID - уникальный идентификатор заказа в базе данных[2], IP - адрес для удаления. Операция выполняется при необходимости удалить определенный IP адрес клиента с сервера.
- Для удаленного IP адреса вызывается функция BILLmanager item.ip.done с параметрами delip со значением yes, elid со значением ITEM_IP и ip со значением удаленного IP адреса.
- fix, где fix - управляющая команда. Команда передается модулю по расписанию для выполнения синхронизации данных сервера с данными BILLmanager.
Так же при необходимости можно добавить в модуль свою логику, запуск работы которой можно добавить в cron или запускать любым другим способом.
Полезные функции BILLmanager
- Для очистки кеша определенной таблицы базы данных, загруженной в BILLmanager можно вызвать функцию drop.cache с параметром elid с именем таблицы.
Взаимодействие с BILLmanager
Для взаимодействия с BILLmanager модуль может обращаться к нему через API[3], либо можно использовать библиотеку Mgr[4]. В общем случае выполнение функции в BILLmanager выглядит как вызов URL сервера BILLmanager с передачей параметра func со значением вызываемой функции и параметров вызываемой функции, в случае необходимости получения ответа от сервера в структурированном формате можно добавить параметр out со значением xml для получения ответа в формате XML документа, json для получения ответа в формате JSON структуры.
Пример реализации обработчика услуг
В этом разделе представлена примерная реализация модуля обработчика заказа услуг виртуального хостинга с использование панели управления Kloxo. Представленная реализация модуля позволяет выполнять следующие действия:
- Загружать список доступных на сервере тарифов
- Проводить открытие услуги на сервере
- Приостанавливать действие услуги
- Возобновлять действие услуги
- Менять тарифный план услуги
Пример реализации остальных возможностей будет добавлен позднее.
Код файла модуля с комментариями
Заголовок файла модуля:
- Обработчик
- подключаемые библиотеки
#! /usr/bin/env python import sys, string, time, httplib2, urllib, os, logging, base64, json, MySQLdb, subprocess, random, traceback from cookielib import debug
Функция обработки неизвестных исключений:
def handleError(self, record):
traceback.print_stack()
logging.Handler.handleError = handleError
Настройка логирования:
- Файл лога
- Загрузка конфигурации логирования
- Дополнительные параметры логирования: id процесса
log_file = "/usr/local/ispmgr/var/cpkloxo.log"
logging.basicConfig(filename=log_file,
level=logging.DEBUG,
format="%(asctime)s [ %(procid)-4d] %(message)s",
datefmt="%b %d %H:%M:%S")
d = {'procid': os.getpid()}
Функции логирования:
- debug - отладочная информация
- log - общая информация
- warning - предупреждения
- error - ошибки
- critical - критические ошибки
def debug(msg):
logging.debug("\033[1;33mDEBUG %s" + msg + "\033[0m", "", extra=d)
def log(msg):
logging.info("\033[1;32mINFO %s" + msg + "\033[0m", "", extra=d)
def warning(msg):
logging.warning("\033[1;35mWARNING %s" + msg + "\033[0m", "", extra=d)
def error(msg):
logging.error("\033[1;31mERROR %s" + msg + "\033[0m", "", extra=d)
def critical(msg):
logging.critical("\033[1;31mFATAL %s" + msg + "\033[0m", "", extra=d)
Функция вывода информации по использованию:
def usage():
debug("Print usage information")
print "Kloxo Integration module"
print "Usage info: ..."
Функция отправки на сервер параметров запроса и получение ответа сервера в виде ассоциативного массива:
def getDictResponse(url, username = "", passwd = "", params = {}):
params.update({"login-class": "client", "login-name" : username, "login-password" : passwd, "output-type" : "json"});
log("URL: " + str(url));
log("Params: " + str(params));
http_client = httplib2.Http()
try:
response, content = http_client.request(url + "/webcommand.php?" + urllib.urlencode(params))
except Exception as exception:
critical("Error while getting response: " + str(exception))
debug("Response: \n" + str(response));
debug("Content: \n" + str(content));
return json.JSONDecoder().decode(content)
Функция подключения к MySQL серверу:
- Считывание параметров из конфигурационного файла
- Подключение к серверу
- Возврат соединения с сервером
def sql_conn():
try:
sql_config = open("etc/billmgr.conf", "r")
sql_params = {}
for line in sql_config.readlines():
line = string.strip(line, " \n")
kv = line.split()
try:
sql_params[kv[0]] = kv[1]
except IndexError as exception:
debug("Config: Skip empty param")
sql_config.close()
except IOError as exception:
critical("Error while reading sql config: " + str(exception))
try:
conn = MySQLdb.connect("localhost", "root", sql_params["DBPassword"], sql_params["DBName"])
except IndexError as exception:
critical(str(exception))
return conn.cursor()
Класс реализующий взаимодействие с панелью управления Kloxo:
class Panel:
Метод переопределения ограничений аккаунта на сервере в соответствии со значениями в BILLmanager (в текущей версии примера не реализовано):
def setdetails(self, item_id):
debug("Set item details")
Метод проверки соединения с сервером Kloxo:
- Отправка тестового запроса
- Если тестовый запрос прошел удачно, передает в стандартный вывод "OK"
def check(self, url, username, passwd = ""):
debug("Check action")
log("Check connection. Server address: "+url+", username: "+username)
params = {"action": "simplelist",
"resource" : "resourceplan"}
data = getDictResponse(url, username, passwd, params)
try:
if data["return"] == "error":
log("Error: " + data["message"])
sys.stdout.write("")
else:
sys.stdout.write("OK\n")
except KeyError:
error("Can't connect to server")
sys.stdout.write("")
Метод получения конфигурации сервера:
- Отправка на сервер запроса о существующих тарифных планах
- Передача полученной информации в BILLmanager
def getconfig(self, srv_id):
debug("Getconfig action")
cursor = sql_conn()
cursor.execute ("select s.url, s.username, s.password "
"from server s "
"where s.id = " + srv_id)
row = cursor.fetchone()
params = {"action": "simplelist",
"resource" : "resourceplan"}
data = getDictResponse(row[0], row[1], row[2], params)
try:
if data["return"] == "error":
log("Error: " + data["message"])
else:
srvconfig = "Ver 1.0\n"
plans = data["result"].items()
for plankey, plan in plans:
srvconfig = srvconfig + "Preset " + plankey + "\n"
subprocess.Popen(["/usr/local/ispmgr/sbin/mgrctl",
"-m", "billmgr",
"-o", "xml",
"server.setconfig",
"elid=" + srv_id,
"config=" + srvconfig]);
except KeyError:
error("Can't connect to server")
cursor.close()
Метод открытия услуги на сервере:
- Получение из базы параметров услуги
- Отправка на сервер запроса создания клиентского аккаунта
- Вызов метода настройки аккаунта
def open(self, item_id):
debug("Open action")
cursor = sql_conn()
cursor.execute("select s.url, s.username, s.password, v.domain, v.username, p.internalname "
"from item i left join user u on i.account=u.account "
"left join vhost v on v.pid=i.id "
"left join account a on i.account=a.id "
"left join pricelist p on i.price=p.id "
"left join server s on i.server = s.id "
"where i.id = " + item_id)
row = cursor.fetchone()
size = 8;
passwd = "".join([random.choice(string.letters + string.digits) for i in range(size)])
params = {"action" : "add",
"class" : "client",
"name" : row[4],
"v-password" : passwd,
"v-type" : "customer",
"v-plan_name" : row[5],
"v-domain_name" : row[3],
"v-dnstemplate_name" : "DNS Default.dnst"}
data = getDictResponse(row[0], row[1], row[2], params)
try:
if data["return"] == "error":
log("Error: " + data["message"])
else:
subprocess.Popen(["/usr/local/ispmgr/sbin/mgrctl",
"-m", "billmgr",
"-o", "xml",
"vhost.open",
"elid=" + item_id,
"password=" + passwd,
"sok=ok"]);
except KeyError:
error("Can't connect to server")
self.setdetails(item_id)
Метод изменения параметров аккаунта на сервере:
- Проверка изменения тарифного плана
- Если необходимо изменение тарифного плана, то отправка на сервер запроса изменение тарифного плана клиента
- Если изменение тарифного плана прошло успешно, вызов BILLmanager item.commitprice для подтверждения изменения тарифного плана, иначе вызов функции BILLmanager item.getbackprice для отмены изменения тарифного плана
- Вызов метода настройки аккаунта:
def setparam(self, item_id):
debug("Setparam action")
cursor = sql_conn()
cursor.execute("select i.lastprice "
"from pricelist p left join item i on i.price=p.id "
"where i.id=" + item_id)
row = cursor.fetchone()
old_plan = row[0]
cursor.execute("select s.url, s.username, s.password, v.domain, v.username, p.internalname, p.id "
"from item i left join user u on i.account=u.account "
"left join vhost v on v.pid=i.id "
"left join account a on i.account=a.id "
"left join pricelist p on i.price=p.id "
"left join server s on i.server = s.id "
"where i.id = " + item_id)
row = cursor.fetchone()
if str(old_plan) == "None":
log("No need change price")
elif old_plan != row[6]:
log("Change price from: " + str(old_plan) + ", to: " + str(row[6]))
params = {"action" : "update",
"subaction" : "change_plan",
"class" : "client",
"name" : row[4],
"v-resourceplan_name" : row[5]}
try:
data = getDictResponse(row[0], row[1], row[2], params)
if data["return"] == "error":
error("Error: " + data["message"])
log("Rollback price")
subprocess.Popen(["/usr/local/ispmgr/sbin/mgrctl",
"-m", "billmgr",
"-o", "xml",
"item.getbackprice",
"elid=" + item_id]);
else:
subprocess.Popen(["/usr/local/ispmgr/sbin/mgrctl",
"-m", "billmgr",
"-o", "xml",
"item.commitprice",
"elid=" + item_id]);
except Exception as exception:
error("Error: " + str(exception))
log("Rollback price")
subprocess.Popen(["/usr/local/ispmgr/sbin/mgrctl",
"-m", "billmgr",
"-o", "xml",
"item.getbackprice",
"elid=" + item_id]);
self.setdetails(item_id)
Метод приостановления действия учетной записи на сервере:
- Отправка на сервер запроса на приостановление действия учетной записи
- Изменение состояния заказа в BILLmanager в случае успеха
def suspend(self, item_id):
debug("Suspend action")
cursor = sql_conn()
cursor.execute("select s.url, s.username, s.password, v.username "
"from item i left join vhost v on v.pid=i.id "
"left join server s on i.server = s.id "
"where i.id = " + item_id)
row = cursor.fetchone()
params = {"action" : "update",
"class" : "client",
"name" : row[3],
"subaction" : "disable"}
data = getDictResponse(row[0], row[1], row[2], params)
try:
if data["return"] == "error":
log("Error: " + data["message"])
else:
subprocess.Popen(["/usr/local/ispmgr/sbin/mgrctl",
"-m", "billmgr",
"-o", "xml",
"item.setstatus",
"elid=" + item_id,
"status=3"]);
except KeyError:
error("Can't connect to server")
Метод возобновления действия учетной записи на сервере:
- Отправка на сервер запроса на возобновление действия учетной записи
- Изменение состояния заказа в BILLmanager в случае успеха
def resume(self, item_id):
debug("Resume action")
cursor = sql_conn()
cursor.execute("select s.url, s.username, s.password, v.username "
"from item i left join vhost v on v.pid=i.id "
"left join server s on i.server = s.id "
"where i.id = " + item_id)
row = cursor.fetchone()
params = {"action" : "update",
"class" : "client",
"name" : row[3],
"subaction" : "enable"}
data = getDictResponse(row[0], row[1], row[2], params)
try:
if data["return"] == "error":
log("Error: " + data["message"])
else:
subprocess.Popen(["/usr/local/ispmgr/sbin/mgrctl",
"-m", "billmgr",
"-o", "xml",
"item.setstatus",
"elid=" + item_id,
"status=2"]);
except KeyError:
error("Can't connect to server")
Метод удаления заказа на сервере:
- Отправка на сервер запроса на удаление учетной записи
- В случае успешного удаления учетной записи на сервере, изменение состояния заказа в BILLmanager
def delete(self, item_id):
debug("Delete action")
cursor = sql_conn()
cursor.execute("select s.url, s.username, s.password, v.username "
"from item i left join vhost v on v.pid=i.id "
"left join server s on i.server = s.id "
"where i.id = " + item_id)
row = cursor.fetchone()
params = {"action" : "delete",
"class" : "client",
"name" : row[3]}
data = getDictResponse(row[0], row[1], row[2], params)
try:
if data["return"] == "error":
log("Error: " + data["message"])
else:
subprocess.Popen(["/usr/local/ispmgr/sbin/mgrctl",
"-m", "billmgr",
"-o", "xml",
"item.setstatus",
"elid=" + item_id,
"status=4"]);
cursor.execute("delete from itemip "
"where item = " + item_id)
subprocess.Popen(["/usr/local/ispmgr/sbin/mgrctl",
"-m", "billmgr",
"-o", "xml",
"drop.cache",
"elid=itemip"]);
except KeyError:
error("Can't connect to server")
Метод получения статистики по использованному учетной записью трафика (в текущей версии примера не реализовано):
def getstat(self, srv_id):
debug("Getstat action")
warning("unsupport action")
Метод получения URL адреса для входа в учетную запись на сервере:
- Формирование URL адреса
- Вывод URL адреса в стандартный поток вывода
def getloginurl(self, item_id):
debug("Getloginurl action")
cursor = sql_conn()
cursor.execute("select s.url "
"from item i left join server s on i.server = s.id "
"where i.id = " + item_id)
row = cursor.fetchone()
sys.stdout.write("location='" + row[0] + "'")
Метод добавления IP адресов к учетной записи на сервере (в текущей версии примера не реализовано):
def newip(self, item_id, domain, count):
debug("Newip action")
warning("unsupport action")
Метод удаления IP адресов из учетной записи на сервере (IP адреса передаются строкой с разделителем ', ')(в текущей версии примера не реализовано):
def delip(self, item_ip, ip):
debug("Delip action")
warning("unsupport action")
Метод синхронизации учетных записей на сервере с их параметрами в BILLmanager (в текущей версии примера не реализовано):
def fix(self):
debug("Fix action")
warning("unsupport action")
Создание экземпляра класса панели:
kloxo = Panel()
log("Start module work");
Обработка консольных команд:
- Проверка количества полученных аргументов
- Вызов соответствующего метода
- Вывод информации по использованию в случае получения не верной управляющей команда
try:
if len(sys.argv) < 2:
usage()
elif len(sys.argv) < 3:
if sys.argv[1] == "fix":
kloxo.fix()
else:
usage()
elif len(sys.argv) < 4:
if sys.argv[1] == "getconfig":
kloxo.getconfig(sys.argv[2])
elif sys.argv[1] == "open":
kloxo.open(sys.argv[2])
elif sys.argv[1] == "setparam":
kloxo.setparam(sys.argv[2])
elif sys.argv[1] == "suspend":
kloxo.suspend(sys.argv[2])
elif sys.argv[1] == "resume":
kloxo.resume(sys.argv[2])
elif sys.argv[1] == "delete":
kloxo.delete(sys.argv[2])
elif sys.argv[1] == "getstat":
kloxo.getstat(sys.argv[2])
else:
usage()
elif len(sys.argv) < 5:
if sys.argv[1] == "check":
passwd = sys.stdin.read();
kloxo.check(sys.argv[2], sys.argv[3], passwd)
elif sys.argv[1] == "delip":
kloxo.delip(sys.argv[2], sys.argv[3])
elif sys.argv[1] == "getloginurl":
kloxo.getloginurl(sys.argv[2])
else:
usage()
elif len(sys.argv) < 6:
if sys.argv[1] == "newip":
kloxo.newip(sys.argv[2], sys.argv[3], sys.argv[4])
else:
usage()
else:
usage()
Обработка удачного завершения работы модуля:
- Проверяем получен ли в переменных окружения параметр MGR_LT_PID
- Если параметр получен сообщаем BILLmanager об удачном завершении работы модуля
MGR_LT_PID = str(os.getenv("MGR_LT_PID"))
log("MGR_LT_PID: " + MGR_LT_PID)
if MGR_LT_PID == "None":
log("Standalong. No finish action")
else:
subprocess.Popen(["/usr/local/ispmgr/sbin/mgrctl",
"-m", "billmgr",
"-o", "xml",
"longtask.finish",
"elid=" + MGR_LT_PID,
"status=ok"]);
Обработка неудачного завершения работы модуля:
- Проверяем получен ли в переменных окружения параметр MGR_LT_PID
- Если параметр получен сообщаем BILLmanager об неудачном завершении работы модуля
except Exception as exception:
critical(str(exception))
MGR_LT_PID = str(os.getenv("MGR_LT_PID"))
log("MGR_LT_PID: " + MGR_LT_PID)
if MGR_LT_PID == "None":
log("Standalong. No finish action")
else:
subprocess.Popen(["/usr/local/ispmgr/sbin/mgrctl",
"-m", "billmgr",
"-o", "xml",
"longtask.finish",
"elid=" + MGR_LT_PID,
"status=err",
"errmsg=" + urllib.quote(str(""))]);
del kloxo
