Browse Source

Make queues in tree

devel
Dmitry Novikov 7 years ago
parent
commit
434ecb7bab
  1. 19
      abonapp/models.py
  2. 8
      abonapp/views.py
  3. 8
      ip_pool/migrations/0003_auto_20181015_1430.py
  4. 2
      ip_pool/models.py
  5. 2
      nas_app/nas_managers/__init__.py
  6. 78
      nas_app/nas_managers/core.py
  7. 340
      nas_app/nas_managers/mod_mikrotik.py
  8. 110
      nas_app/nas_managers/structs.py
  9. 4
      nas_app/views.py

19
abonapp/models.py

@ -1,5 +1,4 @@
from datetime import datetime from datetime import datetime
from ipaddress import ip_address
from typing import Optional from typing import Optional
from django.conf import settings from django.conf import settings
@ -13,7 +12,7 @@ from django.utils import timezone
from django.utils.translation import ugettext_lazy as _, gettext from django.utils.translation import ugettext_lazy as _, gettext
from accounts_app.models import UserProfile, MyUserManager, BaseAccount from accounts_app.models import UserProfile, MyUserManager, BaseAccount
from nas_app.nas_managers import AbonStruct, TariffStruct, NasFailedResult, NasNetworkError
from nas_app.nas_managers import SubnetQueue, NasFailedResult, NasNetworkError
from group_app.models import Group from group_app.models import Group
from djing.lib import LogicError from djing.lib import LogicError
from ip_pool.models import NetworkModel from ip_pool.models import NetworkModel
@ -232,14 +231,16 @@ class Abon(BaseAccount):
def build_agent_struct(self): def build_agent_struct(self):
if not self.ip_address: if not self.ip_address:
return return
abon_address = ip_address(self.ip_address)
abon_tariff = self.active_tariff() abon_tariff = self.active_tariff()
if abon_tariff is None:
agent_trf = None
else:
trf = abon_tariff.tariff
agent_trf = TariffStruct(trf.id, trf.speedIn, trf.speedOut)
return AbonStruct(self.pk, abon_address, agent_trf, self.is_access())
if abon_tariff:
abon_tariff = abon_tariff.tariff
return SubnetQueue(
name="uid%d" % self.pk,
network=self.ip_address,
max_limit=(abon_tariff.speedIn, abon_tariff.speedOut),
queue_type=SubnetQueue.QUEUE_LEAF,
is_access=self.is_access()
)
def nas_sync_self(self) -> Optional[Exception]: def nas_sync_self(self) -> Optional[Exception]:
""" """

8
abonapp/views.py

@ -5,7 +5,7 @@ from django.db import IntegrityError, ProgrammingError, transaction, Operational
from django.db.models import Count, Q from django.db.models import Count, Q
from django.shortcuts import render, redirect, get_object_or_404, resolve_url from django.shortcuts import render, redirect, get_object_or_404, resolve_url
from django.contrib.auth.decorators import login_required from django.contrib.auth.decorators import login_required
from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin as PermissionRequiredMixin_django
from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseRedirect from django.http import HttpResponse, HttpResponseBadRequest, HttpResponseRedirect
from django.contrib import messages from django.contrib import messages
from django.urls import reverse_lazy from django.urls import reverse_lazy
@ -90,7 +90,7 @@ class GroupListView(LoginRequiredMixin, OnlyAdminsMixin, OrderedFilteredList):
return queryset return queryset
class AbonCreateView(AbonappPermissionMixin, CreateView):
class AbonCreateView(LoginRequiredMixin, OnlyAdminsMixin, PermissionRequiredMixin_django, CreateView):
permission_required = 'abonapp.add_abon' permission_required = 'abonapp.add_abon'
group = None group = None
abon = None abon = None
@ -244,7 +244,9 @@ class PayHistoryListView(AbonappPermissionMixin, OrderedFilteredList):
template_name = 'abonapp/payHistory.html' template_name = 'abonapp/payHistory.html'
def get_permission_object(self): def get_permission_object(self):
return self.abon.group
if hasattr(self, 'abon'):
return self.abon.group
return models.Group.objects.filter(pk=self.kwargs.get('gid')).first()
def get_queryset(self): def get_queryset(self):
abon = get_object_or_404(models.Abon, username=self.kwargs.get('uname')) abon = get_object_or_404(models.Abon, username=self.kwargs.get('uname'))

8
ip_pool/migrations/0003_add_leases_history_model.py → ip_pool/migrations/0003_auto_20181015_1430.py

@ -1,4 +1,4 @@
# Generated by Django 2.1 on 2018-10-11 11:55
# Generated by Django 2.1 on 2018-10-15 14:30
from django.db import migrations, models from django.db import migrations, models
import djing.fields import djing.fields
@ -34,4 +34,10 @@ class Migration(migrations.Migration):
model_name='ipleasemodel', model_name='ipleasemodel',
name='is_dynamic', name='is_dynamic',
), ),
migrations.AddField(
model_name='networkmodel',
name='speed',
field=models.FloatField(default=0.0, verbose_name='Speed for subnet'),
preserve_default=False,
),
] ]

