perf: 优化 gateway

pull/9145/head
ibuler 2022-12-01 15:21:53 +08:00
parent d1461b33c5
commit 8162a1b17e
5 changed files with 64 additions and 123 deletions

View File

@ -35,7 +35,7 @@ class GatewayViewSet(OrgBulkModelViewSet):
serializer_class = serializers.GatewaySerializer serializer_class = serializers.GatewaySerializer
def get_queryset(self): def get_queryset(self):
queryset = Host.get_gateway_queryset() queryset = Domain.get_gateway_queryset()
return queryset return queryset
@ -45,17 +45,17 @@ class GatewayTestConnectionApi(SingleObjectMixin, APIView):
} }
def get_queryset(self): def get_queryset(self):
queryset = Host.get_gateway_queryset() queryset = Domain.get_gateway_queryset()
return queryset return queryset
def post(self, request, *args, **kwargs): def post(self, request, *args, **kwargs):
self.object = self.get_object() gateway = self.get_object()
local_port = self.request.data.get('port') or self.object.port local_port = self.request.data.get('port') or gateway.port
try: try:
local_port = int(local_port) local_port = int(local_port)
except ValueError: except ValueError:
raise ValidationError({'port': _('Number required')}) raise ValidationError({'port': _('Number required')})
ok, e = self.object.test_connective(local_port=local_port) ok, e = gateway.test_connective(local_port=local_port)
if ok: if ok:
return Response("ok") return Response("ok")
else: else:

View File

@ -0,0 +1,16 @@
# Generated by Django 3.2.14 on 2022-12-01 07:08
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0115_auto_20221130_1118'),
]
operations = [
migrations.DeleteModel(
name='Gateway',
),
]

View File

@ -0,0 +1,24 @@
# Generated by Django 3.2.14 on 2022-12-01 07:21
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('assets', '0116_delete_gateway'),
]
operations = [
migrations.CreateModel(
name='Gateway',
fields=[
],
options={
'proxy': True,
'indexes': [],
'constraints': [],
},
bases=('assets.host',),
),
]

View File

@ -3,10 +3,4 @@ from .common import Asset
class Host(Asset): class Host(Asset):
pass
@classmethod
def get_gateway_queryset(cls):
queryset = cls.objects.filter(
platform__name=GATEWAY_NAME
)
return queryset

View File

