perf: remove gather model (#9246)

Co-authored-by: feng <1304903146@qq.com>
pull/9248/head
fit2bot 2022-12-27 17:45:41 +08:00 committed by GitHub
parent 0328fd1bb0
commit c81f36cc27
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 77 additions and 128 deletions

View File

@ -4,7 +4,6 @@ from .automations import *
from .category import * from .category import *
from .domain import * from .domain import *
from .favorite_asset import * from .favorite_asset import *
from .gathered_user import *
from .label import * from .label import *
from .mixin import * from .mixin import *
from .node import * from .node import *

View File

@ -1,20 +0,0 @@
# -*- coding: utf-8 -*-
#
from orgs.mixins.api import OrgModelViewSet
from assets.models import GatheredUser
from ..serializers import GatheredUserSerializer
from ..filters import AssetRelatedByNodeFilterBackend
__all__ = ['GatheredUserViewSet']
class GatheredUserViewSet(OrgModelViewSet):
model = GatheredUser
serializer_class = GatheredUserSerializer
extra_filter_backends = [AssetRelatedByNodeFilterBackend]
filterset_fields = ['asset', 'username', 'present', 'asset__address', 'asset__name', 'asset_id']
search_fields = ['username', 'asset__address', 'asset__name']

View File

@ -1,8 +1,9 @@
from django.utils.translation import ugettext_lazy as _
from common.utils import get_logger from common.utils import get_logger
from assets.const import AutomationTypes from assets.const import AutomationTypes, Source
from orgs.utils import tmp_to_org from orgs.utils import tmp_to_org
from .filter import GatherAccountsFilter from .filter import GatherAccountsFilter
from ...models import GatheredUser
from ..base.manager import BasePlaybookManager from ..base.manager import BasePlaybookManager
logger = get_logger(__name__) logger = get_logger(__name__)
@ -26,20 +27,33 @@ class GatherAccountsManager(BasePlaybookManager):
result = GatherAccountsFilter(host).run(self.method_id_meta_mapper, result) result = GatherAccountsFilter(host).run(self.method_id_meta_mapper, result)
return result return result
@staticmethod
def bulk_create_accounts(asset, result):
account_objs = []
account_model = asset.accounts.model
account_usernames = set(asset.accounts.values_list('username', flat=True))
with tmp_to_org(asset.org_id):
accounts_dict = {}
for username, data in result.items():
comment = ''
d = {'asset': asset, 'username': username, 'name': username, 'source': Source.COLLECTED}
if data.get('date'):
comment += f"{_('Date last login')}: {data['date']}\n "
if data.get('address'):
comment += f"{_('IP last login')}: {data['address'][:32]}"
d['comment'] = comment
accounts_dict[username] = d
for username, data in accounts_dict.items():
if username in account_usernames:
continue
account_objs.append(account_model(**data))
account_model.objects.bulk_create(account_objs)
def on_host_success(self, host, result): def on_host_success(self, host, result):
info = result.get('debug', {}).get('res', {}).get('info', {}) info = result.get('debug', {}).get('res', {}).get('info', {})
asset = self.host_asset_mapper.get(host) asset = self.host_asset_mapper.get(host)
org_id = asset.org_id
if asset and info: if asset and info:
result = self.filter_success_result(host, info) result = self.filter_success_result(host, info)
with tmp_to_org(org_id): self.bulk_create_accounts(asset, result)
GatheredUser.objects.filter(asset=asset, present=True).update(present=False)
for username, data in result.items():
defaults = {'asset': asset, 'present': True, 'username': username}
if data.get('date'):
defaults['date_last_login'] = data['date']
if data.get('address'):
defaults['ip_last_login'] = data['address'][:32]
GatheredUser.objects.update_or_create(defaults=defaults, asset=asset, username=username)
else: else:
logger.error("Not found info".format(host)) logger.error("Not found info".format(host))

View File

@ -13,3 +13,14 @@ class SecretType(TextChoices):
SSH_KEY = 'ssh_key', _('SSH key') SSH_KEY = 'ssh_key', _('SSH key')
ACCESS_KEY = 'access_key', _('Access key') ACCESS_KEY = 'access_key', _('Access key')
TOKEN = 'token', _('Token') TOKEN = 'token', _('Token')
class AliasAccount(TextChoices):
ALL = '@ALL', _('All')
INPUT = '@INPUT', _('Manual input')
USER = '@USER', _('Dynamic user')
class Source(TextChoices):
LOCAL = 'local', _('Local')
COLLECTED = 'collected', _('Collected')

View File

@ -126,17 +126,6 @@ class LabelFilterBackend(filters.BaseFilterBackend):
return queryset return queryset
class AssetRelatedByNodeFilterBackend(AssetByNodeFilterBackend):
def filter_node_related_all(self, queryset, node):
return queryset.filter(
Q(asset__nodes__key__istartswith=f'{node.key}:') |
Q(asset__nodes__key=node.key)
).distinct()
def filter_node_related_direct(self, queryset, node):
return queryset.filter(asset__nodes__key=node.key).distinct()
class IpInFilterBackend(filters.BaseFilterBackend): class IpInFilterBackend(filters.BaseFilterBackend):
def filter_queryset(self, request, queryset, view): def filter_queryset(self, request, queryset, view):
ips = request.query_params.get('ips') ips = request.query_params.get('ips')

View File

@ -0,0 +1,21 @@
# Generated by Django 3.2.16 on 2022-12-27 09:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('assets', '0118_auto_20221227_1504'),
]
operations = [
migrations.AddField(
model_name='account',
name='source',
field=models.CharField(default='local', max_length=30, verbose_name='Source'),
),
migrations.DeleteModel(
name='GatheredUser',
),
]

View File

@ -7,7 +7,6 @@ from .gateway import *
from .domain import * from .domain import *
from .node import * from .node import *
from .utils import * from .utils import *
from .gathered_user import *
from .favorite_asset import * from .favorite_asset import *
from .account import * from .account import *
from .backup import * from .backup import *

View File

@ -3,6 +3,7 @@ from django.utils.translation import gettext_lazy as _
from simple_history.models import HistoricalRecords from simple_history.models import HistoricalRecords
from common.utils import lazyproperty from common.utils import lazyproperty
from ..const import AliasAccount, Source
from .base import AbsConnectivity, BaseAccount from .base import AbsConnectivity, BaseAccount
__all__ = ['Account', 'AccountTemplate'] __all__ = ['Account', 'AccountTemplate']
@ -40,11 +41,6 @@ class AccountHistoricalRecords(HistoricalRecords):
class Account(AbsConnectivity, BaseAccount): class Account(AbsConnectivity, BaseAccount):
class AliasAccount(models.TextChoices):
ALL = '@ALL', _('All')
INPUT = '@INPUT', _('Manual input')
USER = '@USER', _('Dynamic user')
asset = models.ForeignKey( asset = models.ForeignKey(
'assets.Asset', related_name='accounts', 'assets.Asset', related_name='accounts',
on_delete=models.CASCADE, verbose_name=_('Asset') on_delete=models.CASCADE, verbose_name=_('Asset')
@ -55,6 +51,7 @@ class Account(AbsConnectivity, BaseAccount):
) )
version = models.IntegerField(default=0, verbose_name=_('Version')) version = models.IntegerField(default=0, verbose_name=_('Version'))
history = AccountHistoricalRecords(included_fields=['id', 'secret', 'secret_type', 'version']) history = AccountHistoricalRecords(included_fields=['id', 'secret', 'secret_type', 'version'])
source = models.CharField(max_length=30, default=Source.LOCAL, verbose_name=_('Source'))
class Meta: class Meta:
verbose_name = _('Account') verbose_name = _('Account')
@ -89,12 +86,12 @@ class Account(AbsConnectivity, BaseAccount):
@classmethod @classmethod
def get_manual_account(cls): def get_manual_account(cls):
""" @INPUT 手动登录的账号(any) """ """ @INPUT 手动登录的账号(any) """
return cls(name=cls.AliasAccount.INPUT.label, username=cls.AliasAccount.INPUT.value, secret=None) return cls(name=AliasAccount.INPUT.label, username=AliasAccount.INPUT.value, secret=None)
@classmethod @classmethod
def get_user_account(cls, username): def get_user_account(cls, username):
""" @USER 动态用户的账号(self) """ """ @USER 动态用户的账号(self) """
return cls(name=cls.AliasAccount.USER.label, username=cls.AliasAccount.USER.value) return cls(name=AliasAccount.USER.label, username=AliasAccount.USER.value)
def get_su_from_accounts(self): def get_su_from_accounts(self):
""" 排除自己和以自己为 su-from 的账号 """ """ 排除自己和以自己为 su-from 的账号 """

