diff --git a/apps/accounts/models/account.py b/apps/accounts/models/account.py index 611f081f8..060948fe1 100644 --- a/apps/accounts/models/account.py +++ b/apps/accounts/models/account.py @@ -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 '' diff --git a/apps/accounts/models/virtual.py b/apps/accounts/models/virtual.py index 9b38d8d12..f5c2e3df7 100644 --- a/apps/accounts/models/virtual.py +++ b/apps/accounts/models/virtual.py @@ -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 diff --git a/apps/assets/api/asset/asset.py b/apps/assets/api/asset/asset.py index ff542e1aa..018b648a0 100644 --- a/apps/assets/api/asset/asset.py +++ b/apps/assets/api/asset/asset.py @@ -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): diff --git a/apps/assets/models/asset/common.py b/apps/assets/models/asset/common.py index a497736a6..93e883045 100644 --- a/apps/assets/models/asset/common.py +++ b/apps/assets/models/asset/common.py @@ -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 diff --git a/apps/terminal/models/applet/applet.py b/apps/terminal/models/applet/applet.py index 04f209e7d..5dd449759 100644 --- a/apps/terminal/models/applet/applet.py +++ b/apps/terminal/models/applet/applet.py @@ -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',