@ -1,25 +1,20 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
import uuid import uuid
import socket
import random import random
import paramiko
import paramiko
from django.db import models from django.db import models
from django.core.cache import cache
from django.db.models.query import QuerySet
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from common.db import fields
from common.utils import get_logger, lazyproperty from common.utils import get_logger, lazyproperty
from orgs.mixins.models import OrgModelMixin from orgs.mixins.models import OrgModelMixin
from assets.models import Host from assets.models import Host
from .base import BaseAccount from assets.const import GATEWAY_NAME
from ..const import SecretType
logger = get_logger(__file__) logger = get_logger(__file__)
__all__ = ['Domain', 'GatewayMixin'] __all__ = ['Domain']
class Domain(OrgModelMixin): class Domain(OrgModelMixin):
@ -36,9 +31,16 @@ class Domain(OrgModelMixin):
def __str__(self): def __str__(self):
return self.name return self.name
@classmethod
def get_gateway_queryset(cls):
queryset = Host.objects.filter(
platform__name=GATEWAY_NAME
)
return queryset
@lazyproperty @lazyproperty
def gateways(self): def gateways(self):
return Host.get_gateway_queryset().filter(domain=self, is_active=True) return self.get_gateway_queryset().filter(domain=self, is_active=True)
def select_gateway(self): def select_gateway(self):
return self.random_gateway() return self.random_gateway()
@ -53,50 +55,11 @@ class Domain(OrgModelMixin):
return random.choice(self.gateways) return random.choice(self.gateways)
class GatewayMixin: class Gateway(Host):
id: uuid.UUID class Meta:
port: int proxy = True
address: str
accounts: QuerySet
private_key_path: str
private_key_obj: paramiko.RSAKey
UNCONNECTED_KEY_TMPL = 'asset_unconnective_gateway_{}'
UNCONNECTED_SILENCE_PERIOD_KEY_TMPL = 'asset_unconnective_gateway_silence_period_{}'
UNCONNECTED_SILENCE_PERIOD_BEGIN_VALUE = 60 * 5
def set_unconnected(self):
unconnected_key = self.UNCONNECTED_KEY_TMPL.format(self.id)
unconnected_silence_period_key = self.UNCONNECTED_SILENCE_PERIOD_KEY_TMPL.format(self.id)
unconnected_silence_period = cache.get(
unconnected_silence_period_key, self.UNCONNECTED_SILENCE_PERIOD_BEGIN_VALUE
)
cache.set(unconnected_silence_period_key, unconnected_silence_period * 2)
cache.set(unconnected_key, unconnected_silence_period, unconnected_silence_period)
def set_connective(self):
unconnected_key = self.UNCONNECTED_KEY_TMPL.format(self.id)
unconnected_silence_period_key = self.UNCONNECTED_SILENCE_PERIOD_KEY_TMPL.format(self.id)
cache.delete(unconnected_key)
cache.delete(unconnected_silence_period_key)
def get_is_unconnected(self):
unconnected_key = self.UNCONNECTED_KEY_TMPL.format(self.id)
return cache.get(unconnected_key, False)
@property
def is_connective(self):
return not self.get_is_unconnected()
@is_connective.setter
def is_connective(self, value):
if value:
self.set_connective()
else:
self.set_unconnected()
def test_connective(self, local_port=None): def test_connective(self, local_port=None):
# TODO 走ansible runner
if local_port is None: if local_port is None:
local_port = self.port local_port = self.port
@ -106,7 +69,7 @@ class GatewayMixin:
proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy()) proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy())
try: try:
proxy.connect(self.address, port=self.port, proxy.connect(self.ip, port=self.port,
username=self.username, username=self.username,
password=self.password, password=self.password,
pkey=self.private_key_obj) pkey=self.private_key_obj)
@ -118,8 +81,8 @@ class GatewayMixin:
socket.gaierror) as e: socket.gaierror) as e:
err = str(e) err = str(e)
if err.startswith('[Errno None] Unable to connect to port'): if err.startswith('[Errno None] Unable to connect to port'):
err = _('Unable to connect to port {port} on {address}') err = _('Unable to connect to port {port} on {ip}')
err = err.format(port=self.port, ip=self.address) err = err.format(port=self.port, ip=self.ip)
elif err == 'Authentication failed.': elif err == 'Authentication failed.':
err = _('Authentication failed') err = _('Authentication failed')
elif err == 'Connect failed': elif err == 'Connect failed':
@ -134,7 +97,7 @@ class GatewayMixin:
client.connect("127.0.0.1", port=local_port, client.connect("127.0.0.1", port=local_port,
username=self.username, username=self.username,
password=self.password, password=self.password,
key_filename=self.private_key_path, key_filename=self.private_key_file,
sock=sock, sock=sock,
timeout=5) timeout=5)
except (paramiko.SSHException, except (paramiko.SSHException,
@ -152,59 +115,3 @@ class GatewayMixin:
client.close() client.close()
self.is_connective = True self.is_connective = True
return True, None return True, None
@lazyproperty
def username(self):
account = self.accounts.all().first()
if account:
return account.username
logger.error(f'Gateway {self} has no account')
return ''
def get_secret(self, secret_type):
account = self.accounts.filter(secret_type=secret_type).first()
if account:
return account.secret
logger.error(f'Gateway {self} has no {secret_type} account')
@lazyproperty
def password(self):
secret_type = SecretType.PASSWORD
return self.get_secret(secret_type)
@lazyproperty
def private_key(self):
secret_type = SecretType.SSH_KEY
return self.get_secret(secret_type)
class Gateway(BaseAccount):
class Protocol(models.TextChoices):
ssh = 'ssh', 'SSH'
name = models.CharField(max_length=128, verbose_name='Name')
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
port = models.IntegerField(default=22, verbose_name=_('Port'))
protocol = models.CharField(
choices=Protocol.choices, max_length=16, default=Protocol.ssh, verbose_name=_("Protocol")
)
domain = models.ForeignKey(Domain, on_delete=models.CASCADE, verbose_name=_("Domain"))
comment = models.CharField(max_length=128, blank=True, null=True, verbose_name=_("Comment"))
is_active = models.BooleanField(default=True, verbose_name=_("Is active"))
password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password'))
private_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH private key'))
public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))
secret = None
secret_type = None
privileged = None
def __str__(self):
return self.name
class Meta:
unique_together = [('name', 'org_id')]
verbose_name = _("Gateway")
permissions = [
('test_gateway', _('Test gateway'))
]