View File

@ -1,31 +0,0 @@
# -*- coding: utf-8 -*-
#
from django.db import models
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.models import JMSOrgBaseModel
__all__ = ['GatheredUser']
class GatheredUser(JMSOrgBaseModel):
asset = models.ForeignKey('assets.Asset', on_delete=models.CASCADE, verbose_name=_("Asset"))
username = models.CharField(max_length=32, blank=True, db_index=True, verbose_name=_('Username'))
present = models.BooleanField(default=True, verbose_name=_("Present"))
date_last_login = models.DateTimeField(null=True, verbose_name=_("Date last login"))
ip_last_login = models.CharField(max_length=39, default='', verbose_name=_("IP last login"))
@property
def name(self):
return self.asset.name
@property
def ip(self):
return self.asset.address
class Meta:
verbose_name = _('GatherUser')
ordering = ['asset']
def __str__(self):
return '{}: {}'.format(self.asset.name, self.username)

View File

@ -6,7 +6,6 @@ from .label import *
from .node import * from .node import *
from .gateway import * from .gateway import *
from .domain import * from .domain import *
from .gathered_user import *
from .favorite_asset import * from .favorite_asset import *
from .account import * from .account import *
from .platform import * from .platform import *

View File

@ -1,26 +0,0 @@
# -*- coding: utf-8 -*-
#
from django.utils.translation import ugettext_lazy as _
from orgs.mixins.serializers import OrgResourceModelSerializerMixin
from common.drf.fields import ObjectRelatedField
from ..models import GatheredUser, Asset
class GatheredUserSerializer(OrgResourceModelSerializerMixin):
asset = ObjectRelatedField(queryset=Asset.objects, label=_('Asset'))
class Meta:
model = GatheredUser
fields_mini = ['id']
fields_small = fields_mini + [
'username', 'ip_last_login', 'present', 'name',
'date_last_login', 'date_created', 'date_updated'
]
fields_fk = ['asset', 'ip']
fields = fields_small + fields_fk
read_only_fields = fields
extra_kwargs = {
'name': {'label': _("Hostname")},
'ip': {'label': 'IP'},
}

View File

