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:
def get_constrains(self):
pass
class BaseType(TextChoices):
"""
约束应该考虑代是对平台对限制避免多余对选项: mysql 开启 ssh, 或者开启了也没有作用, 比如 k8s 开启了 domain目前还不支持
"""
@classmethod
def get_constrains(cls):
constrains = {}
def _get_category_constrains(self) -> dict:
raise NotImplementedError
base = cls._get_base_constrains()
protocols = cls._get_protocol_constrains()
automation = cls._get_automation_constrains()
def _get_protocol_constrains(self) -> dict:
raise NotImplementedError
base_default = base.pop('*', {})
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
@classmethod
def platform_constraints(cls):
return {
'domain_enabled': False,
'su_enabled': False,
'brand_enabled': False,
'ping_enabled': False,
'gather_facts_enabled': False,
'change_password_enabled': False,
'verify_account_enabled': False,
'create_account_enabled': False,
'gather_accounts_enabled': False,
'_protocols': []
}
def _get_protocol_constrains(cls) -> dict:
raise NotImplementedError
@classmethod
def _get_automation_constrains(cls) -> dict:
raise NotImplementedError

View File

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

View File

@ -1,31 +1,40 @@
from django.db import models
from common.db.models import ChoicesMixin
from .base import BaseType
from .category import ConstrainMixin
class CloudTypes(ConstrainMixin, ChoicesMixin, models.TextChoices):
class CloudTypes(BaseType):
PUBLIC = 'public', 'Public cloud'
PRIVATE = 'private', 'Private cloud'
K8S = 'k8s', 'Kubernetes'
def category_constrains(self):
@classmethod
def _get_base_constrains(cls) -> dict:
return {
'domain_enabled': False,
'su_enabled': False,
'ping_enabled': False,
'gather_facts_enabled': False,
'verify_account_enabled': False,
'change_password_enabled': False,
'create_account_enabled': False,
'gather_accounts_enabled': False,
'_protocols': []
'*': {
'domain_enabled': False,
'su_enabled': False,
}
}
@classmethod
def platform_constraints(cls):
return {
cls.K8S: {
'_protocols': ['k8s']
def _get_automation_constrains(cls) -> dict:
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'],
},
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'
MARIADB = 'mariadb', 'MariaDB'
POSTGRESQL = 'postgresql', 'PostgreSQL'
@ -9,28 +11,33 @@ class DatabaseTypes(ConstrainMixin, ChoicesMixin, models.TextChoices):
MONGODB = 'mongodb', 'MongoDB'
REDIS = 'redis', 'Redis'
def category_constrains(self):
@classmethod
def _get_base_constrains(cls) -> dict:
return {
'domain_enabled': True,
'su_enabled': False,
'gather_facts_enabled': True,
'verify_account_enabled': True,
'change_password_enabled': True,
'create_account_enabled': True,
'gather_accounts_enabled': True,
'_protocols': []
'*': {
'domain_enabled': True,
'su_enabled': False,
}
}
@classmethod
def platform_constraints(cls):
meta = {}
for name, label in cls.choices:
meta[name] = {
'_protocols': [name],
'gather_facts_method': f'gather_facts_{name}',
'verify_account_method': f'verify_account_{name}',
'change_password_method': f'change_password_{name}',
'create_account_method': f'create_account_{name}',
'gather_accounts_method': f'gather_accounts_{name}',
def _get_automation_constrains(cls) -> dict:
constrains = {
'*': {
'gather_facts_enabled': True,
'gather_accounts_enabled': True,
'verify_account_enabled': True,
'change_password_enabled': True,
'create_account_enabled': True,
}
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")
SWITCH = 'switch', _("Switch")
ROUTER = 'router', _("Router")
FIREWALL = 'firewall', _("Firewall")
@classmethod
def category_constrains(cls):
def _get_base_constrains(cls) -> dict:
return {
'domain_enabled': True,
'brand_enabled': True,
'brands': [
('huawei', 'Huawei'),
('cisco', 'Cisco'),
('juniper', 'Juniper'),
('h3c', 'H3C'),
('dell', 'Dell'),
('other', 'Other'),
],
'su_enabled': False,
'ping_enabled': True, 'ping_method': 'ping',
'gather_facts_enabled': False,
'verify_account_enabled': False,
'change_password_enabled': False,
'create_account_enabled': False,
'gather_accounts_enabled': False,
'_protocols': ['ssh', 'telnet']
'*': {
'domain_enabled': True,
'su_enabled': False,
}
}
@classmethod
def _get_protocol_constrains(cls) -> dict:
return {
'*': {
'choices': ['ssh', 'telnet']
}
}
@classmethod
def _get_automation_constrains(cls) -> dict:
return {
'*': {
'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'
WINDOWS = 'windows', 'Windows'
UNIX = 'unix', 'Unix'
OTHER_HOST = 'other', _("Other")
OTHER_HOST = 'other', "Other"
@staticmethod
def category_constrains():
@classmethod
def _get_base_constrains(cls) -> dict:
return {
'domain_enabled': True,
'su_enabled': True, 'su_method': 'sudo',
'ping_enabled': True, 'ping_method': 'ping',
'gather_facts_enabled': True, 'gather_facts_method': 'gather_facts_posix',
'verify_account_enabled': True, 'verify_account_method': 'verify_account_posix',
'change_password_enabled': True, 'change_password_method': 'change_password_posix',
'create_account_enabled': True, 'create_account_method': 'create_account_posix',
'gather_accounts_enabled': True, 'gather_accounts_method': 'gather_accounts_posix',
'_protocols': ['ssh', 'telnet'],
'*': {
'domain_enabled': True,
'su_enabled': True,
},
cls.WINDOWS: {
'su_enabled': False,
},
cls.OTHER_HOST: {
'su_enabled': False,
}
}
@classmethod
def platform_constraints(cls):
def _get_protocol_constrains(cls) -> dict:
return {
cls.LINUX: {
'_protocols': ['ssh', 'rdp', 'vnc', 'telnet']
},
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']
'*': {
'choices': ['ssh', 'telnet', 'vnc', 'rdp']
}
}
}
@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.tree import TreeNode
from .base import BaseType
from .category import Category
from .host import HostTypes
from .device import DeviceTypes
from .database import DatabaseTypes
from .web import WebTypes
from .cloud import CloudTypes
from .protocol import Protocol
class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta):
@ -18,24 +20,11 @@ class AllTypes(ChoicesMixin, metaclass=IncludesTextChoicesMeta):
@classmethod
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)
if not types_cls:
return constraints
type_constraints = types_cls.platform_constraints().get(tp) or {}
constraints.update(type_constraints)
_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
return {}
type_constraints = types_cls.get_constrains()
return type_constraints.get(tp, {})
@classmethod
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')
def category_constrains(self):
@classmethod
def _get_base_constrains(cls) -> dict:
return {
'domain_enabled': False,
'su_enabled': False,
'ping_enabled': False,
'gather_facts_enabled': False,
'verify_account_enabled': False,
'change_password_enabled': False,
'create_account_enabled': False,
'gather_accounts_enabled': False,
'_protocols': ['http', 'https']
}
'*': {
'domain_enabled': False,
'su_enabled': False,
}
}
@classmethod
def _get_automation_constrains(cls) -> dict:
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 = [
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(
model_name='asset',
name='port',
@ -26,24 +18,24 @@ class Migration(migrations.Migration):
model_name='asset',
name='protocol',
),
migrations.AddField(
migrations.RenameField(
model_name='asset',
name='_protocols',
field=models.CharField(blank=True, default='ssh/22', max_length=128, verbose_name='Protocols'),
),
migrations.RemoveField(
model_name='asset',
name='protocols',
old_name='protocols',
new_name='_protocols',
),
migrations.AlterField(
model_name='systemuser',
name='protocol',
field=models.CharField(default='ssh', max_length=16, verbose_name='Protocol'),
),
migrations.AddField(
model_name='asset',
name='protocols',
field=models.ManyToManyField(blank=True, to='assets.Protocol', verbose_name='Protocols'),
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')),
('asset', models.ForeignKey(on_delete=models.deletion.CASCADE, related_name='protocols', to='assets.asset', verbose_name='Asset')),
],
),
migrations.DeleteModel(
name='Cluster',

View File

@ -31,7 +31,7 @@ def migrate_asset_protocols(apps, schema_editor):
protocol = protocol_map.get(name_port)
if not protocol:
protocol = protocol_model.objects.get_or_create(
defaults={'name': name, 'port': port},
defaults={'name': name, 'port': port, 'asset': asset},
name=name, port=port
)[0]
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 ._authbook import *
from .protocol import *
from .cmd_filter import *

View File

@ -16,7 +16,7 @@ from orgs.mixins.models import OrgManager, JMSOrgBaseModel
from ..platform import Platform
from ..base import AbsConnectivity
__all__ = ['Asset', 'AssetQuerySet', 'default_node']
__all__ = ['Asset', 'AssetQuerySet', 'default_node', 'Protocol']
logger = logging.getLogger(__name__)
@ -72,11 +72,16 @@ class NodesRelationMixin:
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):
id = models.UUIDField(default=uuid.uuid4, primary_key=True)
name = models.CharField(max_length=128, verbose_name=_('Name'))
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,
verbose_name=_("Platform"), 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)
name = models.CharField(max_length=128, verbose_name=_("Name"))
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'))
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'))

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.fields import LabeledChoiceField, ObjectRelatedField
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
__all__ = [

View File

@ -89,23 +89,6 @@ class PlatformSerializer(JMSWritableNestedModelSerializer):
'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):
id = serializers.CharField(read_only=True)