-
Notifications
You must be signed in to change notification settings - Fork 62
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add api to control devcontainer (#1163)
- Loading branch information
Showing
35 changed files
with
1,696 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# -*- coding: utf-8 -*- | ||
""" | ||
TencentBlueKing is pleased to support the open source community by making | ||
蓝鲸智云 - PaaS 平台 (BlueKing - PaaS System) available. | ||
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved. | ||
Licensed under the MIT License (the "License"); you may not use this file except | ||
in compliance with the License. You may obtain a copy of the License at | ||
http://opensource.org/licenses/MIT | ||
Unless required by applicable law or agreed to in writing, software distributed under | ||
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, | ||
either express or implied. See the License for the specific language governing permissions and | ||
limitations under the License. | ||
We undertake not to change the open source license (MIT license) applicable | ||
to the current version of the project delivered to anyone in the future. | ||
""" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,53 @@ | ||
# -*- coding: utf-8 -*- | ||
""" | ||
TencentBlueKing is pleased to support the open source community by making | ||
蓝鲸智云 - PaaS 平台 (BlueKing - PaaS System) available. | ||
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved. | ||
Licensed under the MIT License (the "License"); you may not use this file except | ||
in compliance with the License. You may obtain a copy of the License at | ||
http://opensource.org/licenses/MIT | ||
Unless required by applicable law or agreed to in writing, software distributed under | ||
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, | ||
either express or implied. See the License for the specific language governing permissions and | ||
limitations under the License. | ||
We undertake not to change the open source license (MIT license) applicable | ||
to the current version of the project delivered to anyone in the future. | ||
""" | ||
from typing import List | ||
|
||
from django.conf import settings | ||
|
||
from .entities import IngressPathBackend, ServicePortPair | ||
|
||
_ingress_service_conf = [ | ||
# dev sandbox 中 devserver 的路径与端口映射 | ||
{ | ||
"path_prefix": "/devserver/", | ||
"service_port_name": "devserver", | ||
"port": 8000, | ||
"target_port": settings.DEV_SANDBOX_DEVSERVER_PORT, | ||
}, | ||
# dev sandbox 中 saas 应用的路径与端口映射 | ||
{"path_prefix": "/", "service_port_name": "app", "port": 80, "target_port": settings.CONTAINER_PORT}, | ||
] | ||
|
||
|
||
DEV_SANDBOX_SVC_PORT_PAIRS: List[ServicePortPair] = [ | ||
ServicePortPair(name=conf["service_port_name"], port=conf["port"], target_port=conf["target_port"]) | ||
for conf in _ingress_service_conf | ||
] | ||
|
||
|
||
def get_ingress_path_backends(service_name: str) -> List[IngressPathBackend]: | ||
"""get ingress path backends from _ingress_service_conf with service_name""" | ||
return [ | ||
IngressPathBackend( | ||
path_prefix=conf["path_prefix"], | ||
service_name=service_name, | ||
service_port_name=conf["service_port_name"], | ||
) | ||
for conf in _ingress_service_conf | ||
] |
144 changes: 144 additions & 0 deletions
144
apiserver/paasng/paas_wl/bk_app/dev_sandbox/controller.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,144 @@ | ||
# -*- coding: utf-8 -*- | ||
""" | ||
TencentBlueKing is pleased to support the open source community by making | ||
蓝鲸智云 - PaaS 平台 (BlueKing - PaaS System) available. | ||
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved. | ||
Licensed under the MIT License (the "License"); you may not use this file except | ||
in compliance with the License. You may obtain a copy of the License at | ||
http://opensource.org/licenses/MIT | ||
Unless required by applicable law or agreed to in writing, software distributed under | ||
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, | ||
either express or implied. See the License for the specific language governing permissions and | ||
limitations under the License. | ||
We undertake not to change the open source license (MIT license) applicable | ||
to the current version of the project delivered to anyone in the future. | ||
""" | ||
from typing import Dict | ||
|
||
from django.conf import settings | ||
|
||
from paas_wl.bk_app.applications.models import WlApp | ||
from paas_wl.bk_app.deploy.app_res.controllers import NamespacesHandler | ||
from paas_wl.bk_app.dev_sandbox.entities import DevSandboxDetail, HealthPhase, Resources, ResourceSpec, Runtime | ||
from paas_wl.bk_app.dev_sandbox.kres_entities import ( | ||
DevSandbox, | ||
DevSandboxIngress, | ||
DevSandboxService, | ||
get_dev_sandbox_name, | ||
get_ingress_name, | ||
) | ||
from paas_wl.infras.resources.kube_res.base import AppEntityManager | ||
from paas_wl.infras.resources.kube_res.exceptions import AppEntityNotFound | ||
from paasng.platform.applications.models import Application | ||
from paasng.platform.modules.constants import DEFAULT_ENGINE_APP_PREFIX, ModuleName | ||
|
||
from .exceptions import DevSandboxAlreadyExists, DevSandboxResourceNotFound | ||
|
||
|
||
class DevSandboxController: | ||
"""DevSandbox Controller | ||
:param app: Application 实例 | ||
:param module_name: 模块名称 | ||
""" | ||
|
||
sandbox_mgr: AppEntityManager[DevSandbox] = AppEntityManager[DevSandbox](DevSandbox) | ||
service_mgr: AppEntityManager[DevSandboxService] = AppEntityManager[DevSandboxService](DevSandboxService) | ||
ingress_mgr: AppEntityManager[DevSandboxIngress] = AppEntityManager[DevSandboxIngress](DevSandboxIngress) | ||
|
||
def __init__(self, app: Application, module_name: str): | ||
self.app = app | ||
self.dev_wl_app: WlApp = _DevWlAppCreator(app, module_name).create() | ||
|
||
def deploy(self, envs: Dict[str, str]): | ||
"""部署 dev sandbox | ||
:param envs: 启动开发沙箱所需要的环境变量 | ||
""" | ||
sandbox_name = get_dev_sandbox_name(self.dev_wl_app) | ||
try: | ||
self.sandbox_mgr.get(self.dev_wl_app, sandbox_name) | ||
except AppEntityNotFound: | ||
self._deploy(envs) | ||
else: | ||
raise DevSandboxAlreadyExists(f"dev sandbox {sandbox_name} already exists") | ||
|
||
def delete(self): | ||
"""通过直接删除命名空间的方式, 销毁 dev sandbox 服务""" | ||
ns_handler = NamespacesHandler.new_by_app(self.dev_wl_app) | ||
ns_handler.delete(namespace=self.dev_wl_app.namespace) | ||
|
||
def get_sandbox_detail(self) -> DevSandboxDetail: | ||
"""获取 dev sandbox 详情""" | ||
try: | ||
ingress_entity: DevSandboxIngress = self.ingress_mgr.get( | ||
self.dev_wl_app, get_ingress_name(self.dev_wl_app) | ||
) | ||
except AppEntityNotFound: | ||
raise DevSandboxResourceNotFound("dev sandbox url not found") | ||
|
||
url = ingress_entity.domains[0].host | ||
|
||
try: | ||
container_entity: DevSandbox = self.sandbox_mgr.get(self.dev_wl_app, get_dev_sandbox_name(self.dev_wl_app)) | ||
except AppEntityNotFound: | ||
raise DevSandboxResourceNotFound("dev sandbox not found") | ||
|
||
status = container_entity.status.to_health_phase() if container_entity.status else HealthPhase.UNKNOWN | ||
return DevSandboxDetail(url=url, envs=container_entity.runtime.envs, status=status) | ||
|
||
def _deploy(self, envs: Dict[str, str]): | ||
"""部署 sandbox 服务""" | ||
# step 1. ensure namespace | ||
ns_handler = NamespacesHandler.new_by_app(self.dev_wl_app) | ||
ns_handler.ensure_namespace(namespace=self.dev_wl_app.namespace) | ||
|
||
# step 2. create dev sandbox | ||
default_resources = Resources( | ||
limits=ResourceSpec(cpu="4", memory="2Gi"), | ||
requests=ResourceSpec(cpu="200m", memory="512Mi"), | ||
) | ||
|
||
sandbox_entity = DevSandbox.create( | ||
self.dev_wl_app, | ||
runtime=Runtime(envs=envs, image=settings.DEV_SANDBOX_IMAGE), | ||
resources=default_resources, | ||
) | ||
self.sandbox_mgr.create(sandbox_entity) | ||
|
||
# step 3. upsert service | ||
service_entity = DevSandboxService.create(self.dev_wl_app) | ||
self.service_mgr.upsert(service_entity) | ||
|
||
# step 4. upsert ingress | ||
ingress_entity = DevSandboxIngress.create(self.dev_wl_app, self.app.code) | ||
self.ingress_mgr.upsert(ingress_entity) | ||
|
||
|
||
class _DevWlAppCreator: | ||
"""WlApp 实例构造器""" | ||
|
||
def __init__(self, app: Application, module_name: str): | ||
self.app = app | ||
self.module_name = module_name | ||
|
||
def create(self) -> WlApp: | ||
"""创建 WlApp 实例""" | ||
dev_wl_app = WlApp(region=self.app.region, name=self._make_dev_wl_app_name(), type=self.app.type) | ||
|
||
# 因为 dev_wl_app 不是查询集结果, 所以需要覆盖 namespace 和 module_name, 以保证 AppEntityManager 模式能够正常工作 | ||
# TODO 考虑更规范的方式处理这两个 cached_property 属性. 如考虑使用 WlAppProtocol 满足 AppEntityManager 模式 | ||
setattr(dev_wl_app, "namespace", f"{DEFAULT_ENGINE_APP_PREFIX}-{self.app.code}-dev".replace("_", "0us0")) | ||
setattr(dev_wl_app, "module_name", self.module_name) | ||
|
||
return dev_wl_app | ||
|
||
def _make_dev_wl_app_name(self) -> str: | ||
"""参考 make_engine_app_name 规则, 生成 dev 环境的 WlApp name""" | ||
if self.module_name == ModuleName.DEFAULT.value: | ||
return f"{DEFAULT_ENGINE_APP_PREFIX}-{self.app.code}-dev" | ||
else: | ||
return f"{DEFAULT_ENGINE_APP_PREFIX}-{self.app.code}-m-{self.module_name}-dev" |
118 changes: 118 additions & 0 deletions
118
apiserver/paasng/paas_wl/bk_app/dev_sandbox/entities.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
# -*- coding: utf-8 -*- | ||
""" | ||
TencentBlueKing is pleased to support the open source community by making | ||
蓝鲸智云 - PaaS 平台 (BlueKing - PaaS System) available. | ||
Copyright (C) 2017 THL A29 Limited, a Tencent company. All rights reserved. | ||
Licensed under the MIT License (the "License"); you may not use this file except | ||
in compliance with the License. You may obtain a copy of the License at | ||
http://opensource.org/licenses/MIT | ||
Unless required by applicable law or agreed to in writing, software distributed under | ||
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, | ||
either express or implied. See the License for the specific language governing permissions and | ||
limitations under the License. | ||
We undertake not to change the open source license (MIT license) applicable | ||
to the current version of the project delivered to anyone in the future. | ||
""" | ||
from dataclasses import dataclass, field | ||
from typing import Dict, List, Optional | ||
|
||
from blue_krill.data_types.enum import EnumField, StructuredEnum | ||
|
||
from paas_wl.workloads.release_controller.constants import ImagePullPolicy | ||
|
||
|
||
class HealthPhase(str, StructuredEnum): | ||
HEALTHY = EnumField("Healthy") | ||
PROGRESSING = EnumField("Progressing") | ||
UNHEALTHY = EnumField("Unhealthy") | ||
UNKNOWN = EnumField("Unknown") | ||
|
||
|
||
@dataclass | ||
class Runtime: | ||
"""容器运行相关配置""" | ||
|
||
envs: Dict[str, str] | ||
image: str | ||
image_pull_policy: ImagePullPolicy = field(default=ImagePullPolicy.ALWAYS) | ||
|
||
|
||
@dataclass | ||
class ResourceSpec: | ||
cpu: str | ||
memory: str | ||
|
||
def to_dict(self): | ||
return { | ||
"cpu": self.cpu, | ||
"memory": self.memory, | ||
} | ||
|
||
|
||
@dataclass | ||
class Resources: | ||
"""计算资源定义""" | ||
|
||
limits: Optional[ResourceSpec] = None | ||
requests: Optional[ResourceSpec] = None | ||
|
||
def to_dict(self): | ||
d = {} | ||
if self.limits: | ||
d["limits"] = self.limits.to_dict() | ||
if self.requests: | ||
d["requests"] = self.requests.to_dict() | ||
return d | ||
|
||
|
||
@dataclass | ||
class Status: | ||
replicas: int | ||
ready_replicas: int | ||
|
||
def to_health_phase(self) -> str: | ||
if self.replicas == self.ready_replicas: | ||
return HealthPhase.HEALTHY | ||
|
||
# TODO 如果需要细化出 Unhealthy, 可以结合 Conditions 处理 | ||
# 将 Unhealthy 也并入 Progressing, 简化需求 | ||
return HealthPhase.PROGRESSING | ||
|
||
|
||
@dataclass | ||
class ServicePortPair: | ||
"""Service port pair""" | ||
|
||
name: str | ||
port: int | ||
target_port: int | ||
protocol: str = "TCP" | ||
|
||
|
||
@dataclass | ||
class IngressPathBackend: | ||
"""Ingress Path Backend object""" | ||
|
||
path_prefix: str | ||
service_name: str | ||
service_port_name: str | ||
|
||
|
||
@dataclass | ||
class IngressDomain: | ||
"""Ingress Domain object""" | ||
|
||
host: str | ||
path_backends: List[IngressPathBackend] | ||
tls_enabled: bool = False | ||
tls_secret_name: str = "" | ||
|
||
|
||
@dataclass | ||
class DevSandboxDetail: | ||
url: str | ||
envs: Dict[str, str] | ||
status: str |
Oops, something went wrong.