mirror of https://github.com/jumpserver/jumpserver
				
				
				
			perf: prepaire using thread timer for bulk_create_decorator
							parent
							
								
									11975842f6
								
							
						
					
					
						commit
						886875d628
					
				|  | @ -86,7 +86,7 @@ class GatheredAccountViewSet(OrgBulkModelViewSet): | |||
|         with transaction.atomic(): | ||||
|             execution.save() | ||||
|         execution.start() | ||||
|         accounts = self.model.objects.filter(asset=asset) | ||||
|         accounts = self.model.objects.filter(asset=asset).prefetch_related('asset', 'asset__platform') | ||||
|         return self.get_paginated_response_from_queryset(accounts) | ||||
| 
 | ||||
|     @action(methods=['post'], detail=False, url_path='sync-accounts') | ||||
|  |  | |||
|  | @ -5,6 +5,7 @@ from django.utils import timezone | |||
| 
 | ||||
| from accounts.models import Account, AccountRisk | ||||
| from assets.automations.base.manager import BaseManager | ||||
| from common.decorators import bulk_create_decorator, bulk_update_decorator | ||||
| from common.utils.strings import color_fmt | ||||
| 
 | ||||
| 
 | ||||
|  | @ -27,63 +28,77 @@ def is_weak_password(password): | |||
|         return True | ||||
| 
 | ||||
|     # 正则表达式判断字符多样性(数字、字母、特殊字符) | ||||
|     if (not re.search(r'[A-Za-z]', password) | ||||
|             or not re.search(r'[0-9]', password) | ||||
|             or not re.search(r'[\W_]', password)): | ||||
|     if ( | ||||
|         not re.search(r"[A-Za-z]", password) | ||||
|         or not re.search(r"[0-9]", password) | ||||
|         or not re.search(r"[\W_]", password) | ||||
|     ): | ||||
|         return True | ||||
|     return False | ||||
| 
 | ||||
| 
 | ||||
| @bulk_create_decorator(AccountRisk) | ||||
| def create_risk(account, risk): | ||||
|     pass | ||||
| 
 | ||||
| 
 | ||||
| @bulk_update_decorator(AccountRisk, update_fields=["details"]) | ||||
| def update_risk(risk): | ||||
|     return risk | ||||
| 
 | ||||
| 
 | ||||
| def check_account_secrets(accounts, assets): | ||||
|     now = timezone.now().isoformat() | ||||
|     risks = [] | ||||
|     tmpl = "Check account %s: %s" | ||||
|     summary = defaultdict(int) | ||||
|     result = defaultdict(list) | ||||
|     summary['accounts'] = len(accounts) | ||||
|     summary['assets'] = len(assets) | ||||
|     summary["accounts"] = len(accounts) | ||||
|     summary["assets"] = len(assets) | ||||
| 
 | ||||
|     for account in accounts: | ||||
|         result_item = { | ||||
|             'asset': str(account.asset), | ||||
|             'username': account.username, | ||||
|             "asset": str(account.asset), | ||||
|             "username": account.username, | ||||
|         } | ||||
|         if not account.secret: | ||||
|             print(tmpl % (account, "no secret")) | ||||
|             summary['no_secret'] += 1 | ||||
|             result['no_secret'].append(result_item) | ||||
|             summary["no_secret"] += 1 | ||||
|             result["no_secret"].append(result_item) | ||||
|             continue | ||||
| 
 | ||||
|         if is_weak_password(account.secret): | ||||
|             print(tmpl % (account, color_fmt("weak", "red"))) | ||||
|             summary['weak_password'] += 1 | ||||
|             result['weak_password'].append(result_item) | ||||
|             risks.append({ | ||||
|                 'account': account, | ||||
|                 'risk': 'weak_password', | ||||
|             }) | ||||
|             summary["weak_password"] += 1 | ||||
|             result["weak_password"].append(result_item) | ||||
|             risks.append( | ||||
|                 { | ||||
|                     "account": account, | ||||
|                     "risk": "weak_password", | ||||
|                 } | ||||
|             ) | ||||
|         else: | ||||
|             summary['ok'] += 1 | ||||
|             result['ok'].append(result_item) | ||||
|             summary["ok"] += 1 | ||||
|             result["ok"].append(result_item) | ||||
|             print(tmpl % (account, color_fmt("ok", "green"))) | ||||
| 
 | ||||
|     origin_risks = AccountRisk.objects.filter(asset__in=assets) | ||||
|     origin_risks_dict = {f'{r.asset_id}_{r.username}_{r.risk}': r for r in origin_risks} | ||||
|     origin_risks_dict = {f"{r.asset_id}_{r.username}_{r.risk}": r for r in origin_risks} | ||||
| 
 | ||||
|     for d in risks: | ||||
|         key = f'{d["account"].asset_id}_{d["account"].username}_{d["risk"]}' | ||||
|         origin_risk = origin_risks_dict.get(key) | ||||
| 
 | ||||
|         if origin_risk: | ||||
|             origin_risk.details.append({'datetime': now}) | ||||
|             origin_risk.save(update_fields=['details']) | ||||
|             origin_risk.details.append({"datetime": now}) | ||||
|             update_risk(origin_risk) | ||||
|         else: | ||||
|             AccountRisk.objects.create( | ||||
|                 asset=d['account'].asset, | ||||
|                 username=d['account'].username, | ||||
|                 risk=d['risk'], | ||||
|                 details=[{'datetime': now}], | ||||
|             ) | ||||
|             create_risk({ | ||||
|                 "asset": d["account"].asset, | ||||
|                 "username": d["account"].username, | ||||
|                 "risk": d["risk"], | ||||
|                 "details": [{"datetime": now}], | ||||
|             }) | ||||
|     return summary, result | ||||
| 
 | ||||
| 
 | ||||
|  | @ -98,17 +113,17 @@ class CheckAccountManager(BaseManager): | |||
|     def pre_run(self): | ||||
|         self.assets = self.execution.get_all_assets() | ||||
|         self.execution.date_start = timezone.now() | ||||
|         self.execution.save(update_fields=['date_start']) | ||||
|         self.execution.save(update_fields=["date_start"]) | ||||
| 
 | ||||
|     def do_run(self, *args, **kwargs): | ||||
|         for engine in self.execution.snapshot.get('engines', []): | ||||
|             if engine == 'check_account_secret': | ||||
|         for engine in self.execution.snapshot.get("engines", []): | ||||
|             if engine == "check_account_secret": | ||||
|                 handle = check_account_secrets | ||||
|             else: | ||||
|                 continue | ||||
| 
 | ||||
|             for i in range(0, len(self.assets), self.batch_size): | ||||
|                 _assets = self.assets[i:i + self.batch_size] | ||||
|                 _assets = self.assets[i : i + self.batch_size] | ||||
|                 accounts = Account.objects.filter(asset__in=_assets) | ||||
|                 summary, result = handle(accounts, _assets) | ||||
| 
 | ||||
|  | @ -121,10 +136,16 @@ class CheckAccountManager(BaseManager): | |||
|         return "Check account report of %s" % self.execution.id | ||||
| 
 | ||||
|     def get_report_template(self): | ||||
|         return 'accounts/check_account_report.html' | ||||
|         return "accounts/check_account_report.html" | ||||
| 
 | ||||
