Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: record service instance which is unbound and recycle later #1775

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
10 changes: 10 additions & 0 deletions apiserver/paasng/paasng/accessories/servicehub/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,3 +58,13 @@ class ServiceBindingType(IntStructuredEnum):

NORMAL = 1
SHARING = 2


class ServiceUnboundStatus:
songzxc789 marked this conversation as resolved.
Show resolved Hide resolved
Unbound = 1
Recycled = 2

CHOICES = (
(Unbound, "Unbound service instance with engine app"),
(Recycled, "Recycled service instance"),
)
4 changes: 4 additions & 0 deletions apiserver/paasng/paasng/accessories/servicehub/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,10 @@ class SvcAttachmentDoesNotExist(BaseServicesException):
"""remote or local service attachment does not exist"""


class UnboundSvcAttachmentDoesNotExist(BaseServicesException):
"""unbound remote or local service attachment does not exist"""


class CanNotModifyPlan(BaseServicesException):
"""remote or local service attachment already provided"""

Expand Down
62 changes: 58 additions & 4 deletions apiserver/paasng/paasng/accessories/servicehub/local/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
# 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.

"""Local services manager
"""
"""Local services manager"""

import json
import logging
import uuid
Expand All @@ -31,14 +31,20 @@
from django.utils.translation import gettext_lazy as _

from paasng.accessories.servicehub import constants
from paasng.accessories.servicehub.constants import ServiceUnboundStatus
from paasng.accessories.servicehub.exceptions import (
BindServiceNoPlansError,
CanNotModifyPlan,
ProvisionInstanceError,
ServiceObjNotFound,
SvcAttachmentDoesNotExist,
UnboundSvcAttachmentDoesNotExist,
)
from paasng.accessories.servicehub.models import (
ServiceEngineAppAttachment,
ServiceModuleAttachment,
UnboundServiceEngineAppAttachment,
)
from paasng.accessories.servicehub.models import ServiceEngineAppAttachment, ServiceModuleAttachment
from paasng.accessories.servicehub.services import (
NOTSET,
BasePlanMgr,
Expand All @@ -50,8 +56,9 @@
ServiceObj,
ServiceSpecificationDefinition,
ServiceSpecificationHelper,
UnboundEngineAppInstanceRel,
)
from paasng.accessories.services.models import Plan, Service
from paasng.accessories.services.models import Plan, Service, ServiceInstance
from paasng.misc.metrics import SERVICE_PROVISION_COUNTER
from paasng.platform.applications.models import ModuleEnvironment
from paasng.platform.engine.constants import AppEnvName
Expand Down Expand Up @@ -165,8 +172,21 @@ def provision(self):
).inc()

def recycle_resource(self):
if self.db_obj.service.prefer_async_delete:
self.mark_unbound()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

解绑过程需要加逻辑 而不是在回收逻辑里面
你需要先在开发者中心试一下解绑服务是由那个接口或者入口触发的

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

由这个接口触发,
DELETE make_app_pattern(f"/services/{SERVICE_UUID}/$", include_envs=False),
,会触发 ModuleCleaner.delete_services(),传入 service_id。 删除module时也会解绑增强服务,也会触发ModuleCleaner.delete_services(),不传service_id。

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

远程增强服务也需要有 self.db_obj.service.prefer_async_delete 类似的判断逻辑

self.db_obj.clean_service_instance()

def mark_unbound(self):
db_env = ModuleEnvironment.objects.get(engine_app=self.db_obj.engine_app)
UnboundServiceEngineAppAttachment.objects.create(
songzxc789 marked this conversation as resolved.
Show resolved Hide resolved
application=db_env.application,
module=db_env.module,
environment=db_env.environment,
engine_app=self.db_obj.engine_app,
service=self.db_obj.service,
service_instance=self.db_obj.service_instance,
)

