perf: applet account select

This commit is contained in:
ibuler 2025-04-16 16:35:49 +08:00 committed by Eric_Lee
parent fa70fb2921
commit 1ee70af93d
5 changed files with 105 additions and 52 deletions

View File

@ -138,9 +138,15 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount, JSONFilterMixin):
return self.username
return str(self.id)
def is_virtual(self):
"""
不要用 username 去判断因为可能是构造的 account 对象设置了同名账号的用户名,
"""
return self.alias.startswith('@')
def is_ds_account(self):
if self.username.startswith('@'):
return False
if self.is_virtual():
return ''
if not self.asset.is_directory_service:
return False
return True
@ -154,6 +160,8 @@ class Account(AbsConnectivity, LabeledMixin, BaseAccount, JSONFilterMixin):
@lazyproperty
def ds_domain(self):
"""这个不能去掉perm_account 会动态设置这个值,以更改 full_username"""
if self.is_virtual():
return ''
if self.ds and self.ds.domain_name:
return self.ds.domain_name
return ''

View File

@ -92,8 +92,9 @@ class VirtualAccount(JMSOrgBaseModel):
from .account import Account
username = user.username
alias = AliasAccount.USER.value
with tmp_to_org(asset.org):
same_account = cls.objects.filter(alias='@USER').first()
same_account = cls.objects.filter(alias=alias).first()
secret = ''
if same_account and same_account.secret_from_login:
@ -101,4 +102,6 @@ class VirtualAccount(JMSOrgBaseModel):
if not secret and not from_permed:
secret = input_secret
return Account(name=AliasAccount.USER.label, username=username, secret=secret)
account = Account(name=AliasAccount.USER.label, username=username, secret=secret)
account.alias = alias
return account

View File

@ -146,7 +146,7 @@ class BaseAssetViewSet(OrgBulkModelViewSet):
def paginate_queryset(self, queryset):
page = super().paginate_queryset(queryset)
page = Asset.compute_accounts_amount(page)
page = Asset.compute_all_accounts_amount(page)
return page
def create(self, request, *args, **kwargs):

View File

