diff --git a/gcloud/core/project.py b/gcloud/core/project.py index fe1b32382a..df6a28dfa7 100644 --- a/gcloud/core/project.py +++ b/gcloud/core/project.py @@ -16,8 +16,8 @@ from django.db import IntegrityError from gcloud.conf import settings -from gcloud.core.utils import get_user_business_list from gcloud.core.models import Business, Project, UserDefaultProject +from gcloud.core.utils import get_user_business_list from gcloud.iam_auth.utils import get_user_projects logger = logging.getLogger("root") @@ -31,6 +31,7 @@ def sync_projects_from_cmdb(username, use_cache=True): biz_list = get_user_business_list(username=username, use_cache=use_cache) business_dict = {} + all_biz_cc_ids = set() archived_biz_cc_ids = set() active_biz_cc_ids = set() @@ -41,6 +42,8 @@ def sync_projects_from_cmdb(username, use_cache=True): biz_cc_id = biz["bk_biz_id"] biz_status = biz.get("bk_data_status", "enable") + all_biz_cc_ids.add(biz_cc_id) + if biz_status == "disabled": archived_biz_cc_ids.add(biz_cc_id) @@ -76,8 +79,14 @@ def sync_projects_from_cmdb(username, use_cache=True): except IntegrityError as e: logger.warning("[sync_project_from_cmdb_business] create projects failed due to: {}".format(e)) + # 计算出 CC 已删除并且存在于项目中的业务 ID 集合,对这部分项目也需要进行归档 + exist_sync_biz_cc_ids = set(Project.objects.filter(from_cmdb=True).values_list("bk_biz_id", flat=True)) + deleted_biz_cc_ids = exist_sync_biz_cc_ids - all_biz_cc_ids + # update project's status which sync from cmdb - Project.objects.update_business_project_status(archived_cc_ids=archived_biz_cc_ids, active_cc_ids=active_biz_cc_ids) + Project.objects.update_business_project_status( + archived_cc_ids=archived_biz_cc_ids | deleted_biz_cc_ids, active_cc_ids=active_biz_cc_ids + ) def get_default_project_for_user(username): diff --git a/gcloud/tests/core/test_project.py b/gcloud/tests/core/test_project.py index 7eb9756263..73c776ff7e 100644 --- a/gcloud/tests/core/test_project.py +++ b/gcloud/tests/core/test_project.py @@ -15,7 +15,6 @@ from gcloud.core import project from gcloud.core.models import Business, Project - from gcloud.tests.mock import * # noqa from gcloud.tests.mock_settings import * # noqa @@ -64,6 +63,30 @@ def tearDown(self): Business.objects.all().delete() Project.objects.all().delete() + @classmethod + def generate_business_update_or_create_params_list(cls, biz_list): + return [ + dict( + cc_id=biz["bk_biz_id"], + defaults={ + "cc_name": biz["bk_biz_name"], + "cc_owner": biz["bk_supplier_account"], + "cc_company": biz["bk_supplier_id"], + "time_zone": biz["time_zone"], + "life_cycle": biz["life_cycle"], + "status": biz["bk_data_status"], + }, + ) + for biz in biz_list + ] + + @classmethod + def generate_business_update_or_create_calls(cls, biz_list): + return [ + mock.call(**update_or_create_params) + for update_or_create_params in cls.generate_business_update_or_create_params_list(biz_list) + ] + @patch(CORE_PROJECT_GET_USER_BUSINESS_LIST, MagicMock(return_value=[])) @patch(CORE_MODEL_PROJECT_SYNC_PROJECT, MagicMock(return_value=[])) @patch(CORE_MODEL_USER_DEFAULT_PROJECT_INIT_USER_DEFAULT_PROJECT, MagicMock()) @@ -102,41 +125,7 @@ def test_sync_projects_from_cmdb(self): project.sync_projects_from_cmdb("user") Business.objects.update_or_create.assert_has_calls( - [ - mock.call( - cc_id=1, - defaults={ - "cc_name": "name_1", - "cc_owner": "supplier_account", - "cc_company": 0, - "time_zone": "time_zone", - "life_cycle": "life_cycle", - "status": "enable", - }, - ), - mock.call( - cc_id=4, - defaults={ - "cc_name": "name_4", - "cc_owner": "supplier_account", - "cc_company": 0, - "time_zone": "time_zone", - "life_cycle": "life_cycle", - "status": "enable", - }, - ), - mock.call( - cc_id=5, - defaults={ - "cc_name": "name_5", - "cc_owner": "supplier_account", - "cc_company": 0, - "time_zone": "time_zone", - "life_cycle": "life_cycle", - "status": "enable", - }, - ), - ] + self.generate_business_update_or_create_calls(CaseData.sync_projects_from_cmdb_business_data()["biz_list"]) ) Project.objects.sync_project_from_cmdb_business.assert_called_once_with( @@ -146,3 +135,37 @@ def test_sync_projects_from_cmdb(self): 5: {"cc_name": "name_5", "time_zone": "time_zone", "creator": "user"}, } ) + + @patch( + CORE_PROJECT_GET_USER_BUSINESS_LIST, + MagicMock(return_value=CaseData.sync_projects_from_cmdb_business_data()["biz_list"][:-1]), + ) + @patch(CORE_MODEL_BUSINESS_UPDATE_OR_CREATE, MagicMock()) + @patch(CORE_MODEL_PROJECT_SYNC_PROJECT, MagicMock(return_value=["token_0", "token_1"])) + @patch(CORE_MODEL_USER_DEFAULT_PROJECT_INIT_USER_DEFAULT_PROJECT, MagicMock()) + @patch(CORE_MODEL_PROJECT_UPDATE_BUSINESS_PROJECT_STATUS, MagicMock()) + def test_sync_projects_from_cmdb__business_deleted(self): + + project_values_list_qs = MagicMock() + project_values_list_qs.values_list = MagicMock(return_value=[1, 4, 5]) + biz_list = CaseData.sync_projects_from_cmdb_business_data()["biz_list"] + with patch(CORE_PROJECT_GET_USER_BUSINESS_LIST, MagicMock(return_value=biz_list)): + with patch(PROJECT_FILTER, MagicMock(return_value=project_values_list_qs)): + project.sync_projects_from_cmdb("user") + + # 模拟从 CC 删除最后一个业务 + with patch(CORE_PROJECT_GET_USER_BUSINESS_LIST, MagicMock(return_value=biz_list[:-1])): + with patch(PROJECT_FILTER, MagicMock(return_value=project_values_list_qs)): + project.sync_projects_from_cmdb("user") + + Business.objects.update_or_create.assert_has_calls( + self.generate_business_update_or_create_calls(biz_list + biz_list[:-1]) + ) + + Project.objects.update_business_project_status.assert_has_calls( + [ + mock.call(archived_cc_ids=set(), active_cc_ids={1, 4, 5}), + # cc_id=5 的业务被删除,需要归档 + mock.call(archived_cc_ids={5}, active_cc_ids={1, 4}), + ] + )