diff --git a/abonapp/models.py b/abonapp/models.py
index 8b2a40a..c7b80e8 100644
--- a/abonapp/models.py
+++ b/abonapp/models.py
@@ -383,44 +383,36 @@ class AbonRawPassword(models.Model):
def abon_post_save(sender, instance, **kwargs):
- try:
- tm = Transmitter()
- agent_abon = instance.build_agent_struct()
- if agent_abon is None:
- return True
- if kwargs['created']:
- # создаём абонента
- tm.add_user(agent_abon)
- else:
- # обновляем абонента на NAS
- # найдём абонента на NAS
- queue = tm.find_queue('uid%d' % instance.pk)
- if queue:
- # если нашли абонента на NAS
- mikrotik_id = queue.sid
- tm.update_user(agent_abon, mikrotik_id)
-
- # если не активен то приостановим услугу
- if instance.is_active:
- tm.start_user(mikrotik_id)
- else:
- tm.pause_user(mikrotik_id)
- else:
- # если не нашли абонента на NAS то добавим
- tm.add_user(agent_abon)
- except NasFailedResult:
+ #try:
+ tm = Transmitter()
+ agent_abon = instance.build_agent_struct()
+ if agent_abon is None:
return True
+ if kwargs['created']:
+ # создаём абонента
+ tm.add_user(agent_abon)
+ else:
+ print('Update')
+ # обновляем абонента на NAS
+ tm.update_user(agent_abon)
+ print('PostUpdate', instance.is_active)
+ # если не активен то приостановим услугу
+ if instance.is_active:
+ tm.start_user(agent_abon)
+ else:
+ tm.pause_user(agent_abon)
+
+ #except NasFailedResult:
+ # return True
def abon_del_signal(sender, instance, **kwargs):
try:
+ ab = instance.build_agent_struct()
# подключаемся к NAS'у
tm = Transmitter()
- # найдём правило удаляемого абонента
- queue = tm.find_queue('uid%d' % instance.pk)
- if queue:
- # нашли абонента, и удаляем его на NAS
- tm.remove_user(queue.sid)
+ # нашли абонента, и удаляем его на NAS
+ tm.remove_user(ab)
except NasFailedResult:
return True
@@ -437,18 +429,7 @@ def abontariff_post_save(sender, instance, **kwargs):
if agent_abon is None:
return True
tm = Transmitter()
- # найдём абонента на NAS
- queue = tm.find_queue('uid%d' % instance.abon.pk)
- if queue:
- mikrotik_id = queue.sid
- # нашли абонента, обновляем его на NAS
- tm.update_user(agent_abon, mikrotik_id)
- if instance.abon.is_active:
- tm.start_user(mikrotik_id)
- else:
- tm.pause_user(mikrotik_id)
- else:
- tm.add_user(agent_abon)
+ tm.update_user(agent_abon)
except NasFailedResult:
return True
@@ -461,10 +442,9 @@ def abontariff_del_signal(sender, instance, **kwargs):
# если у абонента нет ip то и создавать правило не на кого
return
try:
+ agent_abon = instance.abon.build_agent_struct()
tm = Transmitter()
- queue = tm.find_queue('uid%d' % instance.abon.pk)
- if queue:
- tm.pause_user(queue.sid)
+ tm.pause_user(agent_abon)
except NasFailedResult:
return True
diff --git a/abonapp/templates/abonapp/editAbon.html b/abonapp/templates/abonapp/editAbon.html
index ace9565..3f53ef6 100644
--- a/abonapp/templates/abonapp/editAbon.html
+++ b/abonapp/templates/abonapp/editAbon.html
@@ -32,6 +32,13 @@
+
+
diff --git a/abonapp/views.py b/abonapp/views.py
index 129f470..010751f 100644
--- a/abonapp/views.py
+++ b/abonapp/views.py
@@ -518,11 +518,7 @@ def update_nas(request, group_id):
continue
agent_abon = usr.build_agent_struct()
if agent_abon is not None:
- queue = tm.find_queue('uid%d' % usr.pk)
- if queue:
- tm.update_user(agent_abon, queue.sid)
- else:
- tm.add_user(agent_abon)
+ tm.update_user(agent_abon)
except NasFailedResult as e:
messages.error(request, e)
except NasNetworkError as e:
diff --git a/agent/core.py b/agent/core.py
index ad6d5f9..4039483 100644
--- a/agent/core.py
+++ b/agent/core.py
@@ -37,8 +37,8 @@ class BaseTransmitter(metaclass=ABCMeta):
"""добавляем список абонентов в NAS"""
@abstractmethod
- @check_input_type(int)
- def remove_user_range(self, user_ids):
+ @check_input_type(AbonStruct)
+ def remove_user_range(self, users):
"""удаляем список абонентов"""
@abstractmethod
@@ -53,8 +53,8 @@ class BaseTransmitter(metaclass=ABCMeta):
@abstractmethod
@check_input_type(AbonStruct)
- def update_user(self, user, *args):
- """чтоб обновить абонента надо изменить всё кроме его uid, по uid абонент будет найден"""
+ def update_user(self, user):
+ """чтоб обновить абонента можно изменить всё кроме его uid, по uid абонент будет найден"""
@abstractmethod
@check_input_type(AbonStruct)
diff --git a/agent/mod_mikrotik.py b/agent/mod_mikrotik.py
index e1279d3..b194015 100644
--- a/agent/mod_mikrotik.py
+++ b/agent/mod_mikrotik.py
@@ -10,13 +10,21 @@ from djing.settings import DEBUG
import re
+#DEBUG=False
+
+LIST_USERS_ALLOWED = 'DjingUsersAllowed'
+LIST_USERS_BLOCKED = 'DjingUsersBlocked'
+
+
class ApiRos:
"Routeros api"
+
def __init__(self, sk):
self.sk = sk
self.currenttag = 0
def login(self, username, pwd):
+ chal = None
for repl, attrs in self.talk_iter(["/login"]):
chal = binascii.unhexlify(attrs['=ret'])
md = md5()
@@ -38,7 +46,7 @@ class ApiRos:
if (j == -1):
attrs[w] = ''
else:
- attrs[w[:j]] = w[j+1:]
+ attrs[w[:j]] = w[j + 1:]
yield (reply, attrs)
if reply == '!done': return
@@ -133,7 +141,7 @@ class ApiRos:
return ret
-class MikrotikTransmitter(BaseTransmitter):
+class TransmitterManager(BaseTransmitter):
def __init__(self, login=None, password=None, ip=None, port=None):
ip = ip or settings.NAS_IP
if not ping(ip):
@@ -148,109 +156,250 @@ class MikrotikTransmitter(BaseTransmitter):
raise NasNetworkError('Подключение к %s отклонено (Connection Refused)' % ip)
def __del__(self):
- self.s.close()
+ if hasattr(self, 's'):
+ self.s.close()
- def _exec_cmd_iter(self, cmd):
+ def _exec_cmd(self, cmd):
assert isinstance(cmd, list)
result_iter = self.ar.talk_iter(cmd)
+ res = []
for rt in result_iter:
if rt[0] == '!trap':
raise NasFailedResult(rt[1]['=message'])
- yield rt
+ res.append(rt[1])
+ return res
- def _exec_cmd(self, cmd):
+ def _exec_cmd_iter(self, cmd):
assert isinstance(cmd, list)
result_iter = self.ar.talk_iter(cmd)
- res = []
for rt in result_iter:
if rt[0] == '!trap':
raise NasFailedResult(rt[1]['=message'])
- res.append(rt[1])
- return res
+ yield rt
# Строим объект ShapeItem из инфы, присланной из mikrotik'a
def _build_shape_obj(self, info):
+ # Переводим приставку скорости Mikrotik в Mbit/s
+ def parse_speed(text_speed):
+ text_speed_digit = float(text_speed[:-1] or 0.0)
+ text_append = text_speed[-1:]
+ if text_append == 'M':
+ res = text_speed_digit
+ elif text_append == 'k':
+ res = text_speed_digit / 0x400 # 1024
+ #elif text_append == 'G':
+ # res = text_speed_digit * 0x400
+ else:
+ res = float(re.sub(r'[a-zA-Z]', '', text_speed)) / 0x100000 # (1024**2)
+ return res
+
try:
speeds = info['=max-limit'].split('/')
- speeds = [re.sub(r'[a-zA-Z]', '', sp) for sp in speeds]
- #FIXBUG: не может распознать входные данные на скорость 62k, надо фильтровать буквы в скоростях
- t = TariffStruct(speedIn=speeds[0], speedOut=speeds[1])
+ t = TariffStruct(
+ speedIn=parse_speed(speeds[0]),
+ speedOut=parse_speed(speeds[1])
+ )
a = AbonStruct(
uid=int(info['=name'][3:]),
- ip=info['=target-addresses'][:-3],
+ #FIXME: тут в разных микротиках или =target-addresses или =target
+ ip=info['=target'][:-3],
tariff=t
)
return ShapeItem(abon=a, sid=info['=.id'].replace('*', ''))
except KeyError:
return
+
+class QueueManager(TransmitterManager):
# ищем правило по имени, и возвращаем всю инфу о найденном правиле
- def find_queue(self, name):
+ def find(self, name):
ret = self._exec_cmd(['/queue/simple/print', '?name=%s' % name])
- if ret:
+ if len(ret) > 1:
return self._build_shape_obj(ret[0])
+ def add(self, user):
+ assert isinstance(user, AbonStruct)
+ assert isinstance(user.tariff, TariffStruct)
+ return self._exec_cmd(['/queue/simple/add',
+ '=name=uid%d' % user.uid,
+ #FIXME: тут в разных микротиках или =target-addresses или =target
+ '=target=%s' % user.ip.get_str(),
+ '=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn)
+ ])
+
+ def remove(self, user):
+ assert isinstance(user, AbonStruct)
+ q = self.find('uid%d' % user.uid)
+ return self._exec_cmd(['/queue/simple/remove', '=.id=*' + str(q.sid)])
+
+ def remove_range(self, q_ids):
+ names = ['%d' % usr for usr in q_ids]
+ return self._exec_cmd(['/queue/simple/remove', *names])
+
+ def update(self, user):
+ assert isinstance(user, AbonStruct)
+ queue = self.find('uid%d' % user.uid)
+ if queue is None:
+ # не нашли запись в шейпере об абоненте, добавим
+ return self.add(user)
+ else:
+ mk_id = queue.sid
+ # обновляем шейпер абонента
+ return self._exec_cmd(['/queue/simple/set', '=.id=*' + mk_id,
+ '=name=uid%d' % user.uid,
+ '=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn),
+ #FIXME: тут в разных микротиках или =target-addresses или =target
+ '=target=%s' % user.ip.get_str()
+ ])
+
+ # читаем шейпер, возващаем записи о шейпере
+ def read_queue_iter(self):
+ queues = self._exec_cmd_iter(['/queue/simple/print', '=detail'])
+ for queue in queues:
+ if queue[0] == '!done': return
+ yield self._build_shape_obj(queue[1])
+
+ # то же что и выше, только получаем только номера в микротике
+ def read_mikroids_iter(self):
+ queues = self._exec_cmd_iter(['/queue/simple/print', '=detail'])
+ for queue in queues:
+ if queue[0] == '!done': return
+ yield int(queue[1]['=.id'].replace('*', ''), base=16)
+
+ def disable(self, user):
+ assert isinstance(user, AbonStruct)
+ q = self.find('uid%d' % user.uid)
+ if q is None:
+ self.add(user)
+ return self._exec_cmd(['/queue/simple/disable', '=.id=*' + q.sid])
+
+ def enable(self, user):
+ assert isinstance(user, AbonStruct)
+ q = self.find('uid%d' % user.uid)
+ if q is None:
+ self.add(user)
+ return self._exec_cmd(['/queue/simple/enable', '=.id=*' + q.sid])
+
+
+class IpAddressListManager(TransmitterManager):
+ def add(self, list_name, ip):
+ assert isinstance(ip, IpStruct)
+ return self._exec_cmd([
+ '/ip/firewall/address-list/add',
+ '=list=%s' % list_name,
+ '=address=%s' % ip.get_str()
+ ])
+
+ def _edit(self, ip, mk_id):
+ assert isinstance(ip, IpStruct)
+ return self._exec_cmd([
+ '/ip/firewall/address-list/set', '=.id=' + str(mk_id),
+ '?address=%s' % ip.get_str()
+ ])
+
+ def remove(self, mk_id):
+ return self._exec_cmd([
+ '/ip/firewall/address-list/remove',
+ '=.id=*' + str(mk_id)
+ ])
+
+ def find(self, ip, list_name):
+ assert isinstance(ip, IpStruct)
+ return self._exec_cmd([
+ '/ip/firewall/address-list/print', 'where',
+ '?list=%s' % list_name,
+ '?address=%s' % ip.get_str()
+ ])
+
+ def disable(self, user):
+ r = IpAddressListManager.find(self, user.ip, LIST_USERS_ALLOWED)
+ if len(r) > 1:
+ mk_id = r[0]['=.id']
+ return self._exec_cmd([
+ '/ip/firewall/address-list/disable',
+ '=.id=' + str(mk_id),
+ ])
+
+ def enable(self, user):
+ r = IpAddressListManager.find(self, user.ip, LIST_USERS_ALLOWED)
+ if len(r) > 1:
+ mk_id = r[0]['=.id']
+ return self._exec_cmd([
+ '/ip/firewall/address-list/enable',
+ '=.id=' + str(mk_id),
+ ])
+
+
+class MikrotikTransmitter(QueueManager, IpAddressListManager):
+
def add_user_range(self, user_list):
for usr in user_list:
self.add_user(usr)
- # В @user_ids передаём номера правил из mikrotik
- def remove_user_range(self, user_ids):
- names = ['%d' % usr for usr in user_ids]
- return self._exec_cmd(['/queue/simple/remove', *names])
+ def remove_user_range(self, users):
+ queues = [QueueManager.find(self, 'uid%d' % user.uid) for user in users if isinstance(user, AbonStruct)]
+ queue_names = ["uid%d" % queue.sid for queue in queues]
+ QueueManager.remove_range(self, queue_names)
+ ips = [user.ip for user in users if isinstance(user, AbonStruct)]
+ for ip in ips:
+ ip_list_entity = IpAddressListManager.find(self, ip, LIST_USERS_ALLOWED)
+ if len(ip_list_entity) > 1:
+ IpAddressListManager.remove(self, ip_list_entity[0]['=.id'])
- # добавляем правило шейпинга для указанного ip и со скоростью max-limit=Upload/Download
- # Мы уверены что @user это инстанс класса agent.structs.AbonStruct
def add_user(self, user):
assert isinstance(user.tariff, TariffStruct)
assert isinstance(user.ip, IpStruct)
- return self._exec_cmd(['/queue/simple/add',
- '=name=uid%d' % user.uid,
- '=target-addresses=%s' % user.ip.get_str(),
- '=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn)
- ])
+ QueueManager.add(self, user)
+ IpAddressListManager.add(self, LIST_USERS_ALLOWED, user.ip)
+ # удаляем из списка заблокированных абонентов
+ firewall_ip_list_obj = IpAddressListManager.find(self, user.ip, LIST_USERS_BLOCKED)
+ if len(firewall_ip_list_obj) > 1:
+ IpAddressListManager.remove(self, firewall_ip_list_obj[0]['=.id'])
- # удаляем правило шейпера по имени правила
- # В @user передаём номер правила в mikrotik для абонента
def remove_user(self, user):
- assert type(user) is int
- return self._exec_cmd(['/queue/simple/remove', '=.id=*'+str(user)])
+ QueueManager.remove(self, user)
+ firewall_ip_list_obj = IpAddressListManager.find(self, user.ip, LIST_USERS_ALLOWED)
+ if len(firewall_ip_list_obj) > 1:
+ IpAddressListManager.remove(self, firewall_ip_list_obj[0]['=.id'])
# обновляем основную инфу абонента
- # @mk_id это номер в mikrotik
- def update_user(self, user, mk_id=None):
- assert mk_id is not None
+ def update_user(self, user):
assert isinstance(user.tariff, TariffStruct)
assert isinstance(user.ip, IpStruct)
- return self._exec_cmd(['/queue/simple/set', '=.id=*'+mk_id,
- '=name=uid%d' % user.uid,
- '=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn),
- '=target-addresses=%s' % user.ip.get_str()
- ])
- # читаем абонентов, возващаем абнента и номер в микротике
- def read_users_iter(self):
- ret_it = self._exec_cmd_iter(['/queue/simple/print', '=detail'])
- for re in ret_it:
- if re[0] == '!done': return
- yield self._build_shape_obj(re[1])
+ #ищем ip абонента в списке ip
+ find_res = IpAddressListManager.find(self, user.ip, LIST_USERS_ALLOWED)
- # то же что и выше, только получаем только номера в микротике
- def read_users_mikroids_iter(self):
- ret_it = self._exec_cmd_iter(['/queue/simple/print', '=detail'])
- for re in ret_it:
- if re[0] == '!done': return
- yield int(re[1]['=.id'].replace('*', ''), base=16)
+ # если не найден (mikrotik возвращает пустой словарь в списке если ничего нет)
+ if len(find_res) < 2:
+ # добавим запись об абоненте
+ IpAddressListManager.add(self, LIST_USERS_ALLOWED, user.ip)
+ else:
+ # если ip абонента в биллинге не такой как в mikrotik
+ if find_res[0]['=address'] != user.ip.get_str():
+ # то обновляем запись в mikrotik
+ IpAddressListManager._edit(self, user.ip, find_res[0]['=.id'])
+
+ # Проверяем шейпер
+ queue = QueueManager.find(self, 'uid%d' % user.uid)
+ if queue is None:
+ QueueManager.add(self, user)
+ return
+ if queue.abon != user:
+ print('Is ip:', queue.abon.ip, user.ip)
+ print('Is tariff:', queue.abon.tariff, user.tariff)
+ QueueManager.update(self, user)
# приостановливаем обслуживание абонента
- # в @user передаём номер в микротике
def pause_user(self, user):
- self._exec_cmd(['/queue/simple/disable', '=.id=*'+user])
+ print('Pause')
+ IpAddressListManager.disable(self, user)
+ QueueManager.disable(self, user)
# продолжаем обслуживание абонента
- # в @user передаём номер в микротике
def start_user(self, user):
- self._exec_cmd(['/queue/simple/enable', '=.id=*'+user])
+ QueueManager.enable(self, user)
+ IpAddressListManager.enable(self, user)
# Тарифы хранить нам не надо, так что методы тарифов ниже не реализуем
def add_tariff_range(self, tariff_list):
diff --git a/agent/settings.py b/agent/settings.py
index 253eb71..10b6fd9 100644
--- a/agent/settings.py
+++ b/agent/settings.py
@@ -10,7 +10,7 @@ KEYFILE = "/etc/ssl/server.key"
# Использовать-ли при передаче инфы между NAS и основным сервером SSL
IS_USE_SSL = False
-NAS_IP = '10.52.52.2'
+NAS_IP = '10.12.1.11'
NAS_LOGIN = 'admin'
NAS_PASSW = '2ekc3'
NAS_PORT = 8728
diff --git a/cron.py b/cron.py
index b1c19a0..582ce34 100755
--- a/cron.py
+++ b/cron.py
@@ -11,9 +11,6 @@ from agent import Transmitter, NasNetworkError, NasFailedResult
def main():
tm = Transmitter()
- # получим инфу о записях в NAS
- queues = [queue for queue in tm.read_users_iter()]
-
users = Abon.objects.all()
for user in users:
try:
@@ -36,31 +33,13 @@ def main():
continue
# ищем абонента в списке инфы из nas
- abons = [queue for queue in queues if queue is not None]
- abons = [{'abon': queue.abon, 'mikro_id': queue.sid} for queue in abons if queue.abon.uid == user.pk]
- abons_len = len(abons)
- if abons_len < 1:
- # абонент не найден в nas, добавим
- tm.add_user(ab)
- continue
- elif abons_len > 1:
- # удаляем срез из nas, всё кроме 1й записи
- tm.remove_user_range(
- [mkid['mikro_id'] for mkid in abons[1:]]
- )
- # один абонент
- # сравним совпадает-ли инфа об абоненте в базе и в nas
- if ab == abons[0]['abon']:
- # если всё совпадает, то менять нечего
- continue
+ tm.update_user(ab)
+ # если не активен то приостановим услугу
+ if user.is_active:
+ tm.start_user(ab)
else:
- # иначе обновляем абонента
- tm.update_user(ab, abons[0]['mikro_id'])
- # если не активен то приостановим услугу
- if user.is_active:
- tm.start_user(abons[0]['mikro_id'])
- else:
- tm.pause_user(abons[0]['mikro_id'])
+ tm.pause_user(ab)
+
except NasNetworkError as er:
print("Error:", er)
except NasFailedResult as er: