Browse Source

Переделал работу с mikrotik, большой рефакторинг mod_mikrotik.py

devel
bashmak 9 years ago
parent
commit
bdfc80957a
  1. 72
      abonapp/models.py
  2. 7
      abonapp/templates/abonapp/editAbon.html
  3. 6
      abonapp/views.py
  4. 8
      agent/core.py
  5. 257
      agent/mod_mikrotik.py
  6. 2
      agent/settings.py
  7. 33
      cron.py

72
abonapp/models.py

@ -383,44 +383,36 @@ class AbonRawPassword(models.Model):
def abon_post_save(sender, instance, **kwargs): 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 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): def abon_del_signal(sender, instance, **kwargs):
try: try:
ab = instance.build_agent_struct()
# подключаемся к NAS'у # подключаемся к NAS'у
tm = Transmitter() 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: except NasFailedResult:
return True return True
@ -437,18 +429,7 @@ def abontariff_post_save(sender, instance, **kwargs):
if agent_abon is None: if agent_abon is None:
return True return True
tm = Transmitter() 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: except NasFailedResult:
return True return True
@ -461,10 +442,9 @@ def abontariff_del_signal(sender, instance, **kwargs):
# если у абонента нет ip то и создавать правило не на кого # если у абонента нет ip то и создавать правило не на кого
return return
try: try:
agent_abon = instance.abon.build_agent_struct()
tm = Transmitter() 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: except NasFailedResult:
return True return True

7
abonapp/templates/abonapp/editAbon.html

@ -32,6 +32,13 @@
</div> </div>
</div> </div>
<div class="form-group-sm">
<label for="id_ip" class="col-sm-4 control-label">{% trans 'Ip Address' %}</label>
<div class="col-sm-8">
<input type="text" value="{{ ip|default:'' }}" class="form-control" name="ip" placeholder="192.168.0.101" pattern="^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"/>
</div>
</div>
<div class="form-group-sm"> <div class="form-group-sm">
<label for="id_street" class="col-sm-4 control-label">{% trans 'Street' %}</label> <label for="id_street" class="col-sm-4 control-label">{% trans 'Street' %}</label>

6
abonapp/views.py

@ -518,11 +518,7 @@ def update_nas(request, group_id):
continue continue
agent_abon = usr.build_agent_struct() agent_abon = usr.build_agent_struct()
if agent_abon is not None: 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: except NasFailedResult as e:
messages.error(request, e) messages.error(request, e)
except NasNetworkError as e: except NasNetworkError as e:

8
agent/core.py

@ -37,8 +37,8 @@ class BaseTransmitter(metaclass=ABCMeta):
"""добавляем список абонентов в NAS""" """добавляем список абонентов в NAS"""
@abstractmethod @abstractmethod
@check_input_type(int)
def remove_user_range(self, user_ids):
@check_input_type(AbonStruct)
def remove_user_range(self, users):
"""удаляем список абонентов""" """удаляем список абонентов"""
@abstractmethod @abstractmethod
@ -53,8 +53,8 @@ class BaseTransmitter(metaclass=ABCMeta):
@abstractmethod @abstractmethod
@check_input_type(AbonStruct) @check_input_type(AbonStruct)
def update_user(self, user, *args):
"""чтоб обновить абонента надо изменить всё кроме его uid, по uid абонент будет найден"""
def update_user(self, user):
"""чтоб обновить абонента можно изменить всё кроме его uid, по uid абонент будет найден"""
@abstractmethod @abstractmethod
@check_input_type(AbonStruct) @check_input_type(AbonStruct)

257
agent/mod_mikrotik.py

