perf: stash it

pull/8931/head
ibuler 2022-09-19 20:11:55 +08:00
parent 762d84b5c3
commit 5d48d1ab15
16 changed files with 226 additions and 195 deletions

View File

@ -1,30 +1,51 @@
from django.db.models import TextChoices
from .protocol import Protocol
class ConstrainMixin: class BaseType(TextChoices):
def get_constrains(self): """
pass 约束应该考虑代是对平台对限制避免多余对选项: mysql 开启 ssh, 或者开启了也没有作用, 比如 k8s 开启了 domain目前还不支持
"""
@classmethod
def get_constrains(cls):
constrains = {}
def _get_category_constrains(self) -> dict: base = cls._get_base_constrains()
raise NotImplementedError protocols = cls._get_protocol_constrains()
automation = cls._get_automation_constrains()
def _get_protocol_constrains(self) -> dict: base_default = base.pop('*', {})
raise NotImplementedError protocols_default = protocols.pop('*', {})
automation_default = automation.pop('*', {})
def _get_automation_constrains(self) -> dict: for k, v in cls.choices:
tp_base = {**base_default, **base.get(k, {})}
tp_auto = {**automation_default, **automation.get(k, {})}
tp_protocols = {**protocols_default, **protocols.get(k, {})}
tp_protocols = cls._parse_protocols(tp_protocols, k)
tp_constrains = {**tp_base, 'protocols': tp_protocols, 'automation': tp_auto}
constrains[k] = tp_constrains
return constrains
@classmethod
def _parse_protocols(cls, protocol, tp):
default_ports = Protocol.default_ports()
choices = protocol.get('choices', [])
if choices == '__self__':
choices = [tp]
protocols = [{'name': name, 'port': default_ports.get(name, 0)} for name in choices]
return protocols
@classmethod
def _get_base_constrains(cls) -> dict:
raise NotImplementedError raise NotImplementedError
@classmethod @classmethod
def platform_constraints(cls): def _get_protocol_constrains(cls) -> dict:
return { raise NotImplementedError
'domain_enabled': False,
'su_enabled': False, @classmethod
'brand_enabled': False, def _get_automation_constrains(cls) -> dict:
'ping_enabled': False, raise NotImplementedError
'gather_facts_enabled': False,
'change_password_enabled': False,
'verify_account_enabled': False,
'create_account_enabled': False,
'gather_accounts_enabled': False,
'_protocols': []
}

View File

@ -1,17 +1,13 @@
from django.db import models from django.db import models
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from common.db.models import IncludesTextChoicesMeta, ChoicesMixin from common.db.models import ChoicesMixin
__all__ = ['Category']
__all__ = [
'Category', 'ConstrainMixin'
]
class Category(ChoicesMixin, models.TextChoices):
class Category(ConstrainMixin, ChoicesMixin, models.TextChoices):
HOST = 'host', _('Host') HOST = 'host', _('Host')
DEVICE = 'device', _("Device") DEVICE = 'device', _("Device")
DATABASE = 'database', _("Database") DATABASE = 'database', _("Database")

View File

@ -1,31 +1,40 @@
from django.db import models from .base import BaseType
from common.db.models import ChoicesMixin
from .category import ConstrainMixin class CloudTypes(BaseType):
PUBLIC = 'public', 'Public cloud'
PRIVATE = 'private', 'Private cloud'
class CloudTypes(ConstrainMixin, ChoicesMixin, models.TextChoices):
K8S = 'k8s', 'Kubernetes' K8S = 'k8s', 'Kubernetes'
def category_constrains(self): @classmethod
def _get_base_constrains(cls) -> dict:
return { return {
'domain_enabled': False, '*': {
'su_enabled': False, 'domain_enabled': False,
'ping_enabled': False, 'su_enabled': False,
'gather_facts_enabled': False, }
'verify_account_enabled': False,
'change_password_enabled': False,
'create_account_enabled': False,
'gather_accounts_enabled': False,
'_protocols': []
} }
@classmethod @classmethod
def platform_constraints(cls): def _get_automation_constrains(cls) -> dict:
return { constrains = {
cls.K8S: { '*': {
'_protocols': ['k8s'] 'gather_facts_enabled': False,
'verify_account_enabled': False,
'change_password_enabled': False,
'create_account_enabled': False,
'gather_accounts_enabled': False,
}
}
return constrains
@classmethod
def _get_protocol_constrains(cls) -> dict:
return {
'*': {
'choices': ['http', 'api'],
},
cls.K8S: {
'choices': ['k8s']
} }
} }

View File

@ -1,6 +1,8 @@
from .base import BaseType
class DatabaseTypes(ConstrainMixin, ChoicesMixin, models.TextChoices): class DatabaseTypes(BaseType):
MYSQL = 'mysql', 'MySQL' MYSQL = 'mysql', 'MySQL'
MARIADB = 'mariadb', 'MariaDB' MARIADB = 'mariadb', 'MariaDB'
POSTGRESQL = 'postgresql', 'PostgreSQL' POSTGRESQL = 'postgresql', 'PostgreSQL'
@ -9,28 +11,33 @@ class DatabaseTypes(ConstrainMixin, ChoicesMixin, models.TextChoices):
MONGODB = 'mongodb', 'MongoDB' MONGODB = 'mongodb', 'MongoDB'
REDIS = 'redis', 'Redis' REDIS = 'redis', 'Redis'
def category_constrains(self): @classmethod
def _get_base_constrains(cls) -> dict:
return { return {
'domain_enabled': True, '*': {
'su_enabled': False, 'domain_enabled': True,
'gather_facts_enabled': True, 'su_enabled': False,
'verify_account_enabled': True, }
'change_password_enabled': True,
'create_account_enabled': True,
'gather_accounts_enabled': True,
'_protocols': []
} }
@classmethod @classmethod
def platform_constraints(cls): def _get_automation_constrains(cls) -> dict:
meta = {} constrains = {
for name, label in cls.choices: '*': {
meta[name] = { 'gather_facts_enabled': True,
'_protocols': [name], 'gather_accounts_enabled': True,
'gather_facts_method': f'gather_facts_{name}', 'verify_account_enabled': True,
'verify_account_method': f'verify_account_{name}', 'change_password_enabled': True,
'change_password_method': f'change_password_{name}', 'create_account_enabled': True,
'create_account_method': f'create_account_{name}',
'gather_accounts_method': f'gather_accounts_{name}',
} }
return meta }
return constrains
@classmethod
def _get_protocol_constrains(cls) -> dict:
return {
'*': {
'choices': '__self__',
}
}

View File

@ -1,30 +1,40 @@
from django.utils.translation import gettext_lazy as _
from .base import BaseType
class DeviceTypes(ConstrainMixin, ChoicesMixin, models.TextChoices): class DeviceTypes(BaseType):
GENERAL = 'general', _("General device") GENERAL = 'general', _("General device")
SWITCH = 'switch', _("Switch") SWITCH = 'switch', _("Switch")
ROUTER = 'router', _("Router") ROUTER = 'router', _("Router")
FIREWALL = 'firewall', _("Firewall") FIREWALL = 'firewall', _("Firewall")
@classmethod @classmethod
def category_constrains(cls): def _get_base_constrains(cls) -> dict:
return { return {
'domain_enabled': True, '*': {
'brand_enabled': True, 'domain_enabled': True,
'brands': [ 'su_enabled': False,
('huawei', 'Huawei'), }
('cisco', 'Cisco'), }
('juniper', 'Juniper'),
('h3c', 'H3C'), @classmethod
('dell', 'Dell'), def _get_protocol_constrains(cls) -> dict:
('other', 'Other'), return {
], '*': {
'su_enabled': False, 'choices': ['ssh', 'telnet']
'ping_enabled': True, 'ping_method': 'ping', }
'gather_facts_enabled': False, }
'verify_account_enabled': False,
'change_password_enabled': False, @classmethod
'create_account_enabled': False, def _get_automation_constrains(cls) -> dict:
'gather_accounts_enabled': False, return {
'_protocols': ['ssh', 'telnet'] '*': {
'ping_enabled': True,
'gather_facts_enabled': False,
'gather_accounts_enabled': False,
'verify_account_enabled': False,
'change_password_enabled': False,
'create_account_enabled': False,
}
} }

View File

@ -1,40 +1,44 @@
from .base import BaseType
class HostTypes(ConstrainMixin, ChoicesMixin, models.TextChoices):
class HostTypes(BaseType):
LINUX = 'linux', 'Linux' LINUX = 'linux', 'Linux'
WINDOWS = 'windows', 'Windows' WINDOWS = 'windows', 'Windows'
UNIX = 'unix', 'Unix' UNIX = 'unix', 'Unix'
OTHER_HOST = 'other', _("Other") OTHER_HOST = 'other', "Other"
@staticmethod @classmethod
def category_constrains(): def _get_base_constrains(cls) -> dict:
return { return {
'domain_enabled': True, '*': {
'su_enabled': True, 'su_method': 'sudo', 'domain_enabled': True,
'ping_enabled': True, 'ping_method': 'ping', 'su_enabled': True,
'gather_facts_enabled': True, 'gather_facts_method': 'gather_facts_posix', },
'verify_account_enabled': True, 'verify_account_method': 'verify_account_posix', cls.WINDOWS: {
'change_password_enabled': True, 'change_password_method': 'change_password_posix', 'su_enabled': False,
'create_account_enabled': True, 'create_account_method': 'create_account_posix', },
'gather_accounts_enabled': True, 'gather_accounts_method': 'gather_accounts_posix', cls.OTHER_HOST: {
'_protocols': ['ssh', 'telnet'], 'su_enabled': False,
}
} }
@classmethod @classmethod
def platform_constraints(cls): def _get_protocol_constrains(cls) -> dict:
return { return {
cls.LINUX: { '*': {
'_protocols': ['ssh', 'rdp', 'vnc', 'telnet'] 'choices': ['ssh', 'telnet', 'vnc', 'rdp']
},
cls.WINDOWS: {
'gather_facts_method': 'gather_facts_windows',
'verify_account_method': 'verify_account_windows',
'change_password_method': 'change_password_windows',
'create_account_method': 'create_account_windows',
'gather_accounts_method': 'gather_accounts_windows',
'_protocols': ['rdp', 'ssh', 'vnc'],
'su_enabled': False
},
cls.UNIX: {
'_protocols': ['ssh', 'vnc']
} }
} }
@classmethod
def _get_automation_constrains(cls) -> dict:
return {
'*': {
'ping_enabled': True,
'gather_facts_enabled': True,
'gather_accounts_enabled': True,
'verify_account_enabled': True,
'change_password_enabled': True,
'create_account_enabled': True,
}
}

View File

@ -1,12 +1,14 @@
from common.db.models import IncludesTextChoicesMeta, ChoicesMixin from common.db.models import IncludesTextChoicesMeta, ChoicesMixin
from common.tree import TreeNode from common.tree import TreeNode
from .base import BaseType
from .category import Category from .category import Category
from .host import HostTypes from .host import HostTypes
from .device import DeviceTypes from .device import DeviceTypes
from .database import DatabaseTypes from .database import DatabaseTypes
from .web import WebTypes from .web import WebTypes
from .cloud import CloudTypes from .cloud import CloudTypes
from .protocol import Protocol
class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta): class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta):
@ -18,24 +20,11 @@ class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta):
@classmethod @classmethod
def get_constraints(cls, category, tp): def get_constraints(cls, category, tp):
constraints = ConstrainMixin.platform_constraints()
category_constraints = Category.platform_constraints().get(category) or {}
constraints.update(category_constraints)
types_cls = dict(cls.category_types()).get(category) types_cls = dict(cls.category_types()).get(category)
if not types_cls: if not types_cls:
return constraints return {}
type_constraints = types_cls.platform_constraints().get(tp) or {} type_constraints = types_cls.get_constrains()
constraints.update(type_constraints) return type_constraints.get(tp, {})
_protocols = constraints.pop('_protocols', [])
default_ports = Protocol.default_ports()
protocols = []
for p in _protocols:
port = default_ports.get(p, 0)
protocols.append({'name': p, 'port': port})
constraints['protocols'] = protocols
return constraints
@classmethod @classmethod
def category_types(cls): def category_types(cls):