@ -23,7 +23,6 @@ router.register(r'labels', api.LabelViewSet, 'label')
router.register(r'nodes', api.NodeViewSet, 'node') router.register(r'nodes', api.NodeViewSet, 'node')
router.register(r'domains', api.DomainViewSet, 'domain') router.register(r'domains', api.DomainViewSet, 'domain')
router.register(r'gateways', api.GatewayViewSet, 'gateway') router.register(r'gateways', api.GatewayViewSet, 'gateway')
router.register(r'gathered-users', api.GatheredUserViewSet, 'gathered-user')
router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset') router.register(r'favorite-assets', api.FavoriteAssetViewSet, 'favorite-asset')
router.register(r'account-backup-plans', api.AccountBackupPlanViewSet, 'account-backup') router.register(r'account-backup-plans', api.AccountBackupPlanViewSet, 'account-backup')
router.register(r'account-backup-plan-executions', api.AccountBackupPlanExecutionViewSet, 'account-backup-execution') router.register(r'account-backup-plan-executions', api.AccountBackupPlanExecutionViewSet, 'account-backup-execution')
@ -51,7 +50,6 @@ urlpatterns = [
name='account-secret-history'), name='account-secret-history'),
path('nodes/category/tree/', api.CategoryTreeApi.as_view(), name='asset-category-tree'), path('nodes/category/tree/', api.CategoryTreeApi.as_view(), name='asset-category-tree'),
# path('nodes/tree/', api.NodeListAsTreeApi.as_view(), name='node-tree'),
path('nodes/children/tree/', api.NodeChildrenAsTreeApi.as_view(), name='node-children-tree'), path('nodes/children/tree/', api.NodeChildrenAsTreeApi.as_view(), name='node-children-tree'),
path('nodes/<uuid:pk>/children/', api.NodeChildrenApi.as_view(), name='node-children'), path('nodes/<uuid:pk>/children/', api.NodeChildrenApi.as_view(), name='node-children'),
path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'), path('nodes/children/', api.NodeChildrenApi.as_view(), name='node-children-2'),

View File

@ -17,12 +17,10 @@ MODELS_NEED_RECORD = (
"LoginAssetACL", "LoginAssetACL",
"LoginConfirmSetting", "LoginConfirmSetting",
# assets # assets
'Asset', 'Node', 'AdminUser', 'SystemUser', 'Domain', 'Gateway', 'CommandFilterRule', 'Asset', 'Node', 'Domain', 'Gateway', 'CommandFilterRule',
'CommandFilter', 'Platform', 'Label', 'CommandFilter', 'Platform', 'Label',
# applications
'Application',
# account # account
'AuthBook', 'Account',
# orgs # orgs
"Organization", "Organization",
# settings # settings
@ -36,8 +34,7 @@ MODELS_NEED_RECORD = (
# rbac # rbac
'Role', 'SystemRole', 'OrgRole', 'RoleBinding', 'OrgRoleBinding', 'SystemRoleBinding', 'Role', 'SystemRole', 'OrgRole', 'RoleBinding', 'OrgRoleBinding', 'SystemRoleBinding',
# xpack # xpack
'License', 'Account', 'SyncInstanceTask', 'ChangeAuthPlan', 'License', 'Account', 'SyncInstanceTask', 'Interface',
'GatherUserTask', 'Interface',
) )

View File