@ -10,13 +10,21 @@ from djing.settings import DEBUG
import re import re
#DEBUG=False
LIST_USERS_ALLOWED = 'DjingUsersAllowed'
LIST_USERS_BLOCKED = 'DjingUsersBlocked'
class ApiRos: class ApiRos:
"Routeros api" "Routeros api"
def __init__(self, sk): def __init__(self, sk):
self.sk = sk self.sk = sk
self.currenttag = 0 self.currenttag = 0
def login(self, username, pwd): def login(self, username, pwd):
chal = None
for repl, attrs in self.talk_iter(["/login"]): for repl, attrs in self.talk_iter(["/login"]):
chal = binascii.unhexlify(attrs['=ret']) chal = binascii.unhexlify(attrs['=ret'])
md = md5() md = md5()
@ -38,7 +46,7 @@ class ApiRos:
if (j == -1): if (j == -1):
attrs[w] = '' attrs[w] = ''
else: else:
attrs[w[:j]] = w[j+1:]
attrs[w[:j]] = w[j + 1:]
yield (reply, attrs) yield (reply, attrs)
if reply == '!done': return if reply == '!done': return
@ -133,7 +141,7 @@ class ApiRos:
return ret return ret
class MikrotikTransmitter(BaseTransmitter):
class TransmitterManager(BaseTransmitter):
def __init__(self, login=None, password=None, ip=None, port=None): def __init__(self, login=None, password=None, ip=None, port=None):
ip = ip or settings.NAS_IP ip = ip or settings.NAS_IP
if not ping(ip): if not ping(ip):
@ -148,109 +156,250 @@ class MikrotikTransmitter(BaseTransmitter):
raise NasNetworkError('Подключение к %s отклонено (Connection Refused)' % ip) raise NasNetworkError('Подключение к %s отклонено (Connection Refused)' % ip)
def __del__(self): 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) assert isinstance(cmd, list)
result_iter = self.ar.talk_iter(cmd) result_iter = self.ar.talk_iter(cmd)
res = []
for rt in result_iter: for rt in result_iter:
if rt[0] == '!trap': if rt[0] == '!trap':
raise NasFailedResult(rt[1]['=message']) 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) assert isinstance(cmd, list)
result_iter = self.ar.talk_iter(cmd) result_iter = self.ar.talk_iter(cmd)
res = []
for rt in result_iter: for rt in result_iter:
if rt[0] == '!trap': if rt[0] == '!trap':
raise NasFailedResult(rt[1]['=message']) raise NasFailedResult(rt[1]['=message'])
res.append(rt[1])
return res
yield rt
# Строим объект ShapeItem из инфы, присланной из mikrotik'a # Строим объект ShapeItem из инфы, присланной из mikrotik'a
def _build_shape_obj(self, info): 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: try:
speeds = info['=max-limit'].split('/') 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( a = AbonStruct(
uid=int(info['=name'][3:]), uid=int(info['=name'][3:]),
ip=info['=target-addresses'][:-3],
#FIXME: тут в разных микротиках или =target-addresses или =target
ip=info['=target'][:-3],
tariff=t tariff=t
) )
return ShapeItem(abon=a, sid=info['=.id'].replace('*', '')) return ShapeItem(abon=a, sid=info['=.id'].replace('*', ''))
except KeyError: except KeyError:
return return
class QueueManager(TransmitterManager):
# ищем правило по имени, и возвращаем всю инфу о найденном правиле # ищем правило по имени, и возвращаем всю инфу о найденном правиле
def find_queue(self, name):
def find(self, name):
ret = self._exec_cmd(['/queue/simple/print', '?name=%s' % name]) ret = self._exec_cmd(['/queue/simple/print', '?name=%s' % name])
if ret:
if len(ret) > 1:
return self._build_shape_obj(ret[0]) 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): def add_user_range(self, user_list):
for usr in user_list: for usr in user_list:
self.add_user(usr) 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): def add_user(self, user):
assert isinstance(user.tariff, TariffStruct) assert isinstance(user.tariff, TariffStruct)
assert isinstance(user.ip, IpStruct) 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): 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.tariff, TariffStruct)
assert isinstance(user.ip, IpStruct) 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): 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): 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): def add_tariff_range(self, tariff_list):

2
agent/settings.py

@ -10,7 +10,7 @@ KEYFILE = "/etc/ssl/server.key"
# Использовать-ли при передаче инфы между NAS и основным сервером SSL # Использовать-ли при передаче инфы между NAS и основным сервером SSL
IS_USE_SSL = False IS_USE_SSL = False
NAS_IP = '10.52.52.2'
NAS_IP = '10.12.1.11'
NAS_LOGIN = 'admin' NAS_LOGIN = 'admin'
NAS_PASSW = '2ekc3' NAS_PASSW = '2ekc3'
NAS_PORT = 8728 NAS_PORT = 8728

33
cron.py

@ -11,9 +11,6 @@ from agent import Transmitter, NasNetworkError, NasFailedResult
def main(): def main():
tm = Transmitter() tm = Transmitter()
# получим инфу о записях в NAS
queues = [queue for queue in tm.read_users_iter()]
users = Abon.objects.all() users = Abon.objects.all()
for user in users: for user in users:
try: try:
@ -36,31 +33,13 @@ def main():
continue continue
# ищем абонента в списке инфы из nas # ищем абонента в списке инфы из 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: 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: except NasNetworkError as er:
print("Error:", er) print("Error:", er)
except NasFailedResult as er: except NasFailedResult as er:

Loading…
Cancel
Save