Browse Source

revert to single subnet per subscriber

devel
Dmitry Novikov 7 years ago
parent
commit
59e92f4ff4
  1. 1
      abonapp/models.py
  2. 10
      abonapp/templates/abonapp/peoples.html
  3. 27
      ip_pool/migrations/0003_auto_20181019_1230.py
  4. 2
      ip_pool/models.py
  5. 141
      nas_app/nas_managers/mod_mikrotik.py
  6. 24
      nas_app/nas_managers/structs.py

1
abonapp/models.py

@ -238,7 +238,6 @@ class Abon(BaseAccount):
name="uid%d" % self.pk, name="uid%d" % self.pk,
network=self.ip_address, network=self.ip_address,
max_limit=(abon_tariff.speedIn, abon_tariff.speedOut), max_limit=(abon_tariff.speedIn, abon_tariff.speedOut),
queue_type=SubnetQueue.QUEUE_LEAF,
is_access=self.is_access() is_access=self.is_access()
) )

10
abonapp/templates/abonapp/peoples.html

@ -32,6 +32,7 @@
{% if order_by == 'username' %}<span class="glyphicon glyphicon-filter"></span>{% endif %} {% if order_by == 'username' %}<span class="glyphicon glyphicon-filter"></span>{% endif %}
</th> </th>
<th class="hidden-xs">{% trans 'Last traffic' %}</th> <th class="hidden-xs">{% trans 'Last traffic' %}</th>
<th>{% trans 'Network' %}</th>
<th class="col-xs-3"> <th class="col-xs-3">
<a href="{% url 'abonapp:people_list' group.pk %}?{% url_order_by request order_by='fio' %}"> <a href="{% url 'abonapp:people_list' group.pk %}?{% url_order_by request order_by='fio' %}">
{% trans 'fio' %} {% trans 'fio' %}
@ -88,9 +89,10 @@
{% endif %} {% endif %}
{% endif %} {% endif %}
</td> </td>
<td class="col-xs-2">{{ human.fio }}</td>
<td class="col-xs-2">{{ human.street|default:_('Not assigned') }}</td>
<td class="col-xs-1">{{ human.house|default:'-' }}</td>
<td class="col-xs-1">{{ human.ip_address|default_if_none:'&mdash;' }}</td>
<td class="col-xs-2">{{ human.fio|default:'&mdash;' }}</td>
<td class="col-xs-1">{{ human.street|default:_('Not assigned') }}</td>
<td class="col-xs-1">{{ human.house|default:'&mdash;' }}</td>
<td class="col-xs-1"><a href="tel:{{ human.telephone }}">{{ human.telephone }}</a></td> <td class="col-xs-1"><a href="tel:{{ human.telephone }}">{{ human.telephone }}</a></td>
<td class="col-xs-2"> <td class="col-xs-2">
{% if human.current_tariff %} {% if human.current_tariff %}
@ -117,7 +119,7 @@
</tr> </tr>
{% empty %} {% empty %}
<tr> <tr>
<td colspan="11">
<td colspan="12">
{% trans 'Subscribers not found' %}. {% trans 'Subscribers not found' %}.
{% if perms.abonapp.add_abon %} {% if perms.abonapp.add_abon %}
<a href="{% url 'abonapp:add_abon' group.pk %}">{% trans 'Add abon' %}</a> <a href="{% url 'abonapp:add_abon' group.pk %}">{% trans 'Add abon' %}</a>

27
ip_pool/migrations/0003_auto_20181015_1430.py → ip_pool/migrations/0003_auto_20181019_1230.py

