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 ipaddress import ip_address
from typing import Optional
from django.conf import settings
@ -13,7 +12,7 @@ from django.utils import timezone
from django.utils.translation import ugettext_lazy as _, gettext
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 djing.lib import LogicError
from ip_pool.models import NetworkModel
@ -232,14 +231,16 @@ class Abon(BaseAccount):
def build_agent_struct(self):
if not self.ip_address:
return
abon_address = ip_address(self.ip_address)
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]:
"""

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.shortcuts import render, redirect, get_object_or_404, resolve_url
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.contrib import messages
from django.urls import reverse_lazy
@ -90,7 +90,7 @@ class GroupListView(LoginRequiredMixin, OnlyAdminsMixin, OrderedFilteredList):
return queryset
class AbonCreateView(AbonappPermissionMixin, CreateView):
class AbonCreateView(LoginRequiredMixin, OnlyAdminsMixin, PermissionRequiredMixin_django, CreateView):
permission_required = 'abonapp.add_abon'
group = None
abon = None
@ -244,7 +244,9 @@ class PayHistoryListView(AbonappPermissionMixin, OrderedFilteredList):
template_name = 'abonapp/payHistory.html'
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):
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
import djing.fields
@ -34,4 +34,10 @@ class Migration(migrations.Migration):
model_name='ipleasemodel',
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_end = models.GenericIPAddressField(_('End work ip range'))
speed = models.FloatField(_('Speed for subnet'))
def __str__(self):
netw = self.get_network()
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.core import NasNetworkError, NasFailedResult
from nas_app.nas_managers.structs import TariffStruct, AbonStruct
from nas_app.nas_managers.structs import SubnetQueue
# Указываем какие реализации NAS у нас есть, это будет использоваться в
# web интерфейсе

78
nas_app/nas_managers/core.py

@ -1,7 +1,7 @@
from abc import ABC, abstractmethod, abstractproperty
from typing import Iterator, Any, Tuple, Optional
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
@ -33,66 +33,36 @@ class BaseTransmitter(ABC):
return cls.description
@abstractmethod
def add_user_range(self, user_list: VectorAbon):
def add_user_range(self, queue_list: VectorQueue):
"""add subscribers list to NAS
:param user_list: Vector of instances of subscribers
:param queue_list: Vector of instances of subscribers
"""
@abstractmethod
def remove_user_range(self, users: VectorAbon):
def remove_user_range(self, queues):
"""remove subscribers list
:param users: Vector of instances of subscribers
:param queues: Vector of instances of subscribers
"""
@abstractmethod
def add_user(self, user: AbonStruct, *args):
def add_user(self, queue: SubnetQueue, *args):
"""add subscriber
:param user: Subscriber instance
:param queue: Subscriber instance
"""
@abstractmethod
def remove_user(self, user: AbonStruct):
def remove_user(self, queue: SubnetQueue):
"""
remove subscriber
:param user: Subscriber instance
:param queue: Subscriber instance
"""
@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.
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
@ -105,27 +75,9 @@ class BaseTransmitter(ABC):
"""
@abstractmethod
def read_users(self) -> VectorAbon:
def read_users(self) -> VectorQueue:
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]:
"""
: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:
print('\t', la)
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
from abc import ABCMeta
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.utils.translation import ugettext_lazy as _
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)
@ -40,7 +41,8 @@ class ApiRos(object):
md.update(bytes(pwd, 'utf-8'))
md.update(chal)
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
self.is_login = True
@ -100,12 +102,15 @@ class ApiRos(object):
self.write_bytes(bytes(((l >> 8) & 0xff, l & 0xff)))
elif l < 0x200000:
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:
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:
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):
c = self.read_bytes(1)[0]
@ -144,7 +149,7 @@ class ApiRos(object):
while n < len(s):
r = self.sk.send(s[n:])
if r == 0:
raise NasFailedResult("connection closed by remote end")
raise core.NasFailedResult("connection closed by remote end")
n += r
def read_bytes(self, length):
@ -152,7 +157,7 @@ class ApiRos(object):
while len(ret) < length:
s = self.sk.recv(length - len(ret))
if len(s) == 0:
raise NasFailedResult("connection closed by remote end")
raise core.NasFailedResult("connection closed by remote end")
ret += s
return ret
@ -162,19 +167,23 @@ class ApiRos(object):
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')
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:
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)
self.login(username=login, pwd=password)
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:
if not isinstance(cmd, (list, tuple)):
@ -184,7 +193,7 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
if k == '!done':
break
elif k == '!trap':
raise NasFailedResult(v.get('=message'))
raise core.NasFailedResult(v.get('=message'))
r[k] = v or None
return r
@ -195,12 +204,12 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
if k == '!done':
break
elif k == '!trap':
raise NasFailedResult(v.get('=message'))
raise core.NasFailedResult(v.get('=message'))
if v:
yield v
@staticmethod
def _build_shape_obj(info: Dict) -> AbonStruct:
def _build_shape_obj(info: Dict) -> i_structs.SubnetQueue:
# Переводим приставку скорости Mikrotik в Mbit/s
def parse_speed(text_speed):
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
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:
target = info.get('=target')
if target is None:
@ -230,19 +237,24 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
disabled = True if disabled == 'true' else False
if target and name:
# 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
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
except ValueError as e:
print('ValueError:', e)
@ -252,64 +264,65 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
#################################################
# 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))
if r:
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((
'/queue/simple/add',
'=name=uid%d' % user.uid,
'=name=%s' % queue.name,
# 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',
'=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
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:
queue_id = getattr(queue, 'queue_id')
if queue_id is not None:
if queue.queue_id:
self._exec_cmd((
'/queue/simple/remove',
'=.id=%s' % queue_id
'=.id=%s' % queue.queue_id
))
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
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:
return self.add_queue(user)
return self.add_queue(queue, parent_name)
else:
mk_id = getattr(queue, 'queue_id')
cmd = [
'/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',
'=parent=%s' % parent_name,
'=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)
return r
@ -323,13 +336,13 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
# 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
commands = (
'/ip/firewall/address-list/add',
'=list=%s' % list_name,
'=address=%s' % ip
'=address=%s' % net
)
return self._exec_cmd(commands)
@ -345,102 +358,67 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
'=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
r = self._exec_cmd((
'/ip/firewall/address-list/print', 'where',
'?list=%s' % list_name,
'?address=%s' % ip
'?address=%s' % net
))
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',
'?list=%s' % list_name,
'?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
#################################################
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')
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)
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:
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:
self.add_queue(user)
except NasFailedResult as e:
self.add_queue(queue, parent_name=parent_name)
except core.NasFailedResult as e:
print('Error:', e)
ip = user.ip
if not issubclass(ip.__class__, _BaseAddress):
net = queue.network
if not issubclass(net.__class__, _BaseNetwork):
raise TypeError
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)
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')
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]]:
r = self._exec_cmd((
@ -451,7 +429,8 @@ class MikrotikTransmitter(BaseTransmitter, ApiRos, metaclass=type('_ABC_Lazy_mcs
return
interface = r['!re'].get('=interface')
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
))
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'))
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 ipaddress import ip_address, _BaseAddress
from ipaddress import ip_network, _BaseNetwork
from typing import Iterable
@ -7,68 +7,76 @@ class BaseStruct(object, metaclass=ABCMeta):
__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):
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):
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):
r = super(NasCreateView, self).form_valid(form)
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' % {
'title': self.object.title,
'ip': self.object.ip_address,

Loading…
Cancel
Save