|     def print_summary(self): | ||||
|         tmpl = "\n---\nSummary: \nok: %s, weak password: %s, no secret: %s, using time: %ss" % ( | ||||
|             self.summary['ok'], self.summary['weak_password'], self.summary['no_secret'], int(self.duration) | ||||
|         tmpl = ( | ||||
|             "\n---\nSummary: \nok: %s, weak password: %s, no secret: %s, using time: %ss" | ||||
|             % ( | ||||
|                 self.summary["ok"], | ||||
|                 self.summary["weak_password"], | ||||
|                 self.summary["no_secret"], | ||||
|                 int(self.duration), | ||||
|             ) | ||||
|         ) | ||||
|         print(tmpl) | ||||
|  |  | |||
|  | @ -205,7 +205,7 @@ class GatherAccountsManager(AccountBasePlaybookManager): | |||
|         for asset, username in accounts: | ||||
|             self.ori_asset_usernames[asset].add(username) | ||||
| 
 | ||||
|         ga_accounts = GatheredAccount.objects.filter(asset__in=assets) | ||||
|         ga_accounts = GatheredAccount.objects.filter(asset__in=assets).prefetch_related('asset') | ||||
|         for account in ga_accounts: | ||||
|             self.ori_gathered_usernames[account.asset].add(account.username) | ||||
|             key = '{}_{}'.format(account.asset_id, account.username) | ||||
|  |  | |||
|  | @ -53,13 +53,18 @@ def close_old_connections(): | |||
| 
 | ||||
| @contextmanager | ||||
| def safe_db_connection(): | ||||
|     in_atomic_block = connection.in_atomic_block  # 当前是否处于事务中 | ||||
|     autocommit = transaction.get_autocommit()  # 是否启用了自动提交 | ||||
| 
 | ||||
|     try: | ||||
|         close_old_connections()  # 确保旧连接关闭 | ||||
|         if connection.connection:  # 如果连接已关闭,重新连接 | ||||
|         if not connection.is_usable(): | ||||
|             connection.close() | ||||
|             connection.connect() | ||||
|         yield | ||||
|     finally: | ||||
|         close_old_connections()  # 确保最终关闭连接 | ||||
|         # 如果不是事务中(API 请求中可能需要提交事务),则关闭连接 | ||||
|         if not in_atomic_block and autocommit: | ||||
|             close_old_connections() | ||||
| 
 | ||||
| 
 | ||||
| @contextmanager | ||||
|  |  | |||
|  | @ -296,13 +296,7 @@ def cached_method(ttl=20): | |||
|     return decorator | ||||
| 
 | ||||
| 
 | ||||
| def bulk_create_decorator(instance_model, batch_size=50): | ||||
|     """ | ||||
|     装饰器,用于将实例批量保存,并提供 `commit` 方法提交剩余的实例。 | ||||
| 
 | ||||
|     :param instance_model: Django模型类,用于调用 bulk_create 方法。 | ||||
|     :param batch_size: 批量保存的阈值,默认50。 | ||||
|     """ | ||||
| def bulk_create_decorator(instance_model, batch_size=50, ignore_conflict=True): | ||||
|     def decorator(func): | ||||
|         cache = []  # 缓存实例的列表 | ||||
| 
 | ||||
|  | @ -322,7 +316,7 @@ def bulk_create_decorator(instance_model, batch_size=50): | |||
|             # 如果缓存大小达到批量保存阈值,执行保存 | ||||
|             if len(cache) >= batch_size: | ||||
|                 print(f"Batch size reached. Saving {len(cache)} instances...") | ||||
|                 instance_model.objects.bulk_create(cache) | ||||
|                 instance_model.objects.bulk_create(cache, ignore_conflict=ignore_conflict) | ||||
|                 cache.clear() | ||||
| 
 | ||||
|             return instance | ||||
|  | @ -378,16 +372,7 @@ def bulk_update_decorator(instance_model, batch_size=50, update_fields=None): | |||
|             nonlocal cache | ||||
|             if cache: | ||||
|                 print(f"Committing remaining {len(cache)} instances..., {update_fields}") | ||||
|                 # with transaction.atomic(): | ||||
|                 #     for c in cache: | ||||
|                 #         o = instance_model.objects.get(id=str(c.id)) | ||||
|                 #         print("Origin: ", o.id, o.sudoers) | ||||
|                 #         o.sudoers = c.sudoers | ||||
|                 #         o.save() | ||||
|                 #         print("New: ", c.id, c.sudoers) | ||||
|                 instance_model.objects.bulk_update(cache, update_fields) | ||||
|                 # print("Committing remaining instances... done, ", cache[0].sudoers, cache[0].id, instance_model) | ||||
|                 # print(instance_model.objects.get(id=str(cache[0].id)).sudoers) | ||||
|                 cache.clear() | ||||
| 
 | ||||
|         # 将 commit 方法绑定到装饰后的函数 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue
	
	 ibuler
						ibuler