From ebe0de53c1770ba9ae7c725edc55354b6ae71173 Mon Sep 17 00:00:00 2001 From: powerfool Date: Mon, 8 Apr 2024 17:32:59 +0800 Subject: [PATCH] Fix readability of API responses (#288) --- internal/clients/clients.go | 3 +- internal/clients/schema/obtenantrestore.go | 33 ++ .../dashboard/business/oceanbase/obcluster.go | 105 +--- .../business/oceanbase/obcluster_usage.go | 10 +- .../dashboard/business/oceanbase/obtenant.go | 115 +++- internal/dashboard/generated/swagger/docs.go | 495 +++++++++++++++++- .../dashboard/generated/swagger/swagger.json | 495 +++++++++++++++++- .../dashboard/generated/swagger/swagger.yaml | 380 +++++++++++++- internal/dashboard/handler/info_handler.go | 3 +- .../dashboard/handler/obcluster_handler.go | 111 +++- .../dashboard/handler/obtenant_handler.go | 105 ++++ internal/dashboard/model/response/backup.go | 36 +- internal/dashboard/model/response/info.go | 9 +- internal/dashboard/model/response/k8s.go | 72 +-- internal/dashboard/model/response/metrics.go | 28 +- .../dashboard/model/response/obcluster.go | 117 +++-- internal/dashboard/model/response/obtenant.go | 56 +- internal/dashboard/model/response/response.go | 6 +- .../dashboard/model/response/statistics.go | 14 +- .../dashboard/router/v1/obcluster_router.go | 1 + .../dashboard/router/v1/obtenant_router.go | 1 + 21 files changed, 1896 insertions(+), 299 deletions(-) create mode 100644 internal/clients/schema/obtenantrestore.go diff --git a/internal/clients/clients.go b/internal/clients/clients.go index ca621a36a..7d5d08221 100644 --- a/internal/clients/clients.go +++ b/internal/clients/clients.go @@ -26,5 +26,6 @@ var ( BackupJobClient = client.NewDynamicResourceClient[*v1alpha1.OBTenantBackup](schema.OBTenantBackupGVR, schema.OBTenantBackupKind) OperationClient = client.NewDynamicResourceClient[*v1alpha1.OBTenantOperation](schema.OBTenantOperationGVR, schema.OBTenantOperationKind) BackupPolicyClient = client.NewDynamicResourceClient[*v1alpha1.OBTenantBackupPolicy](schema.OBTenantBackupPolicyGVR, schema.OBTenantBackupPolicyKind) - RescueClient = client.NewDynamicResourceClient[*v1alpha1.OBResourceRescue](schema.OBResourceRescueGVR, schema.OBResourceRescueResource) + RescueClient = client.NewDynamicResourceClient[*v1alpha1.OBResourceRescue](schema.OBResourceRescueGVR, schema.OBResourceRescueKind) + RestoreJobClient = client.NewDynamicResourceClient[*v1alpha1.OBTenantRestore](schema.OBTenantRestoreGVR, schema.OBTenantRestoreKind) ) diff --git a/internal/clients/schema/obtenantrestore.go b/internal/clients/schema/obtenantrestore.go new file mode 100644 index 000000000..77c19fdad --- /dev/null +++ b/internal/clients/schema/obtenantrestore.go @@ -0,0 +1,33 @@ +/* +Copyright (c) 2023 OceanBase +ob-operator is licensed under Mulan PSL v2. +You can use this software according to the terms and conditions of the Mulan PSL v2. +You may obtain a copy of Mulan PSL v2 at: + http://license.coscl.org.cn/MulanPSL2 +THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, +EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, +MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. +See the Mulan PSL v2 for more details. +*/ + +package schema + +import "k8s.io/apimachinery/pkg/runtime/schema" + +const ( + OBTenantRestoreKind = "OBTenantRestore" + OBTenantRestoreResource = "obtenantrestores" +) + +var ( + OBTenantRestoreGVR = schema.GroupVersionResource{ + Group: Group, + Version: Version, + Resource: OBTenantRestoreResource, + } + OBTenantRestoreGVK = schema.GroupVersionKind{ + Group: Group, + Version: Version, + Kind: OBTenantRestoreKind, + } +) diff --git a/internal/dashboard/business/oceanbase/obcluster.go b/internal/dashboard/business/oceanbase/obcluster.go index a88899bc8..651dd312a 100644 --- a/internal/dashboard/business/oceanbase/obcluster.go +++ b/internal/dashboard/business/oceanbase/obcluster.go @@ -32,7 +32,6 @@ import ( clusterstatus "github.com/oceanbase/ob-operator/internal/const/status/obcluster" "github.com/oceanbase/ob-operator/internal/dashboard/business/common" "github.com/oceanbase/ob-operator/internal/dashboard/business/constant" - "github.com/oceanbase/ob-operator/internal/dashboard/business/k8s" modelcommon "github.com/oceanbase/ob-operator/internal/dashboard/model/common" "github.com/oceanbase/ob-operator/internal/dashboard/model/param" "github.com/oceanbase/ob-operator/internal/dashboard/model/response" @@ -72,6 +71,19 @@ func buildOBClusterOverview(ctx context.Context, obcluster *v1alpha1.OBCluster) if err != nil { return nil, errors.Wrap(err, "failed to build obcluster topology") } + clusterMode := modelcommon.ClusterModeNormal + annotations := obcluster.GetAnnotations() + if annotations != nil { + if mode, ok := annotations[oceanbaseconst.AnnotationsMode]; ok { + switch mode { + case oceanbaseconst.ModeStandalone: + clusterMode = modelcommon.ClusterModeStandalone + case oceanbaseconst.ModeService: + clusterMode = modelcommon.ClusterModeService + default: + } + } + } return &response.OBClusterOverview{ UID: string(obcluster.UID), Namespace: obcluster.Namespace, @@ -83,6 +95,7 @@ func buildOBClusterOverview(ctx context.Context, obcluster *v1alpha1.OBCluster) CreateTime: obcluster.ObjectMeta.CreationTimestamp.Unix(), Image: obcluster.Status.Image, Topology: topology, + Mode: clusterMode, }, nil } @@ -122,22 +135,6 @@ func buildOBClusterResponse(ctx context.Context, obcluster *v1alpha1.OBCluster) respCluster.BackupVolume.Address = obcluster.Spec.BackupVolume.Volume.NFS.Server respCluster.BackupVolume.Path = obcluster.Spec.BackupVolume.Volume.NFS.Path } - labels := obcluster.GetLabels() - if labels != nil { - if mode, ok := labels[oceanbaseconst.AnnotationsMode]; ok { - switch mode { - case oceanbaseconst.ModeStandalone: - respCluster.Mode = modelcommon.ClusterModeStandalone - case oceanbaseconst.ModeService: - respCluster.Mode = modelcommon.ClusterModeService - default: - respCluster.Mode = modelcommon.ClusterModeNormal - } - } - } - if respCluster.Mode == "" { - respCluster.Mode = modelcommon.ClusterModeNormal - } if obcluster.Spec.OBServerTemplate != nil { respCluster.OBClusterExtra.Resource = response.ResourceSpecRender{ Cpu: obcluster.Spec.OBServerTemplate.Resource.Cpu.Value(), @@ -467,9 +464,9 @@ func generateOBClusterInstance(param *param.CreateOBClusterParam) *v1alpha1.OBCl topology := buildOBClusterTopology(param.Topology) obcluster := &v1alpha1.OBCluster{ ObjectMeta: metav1.ObjectMeta{ - Namespace: param.Namespace, - Name: param.Name, - Labels: map[string]string{}, + Namespace: param.Namespace, + Name: param.Name, + Annotations: map[string]string{}, }, Spec: v1alpha1.OBClusterSpec{ ClusterName: param.ClusterName, @@ -484,54 +481,17 @@ func generateOBClusterInstance(param *param.CreateOBClusterParam) *v1alpha1.OBCl } switch param.Mode { case modelcommon.ClusterModeStandalone: - obcluster.Labels[oceanbaseconst.AnnotationsMode] = oceanbaseconst.ModeStandalone + obcluster.Annotations[oceanbaseconst.AnnotationsMode] = oceanbaseconst.ModeStandalone case modelcommon.ClusterModeService: - obcluster.Labels[oceanbaseconst.AnnotationsMode] = oceanbaseconst.ModeService + obcluster.Annotations[oceanbaseconst.AnnotationsMode] = oceanbaseconst.ModeService default: } return obcluster } -func JudgeResourceEnoughForOBCluster(ctx context.Context, obcluster *v1alpha1.OBCluster) error { - nodes, err := k8s.ListNodeResources(ctx) - if err != nil { - return oberr.Wrap(err, oberr.ErrInternal, "List resource of nodes") - } - requiredMem := obcluster.Spec.OBServerTemplate.Resource.Memory.AsApproximateFloat64() / constant.GB - requiredCpu := obcluster.Spec.OBServerTemplate.Resource.Cpu.AsApproximateFloat64() - - // Judge whether the remain resource is enough for monitor - if obcluster.Spec.MonitorTemplate != nil { - requiredMem += obcluster.Spec.MonitorTemplate.Resource.Memory.AsApproximateFloat64() / constant.GB - requiredCpu += obcluster.Spec.MonitorTemplate.Resource.Cpu.AsApproximateFloat64() - } - unmetCount := 0 - for _, zone := range obcluster.Spec.Topology { - unmetCount += zone.Replica - } - for _, node := range nodes { - if unmetCount == 0 { - break - } - for node.MemoryFree > requiredMem && node.CpuFree > requiredCpu && unmetCount > 0 { - unmetCount-- - node.MemoryFree -= requiredMem - node.CpuFree -= requiredCpu - } - } - if unmetCount > 0 { - return oberr.NewBadRequest("Resource not enough in k8s cluster") - } - return nil -} - func CreateOBCluster(ctx context.Context, param *param.CreateOBClusterParam) (*response.OBCluster, error) { obcluster := generateOBClusterInstance(param) - err := JudgeResourceEnoughForOBCluster(ctx, obcluster) - if err != nil { - return nil, err - } - err = clients.CreateSecretsForOBCluster(ctx, obcluster, param.RootPassword) + err := clients.CreateSecretsForOBCluster(ctx, obcluster, param.RootPassword) if err != nil { return nil, errors.Wrap(err, "Create secrets for obcluster") } @@ -569,13 +529,11 @@ func ScaleOBServer(ctx context.Context, obzoneIdentity *param.OBZoneIdentity, sc } found := false replicaChanged := false - var scaleDelta int for idx, obzone := range obcluster.Spec.Topology { if obzone.Zone == obzoneIdentity.OBZoneName { found = true if obzone.Replica != scaleParam.Replicas { replicaChanged = true - scaleDelta = scaleParam.Replicas - obzone.Replica logger.Infof("Scale obzone %s from %d to %d", obzone.Zone, obzone.Replica, scaleParam.Replicas) obcluster.Spec.Topology[idx].Replica = scaleParam.Replicas } @@ -587,13 +545,6 @@ func ScaleOBServer(ctx context.Context, obzoneIdentity *param.OBZoneIdentity, sc if !replicaChanged { return nil, errors.Errorf("obzone %s replica already satisfied in obcluster %s %s", obzoneIdentity.OBZoneName, obzoneIdentity.Namespace, obzoneIdentity.Name) } - // Judge whether the resource is enough for obcluster if the replica increases - if scaleDelta > 0 { - err := JudgeResourceEnoughForOBCluster(ctx, obcluster) - if err != nil { - return nil, err - } - } cluster, err := clients.UpdateOBCluster(ctx, obcluster) if err != nil { return nil, oberr.NewInternal(err.Error()) @@ -650,10 +601,6 @@ func AddOBZone(ctx context.Context, obclusterIdentity *param.K8sObjectIdentity, NodeSelector: common.KVsToMap(zone.NodeSelector), Replica: zone.Replicas, }) - err = JudgeResourceEnoughForOBCluster(ctx, obcluster) - if err != nil { - return nil, err - } cluster, err := clients.UpdateOBCluster(ctx, obcluster) if err != nil { return nil, oberr.NewInternal(err.Error()) @@ -674,8 +621,8 @@ func DeleteOBCluster(ctx context.Context, obclusterIdentity *param.K8sObjectIden return err == nil, err } -func GetOBClusterStatistic(ctx context.Context) ([]response.OBClusterStastistic, error) { - statisticResult := make([]response.OBClusterStastistic, 0) +func GetOBClusterStatistic(ctx context.Context) ([]response.OBClusterStatistic, error) { + statisticResult := make([]response.OBClusterStatistic, 0) obclusterList, err := clients.ListAllOBClusters(ctx) if err != nil { return statisticResult, errors.Wrap(err, "failed to list obclusters") @@ -699,16 +646,16 @@ func GetOBClusterStatistic(ctx context.Context) ([]response.OBClusterStastistic, } } statisticResult = append(statisticResult, - response.OBClusterStastistic{ + response.OBClusterStatistic{ Status: StatusRunning, Count: runningCount, - }, response.OBClusterStastistic{ + }, response.OBClusterStatistic{ Status: StatusDeleting, Count: deletingCount, - }, response.OBClusterStastistic{ + }, response.OBClusterStatistic{ Status: StatusOperating, Count: operatingCount, - }, response.OBClusterStastistic{ + }, response.OBClusterStatistic{ Status: StatusFailed, Count: failedCount, }) diff --git a/internal/dashboard/business/oceanbase/obcluster_usage.go b/internal/dashboard/business/oceanbase/obcluster_usage.go index 4881cc0c5..927f24bd2 100644 --- a/internal/dashboard/business/oceanbase/obcluster_usage.go +++ b/internal/dashboard/business/oceanbase/obcluster_usage.go @@ -87,11 +87,11 @@ func GetOBClusterUsages(ctx context.Context, nn *param.K8sObjectIdentity) (*resp return essentials, nil } -func getServerUsages(gvservers []model.GVOBServer) ([]response.OBServerAvailableResource, map[string]*response.OBZoneAvaiableResource) { - zoneMapping := make(map[string]*response.OBZoneAvaiableResource) +func getServerUsages(gvservers []model.GVOBServer) ([]response.OBServerAvailableResource, map[string]*response.OBZoneAvailableResource) { + zoneMapping := make(map[string]*response.OBZoneAvailableResource) serverUsages := make([]response.OBServerAvailableResource, 0, len(gvservers)) for _, gvserver := range gvservers { - zoneResource := &response.OBZoneAvaiableResource{ + zoneResource := &response.OBZoneAvailableResource{ ServerCount: 1, OBZone: gvserver.Zone, AvailableCPU: max(gvserver.CPUCapacity-gvserver.CPUAssigned, 0), @@ -100,8 +100,8 @@ func getServerUsages(gvservers []model.GVOBServer) ([]response.OBServerAvailable AvailableDataDisk: max(gvserver.DataDiskCapacity-gvserver.DataDiskAllocated, 0), } serverUsage := response.OBServerAvailableResource{ - OBServerIP: gvserver.ServerIP, - OBZoneAvaiableResource: *zoneResource, + OBServerIP: gvserver.ServerIP, + OBZoneAvailableResource: *zoneResource, } if _, ok := zoneMapping[gvserver.Zone]; !ok { zoneMapping[gvserver.Zone] = zoneResource diff --git a/internal/dashboard/business/oceanbase/obtenant.go b/internal/dashboard/business/oceanbase/obtenant.go index 7c3acd19e..760a48a0c 100644 --- a/internal/dashboard/business/oceanbase/obtenant.go +++ b/internal/dashboard/business/oceanbase/obtenant.go @@ -16,8 +16,10 @@ import ( "context" "errors" "sort" + "strings" corev1 "k8s.io/api/core/v1" + kubeerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" v1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" @@ -135,7 +137,7 @@ func buildDetailFromApiType(t *v1alpha1.OBTenant) *response.OBTenantDetail { OBTenantOverview: *buildOverviewFromApiType(t), } rt.RootCredential = t.Status.Credentials.Root - rt.StandbyROCredentail = t.Status.Credentials.StandbyRO + rt.StandbyROCredential = t.Status.Credentials.StandbyRO if t.Status.Source != nil && t.Status.Source.Tenant != nil { rt.PrimaryTenant = *t.Status.Source.Tenant @@ -213,6 +215,20 @@ func updateOBTenant(ctx context.Context, nn types.NamespacedName, p *param.Creat return buildDetailFromApiType(tenant), nil } +func createPasswordSecret(ctx context.Context, nn types.NamespacedName, password string) error { + k8sclient := client.GetClient() + _, err := k8sclient.ClientSet.CoreV1().Secrets(nn.Namespace).Create(ctx, &corev1.Secret{ + ObjectMeta: v1.ObjectMeta{ + Name: nn.Name, + Namespace: nn.Namespace, + }, + StringData: map[string]string{ + "password": password, + }, + }, v1.CreateOptions{}) + return err +} + func CreateOBTenant(ctx context.Context, nn types.NamespacedName, p *param.CreateOBTenantParam) (*response.OBTenantDetail, error) { t, err := buildOBTenantApiType(nn, p) if err != nil { @@ -223,32 +239,85 @@ func CreateOBTenant(ctx context.Context, nn types.NamespacedName, p *param.Creat } k8sclient := client.GetClient() - if t.Spec.Credentials.Root != "" { - _, err = k8sclient.ClientSet.CoreV1().Secrets(nn.Namespace).Create(ctx, &corev1.Secret{ - ObjectMeta: v1.ObjectMeta{ - Name: t.Spec.Credentials.Root, - Namespace: nn.Namespace, - }, - StringData: map[string]string{ - "password": p.RootPassword, - }, - }, v1.CreateOptions{}) + + if p.Source != nil && p.Source.Tenant != nil { + // Check primary tenant + ns := nn.Namespace + tenantCR := *p.Source.Tenant + if strings.Contains(*p.Source.Tenant, "/") { + splits := strings.Split(*p.Source.Tenant, "/") + if len(splits) != 2 { + return nil, oberr.NewBadRequest("invalid tenant name") + } + ns, tenantCR = splits[0], splits[1] + } + existing, err := clients.GetOBTenant(ctx, types.NamespacedName{ + Namespace: ns, + Name: tenantCR, + }) if err != nil { + if kubeerrors.IsNotFound(err) { + return nil, oberr.NewBadRequest("primary tenant not found") + } return nil, oberr.NewInternal(err.Error()) } - } - t.Spec.Credentials.StandbyRO = p.Name + "-standbyro-" + rand.String(6) - _, err = k8sclient.ClientSet.CoreV1().Secrets(nn.Namespace).Create(ctx, &corev1.Secret{ - ObjectMeta: v1.ObjectMeta{ - Name: t.Spec.Credentials.StandbyRO, + if existing.Status.TenantRole != apiconst.TenantRolePrimary { + return nil, oberr.NewBadRequest("the target tenant is not primary tenant") + } + + // Match root password + rootSecret, err := k8sclient.ClientSet.CoreV1().Secrets(existing.Namespace).Get(ctx, existing.Status.Credentials.Root, v1.GetOptions{}) + if err != nil { + return nil, oberr.NewInternal(err.Error()) + } + if pwd, ok := rootSecret.Data["password"]; ok { + if p.RootPassword != string(pwd) { + return nil, oberr.NewBadRequest("root password not match") + } + if t.Spec.Credentials.Root != "" { + err = createPasswordSecret(ctx, types.NamespacedName{ + Namespace: nn.Namespace, + Name: t.Spec.Credentials.Root, + }, p.RootPassword) + if err != nil { + return nil, oberr.NewInternal(err.Error()) + } + } + } + + // Fetch standbyro password + standbyroSecret, err := k8sclient.ClientSet.CoreV1().Secrets(existing.Namespace).Get(ctx, existing.Status.Credentials.StandbyRO, v1.GetOptions{}) + if err != nil { + return nil, oberr.NewInternal(err.Error()) + } + if pwd, ok := standbyroSecret.Data["password"]; ok { + t.Spec.Credentials.StandbyRO = p.Name + "-standbyro-" + rand.String(6) + err = createPasswordSecret(ctx, types.NamespacedName{ + Namespace: nn.Namespace, + Name: t.Spec.Credentials.StandbyRO, + }, string(pwd)) + if err != nil { + return nil, oberr.NewInternal(err.Error()) + } + } + } else { + if t.Spec.Credentials.Root != "" { + err = createPasswordSecret(ctx, types.NamespacedName{ + Namespace: nn.Namespace, + Name: t.Spec.Credentials.Root, + }, p.RootPassword) + if err != nil { + return nil, oberr.NewInternal(err.Error()) + } + } + t.Spec.Credentials.StandbyRO = p.Name + "-standbyro-" + rand.String(6) + err = createPasswordSecret(ctx, types.NamespacedName{ Namespace: nn.Namespace, - }, - StringData: map[string]string{ - "password": p.RootPassword, // For simplicity, use the same password as root - }, - }, v1.CreateOptions{}) - if err != nil { - return nil, oberr.NewInternal(err.Error()) + Name: t.Spec.Credentials.StandbyRO, + }, rand.String(32)) + if err != nil { + return nil, oberr.NewInternal(err.Error()) + } } if p.Source != nil && p.Source.Restore != nil { diff --git a/internal/dashboard/generated/swagger/docs.go b/internal/dashboard/generated/swagger/docs.go index c7a4fbd6c..41e4173cb 100644 --- a/internal/dashboard/generated/swagger/docs.go +++ b/internal/dashboard/generated/swagger/docs.go @@ -1286,7 +1286,85 @@ const docTemplate = `{ "data": { "type": "array", "items": { - "$ref": "#/definitions/response.OBClusterStastistic" + "$ref": "#/definitions/response.OBClusterStatistic" + } + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/obclusters/{namespace}/{name}/related-events": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "list related events of specific obcluster, including obzone and observer.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "OBCluster" + ], + "summary": "list related events", + "operationId": "ListOBClusterRelatedEvents", + "parameters": [ + { + "type": "string", + "description": "obcluster namespace", + "name": "namespace", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "obcluster name", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/response.K8sEvent" } } } @@ -2325,6 +2403,27 @@ const docTemplate = `{ "schema": { "$ref": "#/definitions/param.TenantPoolSpec" } + }, + { + "type": "string", + "description": "obtenant namespace", + "name": "namespace", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "obtenant name", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "obzone name", + "name": "zoneName", + "in": "path", + "required": true } ], "responses": { @@ -2384,6 +2483,29 @@ const docTemplate = `{ ], "summary": "Delete obtenant pool", "operationId": "DeleteOBTenantPool", + "parameters": [ + { + "type": "string", + "description": "obtenant namespace", + "name": "namespace", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "obtenant name", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "obzone name", + "name": "zoneName", + "in": "path", + "required": true + } + ], "responses": { "200": { "description": "OK", @@ -2450,6 +2572,27 @@ const docTemplate = `{ "schema": { "$ref": "#/definitions/param.TenantPoolSpec" } + }, + { + "type": "string", + "description": "obtenant namespace", + "name": "namespace", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "obtenant name", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "obzone name", + "name": "zoneName", + "in": "path", + "required": true } ], "responses": { @@ -2492,6 +2635,84 @@ const docTemplate = `{ } } }, + "/api/v1/obtenants/{namespace}/{name}/related-events": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "List related events of specific tenant, including restore, backup and backup policy events", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "OBTenant" + ], + "summary": "List related events of specific tenant", + "operationId": "ListOBTenantRelatedEvents", + "parameters": [ + { + "type": "string", + "description": "obtenant namespace", + "name": "namespace", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "obtenant name", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/response.K8sEvent" + } + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/api/v1/obtenants/{namespace}/{name}/role": { "post": { "security": [ @@ -3786,6 +4007,11 @@ const docTemplate = `{ }, "response.APIResponse": { "type": "object", + "required": [ + "data", + "message", + "successful" + ], "properties": { "data": {}, "message": { @@ -3798,6 +4024,18 @@ const docTemplate = `{ }, "response.BackupJob": { "type": "object", + "required": [ + "backupPolicyName", + "name", + "namespace", + "path", + "startTime", + "status", + "statusInDatabase", + "tenantName", + "type", + "uid" + ], "properties": { "backupPolicyName": { "type": "string" @@ -3846,7 +4084,14 @@ const docTemplate = `{ "required": [ "archivePath", "bakDataPath", - "destType" + "createTime", + "destType", + "events", + "name", + "namespace", + "status", + "tenantName", + "uid" ], "properties": { "archivePath": { @@ -3922,6 +4167,13 @@ const docTemplate = `{ }, "response.DashboardInfo": { "type": "object", + "required": [ + "appName", + "publicKey", + "reportHost", + "reportStatistics", + "version" + ], "properties": { "appName": { "type": "string" @@ -3929,6 +4181,9 @@ const docTemplate = `{ "publicKey": { "type": "string" }, + "reportHost": { + "type": "string" + }, "reportStatistics": { "type": "boolean" }, @@ -3939,6 +4194,16 @@ const docTemplate = `{ }, "response.K8sEvent": { "type": "object", + "required": [ + "count", + "firstOccur", + "lastSeen", + "message", + "namespace", + "object", + "reason", + "type" + ], "properties": { "count": { "type": "integer" @@ -3979,6 +4244,11 @@ const docTemplate = `{ }, "response.K8sNodeCondition": { "type": "object", + "required": [ + "message", + "reason", + "type" + ], "properties": { "message": { "type": "string" @@ -3993,6 +4263,20 @@ const docTemplate = `{ }, "response.K8sNodeInfo": { "type": "object", + "required": [ + "conditions", + "cri", + "externalIP", + "internalIP", + "kernel", + "labels", + "name", + "os", + "roles", + "status", + "uptime", + "version" + ], "properties": { "conditions": { "type": "array", @@ -4043,6 +4327,14 @@ const docTemplate = `{ }, "response.K8sNodeResource": { "type": "object", + "required": [ + "cpuFree", + "cpuTotal", + "cpuUsed", + "memoryFree", + "memoryTotal", + "memoryUsed" + ], "properties": { "cpuFree": { "type": "number" @@ -4080,6 +4372,11 @@ const docTemplate = `{ }, "response.MetricClass": { "type": "object", + "required": [ + "description", + "metricGroups", + "name" + ], "properties": { "description": { "type": "string" @@ -4097,6 +4394,10 @@ const docTemplate = `{ }, "response.MetricData": { "type": "object", + "required": [ + "metric", + "values" + ], "properties": { "metric": { "$ref": "#/definitions/response.Metric" @@ -4111,6 +4412,11 @@ const docTemplate = `{ }, "response.MetricGroup": { "type": "object", + "required": [ + "description", + "metrics", + "name" + ], "properties": { "description": { "type": "string" @@ -4128,6 +4434,12 @@ const docTemplate = `{ }, "response.MetricMeta": { "type": "object", + "required": [ + "description", + "key", + "name", + "unit" + ], "properties": { "description": { "type": "string" @@ -4145,6 +4457,10 @@ const docTemplate = `{ }, "response.MetricValue": { "type": "object", + "required": [ + "timestamp", + "value" + ], "properties": { "timestamp": { "type": "number" @@ -4156,6 +4472,10 @@ const docTemplate = `{ }, "response.MonitorSpec": { "type": "object", + "required": [ + "image", + "resource" + ], "properties": { "image": { "type": "string" @@ -4167,6 +4487,10 @@ const docTemplate = `{ }, "response.NFSVolumeSpec": { "type": "object", + "required": [ + "address", + "path" + ], "properties": { "address": { "type": "string" @@ -4178,6 +4502,10 @@ const docTemplate = `{ }, "response.Namespace": { "type": "object", + "required": [ + "namespace", + "status" + ], "properties": { "namespace": { "type": "string" @@ -4189,6 +4517,23 @@ const docTemplate = `{ }, "response.OBCluster": { "type": "object", + "required": [ + "clusterId", + "clusterName", + "createTime", + "image", + "mode", + "name", + "namespace", + "parameters", + "resource", + "rootPasswordSecret", + "status", + "statusDetail", + "storage", + "topology", + "uid" + ], "properties": { "backupVolume": { "$ref": "#/definitions/response.NFSVolumeSpec" @@ -4257,6 +4602,19 @@ const docTemplate = `{ }, "response.OBClusterOverview": { "type": "object", + "required": [ + "clusterId", + "clusterName", + "createTime", + "image", + "mode", + "name", + "namespace", + "status", + "statusDetail", + "topology", + "uid" + ], "properties": { "clusterId": { "type": "integer" @@ -4270,6 +4628,9 @@ const docTemplate = `{ "image": { "type": "string" }, + "mode": { + "$ref": "#/definitions/common.ClusterMode" + }, "name": { "type": "string" }, @@ -4295,6 +4656,9 @@ const docTemplate = `{ }, "response.OBClusterResources": { "type": "object", + "required": [ + "minPoolMemory" + ], "properties": { "minPoolMemory": { "type": "integer", @@ -4309,13 +4673,17 @@ const docTemplate = `{ "obZoneResourceMap": { "type": "object", "additionalProperties": { - "$ref": "#/definitions/response.OBZoneAvaiableResource" + "$ref": "#/definitions/response.OBZoneAvailableResource" } } } }, - "response.OBClusterStastistic": { + "response.OBClusterStatistic": { "type": "object", + "required": [ + "count", + "status" + ], "properties": { "count": { "type": "integer" @@ -4327,6 +4695,11 @@ const docTemplate = `{ }, "response.OBMetrics": { "type": "object", + "required": [ + "cpuPercent", + "diskPercent", + "memoryPercent" + ], "properties": { "cpuPercent": { "type": "integer" @@ -4341,6 +4714,13 @@ const docTemplate = `{ }, "response.OBServer": { "type": "object", + "required": [ + "address", + "name", + "namespace", + "status", + "statusDetail" + ], "properties": { "address": { "type": "string" @@ -4364,6 +4744,15 @@ const docTemplate = `{ }, "response.OBServerAvailableResource": { "type": "object", + "required": [ + "availableCPU", + "availableDataDisk", + "availableLogDisk", + "availableMemory", + "obServerIP", + "obZone", + "serverCount" + ], "properties": { "availableCPU": { "type": "integer", @@ -4396,6 +4785,11 @@ const docTemplate = `{ }, "response.OBServerStorage": { "type": "object", + "required": [ + "dataStorage", + "redoLogStorage", + "sysLogStorage" + ], "properties": { "dataStorage": { "$ref": "#/definitions/response.StorageSpec" @@ -4410,6 +4804,20 @@ const docTemplate = `{ }, "response.OBTenantDetail": { "type": "object", + "required": [ + "charset", + "clusterResourceName", + "createTime", + "locality", + "name", + "namespace", + "primaryZone", + "status", + "tenantName", + "tenantRole", + "uid", + "unitNumber" + ], "properties": { "charset": { "description": "Charset of the tenant", @@ -4448,7 +4856,7 @@ const docTemplate = `{ "rootCredential": { "type": "string" }, - "standbyROCredentail": { + "standbyROCredential": { "type": "string" }, "status": { @@ -4486,6 +4894,20 @@ const docTemplate = `{ "response.OBTenantOverview": { "description": "Brief information about OBTenant", "type": "object", + "required": [ + "charset", + "clusterResourceName", + "createTime", + "locality", + "name", + "namespace", + "primaryZone", + "status", + "tenantName", + "tenantRole", + "uid", + "unitNumber" + ], "properties": { "charset": { "description": "Charset of the tenant", @@ -4546,6 +4968,18 @@ const docTemplate = `{ }, "response.OBTenantReplica": { "type": "object", + "required": [ + "iopsWeight", + "logDiskSize", + "maxCPU", + "maxIops", + "memorySize", + "minCPU", + "minIops", + "priority", + "type", + "zone" + ], "properties": { "iopsWeight": { "type": "integer" @@ -4582,6 +5016,10 @@ const docTemplate = `{ }, "response.OBTenantStatistic": { "type": "object", + "required": [ + "count", + "status" + ], "properties": { "count": { "type": "integer" @@ -4593,6 +5031,14 @@ const docTemplate = `{ }, "response.OBZone": { "type": "object", + "required": [ + "name", + "namespace", + "replicas", + "status", + "statusDetail", + "zone" + ], "properties": { "affinities": { "type": "array", @@ -4641,8 +5087,16 @@ const docTemplate = `{ } } }, - "response.OBZoneAvaiableResource": { + "response.OBZoneAvailableResource": { "type": "object", + "required": [ + "availableCPU", + "availableDataDisk", + "availableLogDisk", + "availableMemory", + "obZone", + "serverCount" + ], "properties": { "availableCPU": { "type": "integer", @@ -4672,6 +5126,10 @@ const docTemplate = `{ }, "response.ResourceSpecRender": { "type": "object", + "required": [ + "cpu", + "memory" + ], "properties": { "cpu": { "type": "integer" @@ -4683,6 +5141,11 @@ const docTemplate = `{ }, "response.RestoreSource": { "type": "object", + "required": [ + "archiveSource", + "bakDataSource", + "type" + ], "properties": { "archiveSource": { "type": "string" @@ -4707,6 +5170,15 @@ const docTemplate = `{ }, "response.StatisticData": { "type": "object", + "required": [ + "backupPolicies", + "clusters", + "k8sNodes", + "servers", + "tenants", + "warningEvents", + "zones" + ], "properties": { "backupPolicies": { "type": "array", @@ -4757,6 +5229,13 @@ const docTemplate = `{ }, "response.StorageClass": { "type": "object", + "required": [ + "allowVolumeExpansion", + "name", + "provisioner", + "reclaimPolicy", + "volumeBindingMode" + ], "properties": { "allowVolumeExpansion": { "type": "boolean" @@ -4789,6 +5268,10 @@ const docTemplate = `{ }, "response.StorageSpec": { "type": "object", + "required": [ + "size", + "storageClass" + ], "properties": { "size": { "type": "string" diff --git a/internal/dashboard/generated/swagger/swagger.json b/internal/dashboard/generated/swagger/swagger.json index 120d2a3b6..bc02af319 100644 --- a/internal/dashboard/generated/swagger/swagger.json +++ b/internal/dashboard/generated/swagger/swagger.json @@ -1279,7 +1279,85 @@ "data": { "type": "array", "items": { - "$ref": "#/definitions/response.OBClusterStastistic" + "$ref": "#/definitions/response.OBClusterStatistic" + } + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, + "/api/v1/obclusters/{namespace}/{name}/related-events": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "list related events of specific obcluster, including obzone and observer.", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "OBCluster" + ], + "summary": "list related events", + "operationId": "ListOBClusterRelatedEvents", + "parameters": [ + { + "type": "string", + "description": "obcluster namespace", + "name": "namespace", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "obcluster name", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/response.K8sEvent" } } } @@ -2318,6 +2396,27 @@ "schema": { "$ref": "#/definitions/param.TenantPoolSpec" } + }, + { + "type": "string", + "description": "obtenant namespace", + "name": "namespace", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "obtenant name", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "obzone name", + "name": "zoneName", + "in": "path", + "required": true } ], "responses": { @@ -2377,6 +2476,29 @@ ], "summary": "Delete obtenant pool", "operationId": "DeleteOBTenantPool", + "parameters": [ + { + "type": "string", + "description": "obtenant namespace", + "name": "namespace", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "obtenant name", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "obzone name", + "name": "zoneName", + "in": "path", + "required": true + } + ], "responses": { "200": { "description": "OK", @@ -2443,6 +2565,27 @@ "schema": { "$ref": "#/definitions/param.TenantPoolSpec" } + }, + { + "type": "string", + "description": "obtenant namespace", + "name": "namespace", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "obtenant name", + "name": "name", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "obzone name", + "name": "zoneName", + "in": "path", + "required": true } ], "responses": { @@ -2485,6 +2628,84 @@ } } }, + "/api/v1/obtenants/{namespace}/{name}/related-events": { + "get": { + "security": [ + { + "ApiKeyAuth": [] + } + ], + "description": "List related events of specific tenant, including restore, backup and backup policy events", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "OBTenant" + ], + "summary": "List related events of specific tenant", + "operationId": "ListOBTenantRelatedEvents", + "parameters": [ + { + "type": "string", + "description": "obtenant namespace", + "name": "namespace", + "in": "path", + "required": true + }, + { + "type": "string", + "description": "obtenant name", + "name": "name", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/response.APIResponse" + }, + { + "type": "object", + "properties": { + "data": { + "type": "array", + "items": { + "$ref": "#/definitions/response.K8sEvent" + } + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "401": { + "description": "Unauthorized", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + }, + "500": { + "description": "Internal Server Error", + "schema": { + "$ref": "#/definitions/response.APIResponse" + } + } + } + } + }, "/api/v1/obtenants/{namespace}/{name}/role": { "post": { "security": [ @@ -3779,6 +4000,11 @@ }, "response.APIResponse": { "type": "object", + "required": [ + "data", + "message", + "successful" + ], "properties": { "data": {}, "message": { @@ -3791,6 +4017,18 @@ }, "response.BackupJob": { "type": "object", + "required": [ + "backupPolicyName", + "name", + "namespace", + "path", + "startTime", + "status", + "statusInDatabase", + "tenantName", + "type", + "uid" + ], "properties": { "backupPolicyName": { "type": "string" @@ -3839,7 +4077,14 @@ "required": [ "archivePath", "bakDataPath", - "destType" + "createTime", + "destType", + "events", + "name", + "namespace", + "status", + "tenantName", + "uid" ], "properties": { "archivePath": { @@ -3915,6 +4160,13 @@ }, "response.DashboardInfo": { "type": "object", + "required": [ + "appName", + "publicKey", + "reportHost", + "reportStatistics", + "version" + ], "properties": { "appName": { "type": "string" @@ -3922,6 +4174,9 @@ "publicKey": { "type": "string" }, + "reportHost": { + "type": "string" + }, "reportStatistics": { "type": "boolean" }, @@ -3932,6 +4187,16 @@ }, "response.K8sEvent": { "type": "object", + "required": [ + "count", + "firstOccur", + "lastSeen", + "message", + "namespace", + "object", + "reason", + "type" + ], "properties": { "count": { "type": "integer" @@ -3972,6 +4237,11 @@ }, "response.K8sNodeCondition": { "type": "object", + "required": [ + "message", + "reason", + "type" + ], "properties": { "message": { "type": "string" @@ -3986,6 +4256,20 @@ }, "response.K8sNodeInfo": { "type": "object", + "required": [ + "conditions", + "cri", + "externalIP", + "internalIP", + "kernel", + "labels", + "name", + "os", + "roles", + "status", + "uptime", + "version" + ], "properties": { "conditions": { "type": "array", @@ -4036,6 +4320,14 @@ }, "response.K8sNodeResource": { "type": "object", + "required": [ + "cpuFree", + "cpuTotal", + "cpuUsed", + "memoryFree", + "memoryTotal", + "memoryUsed" + ], "properties": { "cpuFree": { "type": "number" @@ -4073,6 +4365,11 @@ }, "response.MetricClass": { "type": "object", + "required": [ + "description", + "metricGroups", + "name" + ], "properties": { "description": { "type": "string" @@ -4090,6 +4387,10 @@ }, "response.MetricData": { "type": "object", + "required": [ + "metric", + "values" + ], "properties": { "metric": { "$ref": "#/definitions/response.Metric" @@ -4104,6 +4405,11 @@ }, "response.MetricGroup": { "type": "object", + "required": [ + "description", + "metrics", + "name" + ], "properties": { "description": { "type": "string" @@ -4121,6 +4427,12 @@ }, "response.MetricMeta": { "type": "object", + "required": [ + "description", + "key", + "name", + "unit" + ], "properties": { "description": { "type": "string" @@ -4138,6 +4450,10 @@ }, "response.MetricValue": { "type": "object", + "required": [ + "timestamp", + "value" + ], "properties": { "timestamp": { "type": "number" @@ -4149,6 +4465,10 @@ }, "response.MonitorSpec": { "type": "object", + "required": [ + "image", + "resource" + ], "properties": { "image": { "type": "string" @@ -4160,6 +4480,10 @@ }, "response.NFSVolumeSpec": { "type": "object", + "required": [ + "address", + "path" + ], "properties": { "address": { "type": "string" @@ -4171,6 +4495,10 @@ }, "response.Namespace": { "type": "object", + "required": [ + "namespace", + "status" + ], "properties": { "namespace": { "type": "string" @@ -4182,6 +4510,23 @@ }, "response.OBCluster": { "type": "object", + "required": [ + "clusterId", + "clusterName", + "createTime", + "image", + "mode", + "name", + "namespace", + "parameters", + "resource", + "rootPasswordSecret", + "status", + "statusDetail", + "storage", + "topology", + "uid" + ], "properties": { "backupVolume": { "$ref": "#/definitions/response.NFSVolumeSpec" @@ -4250,6 +4595,19 @@ }, "response.OBClusterOverview": { "type": "object", + "required": [ + "clusterId", + "clusterName", + "createTime", + "image", + "mode", + "name", + "namespace", + "status", + "statusDetail", + "topology", + "uid" + ], "properties": { "clusterId": { "type": "integer" @@ -4263,6 +4621,9 @@ "image": { "type": "string" }, + "mode": { + "$ref": "#/definitions/common.ClusterMode" + }, "name": { "type": "string" }, @@ -4288,6 +4649,9 @@ }, "response.OBClusterResources": { "type": "object", + "required": [ + "minPoolMemory" + ], "properties": { "minPoolMemory": { "type": "integer", @@ -4302,13 +4666,17 @@ "obZoneResourceMap": { "type": "object", "additionalProperties": { - "$ref": "#/definitions/response.OBZoneAvaiableResource" + "$ref": "#/definitions/response.OBZoneAvailableResource" } } } }, - "response.OBClusterStastistic": { + "response.OBClusterStatistic": { "type": "object", + "required": [ + "count", + "status" + ], "properties": { "count": { "type": "integer" @@ -4320,6 +4688,11 @@ }, "response.OBMetrics": { "type": "object", + "required": [ + "cpuPercent", + "diskPercent", + "memoryPercent" + ], "properties": { "cpuPercent": { "type": "integer" @@ -4334,6 +4707,13 @@ }, "response.OBServer": { "type": "object", + "required": [ + "address", + "name", + "namespace", + "status", + "statusDetail" + ], "properties": { "address": { "type": "string" @@ -4357,6 +4737,15 @@ }, "response.OBServerAvailableResource": { "type": "object", + "required": [ + "availableCPU", + "availableDataDisk", + "availableLogDisk", + "availableMemory", + "obServerIP", + "obZone", + "serverCount" + ], "properties": { "availableCPU": { "type": "integer", @@ -4389,6 +4778,11 @@ }, "response.OBServerStorage": { "type": "object", + "required": [ + "dataStorage", + "redoLogStorage", + "sysLogStorage" + ], "properties": { "dataStorage": { "$ref": "#/definitions/response.StorageSpec" @@ -4403,6 +4797,20 @@ }, "response.OBTenantDetail": { "type": "object", + "required": [ + "charset", + "clusterResourceName", + "createTime", + "locality", + "name", + "namespace", + "primaryZone", + "status", + "tenantName", + "tenantRole", + "uid", + "unitNumber" + ], "properties": { "charset": { "description": "Charset of the tenant", @@ -4441,7 +4849,7 @@ "rootCredential": { "type": "string" }, - "standbyROCredentail": { + "standbyROCredential": { "type": "string" }, "status": { @@ -4479,6 +4887,20 @@ "response.OBTenantOverview": { "description": "Brief information about OBTenant", "type": "object", + "required": [ + "charset", + "clusterResourceName", + "createTime", + "locality", + "name", + "namespace", + "primaryZone", + "status", + "tenantName", + "tenantRole", + "uid", + "unitNumber" + ], "properties": { "charset": { "description": "Charset of the tenant", @@ -4539,6 +4961,18 @@ }, "response.OBTenantReplica": { "type": "object", + "required": [ + "iopsWeight", + "logDiskSize", + "maxCPU", + "maxIops", + "memorySize", + "minCPU", + "minIops", + "priority", + "type", + "zone" + ], "properties": { "iopsWeight": { "type": "integer" @@ -4575,6 +5009,10 @@ }, "response.OBTenantStatistic": { "type": "object", + "required": [ + "count", + "status" + ], "properties": { "count": { "type": "integer" @@ -4586,6 +5024,14 @@ }, "response.OBZone": { "type": "object", + "required": [ + "name", + "namespace", + "replicas", + "status", + "statusDetail", + "zone" + ], "properties": { "affinities": { "type": "array", @@ -4634,8 +5080,16 @@ } } }, - "response.OBZoneAvaiableResource": { + "response.OBZoneAvailableResource": { "type": "object", + "required": [ + "availableCPU", + "availableDataDisk", + "availableLogDisk", + "availableMemory", + "obZone", + "serverCount" + ], "properties": { "availableCPU": { "type": "integer", @@ -4665,6 +5119,10 @@ }, "response.ResourceSpecRender": { "type": "object", + "required": [ + "cpu", + "memory" + ], "properties": { "cpu": { "type": "integer" @@ -4676,6 +5134,11 @@ }, "response.RestoreSource": { "type": "object", + "required": [ + "archiveSource", + "bakDataSource", + "type" + ], "properties": { "archiveSource": { "type": "string" @@ -4700,6 +5163,15 @@ }, "response.StatisticData": { "type": "object", + "required": [ + "backupPolicies", + "clusters", + "k8sNodes", + "servers", + "tenants", + "warningEvents", + "zones" + ], "properties": { "backupPolicies": { "type": "array", @@ -4750,6 +5222,13 @@ }, "response.StorageClass": { "type": "object", + "required": [ + "allowVolumeExpansion", + "name", + "provisioner", + "reclaimPolicy", + "volumeBindingMode" + ], "properties": { "allowVolumeExpansion": { "type": "boolean" @@ -4782,6 +5261,10 @@ }, "response.StorageSpec": { "type": "object", + "required": [ + "size", + "storageClass" + ], "properties": { "size": { "type": "string" diff --git a/internal/dashboard/generated/swagger/swagger.yaml b/internal/dashboard/generated/swagger/swagger.yaml index 004af3857..a5d96f3aa 100644 --- a/internal/dashboard/generated/swagger/swagger.yaml +++ b/internal/dashboard/generated/swagger/swagger.yaml @@ -676,6 +676,10 @@ definitions: type: string successful: type: boolean + required: + - data + - message + - successful type: object response.BackupJob: properties: @@ -707,6 +711,17 @@ definitions: type: string uid: type: string + required: + - backupPolicyName + - name + - namespace + - path + - startTime + - status + - statusInDatabase + - tenantName + - type + - uid type: object response.BackupPolicy: properties: @@ -764,7 +779,14 @@ definitions: required: - archivePath - bakDataPath + - createTime - destType + - events + - name + - namespace + - status + - tenantName + - uid type: object response.DashboardInfo: properties: @@ -772,10 +794,18 @@ definitions: type: string publicKey: type: string + reportHost: + type: string reportStatistics: type: boolean version: type: string + required: + - appName + - publicKey + - reportHost + - reportStatistics + - version type: object response.K8sEvent: properties: @@ -795,6 +825,15 @@ definitions: type: string type: type: string + required: + - count + - firstOccur + - lastSeen + - message + - namespace + - object + - reason + - type type: object response.K8sNode: properties: @@ -811,6 +850,10 @@ definitions: type: string type: type: string + required: + - message + - reason + - type type: object response.K8sNodeInfo: properties: @@ -844,6 +887,19 @@ definitions: type: integer version: type: string + required: + - conditions + - cri + - externalIP + - internalIP + - kernel + - labels + - name + - os + - roles + - status + - uptime + - version type: object response.K8sNodeResource: properties: @@ -859,6 +915,13 @@ definitions: type: number memoryUsed: type: number + required: + - cpuFree + - cpuTotal + - cpuUsed + - memoryFree + - memoryTotal + - memoryUsed type: object response.Metric: properties: @@ -879,6 +942,10 @@ definitions: type: array name: type: string + required: + - description + - metricGroups + - name type: object response.MetricData: properties: @@ -888,6 +955,9 @@ definitions: items: $ref: '#/definitions/response.MetricValue' type: array + required: + - metric + - values type: object response.MetricGroup: properties: @@ -899,6 +969,10 @@ definitions: type: array name: type: string + required: + - description + - metrics + - name type: object response.MetricMeta: properties: @@ -910,6 +984,11 @@ definitions: type: string unit: type: string + required: + - description + - key + - name + - unit type: object response.MetricValue: properties: @@ -917,6 +996,9 @@ definitions: type: number value: type: number + required: + - timestamp + - value type: object response.MonitorSpec: properties: @@ -924,6 +1006,9 @@ definitions: type: string resource: $ref: '#/definitions/response.ResourceSpecRender' + required: + - image + - resource type: object response.NFSVolumeSpec: properties: @@ -931,6 +1016,9 @@ definitions: type: string path: type: string + required: + - address + - path type: object response.Namespace: properties: @@ -938,6 +1026,9 @@ definitions: type: string status: type: string + required: + - namespace + - status type: object response.OBCluster: properties: @@ -983,6 +1074,22 @@ definitions: type: string version: type: string + required: + - clusterId + - clusterName + - createTime + - image + - mode + - name + - namespace + - parameters + - resource + - rootPasswordSecret + - status + - statusDetail + - storage + - topology + - uid type: object response.OBClusterOverview: properties: @@ -994,6 +1101,8 @@ definitions: type: integer image: type: string + mode: + $ref: '#/definitions/common.ClusterMode' name: type: string namespace: @@ -1008,6 +1117,18 @@ definitions: type: array uid: type: string + required: + - clusterId + - clusterName + - createTime + - image + - mode + - name + - namespace + - status + - statusDetail + - topology + - uid type: object response.OBClusterResources: properties: @@ -1020,15 +1141,20 @@ definitions: type: array obZoneResourceMap: additionalProperties: - $ref: '#/definitions/response.OBZoneAvaiableResource' + $ref: '#/definitions/response.OBZoneAvailableResource' type: object + required: + - minPoolMemory type: object - response.OBClusterStastistic: + response.OBClusterStatistic: properties: count: type: integer status: type: string + required: + - count + - status type: object response.OBMetrics: properties: @@ -1038,6 +1164,10 @@ definitions: type: integer memoryPercent: type: integer + required: + - cpuPercent + - diskPercent + - memoryPercent type: object response.OBServer: properties: @@ -1053,6 +1183,12 @@ definitions: type: string statusDetail: type: string + required: + - address + - name + - namespace + - status + - statusDetail type: object response.OBServerAvailableResource: properties: @@ -1076,6 +1212,14 @@ definitions: serverCount: example: 3 type: integer + required: + - availableCPU + - availableDataDisk + - availableLogDisk + - availableMemory + - obServerIP + - obZone + - serverCount type: object response.OBServerStorage: properties: @@ -1085,6 +1229,10 @@ definitions: $ref: '#/definitions/response.StorageSpec' sysLogStorage: $ref: '#/definitions/response.StorageSpec' + required: + - dataStorage + - redoLogStorage + - sysLogStorage type: object response.OBTenantDetail: properties: @@ -1115,7 +1263,7 @@ definitions: $ref: '#/definitions/response.RestoreSource' rootCredential: type: string - standbyROCredentail: + standbyROCredential: type: string status: description: Status of the tenant @@ -1139,6 +1287,19 @@ definitions: type: integer version: type: string + required: + - charset + - clusterResourceName + - createTime + - locality + - name + - namespace + - primaryZone + - status + - tenantName + - tenantRole + - uid + - unitNumber type: object response.OBTenantOverview: description: Brief information about OBTenant @@ -1184,6 +1345,19 @@ definitions: unitNumber: description: Number of units in every zone type: integer + required: + - charset + - clusterResourceName + - createTime + - locality + - name + - namespace + - primaryZone + - status + - tenantName + - tenantRole + - uid + - unitNumber type: object response.OBTenantReplica: properties: @@ -1208,6 +1382,17 @@ definitions: type: string zone: type: string + required: + - iopsWeight + - logDiskSize + - maxCPU + - maxIops + - memorySize + - minCPU + - minIops + - priority + - type + - zone type: object response.OBTenantStatistic: properties: @@ -1215,6 +1400,9 @@ definitions: type: integer status: type: string + required: + - count + - status type: object response.OBZone: properties: @@ -1248,8 +1436,15 @@ definitions: type: array zone: type: string + required: + - name + - namespace + - replicas + - status + - statusDetail + - zone type: object - response.OBZoneAvaiableResource: + response.OBZoneAvailableResource: properties: availableCPU: example: 12 @@ -1269,6 +1464,13 @@ definitions: serverCount: example: 3 type: integer + required: + - availableCPU + - availableDataDisk + - availableLogDisk + - availableMemory + - obZone + - serverCount type: object response.ResourceSpecRender: properties: @@ -1276,6 +1478,9 @@ definitions: type: integer memory: type: string + required: + - cpu + - memory type: object response.RestoreSource: properties: @@ -1292,6 +1497,10 @@ definitions: type: string until: type: string + required: + - archiveSource + - bakDataSource + - type type: object response.StatisticData: properties: @@ -1325,6 +1534,14 @@ definitions: items: $ref: '#/definitions/models.OBZone' type: array + required: + - backupPolicies + - clusters + - k8sNodes + - servers + - tenants + - warningEvents + - zones type: object response.StorageClass: properties: @@ -1346,6 +1563,12 @@ definitions: type: string volumeBindingMode: type: string + required: + - allowVolumeExpansion + - name + - provisioner + - reclaimPolicy + - volumeBindingMode type: object response.StorageSpec: properties: @@ -1353,6 +1576,9 @@ definitions: type: string storageClass: type: string + required: + - size + - storageClass type: object info: contact: {} @@ -1825,6 +2051,55 @@ paths: summary: create obcluster tags: - OBCluster + /api/v1/obclusters/{namespace}/{name}/related-events: + get: + consumes: + - application/json + description: list related events of specific obcluster, including obzone and + observer. + operationId: ListOBClusterRelatedEvents + parameters: + - description: obcluster namespace + in: path + name: namespace + required: true + type: string + - description: obcluster name + in: path + name: name + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.APIResponse' + - properties: + data: + items: + $ref: '#/definitions/response.K8sEvent' + type: array + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + security: + - ApiKeyAuth: [] + summary: list related events + tags: + - OBCluster /api/v1/obclusters/{namespace}/{name}/resource-usages: get: consumes: @@ -2192,7 +2467,7 @@ paths: - properties: data: items: - $ref: '#/definitions/response.OBClusterStastistic' + $ref: '#/definitions/response.OBClusterStatistic' type: array type: object "400": @@ -2747,6 +3022,22 @@ paths: - application/json description: Delete an obtenant pool in a specific namespace operationId: DeleteOBTenantPool + parameters: + - description: obtenant namespace + in: path + name: namespace + required: true + type: string + - description: obtenant name + in: path + name: name + required: true + type: string + - description: obzone name + in: path + name: zoneName + required: true + type: string produces: - application/json responses: @@ -2788,6 +3079,21 @@ paths: required: true schema: $ref: '#/definitions/param.TenantPoolSpec' + - description: obtenant namespace + in: path + name: namespace + required: true + type: string + - description: obtenant name + in: path + name: name + required: true + type: string + - description: obzone name + in: path + name: zoneName + required: true + type: string produces: - application/json responses: @@ -2829,6 +3135,21 @@ paths: required: true schema: $ref: '#/definitions/param.TenantPoolSpec' + - description: obtenant namespace + in: path + name: namespace + required: true + type: string + - description: obtenant name + in: path + name: name + required: true + type: string + - description: obzone name + in: path + name: zoneName + required: true + type: string produces: - application/json responses: @@ -2858,6 +3179,55 @@ paths: summary: Create obtenant pool tags: - OBTenant + /api/v1/obtenants/{namespace}/{name}/related-events: + get: + consumes: + - application/json + description: List related events of specific tenant, including restore, backup + and backup policy events + operationId: ListOBTenantRelatedEvents + parameters: + - description: obtenant namespace + in: path + name: namespace + required: true + type: string + - description: obtenant name + in: path + name: name + required: true + type: string + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/response.APIResponse' + - properties: + data: + items: + $ref: '#/definitions/response.K8sEvent' + type: array + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/response.APIResponse' + "401": + description: Unauthorized + schema: + $ref: '#/definitions/response.APIResponse' + "500": + description: Internal Server Error + schema: + $ref: '#/definitions/response.APIResponse' + security: + - ApiKeyAuth: [] + summary: List related events of specific tenant + tags: + - OBTenant /api/v1/obtenants/{namespace}/{name}/role: post: consumes: diff --git a/internal/dashboard/handler/info_handler.go b/internal/dashboard/handler/info_handler.go index 668f1acac..edd2ad8b7 100644 --- a/internal/dashboard/handler/info_handler.go +++ b/internal/dashboard/handler/info_handler.go @@ -61,7 +61,8 @@ func GetProcessInfo(_ *gin.Context) (*response.DashboardInfo, error) { AppName: "oceanbase-dashboard", Version: strings.Join([]string{Version, CommitHash, BuildTime}, "-"), PublicKey: string(pubBytes), - ReportStatistics: os.Getenv("DISABLE_REPORT_STATISTICS") != "true", + ReportStatistics: os.Getenv(telemetry.DisableTelemetryEnvName) != "true", + ReportHost: telemetry.TelemetryReportScheme + "://" + telemetry.TelemetryReportHost, }, nil } diff --git a/internal/dashboard/handler/obcluster_handler.go b/internal/dashboard/handler/obcluster_handler.go index ea86f0732..92b5b2cb6 100644 --- a/internal/dashboard/handler/obcluster_handler.go +++ b/internal/dashboard/handler/obcluster_handler.go @@ -13,14 +13,22 @@ See the Mulan PSL v2 for more details. package handler import ( + "context" + "github.com/gin-gonic/gin" logger "github.com/sirupsen/logrus" + kubeerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/oceanbase/ob-operator/api/v1alpha1" + "github.com/oceanbase/ob-operator/internal/clients" + oceanbaseconst "github.com/oceanbase/ob-operator/internal/const/oceanbase" "github.com/oceanbase/ob-operator/internal/dashboard/business/oceanbase" "github.com/oceanbase/ob-operator/internal/dashboard/model/param" "github.com/oceanbase/ob-operator/internal/dashboard/model/response" crypto "github.com/oceanbase/ob-operator/pkg/crypto" httpErr "github.com/oceanbase/ob-operator/pkg/errors" + "github.com/oceanbase/ob-operator/pkg/k8s/client" ) // @ID GetOBClusterStatistic @@ -29,18 +37,18 @@ import ( // @Tags OBCluster // @Accept application/json // @Produce application/json -// @Success 200 object response.APIResponse{data=[]response.OBClusterStastistic} +// @Success 200 object response.APIResponse{data=[]response.OBClusterStatistic} // @Failure 400 object response.APIResponse // @Failure 401 object response.APIResponse // @Failure 500 object response.APIResponse // @Router /api/v1/obclusters/statistic [GET] -func GetOBClusterStatistic(c *gin.Context) ([]response.OBClusterStastistic, error) { - obclusterStastics, err := oceanbase.GetOBClusterStatistic(c) +func GetOBClusterStatistic(c *gin.Context) ([]response.OBClusterStatistic, error) { + obclusterStatistics, err := oceanbase.GetOBClusterStatistic(c) if err != nil { return nil, err } - logger.Debugf("Get obcluster statistic: %v", obclusterStastics) - return obclusterStastics, nil + logger.Debugf("Get obcluster statistic: %v", obclusterStatistics) + return obclusterStatistics, nil } // @ID ListOBClusters @@ -282,3 +290,96 @@ func ListOBClusterResources(c *gin.Context) (*response.OBClusterResources, error logger.Debugf("Get resource usages of obcluster: %v", obclusterIdentity) return usages, nil } + +// @ID ListOBClusterRelatedEvents +// @Summary list related events +// @Description list related events of specific obcluster, including obzone and observer. +// @Tags OBCluster +// @Accept application/json +// @Produce application/json +// @Param namespace path string true "obcluster namespace" +// @Param name path string true "obcluster name" +// @Success 200 object response.APIResponse{data=[]response.K8sEvent} +// @Failure 400 object response.APIResponse +// @Failure 401 object response.APIResponse +// @Failure 500 object response.APIResponse +// @Router /api/v1/obclusters/{namespace}/{name}/related-events [GET] +// @Security ApiKeyAuth +func ListOBClusterRelatedEvents(c *gin.Context) ([]response.K8sEvent, error) { + nn := ¶m.K8sObjectIdentity{} + err := c.BindUri(nn) + if err != nil { + return nil, httpErr.NewBadRequest(err.Error()) + } + obcluster, err := clients.ClusterClient.Get(c, nn.Namespace, nn.Name, metav1.GetOptions{}) + if err != nil { + if kubeerrors.IsNotFound(err) { + return nil, httpErr.NewBadRequest("obcluster not found") + } + return nil, httpErr.NewInternal(err.Error()) + } + obzoneList := &v1alpha1.OBZoneList{} + err = clients.ZoneClient.List(c, nn.Namespace, obzoneList, metav1.ListOptions{ + LabelSelector: oceanbaseconst.LabelRefOBCluster + "=" + obcluster.Name, + }) + if err != nil { + return nil, httpErr.NewInternal(err.Error()) + } + observerList := &v1alpha1.OBServerList{} + err = clients.ServerClient.List(c, nn.Namespace, observerList, metav1.ListOptions{ + LabelSelector: oceanbaseconst.LabelRefOBCluster + "=" + obcluster.Name, + }) + if err != nil { + return nil, httpErr.NewInternal(err.Error()) + } + var events []response.K8sEvent + + if len(obzoneList.Items) > 0 { + names := make([]string, 0, len(obzoneList.Items)) + for _, obzone := range obzoneList.Items { + names = append(names, obzone.Name) + } + events = append(events, GetScopedEvents(c, nn.Namespace, "OBZone", names)...) + } + + if len(observerList.Items) > 0 { + names := make([]string, 0, len(observerList.Items)) + for _, obzone := range observerList.Items { + names = append(names, obzone.Name) + } + events = append(events, GetScopedEvents(c, nn.Namespace, "OBServer", names)...) + events = append(events, GetScopedEvents(c, nn.Namespace, "Pod", names)...) + } + + logger.Debugf("Get related events of obcluster: %v", nn) + return events, nil +} + +func GetScopedEvents(ctx context.Context, ns, kind string, scoped []string) []response.K8sEvent { + eventList, err := client.GetClient().ClientSet.CoreV1().Events(ns).List(ctx, metav1.ListOptions{ + FieldSelector: "involvedObject.kind=" + kind, + }) + if err != nil { + return nil + } + existMapping := make(map[string]struct{}) + for _, item := range scoped { + existMapping[item] = struct{}{} + } + var events []response.K8sEvent + for _, event := range eventList.Items { + if _, ok := existMapping[event.InvolvedObject.Name]; ok { + events = append(events, response.K8sEvent{ + Namespace: event.Namespace, + Message: event.Message, + Reason: event.Reason, + Type: event.Type, + Object: event.InvolvedObject.Kind + "/" + event.InvolvedObject.Name, + Count: event.Count, + FirstOccur: event.FirstTimestamp.Unix(), + LastSeen: event.LastTimestamp.Unix(), + }) + } + } + return events +} diff --git a/internal/dashboard/handler/obtenant_handler.go b/internal/dashboard/handler/obtenant_handler.go index 921addb45..a4e451e4e 100644 --- a/internal/dashboard/handler/obtenant_handler.go +++ b/internal/dashboard/handler/obtenant_handler.go @@ -23,11 +23,15 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + "github.com/oceanbase/ob-operator/api/v1alpha1" + "github.com/oceanbase/ob-operator/internal/clients" + oceanbaseconst "github.com/oceanbase/ob-operator/internal/const/oceanbase" "github.com/oceanbase/ob-operator/internal/dashboard/business/oceanbase" "github.com/oceanbase/ob-operator/internal/dashboard/model/param" "github.com/oceanbase/ob-operator/internal/dashboard/model/response" crypto "github.com/oceanbase/ob-operator/pkg/crypto" httpErr "github.com/oceanbase/ob-operator/pkg/errors" + "github.com/oceanbase/ob-operator/pkg/k8s/client" ) // @ID ListAllTenants @@ -617,6 +621,9 @@ func GetOBTenantStatistic(c *gin.Context) ([]response.OBTenantStatistic, error) // @Failure 400 object response.APIResponse // @Failure 401 object response.APIResponse // @Failure 500 object response.APIResponse +// @Param namespace path string true "obtenant namespace" +// @Param name path string true "obtenant name" +// @Param zoneName path string true "obzone name" // @Router /api/v1/obtenants/{namespace}/{name}/pools/{zoneName} [PUT] // @Security ApiKeyAuth func CreateOBTenantPool(c *gin.Context) (*response.OBTenantDetail, error) { @@ -645,6 +652,9 @@ func CreateOBTenantPool(c *gin.Context) (*response.OBTenantDetail, error) { // @Failure 400 object response.APIResponse // @Failure 401 object response.APIResponse // @Failure 500 object response.APIResponse +// @Param namespace path string true "obtenant namespace" +// @Param name path string true "obtenant name" +// @Param zoneName path string true "obzone name" // @Router /api/v1/obtenants/{namespace}/{name}/pools/{zoneName} [DELETE] // @Security ApiKeyAuth func DeleteOBTenantPool(c *gin.Context) (*response.OBTenantDetail, error) { @@ -668,6 +678,9 @@ func DeleteOBTenantPool(c *gin.Context) (*response.OBTenantDetail, error) { // @Failure 400 object response.APIResponse // @Failure 401 object response.APIResponse // @Failure 500 object response.APIResponse +// @Param namespace path string true "obtenant namespace" +// @Param name path string true "obtenant name" +// @Param zoneName path string true "obzone name" // @Router /api/v1/obtenants/{namespace}/{name}/pools/{zoneName} [PATCH] // @Security ApiKeyAuth func PatchOBTenantPool(c *gin.Context) (*response.OBTenantDetail, error) { @@ -685,3 +698,95 @@ func PatchOBTenantPool(c *gin.Context) (*response.OBTenantDetail, error) { logger.Infof("Patch obtenant pool with param: %+v", p) return oceanbase.PatchTenantPool(c, nn, &p) } + +// @ID ListOBTenantRelatedEvents +// @Tags OBTenant +// @Summary List related events of specific tenant +// @Description List related events of specific tenant, including restore, backup and backup policy events +// @Accept application/json +// @Produce application/json +// @Success 200 object response.APIResponse{data=[]response.K8sEvent} +// @Failure 400 object response.APIResponse +// @Failure 401 object response.APIResponse +// @Failure 500 object response.APIResponse +// @Param namespace path string true "obtenant namespace" +// @Param name path string true "obtenant name" +// @Router /api/v1/obtenants/{namespace}/{name}/related-events [GET] +// @Security ApiKeyAuth +func ListOBTenantRelatedEvents(c *gin.Context) ([]response.K8sEvent, error) { + nn := ¶m.NamespacedName{} + err := c.BindUri(nn) + if err != nil { + return nil, httpErr.NewBadRequest(err.Error()) + } + + obtenant, err := clients.TenantClient.Get(c, nn.Namespace, nn.Name, metav1.GetOptions{}) + if err != nil { + if kubeerrors.IsNotFound(err) { + return nil, httpErr.NewBadRequest("obtenant not found") + } + return nil, httpErr.NewInternal(err.Error()) + } + events := []response.K8sEvent{} + // Get related events of obtenant + + restoreList := &v1alpha1.OBTenantRestoreList{} + err = clients.RestoreJobClient.List(c, nn.Namespace, restoreList, metav1.ListOptions{ + LabelSelector: fmt.Sprintf("%s=%s", oceanbaseconst.LabelTenantName, obtenant.Name), + }) + if err != nil { + return nil, httpErr.NewInternal(err.Error()) + } + if len(restoreList.Items) > 0 { + names := make([]string, 0, len(restoreList.Items)) + for _, r := range restoreList.Items { + names = append(names, r.Name) + } + events = append(events, GetScopedEvents(c, nn.Namespace, "OBTenantRestore", names)...) + } + + policy, err := oceanbase.GetTenantBackupPolicy(c, types.NamespacedName{Namespace: nn.Namespace, Name: nn.Name}) + if err != nil { + if !kubeerrors.IsNotFound(err) { + return nil, httpErr.NewInternal(err.Error()) + } + } else if policy != nil { + policyEvents, err := client.GetClient().ClientSet.CoreV1().Events(policy.Namespace).List(c, metav1.ListOptions{ + LabelSelector: fmt.Sprintf("involvedObject.kind=OBTenantBackupPolicy,involvedObject.name=%s", policy.Name), + }) + if err != nil { + return nil, httpErr.NewInternal(err.Error()) + } + + for _, e := range policyEvents.Items { + events = append(events, response.K8sEvent{ + Namespace: e.Namespace, + Reason: e.Reason, + Message: e.Message, + Type: e.Type, + Object: e.InvolvedObject.Kind + "/" + e.InvolvedObject.Name, + FirstOccur: e.FirstTimestamp.Unix(), + LastSeen: e.LastTimestamp.Unix(), + }) + } + + backupJobs := &v1alpha1.OBTenantBackupList{} + err = clients.BackupJobClient.List(c, policy.Namespace, backupJobs, metav1.ListOptions{ + LabelSelector: fmt.Sprintf("%s=%s", oceanbaseconst.LabelRefBackupPolicy, policy.Name), + }) + if err != nil { + return nil, httpErr.NewInternal(err.Error()) + } + + if len(backupJobs.Items) > 0 { + names := make([]string, 0, len(backupJobs.Items)) + for _, b := range backupJobs.Items { + names = append(names, b.Name) + } + events = append(events, GetScopedEvents(c, policy.Namespace, "OBTenantBackup", names)...) + } + } + + logger.Debugf("Get related events of obtenant: %v", nn) + return events, nil +} diff --git a/internal/dashboard/model/response/backup.go b/internal/dashboard/model/response/backup.go index 48c7b83c1..f0f06b9bc 100644 --- a/internal/dashboard/model/response/backup.go +++ b/internal/dashboard/model/response/backup.go @@ -19,30 +19,30 @@ import ( type BackupPolicy struct { param.BackupPolicyBase `json:",inline"` - UID string `json:"uid"` - TenantName string `json:"tenantName"` - Name string `json:"name"` - Namespace string `json:"namespace"` - Status string `json:"status"` + UID string `json:"uid" binding:"required"` + TenantName string `json:"tenantName" binding:"required"` + Name string `json:"name" binding:"required"` + Namespace string `json:"namespace" binding:"required"` + Status string `json:"status" binding:"required"` OSSAccessSecret string `json:"ossAccessSecret,omitempty"` BakEncryptionSecret string `json:"bakEncryptionSecret,omitempty"` - CreateTime string `json:"createTime"` - Events []K8sEvent `json:"events"` + CreateTime string `json:"createTime" binding:"required"` + Events []K8sEvent `json:"events" binding:"required"` } type BackupJob struct { - UID string `json:"uid"` - Name string `json:"name"` - Namespace string `json:"namespace"` + UID string `json:"uid" binding:"required"` + Name string `json:"name" binding:"required"` + Namespace string `json:"namespace" binding:"required"` // Enum: FULL, INCR, ARCHIVE, CLEAN - Type string `json:"type"` - TenantName string `json:"tenantName"` - BackupPolicyName string `json:"backupPolicyName"` - Path string `json:"path"` // Empty for Clean job - StartTime string `json:"startTime"` // Start time of the backup job, StartScnDisplay for ARCHIVE job - EndTime string `json:"endTime"` // End time of the backup job, empty for ARCHIVE job - Status string `json:"status"` - StatusInDatabase string `json:"statusInDatabase"` + Type string `json:"type" binding:"required"` + TenantName string `json:"tenantName" binding:"required"` + BackupPolicyName string `json:"backupPolicyName" binding:"required"` + Path string `json:"path" binding:"required"` // Empty for Clean job + StartTime string `json:"startTime" binding:"required"` // Start time of the backup job, StartScnDisplay for ARCHIVE job + EndTime string `json:"endTime"` // End time of the backup job, empty for ARCHIVE job + Status string `json:"status" binding:"required"` + StatusInDatabase string `json:"statusInDatabase" binding:"required"` EncryptionSecret string `json:"encryptionSecret,omitempty"` } diff --git a/internal/dashboard/model/response/info.go b/internal/dashboard/model/response/info.go index 5f005cc71..ed5426dba 100644 --- a/internal/dashboard/model/response/info.go +++ b/internal/dashboard/model/response/info.go @@ -13,8 +13,9 @@ See the Mulan PSL v2 for more details. package response type DashboardInfo struct { - AppName string `json:"appName"` - Version string `json:"version"` - PublicKey string `json:"publicKey"` - ReportStatistics bool `json:"reportStatistics"` + AppName string `json:"appName" binding:"required"` + Version string `json:"version" binding:"required"` + PublicKey string `json:"publicKey" binding:"required"` + ReportStatistics bool `json:"reportStatistics" binding:"required"` + ReportHost string `json:"reportHost" binding:"required"` } diff --git a/internal/dashboard/model/response/k8s.go b/internal/dashboard/model/response/k8s.go index 896229441..81cebeee4 100644 --- a/internal/dashboard/model/response/k8s.go +++ b/internal/dashboard/model/response/k8s.go @@ -15,44 +15,44 @@ package response import "github.com/oceanbase/ob-operator/internal/dashboard/model/common" type K8sEvent struct { - Namespace string `json:"namespace"` - Type string `json:"type"` - Count int32 `json:"count"` - FirstOccur int64 `json:"firstOccur"` - LastSeen int64 `json:"lastSeen"` - Reason string `json:"reason"` - Object string `json:"object"` - Message string `json:"message"` + Namespace string `json:"namespace" binding:"required"` + Type string `json:"type" binding:"required"` + Count int32 `json:"count" binding:"required"` + FirstOccur int64 `json:"firstOccur" binding:"required"` + LastSeen int64 `json:"lastSeen" binding:"required"` + Reason string `json:"reason" binding:"required"` + Object string `json:"object" binding:"required"` + Message string `json:"message" binding:"required"` } type K8sNodeCondition struct { - Type string `json:"type"` - Reason string `json:"reason"` - Message string `json:"message"` + Type string `json:"type" binding:"required"` + Reason string `json:"reason" binding:"required"` + Message string `json:"message" binding:"required"` } type K8sNodeInfo struct { - Name string `json:"name"` - Status string `json:"status"` - Conditions []K8sNodeCondition `json:"conditions"` - Roles []string `json:"roles"` - Labels []common.KVPair `json:"labels"` - Uptime int64 `json:"uptime"` - Version string `json:"version"` - InternalIP string `json:"internalIP"` - ExternalIP string `json:"externalIP"` - OS string `json:"os"` - Kernel string `json:"kernel"` - CRI string `json:"cri"` + Name string `json:"name" binding:"required"` + Status string `json:"status" binding:"required"` + Conditions []K8sNodeCondition `json:"conditions" binding:"required"` + Roles []string `json:"roles" binding:"required"` + Labels []common.KVPair `json:"labels" binding:"required"` + Uptime int64 `json:"uptime" binding:"required"` + Version string `json:"version" binding:"required"` + InternalIP string `json:"internalIP" binding:"required"` + ExternalIP string `json:"externalIP" binding:"required"` + OS string `json:"os" binding:"required"` + Kernel string `json:"kernel" binding:"required"` + CRI string `json:"cri" binding:"required"` } type K8sNodeResource struct { - CpuTotal float64 `json:"cpuTotal"` - CpuUsed float64 `json:"cpuUsed"` - CpuFree float64 `json:"cpuFree"` - MemoryTotal float64 `json:"memoryTotal"` - MemoryUsed float64 `json:"memoryUsed"` - MemoryFree float64 `json:"memoryFree"` + CpuTotal float64 `json:"cpuTotal" binding:"required"` + CpuUsed float64 `json:"cpuUsed" binding:"required"` + CpuFree float64 `json:"cpuFree" binding:"required"` + MemoryTotal float64 `json:"memoryTotal" binding:"required"` + MemoryUsed float64 `json:"memoryUsed" binding:"required"` + MemoryFree float64 `json:"memoryFree" binding:"required"` } type K8sNode struct { @@ -61,16 +61,16 @@ type K8sNode struct { } type Namespace struct { - Namespace string `json:"namespace"` - Status string `json:"status"` + Namespace string `json:"namespace" binding:"required"` + Status string `json:"status" binding:"required"` } type StorageClass struct { - Name string `json:"name"` - Provisioner string `json:"provisioner"` - ReclaimPolicy string `json:"reclaimPolicy"` - VolumeBindingMode string `json:"volumeBindingMode"` - AllowVolumeExpansion bool `json:"allowVolumeExpansion"` + Name string `json:"name" binding:"required"` + Provisioner string `json:"provisioner" binding:"required"` + ReclaimPolicy string `json:"reclaimPolicy" binding:"required"` + VolumeBindingMode string `json:"volumeBindingMode" binding:"required"` + AllowVolumeExpansion bool `json:"allowVolumeExpansion" binding:"required"` MountOptions []string `json:"mountOptions,omitempty"` Parameters []common.KVPair `json:"parameters,omitempty"` } diff --git a/internal/dashboard/model/response/metrics.go b/internal/dashboard/model/response/metrics.go index a85a7b992..6d96357fd 100644 --- a/internal/dashboard/model/response/metrics.go +++ b/internal/dashboard/model/response/metrics.go @@ -15,22 +15,22 @@ package response import "github.com/oceanbase/ob-operator/internal/dashboard/model/common" type MetricClass struct { - Name string `json:"name" yaml:"name"` - Description string `json:"description" yaml:"description"` - MetricGroups []MetricGroup `json:"metricGroups" yaml:"metricGroups"` + Name string `json:"name" yaml:"name" binding:"required"` + Description string `json:"description" yaml:"description" binding:"required"` + MetricGroups []MetricGroup `json:"metricGroups" yaml:"metricGroups" binding:"required"` } type MetricGroup struct { - Name string `json:"name" yaml:"name"` - Description string `json:"description" yaml:"description"` - Metrics []MetricMeta `json:"metrics" yaml:"metrics"` + Name string `json:"name" yaml:"name" binding:"required"` + Description string `json:"description" yaml:"description" binding:"required"` + Metrics []MetricMeta `json:"metrics" yaml:"metrics" binding:"required"` } type MetricMeta struct { - Name string `json:"name" yaml:"name"` - Unit string `json:"unit" yaml:"unit"` - Description string `json:"description" yaml:"description"` - Key string `json:"key" yaml:"key"` + Name string `json:"name" yaml:"name" binding:"required"` + Unit string `json:"unit" yaml:"unit" binding:"required"` + Description string `json:"description" yaml:"description" binding:"required"` + Key string `json:"key" yaml:"key" binding:"required"` } type Metric struct { @@ -39,11 +39,11 @@ type Metric struct { } type MetricValue struct { - Value float64 `json:"value" yaml:"value"` - Timestamp float64 `json:"timestamp" yaml:"timestamp"` + Value float64 `json:"value" yaml:"value" binding:"required"` + Timestamp float64 `json:"timestamp" yaml:"timestamp" binding:"required"` } type MetricData struct { - Metric Metric `json:"metric" yaml:"metric"` - Values []MetricValue `json:"values" yaml:"values"` + Metric Metric `json:"metric" yaml:"metric" binding:"required"` + Values []MetricValue `json:"values" yaml:"values" binding:"required"` } diff --git a/internal/dashboard/model/response/obcluster.go b/internal/dashboard/model/response/obcluster.go index 0c6861cc8..cad7986e2 100644 --- a/internal/dashboard/model/response/obcluster.go +++ b/internal/dashboard/model/response/obcluster.go @@ -16,27 +16,27 @@ import ( "github.com/oceanbase/ob-operator/internal/dashboard/model/common" ) -type OBClusterStastistic struct { - Status string `json:"status"` - Count int `json:"count"` +type OBClusterStatistic struct { + Status string `json:"status" binding:"required"` + Count int `json:"count" binding:"required"` } type OBServer struct { - Namespace string `json:"namespace"` - Name string `json:"name"` - Status string `json:"status"` - StatusDetail string `json:"statusDetail"` - Address string `json:"address"` + Namespace string `json:"namespace" binding:"required"` + Name string `json:"name" binding:"required"` + Status string `json:"status" binding:"required"` + StatusDetail string `json:"statusDetail" binding:"required"` + Address string `json:"address" binding:"required"` Metrics *OBMetrics `json:"metrics"` } type OBZone struct { - Namespace string `json:"namespace"` - Name string `json:"name"` - Zone string `json:"zone"` - Replicas int `json:"replicas"` - Status string `json:"status"` - StatusDetail string `json:"statusDetail"` + Namespace string `json:"namespace" binding:"required"` + Name string `json:"name" binding:"required"` + Zone string `json:"zone" binding:"required"` + Replicas int `json:"replicas" binding:"required"` + Status string `json:"status" binding:"required"` + StatusDetail string `json:"statusDetail" binding:"required"` RootService string `json:"rootService,omitempty"` OBServers []OBServer `json:"observers,omitempty"` NodeSelector []common.KVPair `json:"nodeSelector,omitempty"` @@ -46,22 +46,24 @@ type OBZone struct { } type OBMetrics struct { - CpuPercent int `json:"cpuPercent"` - MemoryPercent int `json:"memoryPercent"` - DiskPercent int `json:"diskPercent"` + CpuPercent int `json:"cpuPercent" binding:"required"` + MemoryPercent int `json:"memoryPercent" binding:"required"` + DiskPercent int `json:"diskPercent" binding:"required"` } type OBClusterOverview struct { - UID string `json:"uid"` - Name string `json:"name"` - Namespace string `json:"namespace"` - ClusterName string `json:"clusterName"` - ClusterId int64 `json:"clusterId"` - Status string `json:"status"` - StatusDetail string `json:"statusDetail"` - CreateTime int64 `json:"createTime"` - Image string `json:"image"` - Topology []OBZone `json:"topology"` + UID string `json:"uid" binding:"required"` + Name string `json:"name" binding:"required"` + Namespace string `json:"namespace" binding:"required"` + ClusterName string `json:"clusterName" binding:"required"` + ClusterId int64 `json:"clusterId" binding:"required"` + Status string `json:"status" binding:"required"` + StatusDetail string `json:"statusDetail" binding:"required"` + CreateTime int64 `json:"createTime" binding:"required"` + Image string `json:"image" binding:"required"` + Topology []OBZone `json:"topology" binding:"required"` + + Mode common.ClusterMode `json:"mode" binding:"required"` } type OBCluster struct { @@ -74,58 +76,57 @@ type OBCluster struct { } type ResourceSpecRender struct { - Cpu int64 `json:"cpu"` - MemoryGB string `json:"memory"` + Cpu int64 `json:"cpu" binding:"required"` + MemoryGB string `json:"memory" binding:"required"` } type OBClusterExtra struct { - Resource ResourceSpecRender `json:"resource"` - Storage OBServerStorage `json:"storage"` - - RootPasswordSecret string `json:"rootPasswordSecret"` - Parameters []common.KVPair `json:"parameters"` - Monitor *MonitorSpec `json:"monitor"` - BackupVolume *NFSVolumeSpec `json:"backupVolume"` - Mode common.ClusterMode `json:"mode"` + Resource ResourceSpecRender `json:"resource" binding:"required"` + Storage OBServerStorage `json:"storage" binding:"required"` + + RootPasswordSecret string `json:"rootPasswordSecret" binding:"required"` + Parameters []common.KVPair `json:"parameters" binding:"required"` + Monitor *MonitorSpec `json:"monitor"` + BackupVolume *NFSVolumeSpec `json:"backupVolume"` } type MonitorSpec struct { - Image string `json:"image"` - Resource ResourceSpecRender `json:"resource"` + Image string `json:"image" binding:"required"` + Resource ResourceSpecRender `json:"resource" binding:"required"` } type NFSVolumeSpec struct { - Address string `json:"address"` - Path string `json:"path"` + Address string `json:"address" binding:"required"` + Path string `json:"path" binding:"required"` } type OBServerStorage struct { - DataStorage StorageSpec `json:"dataStorage"` - RedoLogStorage StorageSpec `json:"redoLogStorage"` - SysLogStorage StorageSpec `json:"sysLogStorage"` + DataStorage StorageSpec `json:"dataStorage" binding:"required"` + RedoLogStorage StorageSpec `json:"redoLogStorage" binding:"required"` + SysLogStorage StorageSpec `json:"sysLogStorage" binding:"required"` } type StorageSpec struct { - StorageClass string `json:"storageClass"` - SizeGB string `json:"size"` + StorageClass string `json:"storageClass" binding:"required"` + SizeGB string `json:"size" binding:"required"` } type OBClusterResources struct { - MinPoolMemory int64 `json:"minPoolMemory" example:"2147483648"` - OBServerResources []OBServerAvailableResource `json:"obServerResources"` - OBZoneResourceMap map[string]*OBZoneAvaiableResource `json:"obZoneResourceMap"` + MinPoolMemory int64 `json:"minPoolMemory" example:"2147483648" binding:"required"` + OBServerResources []OBServerAvailableResource `json:"obServerResources"` + OBZoneResourceMap map[string]*OBZoneAvailableResource `json:"obZoneResourceMap"` } type OBServerAvailableResource struct { - OBServerIP string `json:"obServerIP"` - OBZoneAvaiableResource `json:",inline"` + OBServerIP string `json:"obServerIP" binding:"required"` + OBZoneAvailableResource `json:",inline"` } -type OBZoneAvaiableResource struct { - ServerCount int64 `json:"serverCount" example:"3"` - OBZone string `json:"obZone" example:"zone1"` - AvailableLogDisk int64 `json:"availableLogDisk" example:"5368709120"` - AvailableDataDisk int64 `json:"availableDataDisk" example:"16106127360"` - AvailableMemory int64 `json:"availableMemory" example:"5368709120"` - AvailableCPU int64 `json:"availableCPU" example:"12"` +type OBZoneAvailableResource struct { + ServerCount int64 `json:"serverCount" example:"3" binding:"required"` + OBZone string `json:"obZone" example:"zone1" binding:"required"` + AvailableLogDisk int64 `json:"availableLogDisk" example:"5368709120" binding:"required"` + AvailableDataDisk int64 `json:"availableDataDisk" example:"16106127360" binding:"required"` + AvailableMemory int64 `json:"availableMemory" example:"5368709120" binding:"required"` + AvailableCPU int64 `json:"availableCPU" example:"12" binding:"required"` } diff --git a/internal/dashboard/model/response/obtenant.go b/internal/dashboard/model/response/obtenant.go index ac13f1c4b..4b54dfcc5 100644 --- a/internal/dashboard/model/response/obtenant.go +++ b/internal/dashboard/model/response/obtenant.go @@ -14,25 +14,25 @@ package response // @Description Brief information about OBTenant type OBTenantOverview struct { - UID string `json:"uid"` // Unique identifier of the resource - Name string `json:"name"` // Name of the resource - Namespace string `json:"namespace"` // Namespace of the resource - TenantName string `json:"tenantName"` // Name of the tenant in the database - ClusterName string `json:"clusterResourceName"` // Name of the cluster belonging to - TenantRole string `json:"tenantRole"` // Enum: Primary, Standby - UnitNumber int `json:"unitNumber"` // Number of units in every zone - Topology []OBTenantReplica `json:"topology"` // Topology of the tenant - Status string `json:"status"` // Status of the tenant - CreateTime string `json:"createTime"` // Creation time of the tenant - Locality string `json:"locality"` // Locality of the tenant units - Charset string `json:"charset"` // Charset of the tenant - PrimaryZone string `json:"primaryZone"` // Primary zone of the tenant + UID string `json:"uid" binding:"required"` // Unique identifier of the resource + Name string `json:"name" binding:"required"` // Name of the resource + Namespace string `json:"namespace" binding:"required"` // Namespace of the resource + TenantName string `json:"tenantName" binding:"required"` // Name of the tenant in the database + ClusterName string `json:"clusterResourceName" binding:"required"` // Name of the cluster belonging to + TenantRole string `json:"tenantRole" binding:"required"` // Enum: Primary, Standby + UnitNumber int `json:"unitNumber" binding:"required"` // Number of units in every zone + Topology []OBTenantReplica `json:"topology"` // Topology of the tenant + Status string `json:"status" binding:"required"` // Status of the tenant + CreateTime string `json:"createTime" binding:"required"` // Creation time of the tenant + Locality string `json:"locality" binding:"required"` // Locality of the tenant units + Charset string `json:"charset" binding:"required"` // Charset of the tenant + PrimaryZone string `json:"primaryZone" binding:"required"` // Primary zone of the tenant } type OBTenantDetail struct { OBTenantOverview `json:",inline"` RootCredential string `json:"rootCredential"` - StandbyROCredentail string `json:"standbyROCredentail"` + StandbyROCredential string `json:"standbyROCredential"` Version string `json:"version"` PrimaryTenant string `json:"primaryTenant"` @@ -40,27 +40,27 @@ type OBTenantDetail struct { } type OBTenantReplica struct { - Zone string `json:"zone"` - Priority int `json:"priority"` + Zone string `json:"zone" binding:"required"` + Priority int `json:"priority" binding:"required"` // Enum: Readonly, Full - Type string `json:"type"` - MaxCPU string `json:"maxCPU"` - MemorySize string `json:"memorySize"` - MinCPU string `json:"minCPU,omitempty"` - MaxIops int `json:"maxIops,omitempty"` - MinIops int `json:"minIops,omitempty"` - IopsWeight int `json:"iopsWeight,omitempty"` - LogDiskSize string `json:"logDiskSize,omitempty"` + Type string `json:"type" binding:"required"` + MaxCPU string `json:"maxCPU" binding:"required"` + MemorySize string `json:"memorySize" binding:"required"` + MinCPU string `json:"minCPU,omitempty" binding:"required"` + MaxIops int `json:"maxIops,omitempty" binding:"required"` + MinIops int `json:"minIops,omitempty" binding:"required"` + IopsWeight int `json:"iopsWeight,omitempty" binding:"required"` + LogDiskSize string `json:"logDiskSize,omitempty" binding:"required"` } type RestoreSource struct { // Enum: OSS, NFS - Type string `json:"type"` - ArchiveSource string `json:"archiveSource"` - BakDataSource string `json:"bakDataSource"` + Type string `json:"type" binding:"required"` + ArchiveSource string `json:"archiveSource" binding:"required"` + BakDataSource string `json:"bakDataSource" binding:"required"` OssAccessSecret string `json:"ossAccessSecret,omitempty"` BakEncryptionSecret string `json:"bakEncryptionSecret,omitempty"` Until string `json:"until,omitempty"` } -type OBTenantStatistic OBClusterStastistic +type OBTenantStatistic OBClusterStatistic diff --git a/internal/dashboard/model/response/response.go b/internal/dashboard/model/response/response.go index 93586a591..f45a4f627 100644 --- a/internal/dashboard/model/response/response.go +++ b/internal/dashboard/model/response/response.go @@ -13,7 +13,7 @@ See the Mulan PSL v2 for more details. package response type APIResponse struct { - Data any `json:"data"` - Message string `json:"message"` - Successful bool `json:"successful"` + Data any `json:"data" binding:"required"` + Message string `json:"message" binding:"required"` + Successful bool `json:"successful" binding:"required"` } diff --git a/internal/dashboard/model/response/statistics.go b/internal/dashboard/model/response/statistics.go index e6d043118..310452dc2 100644 --- a/internal/dashboard/model/response/statistics.go +++ b/internal/dashboard/model/response/statistics.go @@ -19,13 +19,13 @@ import ( type StatisticData struct { OperatorVersion string `json:"operatorVersion"` - K8sNodes []K8sNode `json:"k8sNodes"` - Clusters []models.OBCluster `json:"clusters"` - Zones []models.OBZone `json:"zones"` - Servers []models.OBServer `json:"servers"` - Tenants []models.OBTenant `json:"tenants"` - BackupPolicies []models.OBBackupPolicy `json:"backupPolicies"` - WarningEvents []models.K8sEvent `json:"warningEvents"` + K8sNodes []K8sNode `json:"k8sNodes" binding:"required"` + Clusters []models.OBCluster `json:"clusters" binding:"required"` + Zones []models.OBZone `json:"zones" binding:"required"` + Servers []models.OBServer `json:"servers" binding:"required"` + Tenants []models.OBTenant `json:"tenants" binding:"required"` + BackupPolicies []models.OBBackupPolicy `json:"backupPolicies" binding:"required"` + WarningEvents []models.K8sEvent `json:"warningEvents" binding:"required"` } type StatisticDataResponse struct { diff --git a/internal/dashboard/router/v1/obcluster_router.go b/internal/dashboard/router/v1/obcluster_router.go index be8e9c0be..1510ec259 100644 --- a/internal/dashboard/router/v1/obcluster_router.go +++ b/internal/dashboard/router/v1/obcluster_router.go @@ -29,4 +29,5 @@ func InitOBClusterRoutes(g *gin.RouterGroup) { g.POST("/obclusters/namespace/:namespace/name/:name/obzones/:obzoneName/scale", h.Wrap(h.ScaleOBServer)) g.DELETE("/obclusters/namespace/:namespace/name/:name/obzones/:obzoneName", h.Wrap(h.DeleteOBZone)) g.GET("/obclusters/:namespace/:name/resource-usages", h.Wrap(h.ListOBClusterResources)) + g.GET("/obclusters/:namespace/:name/related-events", h.Wrap(h.ListOBClusterRelatedEvents)) } diff --git a/internal/dashboard/router/v1/obtenant_router.go b/internal/dashboard/router/v1/obtenant_router.go index b06357ac5..a3dbdfaa1 100644 --- a/internal/dashboard/router/v1/obtenant_router.go +++ b/internal/dashboard/router/v1/obtenant_router.go @@ -37,4 +37,5 @@ func InitOBTenantRoutes(g *gin.RouterGroup) { g.PUT("/obtenants/:namespace/:name/pools/:zoneName", h.Wrap(h.CreateOBTenantPool)) g.DELETE("/obtenants/:namespace/:name/pools/:zoneName", h.Wrap(h.DeleteOBTenantPool)) g.PATCH("/obtenants/:namespace/:name/pools/:zoneName", h.Wrap(h.PatchOBTenantPool)) + g.GET("/obtenants/:namespace/:name/related-events", h.Wrap(h.ListOBTenantRelatedEvents)) }