View File

@ -1,16 +1,37 @@
from django.utils.translation import gettext_lazy as _
class WebTypes(ConstrainMixin, ChoicesMixin, models.TextChoices): from .base import BaseType
class WebTypes(BaseType):
WEBSITE = 'website', _('General website') WEBSITE = 'website', _('General website')
def category_constrains(self): @classmethod
def _get_base_constrains(cls) -> dict:
return { return {
'domain_enabled': False, '*': {
'su_enabled': False, 'domain_enabled': False,
'ping_enabled': False, 'su_enabled': False,
'gather_facts_enabled': False, }
'verify_account_enabled': False, }
'change_password_enabled': False,
'create_account_enabled': False, @classmethod
'gather_accounts_enabled': False, def _get_automation_constrains(cls) -> dict:
'_protocols': ['http', 'https'] constrains = {
} '*': {
'gather_facts_enabled': False,
'verify_account_enabled': False,
'change_password_enabled': False,
'create_account_enabled': False,
'gather_accounts_enabled': False,
}
}
return constrains
@classmethod
def _get_protocol_constrains(cls) -> dict:
return {
'*': {
'choices': ['http', 'api'],
}
}

View File

@ -10,14 +10,6 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.CreateModel(
name='Protocol',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=32, verbose_name='Name')),
('port', models.IntegerField(verbose_name='Port')),
],
),
migrations.RemoveField( migrations.RemoveField(
model_name='asset', model_name='asset',
name='port', name='port',
@ -26,24 +18,24 @@ class Migration(migrations.Migration):
model_name='asset', model_name='asset',
name='protocol', name='protocol',
), ),
migrations.AddField( migrations.RenameField(
model_name='asset', model_name='asset',
name='_protocols', old_name='protocols',
field=models.CharField(blank=True, default='ssh/22', max_length=128, verbose_name='Protocols'), new_name='_protocols',
),
migrations.RemoveField(
model_name='asset',
name='protocols',
), ),
migrations.AlterField( migrations.AlterField(
model_name='systemuser', model_name='systemuser',
name='protocol', name='protocol',
field=models.CharField(default='ssh', max_length=16, verbose_name='Protocol'), field=models.CharField(default='ssh', max_length=16, verbose_name='Protocol'),
), ),
migrations.AddField( migrations.CreateModel(
model_name='asset', name='Protocol',
name='protocols', fields=[
field=models.ManyToManyField(blank=True, to='assets.Protocol', verbose_name='Protocols'), ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=32, verbose_name='Name')),
('port', models.IntegerField(verbose_name='Port')),
('asset', models.ForeignKey(on_delete=models.deletion.CASCADE, related_name='protocols', to='assets.asset', verbose_name='Asset')),
],
), ),
migrations.DeleteModel( migrations.DeleteModel(
name='Cluster', name='Cluster',

View File

@ -31,7 +31,7 @@ def migrate_asset_protocols(apps, schema_editor):
protocol = protocol_map.get(name_port) protocol = protocol_map.get(name_port)
if not protocol: if not protocol:
protocol = protocol_model.objects.get_or_create( protocol = protocol_model.objects.get_or_create(
defaults={'name': name, 'port': port}, defaults={'name': name, 'port': port, 'asset': asset},
name=name, port=port name=name, port=port
)[0] )[0]
assets_protocols.append(asset_protocol_through(asset_id=asset.id, protocol_id=protocol.id)) assets_protocols.append(asset_protocol_through(asset_id=asset.id, protocol_id=protocol.id))

View File

@ -13,6 +13,5 @@ from .backup import *
from ._user import * from ._user import *
# 废弃以下 # 废弃以下
# from ._authbook import * # from ._authbook import *
from .protocol import *
from .cmd_filter import * from .cmd_filter import *

View File

@ -16,7 +16,7 @@ from orgs.mixins.models import OrgManager, JMSOrgBaseModel
from ..platform import Platform from ..platform import Platform
from ..base import AbsConnectivity from ..base import AbsConnectivity
__all__ = ['Asset', 'AssetQuerySet', 'default_node'] __all__ = ['Asset', 'AssetQuerySet', 'default_node', 'Protocol']
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -72,11 +72,16 @@ class NodesRelationMixin:
return nodes return nodes
class Protocol(models.Model):
name = models.CharField(max_length=32, verbose_name=_("Name"))
port = models.IntegerField(verbose_name=_("Port"))
asset = models.ForeignKey('Asset', on_delete=models.CASCADE, related_name='protocols', verbose_name=_("Asset"))
class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel): class Asset(AbsConnectivity, NodesRelationMixin, JMSOrgBaseModel):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name')) name = models.CharField(max_length=128, verbose_name=_('Name'))
ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True) ip = models.CharField(max_length=128, verbose_name=_('IP'), db_index=True)
protocols = models.ManyToManyField('Protocol', verbose_name=_("Protocols"), blank=True)
platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT, platform = models.ForeignKey(Platform, default=Platform.default, on_delete=models.PROTECT,
verbose_name=_("Platform"), related_name='assets') verbose_name=_("Platform"), related_name='assets')
domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets', domain = models.ForeignKey("assets.Domain", null=True, blank=True, related_name='assets',