def get_instance(self) -> ServiceInstanceObj:
"""Get service instance object"""
if not self.is_provisioned():
Expand Down Expand Up @@ -386,6 +406,17 @@ def get_attachment_by_engine_app(self, service: ServiceObj, engine_app: EngineAp
except ServiceEngineAppAttachment.DoesNotExist as e:
raise SvcAttachmentDoesNotExist from e

def get_unbound_instance_rel_by_instance_id(self, service: ServiceObj, service_instance_id: uuid.UUID):
try:
instance = UnboundServiceEngineAppAttachment.objects.get(
service_id=service.uuid,
service_instance__uuid=service_instance_id,
status=ServiceUnboundStatus.Unbound,
)
except UnboundServiceEngineAppAttachment.DoesNotExist as e:
raise UnboundSvcAttachmentDoesNotExist from e
return LocalUnboundEngineAppInstanceRel(instance)


class LocalPlanMgr(BasePlanMgr):
"""Local in-database plans manager"""
Expand Down Expand Up @@ -450,6 +481,29 @@ def _handle_plan_data(self, data: Dict) -> Dict:
return data


class LocalUnboundEngineAppInstanceRel(UnboundEngineAppInstanceRel):
"""A unbound relationship between EngineApp and Provisioned instance"""

def __init__(self, db_obj: UnboundServiceEngineAppAttachment):
self.db_obj = db_obj

def is_unbound(self):
return self.db_obj.status == ServiceUnboundStatus.Unbound

def is_recycled(self):
return not ServiceInstance.objects.filter(uuid=self.db_obj.service_instance_id).exists()

def recycle_resource(self):
if not self.is_unbound():
raise UnboundSvcAttachmentDoesNotExist("service instance is not unbound")

if self.is_recycled():
self.db_obj.delete()
return

self.db_obj.clean_service_instance()
songzxc789 marked this conversation as resolved.
Show resolved Hide resolved


class LocalPlainInstanceMgr(PlainInstanceMgr):
"""纯粹的本地增强服务实例的管理器, 不涉及增强服务资源申请的流程"""

Expand Down
12 changes: 12 additions & 0 deletions apiserver/paasng/paasng/accessories/servicehub/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import logging
import operator
import uuid
from typing import Callable, Dict, Generator, Iterable, Iterator, List, NamedTuple, Optional, TypeVar, cast

from django.db.models import QuerySet
Expand All @@ -27,6 +28,7 @@
DuplicatedServiceBoundError,
ServiceObjNotFound,
SvcAttachmentDoesNotExist,
UnboundSvcAttachmentDoesNotExist,
)
from paasng.accessories.servicehub.local.manager import LocalPlanMgr, LocalServiceMgr, LocalServiceObj
from paasng.accessories.servicehub.models import (
Expand Down Expand Up @@ -273,6 +275,16 @@ def get_attachment_by_engine_app(self, service: ServiceObj, engine_app: EngineAp
continue
raise SvcAttachmentDoesNotExist(f"engine_app<{engine_app}> has no attachment with service<{service.uuid}>")

def get_unbound_instance_rel_by_instance_id(self, service: ServiceObj, service_instance_id: uuid.UUID):
for mgr in self.mgr_instances:
try:
return mgr.get_unbound_instance_rel_by_instance_id(service, service_instance_id)
except UnboundSvcAttachmentDoesNotExist:
continue
raise UnboundSvcAttachmentDoesNotExist(
f"service<{ServiceObj}> has no attachment with service_instance_id<{service_instance_id}>"
)


class MixedPlanMgr:
"""A hub for managing plans of mixed sources: database and remote REST plans"""
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Generated by Django 4.2.16 on 2024-12-03 10:04

from django.db import migrations, models
import django.db.models.deletion
import paasng.utils.models


class Migration(migrations.Migration):

dependencies = [
('applications', '0013_applicationdeploymentmoduleorder'),
('modules', '0016_auto_20240904_1439'),
('engine', '0022_builtinconfigvar'),
('services', '0006_alter_servicecategory_name_en'),
('servicehub', '0004_auto_20240412_1723'),
]

operations = [
migrations.CreateModel(
name='UnboundServiceEngineAppAttachment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('region', models.CharField(help_text='部署区域', max_length=32)),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('owner', paasng.utils.models.BkUserField(blank=True, db_index=True, max_length=64, null=True)),
('environment', models.CharField(max_length=16, verbose_name='部署环境')),
('status', models.IntegerField(choices=[(1, 'Unbound service instance with engine app'), (2, 'Recycled service instance')], default=1, help_text='1 已解绑; 2 已回收;', verbose_name='解绑状态')),
('application', models.ForeignKey(db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='applications.application', verbose_name='蓝鲸应用')),
('engine_app', models.ForeignKey(db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='engine.engineapp', verbose_name='蓝鲸引擎应用')),
('module', models.ForeignKey(db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='modules.module', verbose_name='蓝鲸应用模块')),
('service', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='services.service', verbose_name='增强服务')),
('service_instance', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='services.serviceinstance', verbose_name='增强服务实例')),
],
options={
'verbose_name': '本地已解绑增强服务',
},
),
migrations.CreateModel(
name='UnboundRemoteServiceEngineAppAttachment',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('region', models.CharField(help_text='部署区域', max_length=32)),
('created', models.DateTimeField(auto_now_add=True)),
('updated', models.DateTimeField(auto_now=True)),
('owner', paasng.utils.models.BkUserField(blank=True, db_index=True, max_length=64, null=True)),
('environment', models.CharField(max_length=16, verbose_name='部署环境')),
('service_id', models.UUIDField(verbose_name='远程增强服务 ID')),
('service_instance_id', models.UUIDField(null=True, verbose_name='远程增强服务实例 ID')),
('status', models.IntegerField(choices=[(1, 'Unbound service instance with engine app'), (2, 'Recycled service instance')], default=1, help_text='1 已解绑; 2 已回收;', verbose_name='解绑状态')),
('application', models.ForeignKey(db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='applications.application', verbose_name='蓝鲸应用')),
('engine_app', models.ForeignKey(db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='engine.engineapp', verbose_name='蓝鲸引擎应用')),
('module', models.ForeignKey(db_constraint=False, null=True, on_delete=django.db.models.deletion.SET_NULL, to='modules.module', verbose_name='蓝鲸应用模块')),
],
options={
'verbose_name': '远程已解绑增强服务',
},
),
]
60 changes: 59 additions & 1 deletion apiserver/paasng/paasng/accessories/servicehub/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@