@ -15,7 +15,7 @@ from .serializers import (
from users.models import User, UserGroup from users.models import User, UserGroup
from assets.models import ( from assets.models import (
Asset, Domain, Label, Node, Asset, Domain, Label, Node,
CommandFilter, CommandFilterRule, GatheredUser CommandFilter, CommandFilterRule
) )
from perms.models import AssetPermission from perms.models import AssetPermission
from orgs.utils import current_org, tmp_to_root_org from orgs.utils import current_org, tmp_to_root_org
@ -28,8 +28,7 @@ logger = get_logger(__file__)
# 部分 org 相关的 model需要清空这些数据之后才能删除该组织 # 部分 org 相关的 model需要清空这些数据之后才能删除该组织
org_related_models = [ org_related_models = [
User, UserGroup, Asset, Label, Domain, Node, Label, User, UserGroup, Asset, Label, Domain, Node, Label,
CommandFilter, CommandFilterRule, GatheredUser, CommandFilter, CommandFilterRule, AssetPermission,
AssetPermission,
] ]

View File

@ -11,6 +11,7 @@ from rest_framework.exceptions import PermissionDenied, NotFound
from assets.utils import KubernetesTree from assets.utils import KubernetesTree
from assets.models import Asset, Account from assets.models import Asset, Account
from assets.const import AliasAccount
from assets.api import SerializeToTreeNodeMixin from assets.api import SerializeToTreeNodeMixin
from authentication.models import ConnectionToken from authentication.models import ConnectionToken
from common.utils import get_object_or_none, lazyproperty from common.utils import get_object_or_none, lazyproperty
@ -157,7 +158,7 @@ class UserGrantedK8sAsTreeApi(SelfOrPKUserMixin, ListAPIView):
raise NotFound('Account is not found') raise NotFound('Account is not found')
account = accounts[0] account = accounts[0]
if account.username in [ if account.username in [
Account.AliasAccount.INPUT, Account.AliasAccount.USER AliasAccount.INPUT, AliasAccount.USER
]: ]:
return token.input_secret return token.input_secret
else: else:

View File

@ -13,6 +13,7 @@ from common.utils import date_expired_default
from common.utils.timezone import local_now from common.utils.timezone import local_now
from perms.const import ActionChoices from perms.const import ActionChoices
from assets.const import AliasAccount
__all__ = ['AssetPermission', 'ActionChoices'] __all__ = ['AssetPermission', 'ActionChoices']
@ -38,7 +39,7 @@ class AssetPermissionQuerySet(models.QuerySet):
def filter_by_accounts(self, accounts): def filter_by_accounts(self, accounts):
q = Q(accounts__contains=list(accounts)) | \ q = Q(accounts__contains=list(accounts)) | \
Q(accounts__contains=Account.AliasAccount.ALL.value) Q(accounts__contains=AliasAccount.ALL.value)
return self.filter(q) return self.filter(q)
@ -127,7 +128,7 @@ class AssetPermission(JMSOrgBaseModel):
""" """
asset_ids = self.get_all_assets(flat=True) asset_ids = self.get_all_assets(flat=True)
q = Q(asset_id__in=asset_ids) q = Q(asset_id__in=asset_ids)
if Account.AliasAccount.ALL not in self.accounts: if AliasAccount.ALL not in self.accounts:
q &= Q(username__in=self.accounts) q &= Q(username__in=self.accounts)
accounts = Account.objects.filter(q).order_by('asset__name', 'name', 'username') accounts = Account.objects.filter(q).order_by('asset__name', 'name', 'username')
if not flat: if not flat:

View File

@ -1,6 +1,7 @@
from collections import defaultdict from collections import defaultdict
from assets.models import Account from assets.models import Account
from assets.const import AliasAccount
from .permission import AssetPermissionUtil from .permission import AssetPermissionUtil
__all__ = ['PermAccountUtil'] __all__ = ['PermAccountUtil']
@ -44,21 +45,21 @@ class PermAccountUtil(AssetPermissionUtil):
cleaned_accounts_expired = defaultdict(list) cleaned_accounts_expired = defaultdict(list)
# @ALL 账号先处理,后面的每个最多映射一个账号 # @ALL 账号先处理,后面的每个最多映射一个账号
all_action_bit = alias_action_bit_mapper.pop(Account.AliasAccount.ALL, None) all_action_bit = alias_action_bit_mapper.pop(AliasAccount.ALL, None)
if all_action_bit: if all_action_bit:
for account in asset_accounts: for account in asset_accounts:
cleaned_accounts_action_bit[account] |= all_action_bit cleaned_accounts_action_bit[account] |= all_action_bit
cleaned_accounts_expired[account].extend( cleaned_accounts_expired[account].extend(
alias_expired_mapper[Account.AliasAccount.ALL] alias_expired_mapper[AliasAccount.ALL]
) )
for alias, action_bit in alias_action_bit_mapper.items(): for alias, action_bit in alias_action_bit_mapper.items():
if alias == Account.AliasAccount.USER: if alias == AliasAccount.USER:
if user.username in username_account_mapper: if user.username in username_account_mapper:
account = username_account_mapper[user.username] account = username_account_mapper[user.username]
else: else:
account = Account.get_user_account(user.username) account = Account.get_user_account(user.username)
elif alias == Account.AliasAccount.INPUT: elif alias == AliasAccount.INPUT:
account = Account.get_manual_account() account = Account.get_manual_account()
elif alias in username_account_mapper: elif alias in username_account_mapper:
account = username_account_mapper[alias] account = username_account_mapper[alias]