@ -255,10 +255,15 @@ class Asset(NodesRelationMixin, LabeledMixin, AbsConnectivity, JSONFilterMixin,
@property
def all_accounts(self):
if not self.joined_dir_svc_ids:
if not self.joined_dir_svcs:
queryset = self.accounts.all()
else:
queryset = self.accounts.model.objects.filter(asset__in=[self.id, *self.joined_dir_svc_ids])
queryset = self.accounts.model.objects.filter(asset__in=[self.id, *self.joined_dir_svcs])
return queryset
@property
def dc_accounts(self):
queryset = self.accounts.model.objects.filter(asset__in=[*self.joined_dir_svcs])
return queryset
@lazyproperty
@ -285,27 +290,22 @@ class Asset(NodesRelationMixin, LabeledMixin, AbsConnectivity, JSONFilterMixin,
return self.category == const.Category.DS
@property
def joined_dir_svc_ids(self):
def joined_dir_svcs(self):
return self.directory_services.all()
def is_joined_ad(self):
if self.joined_dir_svc_ids:
return True
else:
return False
@classmethod
def compute_accounts_amount(cls, assets):
def compute_all_accounts_amount(cls, assets):
from .ds import DirectoryService
asset_ids = [asset.id for asset in assets]
asset_id_dc_ids_mapper = defaultdict(list)
dc_ids = set()
relations = (
asset_dc_relations = (
Asset.directory_services.through.objects
.filter(asset_id__in=asset_ids)
.values_list('asset_id', 'directoryservice_id')
)
for asset_id, ds_id in relations:
for asset_id, ds_id in asset_dc_relations:
dc_ids.add(ds_id)
asset_id_dc_ids_mapper[asset_id].append(ds_id)
@ -313,11 +313,11 @@ class Asset(NodesRelationMixin, LabeledMixin, AbsConnectivity, JSONFilterMixin,
DirectoryService.objects.filter(id__in=dc_ids)
.annotate(accounts_amount=Count('accounts'))
)
ds_accounts_mapper = {ds.id: ds.accounts_amount for ds in directory_services}
ds_accounts_amount_mapper = {ds.id: ds.accounts_amount for ds in directory_services}
for asset in assets:
asset_dc_ids = asset_id_dc_ids_mapper.get(asset.id, [])
for dc_id in asset_dc_ids:
ds_accounts = ds_accounts_mapper.get(dc_id, 0)
ds_accounts = ds_accounts_amount_mapper.get(dc_id, 0)
asset.accounts_amount += ds_accounts
return assets

View File

@ -166,9 +166,42 @@ class Applet(JMSBaseModel):
hosts = list(sorted(hosts, key=lambda h: counts[str(h.id)]))
return hosts[0] if hosts else None
def select_host(self, user, asset):
def _filter_published_hosts(self, hosts):
if settings.DEBUG_DEV:
return True
exclude_status = [PublishStatus.pending, PublishStatus.failed]
publications = (
AppletPublication.objects
.filter(applet=self, host__in=hosts)
.exclude(status__in=exclude_status)
)
if not publications:
return None
return [p.host for p in publications]
def filter_available_hosts(self):
hosts = self.hosts.filter(is_active=True)
if not hosts:
logger.info("No active host for applet: {}".format(self.name))
return None
if settings.DEBUG_DEV:
return hosts
hosts = [host for host in hosts if host.load != 'offline']
if not hosts:
logger.info("No online host for applet: {}".format(self.name))
return None
hosts = self._filter_published_hosts(hosts)
if not hosts:
logger.info("No published host for applet: {}".format(self.name))
return None
return hosts
def select_host(self, user, asset):
hosts = self.filter_available_hosts()
if not hosts:
return None
@ -230,15 +263,50 @@ class Applet(JMSBaseModel):
account = self.random_select_prefer_account(user, host, public_accounts)
return account
@staticmethod
def _try_virtual_private_account(user, host):
from accounts.models import VirtualAccount
if not host.using_same_account:
return
account = VirtualAccount.get_same_account(user, host)
if not account.secret:
return
return account
@staticmethod
def _try_local_private_account(user, valid_accounts):
private_account = valid_accounts.filter(username='js_{}'.format(user.username)).first()
return private_account
@staticmethod
def _try_dc_private_account(user, host):
if not host.joined_dir_svcs:
return None
account = host.dc_accounts.filter(username=user.username).first()
return account
def _select_a_private_account(self, user, host, valid_accounts):
private_account = self._try_virtual_private_account(user, host)
if not private_account:
logger.debug('Virtual private account not found ...')
private_account = self._try_dc_private_account(user, host)
if not private_account:
logger.debug('DC private account not found ...')
private_account = self._try_local_private_account(user, valid_accounts)
if not private_account:
logger.debug('Private account not found ...')
return None
return private_account
def try_to_use_private_account(self, user, host, valid_accounts):
host_can_concurrent = str(host.deploy_options.get('RDS_fSingleSessionPerUser', 0)) == '0'
app_can_concurrent = self.can_concurrent or self.type == 'web'
all_can_concurrent = host_can_concurrent and app_can_concurrent
private_account = valid_accounts.filter(username='js_{}'.format(user.username)).first()
if not private_account:
logger.debug('Private account not found ...')
return None
private_account = self._select_a_private_account(user, host, valid_accounts)
# 优先使用 private account支持并发或者不支持并发时如果私有没有被占用则使用私有
account = None
# 如果都支持,不管私有是否被占用,都使用私有
@ -261,32 +329,14 @@ class Applet(JMSBaseModel):
account = private_account
return account
@staticmethod
def try_to_use_same_account(user, host):
from accounts.models import VirtualAccount
if not host.using_same_account:
return
account = VirtualAccount.get_same_account(user, host)
if not account.secret:
return
return account
def select_host_account(self, user, asset):
# 选择激活的发布机
host = self.select_host(user, asset)
if not host:
return None
logger.info('Select applet host: {}'.format(host.name))
if not self.is_available_on_host(host):
logger.debug('No available applet {} for applet host: {}'.format(self.name, host.name))
return None
valid_accounts = host.all_valid_accounts.all().filter(privileged=False)
account = self.try_to_use_same_account(user, host)
if not account:
logger.debug('No same account, try to use private account')
account = self.try_to_use_private_account(user, host, valid_accounts)
valid_accounts = host.accounts.all().filter(privileged=False)
account = self.try_to_use_private_account(user, host, valid_accounts)
if not account:
logger.debug('No private account, try to use public account')
account = self.select_a_public_account(user, host, valid_accounts)
@ -313,14 +363,6 @@ class Applet(JMSBaseModel):
platform.delete()
return super().delete(using, keep_parents)
def is_available_on_host(self, host):
publication = AppletPublication.objects.filter(applet=self, host=host).first()
if not publication:
return False
if publication.status in [PublishStatus.pending, PublishStatus.failed]:
return False
return True
class AppletPublication(JMSBaseModel):
applet = models.ForeignKey('Applet', on_delete=models.CASCADE, related_name='publications',