View File

@ -58,6 +58,8 @@ class BaseAccount(OrgModelMixin):
id = models.UUIDField(default=uuid.uuid4, primary_key=True) id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_("Name")) name = models.CharField(max_length=128, verbose_name=_("Name"))
username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True) username = models.CharField(max_length=128, blank=True, verbose_name=_('Username'), db_index=True)
secret_type = models.CharField(max_length=16, default='password', verbose_name=_('Secret type'))
secret = fields.EncryptTextField(blank=True, null=True, verbose_name=_('Secret'))
password = fields.EncryptCharField(max_length=256, blank=True, null=True, verbose_name=_('Password')) 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')) 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')) public_key = fields.EncryptTextField(blank=True, null=True, verbose_name=_('SSH public key'))

View File

@ -1,7 +0,0 @@
from django.db import models
from django.utils.translation import gettext_lazy as _
class Protocol(models.Model):
name = models.CharField(max_length=32, verbose_name=_("Name"))
port = models.IntegerField(verbose_name=_("Port"))

View File

@ -8,7 +8,7 @@ from django.db.models import F
from common.drf.serializers import JMSWritableNestedModelSerializer from common.drf.serializers import JMSWritableNestedModelSerializer
from common.drf.fields import LabeledChoiceField, ObjectRelatedField from common.drf.fields import LabeledChoiceField, ObjectRelatedField
from ..account import AccountSerializer from ..account import AccountSerializer
from ...models import Asset, Node, Platform, Protocol, Label, Domain, Account from ...models import Asset, Node, Platform, Label, Domain, Account, Protocol
from ...const import Category, AllTypes from ...const import Category, AllTypes
__all__ = [ __all__ = [

View File

@ -89,23 +89,6 @@ class PlatformSerializer(JMSWritableNestedModelSerializer):
'domain_default': {'label': "默认网域"}, 'domain_default': {'label': "默认网域"},
} }
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set_brand_choices()
def set_brand_choices(self):
field = self.fields.get('brand')
request = self.context.get('request')
if not field or not request:
return
category = request.query_params.get('category', '')
constraints = Category.platform_constraints().get(category)
if not constraints:
return
field.choices = constraints.get('brands', [])
if field.choices:
field.required = True
class PlatformOpsMethodSerializer(serializers.Serializer): class PlatformOpsMethodSerializer(serializers.Serializer):
id = serializers.CharField(read_only=True) id = serializers.CharField(read_only=True)