@ -1,4 +1,4 @@
# Generated by Django 2.1 on 2018-10-15 14:30
# Generated by Django 2.1 on 2018-10-19 12:30
from django.db import migrations, models from django.db import migrations, models
import djing.fields import djing.fields
@ -14,10 +14,23 @@ class Migration(migrations.Migration):
migrations.CreateModel( migrations.CreateModel(
name='LeasesHistory', name='LeasesHistory',
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('id', models.AutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name='ID'
)),
('ip', models.GenericIPAddressField(verbose_name='Ip address')), ('ip', models.GenericIPAddressField(verbose_name='Ip address')),
('lease_time', models.DateTimeField(auto_now_add=True, verbose_name='Lease time')),
('mac_addr', djing.fields.MACAddressField(blank=True, integer=True, null=True, verbose_name='Mac address')),
('lease_time', models.DateTimeField(
auto_now_add=True,
verbose_name='Lease time'
)),
('mac_addr', djing.fields.MACAddressField(
blank=True,
integer=True,
null=True,
verbose_name='Mac address'
)),
], ],
options={ options={
'verbose_name': 'History lease', 'verbose_name': 'History lease',
@ -34,10 +47,4 @@ 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,8 +39,6 @@ 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)

141
nas_app/nas_managers/mod_mikrotik.py

@ -4,14 +4,13 @@ import socket
from abc import ABCMeta from abc import ABCMeta
from hashlib import md5 from hashlib import md5
from ipaddress import ip_network, _BaseNetwork from ipaddress import ip_network, _BaseNetwork
from typing import Iterable, Optional, Tuple, Generator, Dict, Iterator, Any
from typing import Iterable, Optional, Tuple, Generator, Dict, Iterator
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 import core from nas_app.nas_managers import core
from nas_app.nas_managers import structs as i_structs 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)
@ -21,14 +20,14 @@ LIST_DEVICES_ALLOWED = 'DjingDevicesAllowed'
class ApiRos(object): class ApiRos(object):
"""Routeros api""" """Routeros api"""
sk = None
__sk = None
is_login = False is_login = False
def __init__(self, ip: str, port: int): def __init__(self, ip: str, port: int):
if self.sk is None:
if self.__sk is None:
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk.connect((ip, port or 8728)) sk.connect((ip, port or 8728))
self.sk = sk
self.__sk = sk
def login(self, username, pwd): def login(self, username, pwd):
if self.is_login: if self.is_login:
@ -147,7 +146,7 @@ class ApiRos(object):
def write_bytes(self, s): def write_bytes(self, s):
n = 0 n = 0
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 core.NasFailedResult("connection closed by remote end") raise core.NasFailedResult("connection closed by remote end")
n += r n += r
@ -155,16 +154,15 @@ class ApiRos(object):
def read_bytes(self, length): def read_bytes(self, length):
ret = b'' ret = b''
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 core.NasFailedResult("connection closed by remote end") raise core.NasFailedResult("connection closed by remote end")
ret += s ret += s
return ret return ret
def __del__(self): def __del__(self):
sk = getattr(self, 'sk')
if sk is not None:
self.sk.close()
if self.__sk is not None:
self.__sk.close()
class MikrotikTransmitter(core.BaseTransmitter, ApiRos, class MikrotikTransmitter(core.BaseTransmitter, ApiRos,
@ -247,14 +245,6 @@ class MikrotikTransmitter(core.BaseTransmitter, ApiRos,
is_access=not disabled, is_access=not disabled,
queue_id=info.get('=.id') 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)
@ -269,8 +259,7 @@ class MikrotikTransmitter(core.BaseTransmitter, ApiRos,
if r: if r:
return self._build_shape_obj(r.get('!re')) return self._build_shape_obj(r.get('!re'))
def add_queue(self, queue: i_structs.SubnetQueue,
parent_name: str) -> None:
def add_queue(self, queue: i_structs.SubnetQueue) -> None:
if not isinstance(queue, i_structs.SubnetQueue): if not isinstance(queue, i_structs.SubnetQueue):
raise TypeError('queue must be instance of SubnetQueue') raise TypeError('queue must be instance of SubnetQueue')
self._exec_cmd(( self._exec_cmd((
@ -279,10 +268,9 @@ class MikrotikTransmitter(core.BaseTransmitter, ApiRos,
# FIXME: тут в разных микротиках или =target-addresses или =target # FIXME: тут в разных микротиках или =target-addresses или =target
'=target=%s' % queue.network, '=target=%s' % queue.network,
'=max-limit=%.3fM/%.3fM' % queue.max_limit, '=max-limit=%.3fM/%.3fM' % queue.max_limit,
'=queue=Djing_SFQ/Djing_SFQ',
'=queue=Djing_pcq/Djing_pcq',
'=burst-time=1/1', '=burst-time=1/1',
'=parent=%s' % parent_name,
'=total-queue=Djing_SFQ'
'=total-queue=Djing_pcq'
)) ))
def remove_queue(self, queue: i_structs.SubnetQueue) -> None: def remove_queue(self, queue: i_structs.SubnetQueue) -> None:
@ -302,13 +290,13 @@ class MikrotikTransmitter(core.BaseTransmitter, ApiRos,
if len(ids) > 1: if len(ids) > 1:
self._exec_cmd(('/queue/simple/remove', '=numbers=%s' % ids)) self._exec_cmd(('/queue/simple/remove', '=numbers=%s' % ids))
def update_queue(self, queue: i_structs.SubnetQueue, parent_name: str):
def update_queue(self, queue: i_structs.SubnetQueue):
if not isinstance(queue, i_structs.SubnetQueue): if not isinstance(queue, i_structs.SubnetQueue):
raise TypeError raise TypeError
if not queue.queue_id: if not queue.queue_id:
queue = self.find_queue(queue.name) queue = self.find_queue(queue.name)
if queue is None: if queue is None:
return self.add_queue(queue, parent_name)
return self.add_queue(queue)
else: else:
cmd = [ cmd = [
'/queue/simple/set', '/queue/simple/set',
@ -317,8 +305,7 @@ class MikrotikTransmitter(core.BaseTransmitter, ApiRos,
# FIXME: тут в разных версиях прошивки микротика # FIXME: тут в разных версиях прошивки микротика
# или =target-addresses или =target # или =target-addresses или =target
'=target=%s' % queue.network, '=target=%s' % queue.network,
'=queue=Djing_SFQ/Djing_SFQ',
'=parent=%s' % parent_name,
'=queue=Djing_pcq/Djing_pcq',
'=burst-time=1/1' '=burst-time=1/1'
] ]
if queue.queue_id: if queue.queue_id:
@ -375,8 +362,9 @@ class MikrotikTransmitter(core.BaseTransmitter, ApiRos,
'?dynamic=no' '?dynamic=no'
)) ))
for dat in nets: for dat in nets:
yield ip_network(dat.get('=address'), strict=False), dat.get(
'=.id')
n = ip_network(dat.get('=address'))
n.queue_id = dat.get('=.id')
yield n
################################################# #################################################
# BaseTransmitter implementation # BaseTransmitter implementation
@ -397,9 +385,9 @@ class MikrotikTransmitter(core.BaseTransmitter, ApiRos,
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, queue: i_structs.SubnetQueue, parent_name=None, *args):
def add_user(self, queue: i_structs.SubnetQueue, *args):
try: try:
self.add_queue(queue, parent_name=parent_name)
self.add_queue(queue)
except core.NasFailedResult as e: except core.NasFailedResult as e:
print('Error:', e) print('Error:', e)
net = queue.network net = queue.network
@ -416,9 +404,8 @@ class MikrotikTransmitter(core.BaseTransmitter, ApiRos,
ip_id = r.get('=.id') ip_id = r.get('=.id')
self.remove_ip(ip_id) self.remove_ip(ip_id)
def update_user(self, queue: i_structs.SubnetQueue, parent_name=None,
*args):
self.update_queue(queue, parent_name)
def update_user(self, queue: i_structs.SubnetQueue, *args):
self.update_queue(queue)
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((
@ -441,80 +428,30 @@ class MikrotikTransmitter(core.BaseTransmitter, ApiRos,
def read_users(self) -> i_structs.VectorQueue: def read_users(self) -> i_structs.VectorQueue:
return self.read_queue_iter() 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
)
# выберем структуры подсетей
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 = (
def sync_nas(self, users_from_db: Iterator):
queues_from_db = (
ab.build_agent_struct() for ab in users_from_db ab.build_agent_struct() for ab in users_from_db
if ab is not None and ab.is_access() if ab is not None and ab.is_access()
) )
queues_from_db = set(filter(lambda x: x is not None, queues_from_db))
queues_from_gw = self.read_queue_iter()
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 _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())
# TODO: надо чтоб корневая очередь тоже создавалась
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))
user_q_for_add, user_q_for_del = core.diff_set(queues_from_db,
set(queues_from_gw))
self.remove_queue_range( self.remove_queue_range(
(q.queue_id for q in user_q_for_del) (q.queue_id for q in user_q_for_del)
) )
for q in user_q_for_add: 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)
self.add_queue(q)
del user_q_for_add, user_q_for_del
# sync ip addrs list
db_nets = set(net.network for net in queues_from_db)
gw_nets = set(self.read_nets_iter(LIST_USERS_ALLOWED))
nets_add, nets_del = core.diff_set(db_nets, gw_nets)
self.remove_ip_range(
(q.queue_id for q in nets_del)
)
for q in nets_add:
self.add_ip(LIST_USERS_ALLOWED, q)

24
nas_app/nas_managers/structs.py

@ -8,22 +8,14 @@ class BaseStruct(object, metaclass=ABCMeta):
class SubnetQueue(BaseStruct): class SubnetQueue(BaseStruct):
__slots__ = ('name', '_net', '_max_limit', '_queue_type',
'is_access', 'queue_id')
# Queue types
QUEUE_UNKNOWN = 0
QUEUE_ROOT = 1
QUEUE_SUBNET = 2
QUEUE_LEAF = 3
__slots__ = ('name', '_net', '_max_limit', 'is_access', 'queue_id')
def __init__(self, name: str, network, max_limit=0.0, def __init__(self, name: str, network, max_limit=0.0,
queue_type=QUEUE_UNKNOWN, is_access=True, queue_id=None):
is_access=True, queue_id=None):
super().__init__() super().__init__()
self.name = name self.name = name
self.network = network self.network = network
self.max_limit = max_limit self.max_limit = max_limit
self.queue_type = queue_type
self.is_access = is_access self.is_access = is_access
self.queue_id = queue_id self.queue_id = queue_id
@ -57,18 +49,6 @@ class SubnetQueue(BaseStruct):
network = property(get_network, set_network) network = property(get_network, set_network)
def get_queue_type(self):
return self._queue_type
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
queue_type = property(get_queue_type, set_queue_type)
def __eq__(self, other): def __eq__(self, other):
return self.network == other.network and self.max_limit == other.max_limit return self.network == other.network and self.max_limit == other.max_limit

Loading…
Cancel
Save