2
ip_pool/models.py

@ -39,6 +39,8 @@ class NetworkModel(models.Model):
ip_start = models.GenericIPAddressField(_('Start work ip range')) ip_start = models.GenericIPAddressField(_('Start work ip range'))
ip_end = models.GenericIPAddressField(_('End work ip range')) ip_end = models.GenericIPAddressField(_('End work ip range'))
speed = models.FloatField(_('Speed for subnet'))
def __str__(self): def __str__(self):
netw = self.get_network() netw = self.get_network()
return "%s: %s" % (self.description, netw.with_prefixlen) return "%s: %s" % (self.description, netw.with_prefixlen)

2
nas_app/nas_managers/__init__.py

@ -1,6 +1,6 @@
from nas_app.nas_managers.mod_mikrotik import MikrotikTransmitter from nas_app.nas_managers.mod_mikrotik import MikrotikTransmitter
from nas_app.nas_managers.core import NasNetworkError, NasFailedResult from nas_app.nas_managers.core import NasNetworkError, NasFailedResult
from nas_app.nas_managers.structs import TariffStruct, AbonStruct
from nas_app.nas_managers.structs import SubnetQueue
# Указываем какие реализации NAS у нас есть, это будет использоваться в # Указываем какие реализации NAS у нас есть, это будет использоваться в
# web интерфейсе # web интерфейсе

78
nas_app/nas_managers/core.py

@ -1,7 +1,7 @@
from abc import ABC, abstractmethod, abstractproperty from abc import ABC, abstractmethod, abstractproperty
from typing import Iterator, Any, Tuple, Optional from typing import Iterator, Any, Tuple, Optional
from djing import ping from djing import ping
from nas_app.nas_managers.structs import AbonStruct, TariffStruct, VectorAbon, VectorTariff
from nas_app.nas_managers.structs import SubnetQueue, VectorQueue
# Raised if NAS has returned failed result # Raised if NAS has returned failed result
@ -33,66 +33,36 @@ class BaseTransmitter(ABC):
return cls.description return cls.description
@abstractmethod @abstractmethod
def add_user_range(self, user_list: VectorAbon):
def add_user_range(self, queue_list: VectorQueue):
"""add subscribers list to NAS """add subscribers list to NAS
:param user_list: Vector of instances of subscribers
:param queue_list: Vector of instances of subscribers
""" """
@abstractmethod @abstractmethod
def remove_user_range(self, users: VectorAbon):
def remove_user_range(self, queues):
"""remove subscribers list """remove subscribers list
:param users: Vector of instances of subscribers
:param queues: Vector of instances of subscribers
""" """
@abstractmethod @abstractmethod
def add_user(self, user: AbonStruct, *args):
def add_user(self, queue: SubnetQueue, *args):
"""add subscriber """add subscriber
:param user: Subscriber instance
:param queue: Subscriber instance
""" """
@abstractmethod @abstractmethod
def remove_user(self, user: AbonStruct):
def remove_user(self, queue: SubnetQueue):
""" """
remove subscriber remove subscriber
:param user: Subscriber instance
:param queue: Subscriber instance
""" """
@abstractmethod @abstractmethod
def update_user(self, user: AbonStruct, *args):
def update_user(self, queue: SubnetQueue, *args):
""" """
Update subscriber by uid, you can change everything except its uid. Update subscriber by uid, you can change everything except its uid.
Subscriber will found by UID. Subscriber will found by UID.
:param user: Subscriber instance
"""
@abstractmethod
def add_tariff_range(self, tariff_list: VectorTariff):
"""Add services list to NAS.
:param tariff_list: Vector of TariffStruct
"""
@abstractmethod
def remove_tariff_range(self, tariff_list: VectorTariff):
"""Remove tariff list by unique id list.
:param tariff_list: Vector of TariffStruct
"""
@abstractmethod
def add_tariff(self, tariff: TariffStruct):
pass
@abstractmethod
def update_tariff(self, tariff: TariffStruct):
"""
Update tariff by uid, you can change everything except its uid.
Tariff will found by UID.
:param tariff: Service for update
"""
@abstractmethod
def remove_tariff(self, tid: int):
"""
:param tid: unique id of tariff.
:param queue: Subscriber instance
""" """
@abstractmethod @abstractmethod
@ -105,27 +75,9 @@ class BaseTransmitter(ABC):
""" """
@abstractmethod @abstractmethod
def read_users(self) -> VectorAbon:
def read_users(self) -> VectorQueue:
pass pass
# @abstractmethod
# def lease_free(self, user: AbonStruct, lease):
# """
# Remove ip lease from allowed to network
# :param lease: ip_address for lease
# :param user: Subscriber instance
# :return:
# """
#
# @abstractmethod
# def lease_start(self, user: AbonStruct, lease):
# """
# Starts ip lease to allowed to network
# :param lease: ip_address for lease
# :param user: Subscriber instance
# :return:
# """
def _diff_users(self, users_from_db: Iterator[Any]) -> Tuple[set, set]: def _diff_users(self, users_from_db: Iterator[Any]) -> Tuple[set, set]:
""" """
:param users_from_db: QuerySet of all subscribers that can have service :param users_from_db: QuerySet of all subscribers that can have service
@ -153,3 +105,9 @@ class BaseTransmitter(ABC):
for la in list_for_add: for la in list_for_add:
print('\t', la) print('\t', la)
self.add_user_range(list_for_add) self.add_user_range(list_for_add)
def diff_set(one: set, two: set) -> Tuple[set, set]:
list_for_del = (one ^ two) - one
list_for_add = one - two
return list_for_add, list_for_del

