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: