From 340f864555389b1164a2e00dfbbdf4f1dde7ad97 Mon Sep 17 00:00:00 2001 From: ChrisLiu Date: Thu, 13 Jul 2023 16:55:37 +0800 Subject: [PATCH] feat: support to delete & restart Signed-off-by: ChrisLiu --- aigc-dashboard/src/components/engine.vue | 32 +++++- aigc-dashboard/src/components/engines.vue | 2 +- .../aigc-gateway/templates/aigc-gateway.yaml | 14 +++ pkg/resources/resource_manager.go | 75 ++++++++++++ pkg/routers/resource.go | 107 ++++++++++++++++++ pkg/routers/sign.go | 3 + 6 files changed, 231 insertions(+), 2 deletions(-) diff --git a/aigc-dashboard/src/components/engine.vue b/aigc-dashboard/src/components/engine.vue index b4eb67d..c8bd080 100644 --- a/aigc-dashboard/src/components/engine.vue +++ b/aigc-dashboard/src/components/engine.vue @@ -98,6 +98,26 @@ export default { this.items = response.data }).catch((error) => { }) + }, + del: function () { + let engine = this.engine; + let name = engine.metadata.name; + let namespace = engine.metadata.namespace; + + this.axios.delete("/resource/" + namespace + "/" + name).then((response) => { + this.items = response.data + }).catch((error) => { + }) + }, + restart: function () { + let engine = this.engine; + let name = engine.metadata.name; + let namespace = engine.metadata.namespace; + + this.axios.post("/resource/" + namespace + "/" + name + "/restart").then((response) => { + this.items = response.data + }).catch((error) => { + }) } }, created: function () { @@ -146,7 +166,7 @@ export default { > - + @@ -169,6 +189,16 @@ export default { Pause + + + Uninstall + + + + + Restart + + diff --git a/aigc-dashboard/src/components/engines.vue b/aigc-dashboard/src/components/engines.vue index 1ce81e9..15f04c8 100644 --- a/aigc-dashboard/src/components/engines.vue +++ b/aigc-dashboard/src/components/engines.vue @@ -13,7 +13,7 @@ export default { this.axios.get("/resources").then((response) => { this.items = response.data }).catch((error) => { - this.items = [{"kind":"GameServerSet","apiVersion":"game.kruise.io/v1alpha1","metadata":{"name":"stable-diffusion-cpu","namespace":"default","uid":"198242b9-f6c8-4d33-8a80-5bbee44fa353","resourceVersion":"3534496","generation":11,"creationTimestamp":"2023-05-19T11:05:15Z","annotations":{"game.kruise.io/reserve-ids":"3,2,1","kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"game.kruise.io/v1alpha1\",\"kind\":\"GameServerSet\",\"metadata\":{\"annotations\":{},\"name\":\"stable-diffusion-cpu\",\"namespace\":\"default\"},\"spec\":{\"gameServerTemplate\":{\"spec\":{\"containers\":[{\"args\":[\"--listen\",\"--skip-torch-cuda-test\",\"--no-half\"],\"command\":[\"python3\",\"launch.py\"],\"env\":[{\"name\":\"POD_NAME\",\"valueFrom\":{\"fieldRef\":{\"apiVersion\":\"v1\",\"fieldPath\":\"metadata.name\"}}}],\"image\":\"yunqi-registry.cn-shanghai.cr.aliyuncs.com/lab/stable-diffusion:v1.0.0-cpu\",\"name\":\"stable-diffusion\",\"readinessProbe\":{\"failureThreshold\":3,\"initialDelaySeconds\":5,\"periodSeconds\":10,\"successThreshold\":1,\"tcpSocket\":{\"port\":7860},\"timeoutSeconds\":1}}]}},\"network\":{\"networkConf\":[{\"name\":\"IngressClassName\",\"value\":\"nginx\"},{\"name\":\"Port\",\"value\":\"7860\"},{\"name\":\"Host\",\"value\":\"instances\\u003cid\\u003e.c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com\"},{\"name\":\"PathType\",\"value\":\"ImplementationSpecific\"},{\"name\":\"Path\",\"value\":\"/\"},{\"name\":\"Annotation\",\"value\":\"nginx.ingress.kubernetes.io/auth-url: https://dashboard.c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com/sign-in\"},{\"name\":\"Annotation\",\"value\":\"nginx.ingress.kubernetes.io/auth-signin: https://dashboard.c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com/\"}],\"networkType\":\"Kubernetes-Ingress\"},\"replicas\":0,\"scaleStrategy\":{\"scaleDownStrategyType\":\"ReserveIds\"},\"updateStrategy\":{\"rollingUpdate\":{\"maxUnavailable\":\"100%\",\"podUpdatePolicy\":\"InPlaceIfPossible\"}}}}\n"},"managedFields":[{"manager":"manager","operation":"Update","apiVersion":"game.kruise.io/v1alpha1","time":"2023-05-19T11:06:04Z","fieldsType":"FieldsV1","fieldsV1":{"f:status":{".":{},"f:availableReplicas":{},"f:currentReplicas":{},"f:labelSelector":{},"f:maintainingReplicas":{},"f:observedGeneration":{},"f:readyReplicas":{},"f:replicas":{},"f:updatedReadyReplicas":{},"f:updatedReplicas":{},"f:waitToBeDeletedReplicas":{}}},"subresource":"status"},{"manager":"kubectl-client-side-apply","operation":"Update","apiVersion":"game.kruise.io/v1alpha1","time":"2023-05-22T08:05:50Z","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:kubectl.kubernetes.io/last-applied-configuration":{}}},"f:spec":{".":{},"f:gameServerTemplate":{".":{},"f:spec":{}},"f:network":{".":{},"f:networkConf":{},"f:networkType":{}},"f:scaleStrategy":{},"f:updateStrategy":{".":{},"f:rollingUpdate":{".":{},"f:maxUnavailable":{},"f:podUpdatePolicy":{}}}}}},{"manager":"manager","operation":"Update","apiVersion":"game.kruise.io/v1alpha1","time":"2023-05-22T08:05:50Z","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{"f:game.kruise.io/reserve-ids":{}}}}},{"manager":"aigc-gateway","operation":"Update","apiVersion":"game.kruise.io/v1alpha1","time":"2023-05-22T08:12:48Z","fieldsType":"FieldsV1","fieldsV1":{"f:spec":{"f:gameServerTemplate":{"f:metadata":{".":{},"f:creationTimestamp":{}},"f:spec":{"f:containers":{}}},"f:replicas":{},"f:reserveGameServerIds":{}}}}]},"spec":{"replicas":2,"gameServerTemplate":{"metadata":{"creationTimestamp":null},"spec":{"containers":[{"name":"stable-diffusion","image":"yunqi-registry.cn-shanghai.cr.aliyuncs.com/lab/stable-diffusion:v1.0.0-cpu","command":["python3","launch.py"],"args":["--listen","--skip-torch-cuda-test","--no-half"],"env":[{"name":"POD_NAME","valueFrom":{"fieldRef":{"apiVersion":"v1","fieldPath":"metadata.name"}}}],"resources":{},"readinessProbe":{"tcpSocket":{"port":7860},"initialDelaySeconds":5,"timeoutSeconds":1,"periodSeconds":10,"successThreshold":1,"failureThreshold":3}}]}},"reserveGameServerIds":[3,2,1],"updateStrategy":{"rollingUpdate":{"maxUnavailable":"100%","podUpdatePolicy":"InPlaceIfPossible"}},"scaleStrategy":{},"network":{"networkType":"Kubernetes-Ingress","networkConf":[{"name":"IngressClassName","value":"nginx"},{"name":"Port","value":"7860"},{"name":"Host","value":"instances\u003cid\u003e.c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com"},{"name":"PathType","value":"ImplementationSpecific"},{"name":"Path","value":"/"},{"name":"Annotation","value":"nginx.ingress.kubernetes.io/auth-url: https://dashboard.c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com/sign-in"},{"name":"Annotation","value":"nginx.ingress.kubernetes.io/auth-signin: https://dashboard.c5464a5f2c39341d3b3eda6e2dd37b505.cn-hangzhou.alicontainer.com/"}]}},"status":{"observedGeneration":11,"replicas":2,"readyReplicas":1,"availableReplicas":1,"currentReplicas":2,"updatedReplicas":2,"updatedReadyReplicas":1,"maintainingReplicas":0,"waitToBeDeletedReplicas":0,"labelSelector":"game.kruise.io/owner-gss=stable-diffusion-cpu"}}] + this.items = [] }) } }, diff --git a/deploy/helm/aigc-gateway/templates/aigc-gateway.yaml b/deploy/helm/aigc-gateway/templates/aigc-gateway.yaml index 2ecc9ba..fdf76a4 100644 --- a/deploy/helm/aigc-gateway/templates/aigc-gateway.yaml +++ b/deploy/helm/aigc-gateway/templates/aigc-gateway.yaml @@ -143,6 +143,20 @@ rules: - get - patch - update + - apiGroups: + - "" + resources: + - persistentvolumeclaims + verbs: + - delete + - get + - apiGroups: + - "" + resources: + - pods + verbs: + - get + - delete --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding diff --git a/pkg/resources/resource_manager.go b/pkg/resources/resource_manager.go index d958f9d..c5d5f19 100644 --- a/pkg/resources/resource_manager.go +++ b/pkg/resources/resource_manager.go @@ -4,6 +4,7 @@ import ( "context" gamekruiseiov1alpha1 "github.com/openkruise/kruise-game/apis/v1alpha1" "github.com/openkruise/kruise-game/pkg/util" + v1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" @@ -214,6 +215,80 @@ func (rm *ResourceManager) RecoverResource(meta *ResourceMeta) (Resource, error) return gs, nil } +func (rm *ResourceManager) DeleteResource(meta *ResourceMeta) error { + if err := checkResourceMeta(meta, &metaNeed{ID: true, Name: true, Namespace: true}); err != nil { + return err + } + + // get GameServerSet + gss := &gamekruiseiov1alpha1.GameServerSet{} + err := rm.Get(context.Background(), types.NamespacedName{ + Name: meta.Name, + Namespace: meta.Namespace, + }, gss) + if err != nil { + return NewResourceError(ApiCallError, "", err.Error()) + } + + idInt, _ := strconv.Atoi(meta.ID) + // check if gs exist or not + if !util.IsNumInList(idInt, gss.Spec.ReserveGameServerIds) { + // update GameServerSet to delete gs + gss.Spec.Replicas = pointer.Int32(*gss.Spec.Replicas - 1) + gss.Spec.ReserveGameServerIds = append(gss.Spec.ReserveGameServerIds, []int{idInt}...) + err = rm.Update(context.Background(), gss) + if err != nil { + return err + } + } + + // delete pvcs related to gss + for _, vct := range gss.Spec.GameServerTemplate.VolumeClaimTemplates { + pvc := &v1.PersistentVolumeClaim{} + err = rm.Get(context.Background(), types.NamespacedName{ + Name: vct.GetName() + "-" + meta.Name + "-" + meta.ID, + Namespace: meta.Namespace, + }, pvc) + if err != nil { + if errors.IsNotFound(err) { + return nil + } + return NewResourceError(ApiCallError, "", err.Error()) + } + err = rm.Delete(context.Background(), pvc) + if err != nil && !errors.IsNotFound(err) { + return NewResourceError(ApiCallError, "", err.Error()) + } + } + + return nil +} + +func (rm *ResourceManager) RestartResource(meta *ResourceMeta) error { + if err := checkResourceMeta(meta, &metaNeed{ID: true, Name: true, Namespace: true}); err != nil { + return err + } + + // delete pod + pod := &v1.Pod{} + err := rm.Get(context.Background(), types.NamespacedName{ + Name: meta.Name + "-" + meta.ID, + Namespace: meta.Namespace, + }, pod) + if err != nil { + if errors.IsNotFound(err) { + return nil + } + return NewResourceError(ApiCallError, "", err.Error()) + } + err = rm.Delete(context.Background(), pod) + if err != nil && !errors.IsNotFound(err) { + return NewResourceError(ApiCallError, "", err.Error()) + } + + return nil +} + type metaNeed struct { ID bool Name bool diff --git a/pkg/routers/resource.go b/pkg/routers/resource.go index b7c0b70..6c626f7 100644 --- a/pkg/routers/resource.go +++ b/pkg/routers/resource.go @@ -222,6 +222,113 @@ func RegisterResourceRouters(router *gin.Engine, logtoConfig *client.LogtoConfig ctx.Status(200) return }) + + router.DELETE("/resource/:namespace/:name", func(ctx *gin.Context) { + name := ctx.Param("name") + namespace := ctx.Param("namespace") + + session := sessions.Default(ctx) + logtoClient := client.NewLogtoClient( + logtoConfig, + &mem.SessionStorage{Session: session}, + ) + + userInfo, err := logtoClient.FetchUserInfo() + + if err != nil { + ctx.Error(err) + return + } + + cm := userInfo.CustomData + + key := fmt.Sprintf("%s-%s", namespace, name) + value := cm[key] + if value == nil { + ctx.Status(400) + return + } + valueBytes, err := interfaceToBytes(value) + if err != nil { + ctx.Error(err) + return + } + + rm := &resources.ResourceMeta{} + err = json.Unmarshal(valueBytes, rm) + if err != nil { + ctx.Error(err) + return + } + + // add json wrapper + resourceManager := resources.NewResourceManager() + err = resourceManager.DeleteResource(rm) + if err != nil { + ctx.Error(err) + } + + cm[key] = nil + + err = user.UpdateUserMetaData(userInfo.Sub, cm) + if err != nil { + ctx.Error(err) + return + } + + ctx.Status(200) + return + }) + + router.POST("/resource/:namespace/:name/restart", func(ctx *gin.Context) { + name := ctx.Param("name") + namespace := ctx.Param("namespace") + + session := sessions.Default(ctx) + logtoClient := client.NewLogtoClient( + logtoConfig, + &mem.SessionStorage{Session: session}, + ) + + userInfo, err := logtoClient.FetchUserInfo() + + if err != nil { + ctx.Error(err) + return + } + + cm := userInfo.CustomData + + key := fmt.Sprintf("%s-%s", namespace, name) + value := cm[key] + if value == nil { + ctx.Status(400) + return + } + valueBytes, err := interfaceToBytes(value) + if err != nil { + ctx.Error(err) + return + } + + rm := &resources.ResourceMeta{} + err = json.Unmarshal(valueBytes, rm) + if err != nil { + ctx.Error(err) + return + } + + // add json wrapper + resourceManager := resources.NewResourceManager() + err = resourceManager.RestartResource(rm) + if err != nil { + ctx.Error(err) + return + } + + ctx.Status(200) + return + }) } func interfaceToBytes(data interface{}) ([]byte, error) { diff --git a/pkg/routers/sign.go b/pkg/routers/sign.go index 9b801ef..a283b47 100644 --- a/pkg/routers/sign.go +++ b/pkg/routers/sign.go @@ -58,6 +58,9 @@ func RegisterSignRouters(router *gin.Engine, logtoConfig *client.LogtoConfig) { resourceManager := resources.NewResourceManager() for _, info := range userInfo.CustomData { + if info == nil { + continue + } valueBytes, err := interfaceToBytes(info) if err != nil { ctx.Error(err)