340
nas_app/nas_managers/mod_mikrotik.py

@ -3,14 +3,15 @@ import re
import socket import socket
from abc import ABCMeta from abc import ABCMeta
from hashlib import md5 from hashlib import md5
from ipaddress import _BaseAddress, ip_address
from typing import Iterable, Optional, Tuple, Generator, Dict
from ipaddress import ip_network, _BaseNetwork
from typing import Iterable, Optional, Tuple, Generator, Dict, Iterator, Any
from django.conf import settings from django.conf import settings
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from djing.lib.decorators import LazyInitMetaclass from djing.lib.decorators import LazyInitMetaclass
from nas_app.nas_managers.core import BaseTransmitter, NasNetworkError, NasFailedResult
from nas_app.nas_managers.structs import TariffStruct, AbonStruct, VectorAbon, VectorTariff
from nas_app.nas_managers import core
from nas_app.nas_managers import structs as i_structs
from ip_pool.models import NetworkModel
DEBUG = getattr(settings, 'DEBUG', False) DEBUG = getattr(settings, 'DEBUG', False)
@ -40,7 +41,8 @@ class ApiRos(object):
md.update(bytes(pwd, 'utf-8')) md.update(bytes(pwd, 'utf-8'))
md.update(chal) md.update(chal)
for _ in self.talk_iter(("/login", "=name=" + username, for _ in self.talk_iter(("/login", "=name=" + username,
"=response=00" + binascii.hexlify(md.digest()).decode('utf-8'))):
"=response=00" + binascii.hexlify(
md.digest()).decode('utf-8'))):
pass pass
self.is_login = True self.is_login = True
@ -100,12 +102,15 @@ class ApiRos(object):
self.write_bytes(bytes(((l >> 8) & 0xff, l & 0xff))) self.write_bytes(bytes(((l >> 8) & 0xff, l & 0xff)))
elif l < 0x200000: elif l < 0x200000:
l |= 0xC00000 l |= 0xC00000
self.write_bytes(bytes(((l >> 16) & 0xff, (l >> 8) & 0xff, l & 0xff)))
self.write_bytes(
bytes(((l >> 16) & 0xff, (l >> 8) & 0xff, l & 0xff)))
elif l < 0x10000000: elif l < 0x10000000:
l |= 0xE0000000 l |= 0xE0000000
self.write_bytes(bytes(((l >> 24) & 0xff, (l >> 16) & 0xff, (l >> 8) & 0xff, l & 0xff)))
self.write_bytes(bytes(((l >> 24) & 0xff, (l >> 16) & 0xff,
(l >> 8) & 0xff, l & 0xff)))
else: else:
self.write_bytes(bytes((0xf0, (l >> 24) & 0xff, (l >> 16) & 0xff, (l >> 8) & 0xff, l & 0xff)))
self.write_bytes(bytes((0xf0, (l >> 24) & 0xff, (l >> 16) & 0xff,
(l >> 8) & 0xff, l & 0xff)))
def read_len(self): def read_len(self):
c = self.read_bytes(1)[0] c = self.read_bytes(1)[0]
@ -144,7 +149,7 @@ class ApiRos(object):
while n < len(s): while n < len(s):
r = self.sk.send(s[n:]) r = self.sk.send(s[n:])
if r == 0: if r == 0:
raise NasFailedResult("connection closed by remote end")
raise core.NasFailedResult("connection closed by remote end")
n += r n += r
def read_bytes(self, length): def read_bytes(self, length):
@ -152,7 +157,7 @@ class ApiRos(object):
while len(ret) < length: while len(ret) < length:
s = self.sk.recv(length - len(ret)) s = self.sk.recv(length - len(ret))
if len(s) == 0: if len(s) == 0:
raise NasFailedResult("connection closed by remote end")
raise core.NasFailedResult("connection closed by remote end")
ret += s ret += s
return ret return ret
@ -162,19 +167,23 @@ class ApiRos(object):
self.sk.close() self.sk.close()
class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs', (ABCMeta, LazyInitMetaclass), {})):
class MikrotikTransmitter(core.BaseTransmitter, ApiRos,
metaclass=type('_ABC_Lazy_mcs',
(ABCMeta, LazyInitMetaclass), {})):
description = _('Mikrotik NAS') description = _('Mikrotik NAS')
def __init__(self, login: str, password: str, ip: str, port: int, *args, **kwargs):
def __init__(self, login: str, password: str, ip: str, port: int, *args,
**kwargs):
try: try:
BaseTransmitter.__init__(self,
login=login, password=password, ip=ip,
port=port, *args, **kwargs
)
core.BaseTransmitter.__init__(self,
login=login, password=password,
ip=ip,
port=port, *args, **kwargs
)
ApiRos.__init__(self, ip, port) ApiRos.__init__(self, ip, port)
self.login(username=login, pwd=password) self.login(username=login, pwd=password)
except ConnectionRefusedError: except ConnectionRefusedError:
raise NasNetworkError('Connection to %s is Refused' % ip)
raise core.NasNetworkError('Connection to %s is Refused' % ip)
def _exec_cmd(self, cmd: Iterable) -> Dict: def _exec_cmd(self, cmd: Iterable) -> Dict:
if not isinstance(cmd, (list, tuple)): if not isinstance(cmd, (list, tuple)):
@ -184,7 +193,7 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
if k == '!done': if k == '!done':
break break
elif k == '!trap': elif k == '!trap':
raise NasFailedResult(v.get('=message'))
raise core.NasFailedResult(v.get('=message'))
r[k] = v or None r[k] = v or None
return r return r
@ -195,12 +204,12 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
if k == '!done': if k == '!done':
break break
elif k == '!trap': elif k == '!trap':
raise NasFailedResult(v.get('=message'))
raise core.NasFailedResult(v.get('=message'))
if v: if v:
yield v yield v
@staticmethod @staticmethod
def _build_shape_obj(info: Dict) -> AbonStruct:
def _build_shape_obj(info: Dict) -> i_structs.SubnetQueue:
# Переводим приставку скорости Mikrotik в Mbit/s # Переводим приставку скорости Mikrotik в Mbit/s
def parse_speed(text_speed): def parse_speed(text_speed):
text_speed_digit = float(text_speed[:-1] or 0.0) text_speed_digit = float(text_speed[:-1] or 0.0)
@ -216,10 +225,8 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
return res return res
speed_out, speed_in = info['=max-limit'].split('/') speed_out, speed_in = info['=max-limit'].split('/')
t = TariffStruct(
speed_in=parse_speed(speed_in),
speed_out=parse_speed(speed_out)
)
speed_in = parse_speed(speed_in)
speed_out = parse_speed(speed_out)
try: try:
target = info.get('=target') target = info.get('=target')
if target is None: if target is None:
@ -230,19 +237,24 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
disabled = True if disabled == 'true' else False disabled = True if disabled == 'true' else False
if target and name: if target and name:
# target may be '192.168.0.3/32,192.168.0.2/32' # target may be '192.168.0.3/32,192.168.0.2/32'
ip = target.split(',')[0]
if not ip:
return
ip, mask_bits = ip.split('/')
if not ip:
net = target.split(',')[0]
if not net:
return return
a = AbonStruct(
uid=int(name[3:]),
ip=ip,
tariff=t,
is_access=not disabled
a = i_structs.SubnetQueue(
name=name,
network=net,
max_limit=(speed_in, speed_out),
is_access=not disabled,
queue_id=info.get('=.id')
) )
a.queue_id = info.get('=.id')
if name.startswith('uid'):
a.queue_type = i_structs.SubnetQueue.QUEUE_LEAF
elif name.startswith('net_'):
a.queue_type = i_structs.SubnetQueue.QUEUE_SUBNET
elif name == 'queue-root':
a.queue_type = i_structs.SubnetQueue.QUEUE_ROOT
else:
a.queue_type = i_structs.SubnetQueue.QUEUE_UNKNOWN
return a return a
except ValueError as e: except ValueError as e:
print('ValueError:', e) print('ValueError:', e)
@ -252,64 +264,65 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
################################################# #################################################
# Find queue by name # Find queue by name
def find_queue(self, name: str) -> Optional[AbonStruct]:
def find_queue(self, name: str) -> Optional[i_structs.SubnetQueue]:
r = self._exec_cmd(('/queue/simple/print', '?name=%s' % name)) r = self._exec_cmd(('/queue/simple/print', '?name=%s' % name))
if r: if r:
return self._build_shape_obj(r.get('!re')) return self._build_shape_obj(r.get('!re'))
def add_queue(self, user: AbonStruct) -> None:
if not isinstance(user, AbonStruct):
raise TypeError
if user.tariff is None or not isinstance(user.tariff, TariffStruct):
return
def add_queue(self, queue: i_structs.SubnetQueue,
parent_name: str) -> None:
if not isinstance(queue, i_structs.SubnetQueue):
raise TypeError('queue must be instance of SubnetQueue')
self._exec_cmd(( self._exec_cmd((
'/queue/simple/add', '/queue/simple/add',
'=name=uid%d' % user.uid,
'=name=%s' % queue.name,
# FIXME: тут в разных микротиках или =target-addresses или =target # FIXME: тут в разных микротиках или =target-addresses или =target
'=target=%s' % user.ip,
'=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn),
'=target=%s' % queue.network,
'=max-limit=%.3fM/%.3fM' % queue.max_limit,
'=queue=Djing_SFQ/Djing_SFQ', '=queue=Djing_SFQ/Djing_SFQ',
'=burst-time=1/1'
'=burst-time=1/1',
'=parent=%s' % parent_name,
'=total-queue=Djing_SFQ'
)) ))
def remove_queue(self, user: AbonStruct, queue: AbonStruct = None) -> None:
if not isinstance(user, AbonStruct):
def remove_queue(self, queue: i_structs.SubnetQueue) -> None:
if not isinstance(queue, i_structs.SubnetQueue):
raise TypeError raise TypeError
if queue is None:
queue = self.find_queue('uid%d' % user.uid)
if not queue.queue_id:
queue = self.find_queue(queue.name)
if queue is not None: if queue is not None:
queue_id = getattr(queue, 'queue_id')
if queue_id is not None:
if queue.queue_id:
self._exec_cmd(( self._exec_cmd((
'/queue/simple/remove', '/queue/simple/remove',
'=.id=%s' % queue_id
'=.id=%s' % queue.queue_id
)) ))
def remove_queue_range(self, q_ids: Iterable[str]): def remove_queue_range(self, q_ids: Iterable[str]):
self._exec_cmd(('/queue/simple/remove', '=numbers=' + ','.join(q_ids)))
ids = ','.join(q_ids)
if len(ids) > 1:
self._exec_cmd(('/queue/simple/remove', '=numbers=%s' % ids))
def update_queue(self, user: AbonStruct, queue=None):
if not isinstance(user, AbonStruct):
def update_queue(self, queue: i_structs.SubnetQueue, parent_name: str):
if not isinstance(queue, i_structs.SubnetQueue):
raise TypeError raise TypeError
if user.tariff is None:
return
if queue is None:
queue = self.find_queue('uid%d' % user.uid)
if not queue.queue_id:
queue = self.find_queue(queue.name)
if queue is None: if queue is None:
return self.add_queue(user)
return self.add_queue(queue, parent_name)
else: else:
mk_id = getattr(queue, 'queue_id')
cmd = [ cmd = [
'/queue/simple/set', '/queue/simple/set',
'=name=uid%d' % user.uid,
'=max-limit=%.3fM/%.3fM' % (user.tariff.speedOut, user.tariff.speedIn),
# FIXME: тут в разных версиях прошивки микротика или =target-addresses или =target
'=target=%s' % user.ip,
'=name=%s' % queue.name,
'=max-limit=%.3fM/%.3fM' % queue.max_limit,
# FIXME: тут в разных версиях прошивки микротика
# или =target-addresses или =target
'=target=%s' % queue.network,
'=queue=Djing_SFQ/Djing_SFQ', '=queue=Djing_SFQ/Djing_SFQ',
'=parent=%s' % parent_name,
'=burst-time=1/1' '=burst-time=1/1'
] ]
if mk_id is not None:
cmd.insert(1, '=.id=%s' % mk_id)
if queue.queue_id:
cmd.insert(1, '=.id=%s' % queue.queue_id)
r = self._exec_cmd(cmd) r = self._exec_cmd(cmd)
return r return r
@ -323,13 +336,13 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
# Ip->firewall->address list # Ip->firewall->address list
################################################# #################################################
def add_ip(self, list_name: str, ip):
if not issubclass(ip.__class__, _BaseAddress):
def add_ip(self, list_name: str, net):
if not issubclass(net.__class__, _BaseNetwork):
raise TypeError raise TypeError
commands = ( commands = (
'/ip/firewall/address-list/add', '/ip/firewall/address-list/add',
'=list=%s' % list_name, '=list=%s' % list_name,
'=address=%s' % ip
'=address=%s' % net
) )
return self._exec_cmd(commands) return self._exec_cmd(commands)
@ -345,102 +358,67 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
'=numbers=%s' % ','.join(ip_firewall_ids) '=numbers=%s' % ','.join(ip_firewall_ids)
)) ))
def find_ip(self, ip, list_name: str):
if not issubclass(ip.__class__, _BaseAddress):
def find_ip(self, net, list_name: str):
if not issubclass(net.__class__, _BaseNetwork):
raise TypeError raise TypeError
r = self._exec_cmd(( r = self._exec_cmd((
'/ip/firewall/address-list/print', 'where', '/ip/firewall/address-list/print', 'where',
'?list=%s' % list_name, '?list=%s' % list_name,
'?address=%s' % ip
'?address=%s' % net
)) ))
return r.get('!re') return r.get('!re')
def read_ips_iter(self, list_name: str) -> Generator:
ips = self._exec_cmd_iter((
def read_nets_iter(self, list_name: str) -> Generator:
nets = self._exec_cmd_iter((
'/ip/firewall/address-list/print', 'where', '/ip/firewall/address-list/print', 'where',
'?list=%s' % list_name, '?list=%s' % list_name,
'?dynamic=no' '?dynamic=no'
)) ))
for dat in ips:
yield ip_address(dat.get('=address')), dat.get('=.id')
for dat in nets:
yield ip_network(dat.get('=address'), strict=False), dat.get(
'=.id')
################################################# #################################################
# BaseTransmitter implementation # BaseTransmitter implementation
################################################# #################################################
def add_user_range(self, user_list: VectorAbon):
for usr in user_list:
self.add_user(usr)
def add_user_range(self, queue_list: i_structs.VectorQueue):
for q in queue_list:
self.add_user(q)
def remove_user_range(self, users: VectorAbon):
if not isinstance(users, (tuple, list, set)):
def remove_user_range(self, queues: i_structs.VectorQueue):
if not isinstance(queues, (tuple, list, set)):
raise ValueError('*users* is used twice, generator does not fit') raise ValueError('*users* is used twice, generator does not fit')
queue_ids = (usr.queue_id for usr in users if usr is not None)
queue_ids = (q.queue_id for q in queues if q)
self.remove_queue_range(queue_ids) self.remove_queue_range(queue_ids)
for user in users:
if isinstance(user, AbonStruct):
ip_list_entity = self.find_ip(user.ip, LIST_USERS_ALLOWED)
for q in queues:
if isinstance(q, i_structs.SubnetQueue):
ip_list_entity = self.find_ip(q.network, LIST_USERS_ALLOWED)
if ip_list_entity: if ip_list_entity:
self.remove_ip(ip_list_entity.get('=.id')) self.remove_ip(ip_list_entity.get('=.id'))
def add_user(self, user: AbonStruct, *args):
if user.tariff is None:
return
if not isinstance(user.tariff, TariffStruct):
raise TypeError
def add_user(self, queue: i_structs.SubnetQueue, parent_name=None, *args):
try: try:
self.add_queue(user)
except NasFailedResult as e:
self.add_queue(queue, parent_name=parent_name)
except core.NasFailedResult as e:
print('Error:', e) print('Error:', e)
ip = user.ip
if not issubclass(ip.__class__, _BaseAddress):
net = queue.network
if not issubclass(net.__class__, _BaseNetwork):
raise TypeError raise TypeError
try: try:
self.add_ip(LIST_USERS_ALLOWED, ip)
except NasFailedResult as e:
self.add_ip(LIST_USERS_ALLOWED, net)
except core.NasFailedResult as e:
print('Error:', e) print('Error:', e)
def remove_user(self, user: AbonStruct):
self.remove_queue(user)
r = self.find_ip(user.ip, LIST_USERS_ALLOWED)
def remove_user(self, queue: i_structs.SubnetQueue):
self.remove_queue(queue)
r = self.find_ip(queue.network, LIST_USERS_ALLOWED)
ip_id = r.get('=.id') ip_id = r.get('=.id')
self.remove_ip(ip_id) self.remove_ip(ip_id)
def update_user(self, user: AbonStruct, *args):
# queue is instance of AbonStruct
queue = self.find_queue('uid%d' % user.uid)
ip = user.ip
if not issubclass(ip.__class__, _BaseAddress):
raise TypeError
nas_ip = self.find_ip(ip, LIST_USERS_ALLOWED)
if user.is_access:
if nas_ip is None:
self.add_ip(LIST_USERS_ALLOWED, ip)
else:
# если не активен - то и обновлять не надо
# но и выключить на всяк случай надо, а то вдруг был включён
if nas_ip:
# и если найден был - то удалим ip из разрешённых
self.remove_ip(nas_ip.get('=.id'))
if queue is not None:
self.remove_queue(user, queue)
queue = None
# если нет услуги то её не должно быть и в nas
if user.tariff is None:
if queue is not None:
self.remove_queue(user, queue)
return
if not user.is_access:
return
# Проверяем шейпер
if queue is None:
self.add_queue(user)
return
if queue != user:
self.update_queue(user, queue)
def update_user(self, queue: i_structs.SubnetQueue, parent_name=None,
*args):
self.update_queue(queue, parent_name)
def ping(self, host, count=10) -> Optional[Tuple[int, int]]: def ping(self, host, count=10) -> Optional[Tuple[int, int]]:
r = self._exec_cmd(( r = self._exec_cmd((
@ -451,7 +429,8 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
return return
interface = r['!re'].get('=interface') interface = r['!re'].get('=interface')
r = self._exec_cmd(( r = self._exec_cmd((
'/ping', '=address=%s' % host, '=arp-ping=yes', '=interval=100ms', '=count=%d' % count,
'/ping', '=address=%s' % host, '=arp-ping=yes', '=interval=100ms',
'=count=%d' % count,
'=interface=%s' % interface '=interface=%s' % interface
)) ))
res = r.get('!re') res = r.get('!re')
@ -459,22 +438,83 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
received, sent = int(res.get('=received')), int(res.get('=sent')) received, sent = int(res.get('=received')), int(res.get('=sent'))
return received, sent return received, sent
def add_tariff_range(self, tariff_list: VectorTariff):
pass
def read_users(self) -> i_structs.VectorQueue:
return self.read_queue_iter()
@staticmethod
def _build_db_queues(users_from_db: Iterator[Any]) -> Generator:
# Корневая очередь
# FIXME: Корневую очередь надо брать откуда-то
root_queue = i_structs.SubnetQueue(
name='queue-root',
network='10.0.0.0/8',
max_limit=2048,
queue_type=i_structs.SubnetQueue.QUEUE_ROOT
)
def remove_tariff_range(self, tariff_list: VectorTariff):
pass
# выберем структуры подсетей
db_subnet_queues = (i_structs.SubnetQueue(
name="net_%s" % db_net.network,
network=db_net.get_network(),
max_limit=float(db_net.speed),
queue_type=i_structs.SubnetQueue.QUEUE_SUBNET
) for db_net in NetworkModel.objects.all().iterator())
queues_struct_gen = (
ab.build_agent_struct() for ab in users_from_db
if ab is not None and ab.is_access()
)
def add_tariff(self, tariff: TariffStruct):
pass
r = [q for q in queues_struct_gen if q is not None]
r.insert(0, root_queue)
r.extend(db_subnet_queues)
return r, root_queue
def update_tariff(self, tariff: TariffStruct):
pass
def _queues_diff(self, users_from_db: Iterator):
queues_from_db, root_queue = self._build_db_queues(users_from_db)
queues_from_gw = tuple(self.read_queue_iter())
def remove_tariff(self, tid: int):
pass
# TODO: надо чтоб корневая очередь тоже создавалась
def read_users(self) -> VectorAbon:
all_ips = set(ip for ip, mkid in self.read_ips_iter(LIST_USERS_ALLOWED))
queues = (q for q in self.read_queue_iter() if all_ips.issuperset(q.ips))
return queues
db_queues_subnets = (
q for q in queues_from_db
if q.queue_type == i_structs.SubnetQueue.QUEUE_SUBNET
)
gw_queues_subnets = tuple(
q for q in queues_from_gw
if q.queue_type == i_structs.SubnetQueue.QUEUE_SUBNET
)
subnets_for_add, subnets_for_del = core.diff_set(
set(db_queues_subnets), set(gw_queues_subnets))
self.remove_queue_range(
(q.queue_id for q in subnets_for_del)
)
for q in subnets_for_add:
self.add_queue(q, parent_name=root_queue.name)
del subnets_for_add, subnets_for_del
db_queue_users = (
q for q in queues_from_db
if q.queue_type == i_structs.SubnetQueue.QUEUE_LEAF
)
gw_queue_users = (
q for q in queues_from_gw
if q.queue_type == i_structs.SubnetQueue.QUEUE_LEAF
)
user_q_for_add, user_q_for_del = core.diff_set(set(db_queue_users),
set(gw_queue_users))
self.remove_queue_range(
(q.queue_id for q in user_q_for_del)
)
for q in user_q_for_add:
find_filter = filter(
lambda qe: qe.network.overlaps(q.network),
gw_queues_subnets
)
parent_subnet = next(find_filter, root_queue)
self.add_queue(q, parent_name=parent_subnet.name)
def sync_nas(self, users_from_db: Iterator):
self._queues_diff(users_from_db)

110
nas_app/nas_managers/structs.py

@ -1,5 +1,5 @@
from abc import ABCMeta from abc import ABCMeta
from ipaddress import ip_address, _BaseAddress
from ipaddress import ip_network, _BaseNetwork
from typing import Iterable from typing import Iterable
@ -7,68 +7,76 @@ class BaseStruct(object, metaclass=ABCMeta):
__slots__ = () __slots__ = ()
# Как обслуживается абонент
class TariffStruct(BaseStruct):
__slots__ = ('tid', 'speedIn', 'speedOut')
class SubnetQueue(BaseStruct):
__slots__ = ('name', '_net', '_max_limit', '_queue_type',
'is_access', 'queue_id')
def __init__(self, tariff_id=0, speed_in=None, speed_out=None):
self.tid = int(tariff_id)
self.speedIn = speed_in or 0
self.speedOut = speed_out or 0
# Queue types
QUEUE_UNKNOWN = 0
QUEUE_ROOT = 1
QUEUE_SUBNET = 2
QUEUE_LEAF = 3
# Yes, if all variables is zeroed
def is_empty(self):
return self.tid == 0 and self.speedIn == 0 and self.speedOut == 0
def __eq__(self, other):
# не сравниваем id, т.к. тарифы с одинаковыми скоростями для NAS одинаковы
# Да и иногда не удобно доставать из nas id тарифы из базы
return self.speedIn == other.speedIn and self.speedOut == other.speedOut
def __str__(self):
return "Id=%d, speedIn=%.2f, speedOut=%.2f" % (self.tid, self.speedIn, self.speedOut)
def __init__(self, name: str, network, max_limit=0.0,
queue_type=QUEUE_UNKNOWN, is_access=True, queue_id=None):
super().__init__()
self.name = name
self.network = network
self.max_limit = max_limit
self.queue_type = queue_type
self.is_access = is_access
self.queue_id = queue_id
def get_max_limit(self):
return self._max_limit
def set_max_limit(self, v):
if isinstance(v, tuple):
self._max_limit = v
elif isinstance(v, str):
s_in, s_out = v.split('/')
self._max_limit = float(s_in), float(s_out)
elif isinstance(v, (int, float)):
sp = float(v)
self._max_limit = sp, sp
else:
raise ValueError('Unexpected format for max_limit')
# нужно чтоб хеши тарифов In10,Out20 и In20,Out10 были разными
# поэтому сначала float->str и потом хеш
def __hash__(self):
return hash(str(self.speedIn) + str(self.speedOut))
max_limit = property(get_max_limit, set_max_limit)
def get_network(self):
return self._net
# Abon from database
class AbonStruct(BaseStruct):
__slots__ = ('uid', '_ip', 'tariff', 'is_access', 'queue_id')
def set_network(self, v):
if isinstance(v, (str, int)):
self._net = ip_network(v, strict=False)
elif issubclass(v.__class__, _BaseNetwork):
self._net = v
else:
raise ValueError('Unexpected format for network')
def __init__(self, uid=0, ip=None, tariff=None, is_access=True):
self.uid = int(uid or 0)
self._ip = ip
self.tariff = tariff
self.is_access = is_access
self.queue_id = 0
network = property(get_network, set_network)
def get_ip(self):
return self._ip
def get_queue_type(self):
return self._queue_type
def set_ip(self, v):
if issubclass(v.__class__, _BaseAddress):
self._ip = v
else:
self._ip = ip_address(v)
def set_queue_type(self, v):
if not isinstance(v, int):
raise ValueError('queue_type must be int')
if v < self.QUEUE_UNKNOWN or v > self.QUEUE_LEAF:
raise IndexError('queue_type out of range')
self._queue_type = v
ip = property(get_ip, set_ip, doc='Ip address')
queue_type = property(get_queue_type, set_queue_type)
def __eq__(self, other): def __eq__(self, other):
if not isinstance(other, AbonStruct):
raise TypeError
r = self.uid == other.uid and self._ip == other._ip
r = r and self.tariff == other.tariff
return r
def __str__(self):
return "uid=%d, ip=[%s], tariff=%s" % (self.uid, self._ip, self.tariff or '<No Service>')
return self.network == other.network and self.max_limit == other.max_limit
def __hash__(self): def __hash__(self):
return hash(hash(self._ip) + hash(self.tariff) if self.tariff is not None else 0)
return hash(str(self.max_limit) + str(self.network))
def __repr__(self):
return "net %s" % self.network
VectorAbon = Iterable[AbonStruct]
VectorTariff = Iterable[TariffStruct]
VectorQueue = Iterable[SubnetQueue]

4
nas_app/views.py

@ -34,8 +34,8 @@ class NasCreateView(CreateView):
def form_valid(self, form): def form_valid(self, form):
r = super(NasCreateView, self).form_valid(form) r = super(NasCreateView, self).form_valid(form)
assign_perm("nas_app.change_nasmodel", self.request.user, self.object) assign_perm("nas_app.change_nasmodel", self.request.user, self.object)
assign_perm("nas_app.view_nas", self.request.user, self.object)
assign_perm("nas_app.delete_nas", self.request.user, self.object)
assign_perm("nas_app.view_nasmodel", self.request.user, self.object)
assign_perm("nas_app.delete_nasmodel", self.request.user, self.object)
self.request.user.log(self.request.META, 'cnas', '"%(title)s", %(ip)s, %(type)s' % { self.request.user.log(self.request.META, 'cnas', '"%(title)s", %(ip)s, %(type)s' % {
'title': self.object.title, 'title': self.object.title,
'ip': self.object.ip_address, 'ip': self.object.ip_address,

Loading…
Cancel
Save