From 8162a1b17e9f2ae3ec00654e3f6329c1d09cde04 Mon Sep 17 00:00:00 2001 From: ibuler Date: Thu, 1 Dec 2022 15:21:53 +0800 Subject: [PATCH] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20gateway?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- apps/assets/api/domain.py | 10 +- apps/assets/migrations/0116_delete_gateway.py | 16 +++ apps/assets/migrations/0117_gateway.py | 24 ++++ apps/assets/models/asset/host.py | 8 +- apps/assets/models/domain.py | 129 +++--------------- 5 files changed, 64 insertions(+), 123 deletions(-) create mode 100644 apps/assets/migrations/0116_delete_gateway.py create mode 100644 apps/assets/migrations/0117_gateway.py diff --git a/apps/assets/api/domain.py b/apps/assets/api/domain.py index b98cd7273..948bb6a7b 100644 --- a/apps/assets/api/domain.py +++ b/apps/assets/api/domain.py @@ -35,7 +35,7 @@ class GatewayViewSet(OrgBulkModelViewSet): serializer_class = serializers.GatewaySerializer def get_queryset(self): - queryset = Host.get_gateway_queryset() + queryset = Domain.get_gateway_queryset() return queryset @@ -45,17 +45,17 @@ class GatewayTestConnectionApi(SingleObjectMixin, APIView): } def get_queryset(self): - queryset = Host.get_gateway_queryset() + queryset = Domain.get_gateway_queryset() return queryset def post(self, request, *args, **kwargs): - self.object = self.get_object() - local_port = self.request.data.get('port') or self.object.port + gateway = self.get_object() + local_port = self.request.data.get('port') or gateway.port try: local_port = int(local_port) except ValueError: 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: return Response("ok") else: diff --git a/apps/assets/migrations/0116_delete_gateway.py b/apps/assets/migrations/0116_delete_gateway.py new file mode 100644 index 000000000..9ccb1c1b7 --- /dev/null +++ b/apps/assets/migrations/0116_delete_gateway.py @@ -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', + ), + ] diff --git a/apps/assets/migrations/0117_gateway.py b/apps/assets/migrations/0117_gateway.py new file mode 100644 index 000000000..6bf8ce138 --- /dev/null +++ b/apps/assets/migrations/0117_gateway.py @@ -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',), + ), + ] diff --git a/apps/assets/models/asset/host.py b/apps/assets/models/asset/host.py index a1dbb6de3..6ca93b89f 100644 --- a/apps/assets/models/asset/host.py +++ b/apps/assets/models/asset/host.py @@ -3,10 +3,4 @@ from .common import Asset class Host(Asset): - - @classmethod - def get_gateway_queryset(cls): - queryset = cls.objects.filter( - platform__name=GATEWAY_NAME - ) - return queryset + pass diff --git a/apps/assets/models/domain.py b/apps/assets/models/domain.py index 248e02f20..daa08a343 100644 --- a/apps/assets/models/domain.py +++ b/apps/assets/models/domain.py @@ -1,25 +1,20 @@ # -*- coding: utf-8 -*- # import uuid -import socket import random -import paramiko +import paramiko 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 common.db import fields from common.utils import get_logger, lazyproperty from orgs.mixins.models import OrgModelMixin from assets.models import Host -from .base import BaseAccount -from ..const import SecretType +from assets.const import GATEWAY_NAME logger = get_logger(__file__) -__all__ = ['Domain', 'GatewayMixin'] +__all__ = ['Domain'] class Domain(OrgModelMixin): @@ -36,9 +31,16 @@ class Domain(OrgModelMixin): def __str__(self): return self.name + @classmethod + def get_gateway_queryset(cls): + queryset = Host.objects.filter( + platform__name=GATEWAY_NAME + ) + return queryset + @lazyproperty 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): return self.random_gateway() @@ -53,50 +55,11 @@ class Domain(OrgModelMixin): return random.choice(self.gateways) -class GatewayMixin: - id: uuid.UUID - port: int - 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() +class Gateway(Host): + class Meta: + proxy = True def test_connective(self, local_port=None): - # TODO čµ°ansible runner if local_port is None: local_port = self.port @@ -106,7 +69,7 @@ class GatewayMixin: proxy.set_missing_host_key_policy(paramiko.AutoAddPolicy()) try: - proxy.connect(self.address, port=self.port, + proxy.connect(self.ip, port=self.port, username=self.username, password=self.password, pkey=self.private_key_obj) @@ -118,8 +81,8 @@ class GatewayMixin: socket.gaierror) as e: err = str(e) if err.startswith('[Errno None] Unable to connect to port'): - err = _('Unable to connect to port {port} on {address}') - err = err.format(port=self.port, ip=self.address) + err = _('Unable to connect to port {port} on {ip}') + err = err.format(port=self.port, ip=self.ip) elif err == 'Authentication failed.': err = _('Authentication failed') elif err == 'Connect failed': @@ -134,7 +97,7 @@ class GatewayMixin: client.connect("127.0.0.1", port=local_port, username=self.username, password=self.password, - key_filename=self.private_key_path, + key_filename=self.private_key_file, sock=sock, timeout=5) except (paramiko.SSHException, @@ -152,59 +115,3 @@ class GatewayMixin: client.close() self.is_connective = True 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')) - ]