from django.db import models

from paasng.accessories.servicehub.constants import ServiceType
from paasng.accessories.servicehub.constants import ServiceType, ServiceUnboundStatus
from paasng.accessories.servicehub.services import ServiceObj
from paasng.accessories.services.models import Plan, Service, ServiceInstance
from paasng.platform.applications.models import ApplicationEnvironment
Expand Down Expand Up @@ -123,6 +123,38 @@ def __str__(self):
return "{prefix}-no-provision".format(prefix=prefix)


class UnboundServiceEngineAppAttachment(OwnerTimestampedModel):
songzxc789 marked this conversation as resolved.
Show resolved Hide resolved
"""Local service instance which is unbound with engine app"""

application = models.ForeignKey(
"applications.Application", on_delete=models.SET_NULL, null=True, db_constraint=False, verbose_name="蓝鲸应用"
)
module = models.ForeignKey(
"modules.Module", on_delete=models.SET_NULL, null=True, db_constraint=False, verbose_name="蓝鲸应用模块"
)
environment = models.CharField(verbose_name="部署环境", max_length=16)
engine_app = models.ForeignKey(
"engine.EngineApp", on_delete=models.SET_NULL, null=True, db_constraint=False, verbose_name="蓝鲸引擎应用"
)
service = models.ForeignKey(Service, on_delete=models.CASCADE, verbose_name="增强服务")
service_instance = models.ForeignKey(
ServiceInstance, on_delete=models.CASCADE, null=True, blank=True, verbose_name="增强服务实例"
)
status = models.IntegerField(
choices=ServiceUnboundStatus.CHOICES,
default=ServiceUnboundStatus.Unbound,
verbose_name="解绑状态",
help_text="1 已解绑; 2 已回收;",
)

class Meta:
songzxc789 marked this conversation as resolved.
Show resolved Hide resolved
verbose_name = "本地已解绑增强服务"

def clean_service_instance(self):
"""回收增强服务资源"""
self.service.delete_service_instance(self.service_instance)


class RemoteServiceModuleAttachment(OwnerTimestampedModel):
"""Binding relationship of module <-> remote service"""

Expand Down Expand Up @@ -151,6 +183,32 @@ class Meta:
unique_together = ("service_id", "engine_app")


class UnboundRemoteServiceEngineAppAttachment(OwnerTimestampedModel):
songzxc789 marked this conversation as resolved.
Show resolved Hide resolved
"""Remote service instance which is unbound with engine app"""

application = models.ForeignKey(
"applications.Application", on_delete=models.SET_NULL, null=True, db_constraint=False, verbose_name="蓝鲸应用"
)
module = models.ForeignKey(
"modules.Module", on_delete=models.SET_NULL, null=True, db_constraint=False, verbose_name="蓝鲸应用模块"
)
environment = models.CharField(verbose_name="部署环境", max_length=16)
engine_app = models.ForeignKey(
"engine.EngineApp", on_delete=models.SET_NULL, null=True, db_constraint=False, verbose_name="蓝鲸引擎应用"
)
service_id = models.UUIDField(verbose_name="远程增强服务 ID")
service_instance_id = models.UUIDField(null=True, verbose_name="远程增强服务实例 ID")
status = models.IntegerField(
choices=ServiceUnboundStatus.CHOICES,
default=ServiceUnboundStatus.Unbound,
verbose_name="解绑状态",
help_text="1 已解绑; 2 已回收;",
)

class Meta:
verbose_name = "远程已解绑增强服务"


class ServiceDBProperties:
"""Storing service related database properties"""

Expand Down
16 changes: 14 additions & 2 deletions apiserver/paasng/paasng/accessories/servicehub/remote/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
# 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.

"""Client for remote services
"""
"""Client for remote services"""

import json
import logging
from contextlib import contextmanager
Expand Down Expand Up @@ -251,6 +251,18 @@ def delete_instance(self, instance_id: str):
self.validate_resp(resp)
return

def delete_instance_synchronously(self, instance_id: str):
"""Delete a provisioned instance synchronously

We assume the remote service is already able to recycle resources
"""
url = self.config.delete_instance_url.format(instance_id=instance_id)

with wrap_request_exc(self):
resp = requests.delete(url, auth=self.auth, timeout=self.REQUEST_DELETE_TIMEOUT)
self.validate_resp(resp)
return

def update_instance_config(self, instance_id: str, config: Dict):
"""Update an provisioned instance's config

Expand Down
Loading