From f32ec185cd78681164657f00017fbed92d8b027e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EB=8F=99=ED=98=84?= Date: Tue, 12 May 2026 10:03:30 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat(API):=20=EC=99=B8=EB=B6=80=20=EC=84=9C?= =?UTF-8?q?=EB=B2=84=EB=93=A4=20=EC=97=B0=EA=B2=B0=20=EC=8B=A4=ED=8C=A8=20?= =?UTF-8?q?=EC=8B=9C=20=EB=A1=9C=EA=B7=B8=20=EB=9D=84=EC=9A=B0=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/infra/client/monitoring_impl.py | 6 ++++++ src/infra/client/project_resource_impl.py | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/src/infra/client/monitoring_impl.py b/src/infra/client/monitoring_impl.py index 7377995..cb021d1 100644 --- a/src/infra/client/monitoring_impl.py +++ b/src/infra/client/monitoring_impl.py @@ -1,3 +1,4 @@ +import logging from enum import Enum from src.core.client.http import HttpClient @@ -6,6 +7,8 @@ from src.common.config.monitoring_server import MonitoringServerConfig from src.infra.client.asyncio_http import AioHttpClient +logger = logging.getLogger(__name__) + class MonitoringAPIUrls(str, Enum): GET_UNIQUE_USERS = "/monitoring/project/{project_id}/users" @@ -29,10 +32,12 @@ async def get_unique_users(self, project_id: str) -> UniqueUsersData: ), ) except Exception as e: + logger.error(f"모니터링 서버 연결 실패: {self.base_url}, error: {e}") raise MonitoringServerException() from e try: if response.status != 200: + logger.error(f"모니터링 서버 응답 오류: {self.base_url}, status: {response.status}") raise MonitoringServerException( f"모니터링 서버 응답 오류: {response.status}" ) @@ -46,6 +51,7 @@ async def get_unique_users(self, project_id: str) -> UniqueUsersData: except MonitoringServerException: raise except Exception as e: + logger.error(f"모니터링 서버 응답 처리 오류: {self.base_url}, error: {e}") raise MonitoringServerException() from e finally: response.release() diff --git a/src/infra/client/project_resource_impl.py b/src/infra/client/project_resource_impl.py index 3d2b53e..9cf7003 100644 --- a/src/infra/client/project_resource_impl.py +++ b/src/infra/client/project_resource_impl.py @@ -1,3 +1,4 @@ +import logging from enum import Enum from src.core.domain.project import Project @@ -16,6 +17,8 @@ ExternalResourceUpdate, ) +logger = logging.getLogger(__name__) + class ProjectResourceAPIUrls(str, Enum): CREATE_PROJECT = "/projects" @@ -38,8 +41,10 @@ async def _request(self, coro) -> None: try: response = await coro except Exception as e: + logger.error(f"리소스 서버 연결 실패: {self.base_url}, error: {e}") raise ResourceServerException() from e if not response.ok: + logger.error(f"리소스 서버 응답 오류: {self.base_url}, status: {response.status}") raise ResourceServerException( f"리소스 서버 응답 오류: {response.status}" ) @@ -103,10 +108,12 @@ async def get_usage( ), ) except Exception as e: + logger.error(f"리소스 서버 연결 실패: {self.base_url}, error: {e}") raise ResourceServerException() from e try: if response.status != 200: + logger.error(f"리소스 서버 응답 오류: {self.base_url}, status: {response.status}") raise ResourceServerException( f"리소스 서버 응답 오류: {response.status}" ) @@ -141,6 +148,7 @@ async def get_usage( except ResourceServerException: raise except Exception as e: + logger.error(f"리소스 서버 응답 처리 오류: {self.base_url}, error: {e}") raise ResourceServerException() from e finally: response.release() From 2cf83b1419969275e0899febaaaceca02c4a8ab8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=B0=95=EB=8F=99=ED=98=84?= Date: Sat, 30 May 2026 00:22:02 +0900 Subject: [PATCH 2/2] =?UTF-8?q?refactor(Project):=20memory=20=EC=83=81?= =?UTF-8?q?=ED=95=9C=20=EC=83=81=EC=8A=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/project/schemas.py | 23 +++++++++++++---------- src/common/config/resource_limit.py | 25 +++++++++++++++++++++++++ tests/test_project_schemas.py | 12 ++++++------ 3 files changed, 44 insertions(+), 16 deletions(-) create mode 100644 src/common/config/resource_limit.py diff --git a/src/app/project/schemas.py b/src/app/project/schemas.py index db0434f..20b4c42 100644 --- a/src/app/project/schemas.py +++ b/src/app/project/schemas.py @@ -3,8 +3,11 @@ from pydantic import BaseModel, ConfigDict, Field, constr +from src.common.config.resource_limit import get_resource_limit_config from src.core.client.application import ApplicationHealthStatus +_limits = get_resource_limit_config() + class ProjectMemberRole(str): OWNER = "OWNER" MEMBER = "MEMBER" @@ -19,20 +22,20 @@ class ProjectCreate(BaseModel): max_cpu: float = Field( 0.1, ge=0.1, - le=4.0, + le=_limits.max_cpu, description="vCPU 기준 (기본 0.1v)", ) max_memory: float = Field( 0.5, ge=0.5, - le=1.0, - description="GB 단위 (512MB ~ 1024MB, 기본 0.5GB)", + le=_limits.max_memory, + description=f"GB 단위 (512MB ~ {int(_limits.max_memory * 1024)}MB, 기본 0.5GB)", ) max_disk: float = Field( 2.0, ge=2.0, - le=50.0, - description="GB 단위 (2GB ~ 50GB, 기본 2GB)", + le=_limits.max_disk, + description=f"GB 단위 (2GB ~ {int(_limits.max_disk)}GB, 기본 2GB)", ) @@ -58,20 +61,20 @@ class ProjectResourceUpdate(BaseModel): max_cpu: float | None = Field( None, ge=0.1, - le=4.0, + le=_limits.max_cpu, description="vCPU 기준 (0.1v ~ 4v)", ) max_memory: float | None = Field( None, ge=0.5, - le=1.0, - description="GB 단위 (512MB ~ 1024MB)", + le=_limits.max_memory, + description=f"GB 단위 (512MB ~ {int(_limits.max_memory * 1024)}MB)", ) max_disk: float | None = Field( None, ge=2.0, - le=50.0, - description="GB 단위 (2GB ~ 50GB, 늘리기만 가능)", + le=_limits.max_disk, + description=f"GB 단위 (2GB ~ {int(_limits.max_disk)}GB, 늘리기만 가능)", ) diff --git a/src/common/config/resource_limit.py b/src/common/config/resource_limit.py new file mode 100644 index 0000000..dbd0842 --- /dev/null +++ b/src/common/config/resource_limit.py @@ -0,0 +1,25 @@ +from functools import lru_cache + +from pydantic_settings import SettingsConfigDict + +from src.common.config.settings import LoggedSettings, register_config +from src.common.const.vault import VAULT_ENV_FILE + + +class ResourceLimitConfig(LoggedSettings): + model_config = SettingsConfigDict( + env_prefix="PROJECT_RESOURCE_", + extra="ignore", + env_file=VAULT_ENV_FILE, + env_file_encoding="utf-8", + ) + + max_cpu: float = 4.0 + max_memory: float = 2.0 + max_disk: float = 50.0 + + +@register_config +@lru_cache +def get_resource_limit_config() -> ResourceLimitConfig: + return ResourceLimitConfig() diff --git a/tests/test_project_schemas.py b/tests/test_project_schemas.py index 734b10b..6d1f1c0 100644 --- a/tests/test_project_schemas.py +++ b/tests/test_project_schemas.py @@ -14,17 +14,17 @@ def test_project_create_accepts_memory_and_disk_boundaries() -> None: maximum = ProjectCreate( name="beta", max_cpu=1.0, - max_memory=1.0, + max_memory=2.0, max_disk=50.0, ) assert minimum.max_memory == 0.5 assert minimum.max_disk == 2.0 - assert maximum.max_memory == 1.0 + assert maximum.max_memory == 2.0 assert maximum.max_disk == 50.0 -@pytest.mark.parametrize("max_memory", [0.49, 1.01]) +@pytest.mark.parametrize("max_memory", [0.49, 2.01]) def test_project_create_rejects_out_of_range_memory(max_memory: float) -> None: with pytest.raises(ValidationError): ProjectCreate( @@ -48,15 +48,15 @@ def test_project_create_rejects_out_of_range_disk(max_disk: float) -> None: def test_project_resource_update_accepts_memory_and_disk_boundaries() -> None: minimum = ProjectResourceUpdate(max_memory=0.5, max_disk=2.0) - maximum = ProjectResourceUpdate(max_memory=1.0, max_disk=50.0) + maximum = ProjectResourceUpdate(max_memory=2.0, max_disk=50.0) assert minimum.max_memory == 0.5 assert minimum.max_disk == 2.0 - assert maximum.max_memory == 1.0 + assert maximum.max_memory == 2.0 assert maximum.max_disk == 50.0 -@pytest.mark.parametrize("max_memory", [0.49, 1.01]) +@pytest.mark.parametrize("max_memory", [0.49, 2.01]) def test_project_resource_update_rejects_out_of_range_memory(max_memory: float) -> None: with pytest.raises(ValidationError): ProjectResourceUpdate(max_memory=max_memory)