diff --git a/internal/cli/utils/parser.go b/internal/cli/utils/parser.go index a5200aa52..ecc9bb7a8 100644 --- a/internal/cli/utils/parser.go +++ b/internal/cli/utils/parser.go @@ -43,7 +43,6 @@ func MapZonesToTopology(zones map[string]string) ([]param.ZoneTopology, error) { Zone: zoneName, Replicas: replica, NodeSelector: make([]common.KVPair, 0), - Tolerations: make([]common.KVPair, 0), Affinities: make([]common.AffinitySpec, 0), }) } diff --git a/internal/dashboard/business/oceanbase/obcluster.go b/internal/dashboard/business/oceanbase/obcluster.go index 7a711fe89..7f0c32e52 100644 --- a/internal/dashboard/business/oceanbase/obcluster.go +++ b/internal/dashboard/business/oceanbase/obcluster.go @@ -20,11 +20,13 @@ import ( "github.com/google/uuid" "github.com/pkg/errors" + "github.com/sirupsen/logrus" logger "github.com/sirupsen/logrus" corev1 "k8s.io/api/core/v1" apiresource "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/oceanbase/ob-operator/api/constants" apitypes "github.com/oceanbase/ob-operator/api/types" "github.com/oceanbase/ob-operator/api/v1alpha1" "github.com/oceanbase/ob-operator/internal/clients" @@ -35,7 +37,9 @@ import ( 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" + "github.com/oceanbase/ob-operator/internal/dashboard/utils" oberr "github.com/oceanbase/ob-operator/pkg/errors" + models "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/model" ) const ( @@ -115,11 +119,16 @@ func buildOBClusterResponse(ctx context.Context, obcluster *v1alpha1.OBCluster) // TODO: add metrics Metrics: nil, } - var parameters []modelcommon.KVPair + var parameters []response.ParameterSpec + statusParameterMap := make(map[string]string, 0) + for _, param := range obcluster.Status.Parameters { + statusParameterMap[param.Name] = param.Value + } for _, param := range obcluster.Spec.Parameters { - parameters = append(parameters, modelcommon.KVPair{ - Key: param.Name, - Value: param.Value, + parameters = append(parameters, response.ParameterSpec{ + Name: param.Name, + SpecValue: param.Value, + Value: statusParameterMap[param.Name], }) } respCluster.Parameters = parameters @@ -537,6 +546,7 @@ func generateOBClusterInstance(param *param.CreateOBClusterParam) *v1alpha1.OBCl Parameters: parameters, Topology: topology, UserSecrets: generateUserSecrets(param.Name, param.ClusterId), + Scenario: param.Scenario, }, } switch param.Mode { @@ -546,6 +556,12 @@ func generateOBClusterInstance(param *param.CreateOBClusterParam) *v1alpha1.OBCl obcluster.Annotations[oceanbaseconst.AnnotationsMode] = oceanbaseconst.ModeService default: } + if param.DeletionProtection { + obcluster.Annotations[oceanbaseconst.AnnotationsIgnoreDeletion] = "true" + } + if param.PvcIndependent { + obcluster.Annotations[oceanbaseconst.AnnotationsIndependentPVCLifecycle] = "true" + } return obcluster } @@ -723,3 +739,175 @@ func GetOBClusterStatistic(ctx context.Context) ([]response.OBClusterStatistic, }) return statisticResult, nil } + +func PatchOBCluster(ctx context.Context, nn *param.K8sObjectIdentity, param *param.PatchOBClusterParam) (*response.OBCluster, error) { + obcluster, err := clients.GetOBCluster(ctx, nn.Namespace, nn.Name) + if err != nil { + return nil, errors.Wrapf(err, "Get obcluster %s %s", nn.Namespace, nn.Name) + } + if obcluster.Status.Status != clusterstatus.Running { + return nil, errors.Errorf("OBCluster status is invalid %s", obcluster.Status.Status) + } + alreadyIgnoredDeletion := obcluster.Annotations[oceanbaseconst.AnnotationsIgnoreDeletion] == "true" + + if obcluster.Spec.OBServerTemplate != nil { + // Update resource if specified + obcluster.Spec.OBServerTemplate.Resource = &apitypes.ResourceSpec{ + Cpu: *apiresource.NewQuantity(param.Resource.Cpu, apiresource.DecimalSI), + Memory: *apiresource.NewQuantity(param.Resource.MemoryGB*constant.GB, apiresource.BinarySI), + } + } else if param.Storage != nil && obcluster.Spec.OBServerTemplate != nil { + // Update storage if specified + obcluster.Spec.OBServerTemplate.Storage = &apitypes.OceanbaseStorageSpec{ + DataStorage: &apitypes.StorageSpec{ + StorageClass: param.Storage.Data.StorageClass, + Size: *apiresource.NewQuantity(param.Storage.Data.SizeGB*constant.GB, apiresource.BinarySI), + }, + RedoLogStorage: &apitypes.StorageSpec{ + StorageClass: param.Storage.RedoLog.StorageClass, + Size: *apiresource.NewQuantity(param.Storage.RedoLog.SizeGB*constant.GB, apiresource.BinarySI), + }, + LogStorage: &apitypes.StorageSpec{ + StorageClass: param.Storage.Log.StorageClass, + Size: *apiresource.NewQuantity(param.Storage.Log.SizeGB*constant.GB, apiresource.BinarySI), + }, + } + } else if param.Monitor != nil && obcluster.Spec.MonitorTemplate == nil { + // Update monitor if specified + obcluster.Spec.MonitorTemplate = &apitypes.MonitorTemplate{ + Image: param.Monitor.Image, + Resource: &apitypes.ResourceSpec{ + Cpu: *apiresource.NewQuantity(param.Monitor.Resource.Cpu, apiresource.DecimalSI), + Memory: *apiresource.NewQuantity(param.Monitor.Resource.MemoryGB*constant.GB, apiresource.BinarySI), + }, + } + } else if param.RemoveMonitor { + // Remove monitor if specified + obcluster.Spec.MonitorTemplate = nil + } else if param.BackupVolume != nil && obcluster.Spec.BackupVolume == nil { + // Update backup volume if specified + obcluster.Spec.BackupVolume = &apitypes.BackupVolumeSpec{ + Volume: &corev1.Volume{ + Name: "ob-backup", + VolumeSource: corev1.VolumeSource{ + NFS: &corev1.NFSVolumeSource{ + Server: param.BackupVolume.Address, + Path: param.BackupVolume.Path, + ReadOnly: false, + }, + }, + }, + } + } else if param.RemoveBackupVolume { + // Remove backup volume if specified + obcluster.Spec.BackupVolume = nil + } else if len(param.Parameters) > 0 { + // Update parameters if specified + obcluster.Spec.Parameters = buildOBClusterParameters(param.Parameters) + } + + if param.AddDeletionProtection && !alreadyIgnoredDeletion { + // Update deletion protection if specified + obcluster.Annotations[oceanbaseconst.AnnotationsIgnoreDeletion] = "true" + } else if param.RemoveDeletionProtection && alreadyIgnoredDeletion { + delete(obcluster.Annotations, oceanbaseconst.AnnotationsIgnoreDeletion) + } + + cluster, err := clients.UpdateOBCluster(ctx, obcluster) + if err != nil { + return nil, oberr.NewInternal(err.Error()) + } + return buildOBClusterResponse(ctx, cluster) +} + +func RestartOBServers(ctx context.Context, nn *param.K8sObjectIdentity, param *param.RestartOBServersParam) (*response.OBCluster, error) { + obcluster, err := clients.GetOBCluster(ctx, nn.Namespace, nn.Name) + if err != nil { + return nil, errors.Wrapf(err, "Get obcluster %s %s", nn.Namespace, nn.Name) + } + if obcluster.Status.Status != clusterstatus.Running { + return nil, errors.Errorf("OBCluster status is invalid %s", obcluster.Status.Status) + } + + // Create OBClusterOperation for restarting observers + operation := &v1alpha1.OBClusterOperation{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "restart-observers-", + Namespace: nn.Namespace, + }, + Spec: v1alpha1.OBClusterOperationSpec{ + Type: constants.ClusterOpTypeRestartOBServers, + OBCluster: nn.Name, + RestartOBServers: &v1alpha1.RestartOBServersConfig{ + OBServers: param.OBServers, + OBZones: param.OBZones, + All: param.All, + }, + }, + } + + _, err = clients.CreateOBClusterOperation(ctx, operation) + if err != nil { + return nil, oberr.NewInternal(err.Error()) + } + + return buildOBClusterResponse(ctx, obcluster) +} + +func DeleteOBServers(ctx context.Context, nn *param.K8sObjectIdentity, param *param.DeleteOBServersParam) (*response.OBCluster, error) { + obcluster, err := clients.GetOBCluster(ctx, nn.Namespace, nn.Name) + if err != nil { + return nil, errors.Wrapf(err, "Get obcluster %s %s", nn.Namespace, nn.Name) + } + if obcluster.Status.Status != clusterstatus.Running { + return nil, errors.Errorf("OBCluster status is invalid %s", obcluster.Status.Status) + } + + // Create OBClusterOperation for deleting observers + operation := &v1alpha1.OBClusterOperation{ + ObjectMeta: metav1.ObjectMeta{ + GenerateName: "delete-observers-", + Namespace: nn.Namespace, + }, + Spec: v1alpha1.OBClusterOperationSpec{ + Type: constants.ClusterOpTypeDeleteOBServers, + OBCluster: nn.Name, + DeleteOBServers: &v1alpha1.DeleteOBServersConfig{ + OBServers: param.OBServers, + }, + }, + } + + _, err = clients.CreateOBClusterOperation(ctx, operation) + if err != nil { + return nil, oberr.NewInternal(err.Error()) + } + + return buildOBClusterResponse(ctx, obcluster) +} + +func ListOBClusterParameters(ctx context.Context, nn *param.K8sObjectIdentity) ([]*models.Parameter, error) { + obcluster, err := clients.GetOBCluster(ctx, nn.Namespace, nn.Name) + if err != nil { + return nil, errors.Wrapf(err, "Get obcluster %s %s", nn.Namespace, nn.Name) + } + observerList := v1alpha1.OBServerList{} + err = clients.ServerClient.List(ctx, nn.Namespace, &observerList, metav1.ListOptions{ + LabelSelector: fmt.Sprintf("%s=%s", oceanbaseconst.LabelRefOBCluster, nn.Name), + }) + if err != nil { + logrus.WithError(err).Error("Failed to list observers") + return nil, errors.Wrap(err, "List observers") + } + conn, err := utils.GetOBConnection(ctx, obcluster, "root", "sys", obcluster.Spec.UserSecrets.Root) + if err != nil { + logrus.Info("Failed to get OceanBase database connection") + return nil, errors.Wrap(err, "Get OceanBase database connection") + } + parameters, err := conn.ListParametersWithTenantID(ctx, 1) + if err != nil { + logrus.WithError(err).Error("Failed to query parameters") + return nil, errors.Wrap(err, "Query parameters") + } + return parameters, nil +} diff --git a/internal/dashboard/business/oceanbase/obcluster_test.go b/internal/dashboard/business/oceanbase/obcluster_test.go index 095f530d8..be30a3add 100644 --- a/internal/dashboard/business/oceanbase/obcluster_test.go +++ b/internal/dashboard/business/oceanbase/obcluster_test.go @@ -41,9 +41,11 @@ func getMockedCreateClusterParam() *param.CreateOBClusterParam { Key: "test-node-selector", Value: "test-node-selector-value", }}, - Tolerations: []common.KVPair{{ - Key: "test-toleration", - Value: "test-toleration-value", + Tolerations: []common.TolerationSpec{{ + KVPair: common.KVPair{ + Key: "test-toleration", + Value: "test-toleration-value", + }, }}, Affinities: []common.AffinitySpec{{ SelectorExpression: common.SelectorExpression{ diff --git a/internal/dashboard/business/oceanbase/obtenant.go b/internal/dashboard/business/oceanbase/obtenant.go index 3fc939329..4dc6f1ae0 100644 --- a/internal/dashboard/business/oceanbase/obtenant.go +++ b/internal/dashboard/business/oceanbase/obtenant.go @@ -31,6 +31,7 @@ import ( "github.com/oceanbase/ob-operator/api/v1alpha1" "github.com/oceanbase/ob-operator/internal/clients" "github.com/oceanbase/ob-operator/internal/clients/schema" + oceanbaseconst "github.com/oceanbase/ob-operator/internal/const/oceanbase" "github.com/oceanbase/ob-operator/internal/const/status/tenantstatus" "github.com/oceanbase/ob-operator/internal/dashboard/model/param" "github.com/oceanbase/ob-operator/internal/dashboard/model/response" @@ -41,8 +42,10 @@ import ( func buildOBTenantApiType(nn types.NamespacedName, p *param.CreateOBTenantParam) (*v1alpha1.OBTenant, error) { t := &v1alpha1.OBTenant{ ObjectMeta: v1.ObjectMeta{ - Name: nn.Name, - Namespace: nn.Namespace, + Name: nn.Name, + Namespace: nn.Namespace, + Annotations: make(map[string]string), + Labels: make(map[string]string), }, TypeMeta: v1.TypeMeta{ Kind: schema.OBTenantKind, @@ -58,6 +61,8 @@ func buildOBTenantApiType(nn types.NamespacedName, p *param.CreateOBTenantParam) // guard non-nil Pools: []v1alpha1.ResourcePoolSpec{}, + + Scenario: p.Scenario, }, } @@ -141,6 +146,27 @@ func buildOBTenantApiType(nn types.NamespacedName, p *param.CreateOBTenantParam) } } } + if len(p.Variables) > 0 { + t.Spec.Variables = make([]apitypes.Variable, 0, len(p.Variables)) + for i := range p.Variables { + t.Spec.Variables = append(t.Spec.Variables, apitypes.Variable{ + Name: p.Variables[i].Key, + Value: p.Variables[i].Value, + }) + } + } + if len(p.Parameters) > 0 { + t.Spec.Parameters = make([]apitypes.Parameter, 0, len(p.Parameters)) + for i := range p.Parameters { + t.Spec.Parameters = append(t.Spec.Parameters, apitypes.Parameter{ + Name: p.Parameters[i].Key, + Value: p.Parameters[i].Value, + }) + } + } + if p.DeletionProtection { + t.Annotations[oceanbaseconst.AnnotationsIgnoreDeletion] = "true" + } return t, nil } @@ -351,7 +377,7 @@ func CreateOBTenant(ctx context.Context, nn types.NamespacedName, p *param.Creat } if p.Source.Restore.OSSAccessID != "" && p.Source.Restore.OSSAccessKey != "" { - ossSecretName := p.Name + "-oss-access-" + rand.String(6) + ossSecretName := nn.Name + "-backup-" + strings.ToLower(strings.ReplaceAll(string(p.Source.Restore.Type), "_", "-")) + "-secret-" + rand.String(6) t.Spec.Source.Restore.ArchiveSource.OSSAccessSecret = ossSecretName t.Spec.Source.Restore.BakDataSource.OSSAccessSecret = ossSecretName _, err = k8sclient.ClientSet.CoreV1().Secrets(nn.Namespace).Create(ctx, &corev1.Secret{ @@ -362,6 +388,8 @@ func CreateOBTenant(ctx context.Context, nn types.NamespacedName, p *param.Creat StringData: map[string]string{ "accessId": p.Source.Restore.OSSAccessID, "accessKey": p.Source.Restore.OSSAccessKey, + "appId": p.Source.Restore.AppID, + "s3Region": p.Source.Restore.Region, }, }, v1.CreateOptions{}) if err != nil { @@ -548,6 +576,7 @@ func PatchTenant(ctx context.Context, nn types.NamespacedName, p *param.PatchTen if err != nil { return nil, err } + alreadyIgnoreDeletion := tenant.Annotations[oceanbaseconst.AnnotationsIgnoreDeletion] == "true" if p.UnitNumber != nil { tenant.Spec.UnitNumber = *p.UnitNumber } @@ -595,6 +624,31 @@ func PatchTenant(ctx context.Context, nn types.NamespacedName, p *param.PatchTen } } } + if alreadyIgnoreDeletion && p.RemoveDeletionProtection { + delete(tenant.Annotations, oceanbaseconst.AnnotationsIgnoreDeletion) + } else if !alreadyIgnoreDeletion && p.AddDeletionProtection { + tenant.Annotations[oceanbaseconst.AnnotationsIgnoreDeletion] = "true" + } + if len(p.Variables) > 0 { + newVars := make([]apitypes.Variable, 0, len(p.Variables)) + for i := range p.Variables { + newVars = append(newVars, apitypes.Variable{ + Name: p.Variables[i].Key, + Value: p.Variables[i].Value, + }) + } + tenant.Spec.Variables = newVars + } + if len(p.Parameters) > 0 { + newParameters := make([]apitypes.Parameter, 0, len(p.Parameters)) + for i := range p.Parameters { + newParameters = append(newParameters, apitypes.Parameter{ + Name: p.Parameters[i].Key, + Value: p.Parameters[i].Value, + }) + } + tenant.Spec.Parameters = newParameters + } tenant, err = clients.UpdateOBTenant(ctx, tenant) if err != nil { return nil, err diff --git a/internal/dashboard/business/oceanbase/obtenantbackup.go b/internal/dashboard/business/oceanbase/obtenantbackup.go index e041158d9..e15923d7a 100644 --- a/internal/dashboard/business/oceanbase/obtenantbackup.go +++ b/internal/dashboard/business/oceanbase/obtenantbackup.go @@ -354,8 +354,8 @@ func CreateTenantBackupPolicy(ctx context.Context, nn types.NamespacedName, p *p return nil, err } - if p.DestType == "OSS" && p.OSSAccessID != "" && p.OSSAccessKey != "" { - ossSecretName := nn.Name + "-backup-oss-secret-" + rand.String(6) + if p.DestType != param.BackupDestNFS && p.OSSAccessID != "" && p.OSSAccessKey != "" { + ossSecretName := nn.Name + "-backup-" + strings.ToLower(strings.ReplaceAll(string(p.DestType), "_", "-")) + "-secret-" + rand.String(6) backupPolicy.Spec.LogArchive.Destination.OSSAccessSecret = ossSecretName backupPolicy.Spec.DataBackup.Destination.OSSAccessSecret = ossSecretName secret := &corev1.Secret{ @@ -366,6 +366,8 @@ func CreateTenantBackupPolicy(ctx context.Context, nn types.NamespacedName, p *p StringData: map[string]string{ "accessId": p.OSSAccessID, "accessKey": p.OSSAccessKey, + "s3Region": p.Region, + "appId": p.AppID, }, } _, err := client.GetClient().ClientSet.CoreV1().Secrets(nn.Namespace).Create(ctx, secret, metav1.CreateOptions{}) diff --git a/internal/dashboard/handler/obcluster_handler.go b/internal/dashboard/handler/obcluster_handler.go index c61768501..8ecec6808 100644 --- a/internal/dashboard/handler/obcluster_handler.go +++ b/internal/dashboard/handler/obcluster_handler.go @@ -29,6 +29,7 @@ import ( crypto "github.com/oceanbase/ob-operator/pkg/crypto" httpErr "github.com/oceanbase/ob-operator/pkg/errors" "github.com/oceanbase/ob-operator/pkg/k8s/client" + "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/model" ) // @ID GetOBClusterStatistic @@ -383,3 +384,115 @@ func GetScopedEvents(ctx context.Context, ns, kind string, scoped []string) []re } return events } + +// @ID PatchOBCluster +// @Summary patch obcluster +// @Description patch obcluster configuration including resources, storage, monitor and parameters +// @Tags OBCluster +// @Accept application/json +// @Produce application/json +// @Param namespace path string true "obcluster namespace" +// @Param name path string true "obcluster name" +// @Param body body param.PatchOBClusterParam true "patch obcluster request body" +// @Success 200 object response.APIResponse{data=response.OBCluster} +// @Failure 400 object response.APIResponse +// @Failure 401 object response.APIResponse +// @Failure 500 object response.APIResponse +// @Router /api/v1/obclusters/namespace/{namespace}/name/{name} [PATCH] +// @Security ApiKeyAuth +func PatchOBCluster(c *gin.Context) (*response.OBCluster, error) { + obclusterIdentity := ¶m.K8sObjectIdentity{} + err := c.BindUri(obclusterIdentity) + if err != nil { + return nil, httpErr.NewBadRequest(err.Error()) + } + patchParam := ¶m.PatchOBClusterParam{} + err = c.Bind(patchParam) + if err != nil { + return nil, httpErr.NewBadRequest(err.Error()) + } + logger.Infof("Patch obcluster with param: %+v", patchParam) + return oceanbase.PatchOBCluster(c, obclusterIdentity, patchParam) +} + +// @ID RestartOBServers +// @Summary restart observers +// @Description restart specified observers in the obcluster +// @Tags OBCluster +// @Accept application/json +// @Produce application/json +// @Param namespace path string true "obcluster namespace" +// @Param name path string true "obcluster name" +// @Param body body param.RestartOBServersParam true "restart observers request body" +// @Success 200 object response.APIResponse{data=response.OBCluster} +// @Failure 400 object response.APIResponse +// @Failure 401 object response.APIResponse +// @Failure 500 object response.APIResponse +// @Router /api/v1/obclusters/namespace/{namespace}/name/{name}/restart [POST] +// @Security ApiKeyAuth +func RestartOBServers(c *gin.Context) (*response.OBCluster, error) { + obclusterIdentity := ¶m.K8sObjectIdentity{} + err := c.BindUri(obclusterIdentity) + if err != nil { + return nil, httpErr.NewBadRequest(err.Error()) + } + restartParam := ¶m.RestartOBServersParam{} + err = c.Bind(restartParam) + if err != nil { + return nil, httpErr.NewBadRequest(err.Error()) + } + logger.Infof("Restart observers with param: %+v", restartParam) + return oceanbase.RestartOBServers(c, obclusterIdentity, restartParam) +} + +// @ID DeleteOBServers +// @Summary delete observers +// @Description delete specified observers from the obcluster +// @Tags OBCluster +// @Accept application/json +// @Produce application/json +// @Param namespace path string true "obcluster namespace" +// @Param name path string true "obcluster name" +// @Param body body param.DeleteOBServersParam true "delete observers request body" +// @Success 200 object response.APIResponse{data=response.OBCluster} +// @Failure 400 object response.APIResponse +// @Failure 401 object response.APIResponse +// @Failure 500 object response.APIResponse +// @Router /api/v1/obclusters/namespace/{namespace}/name/{name}/observers [DELETE] +// @Security ApiKeyAuth +func DeleteOBServers(c *gin.Context) (*response.OBCluster, error) { + obclusterIdentity := ¶m.K8sObjectIdentity{} + err := c.BindUri(obclusterIdentity) + if err != nil { + return nil, httpErr.NewBadRequest(err.Error()) + } + deleteParam := ¶m.DeleteOBServersParam{} + err = c.Bind(deleteParam) + if err != nil { + return nil, httpErr.NewBadRequest(err.Error()) + } + logger.Infof("Delete observers with param: %+v", deleteParam) + return oceanbase.DeleteOBServers(c, obclusterIdentity, deleteParam) +} + +// @ID ListOBClusterParameters +// @Summary List OBCluster Parameters +// @Description List OBCluster Parameters by namespace and name +// @Tags OBCluster +// @Accept application/json +// @Produce application/json +// @Param namespace path string true "namespace of obcluster resource" +// @Param name path string true "name of obcluster resource" +// @Success 200 object response.APIResponse{data=[]model.Parameter} +// @Failure 400 object response.APIResponse +// @Failure 401 object response.APIResponse +// @Failure 500 object response.APIResponse +// @Router /api/v1/obclusters/namespace/{namespace}/name/{name}/parameters [GET] +func ListOBClusterParameters(c *gin.Context) ([]*model.Parameter, error) { + nn := ¶m.K8sObjectIdentity{} + err := c.BindUri(nn) + if err != nil { + return nil, httpErr.NewBadRequest(err.Error()) + } + return oceanbase.ListOBClusterParameters(c, nn) +} diff --git a/internal/dashboard/handler/obtenant_handler.go b/internal/dashboard/handler/obtenant_handler.go index 4fb6d8574..0cb8ded3a 100644 --- a/internal/dashboard/handler/obtenant_handler.go +++ b/internal/dashboard/handler/obtenant_handler.go @@ -443,7 +443,10 @@ func CreateBackupPolicy(c *gin.Context) (*response.BackupPolicy, error) { if err != nil { return nil, httpErr.NewBadRequest(err.Error()) } - if createPolicyParam.DestType == "OSS" { + if createPolicyParam.DestType != param.BackupDestNFS { + if createPolicyParam.Host == "" { + return nil, httpErr.NewBadRequest("Host is required for non-NFS type destination") + } createPolicyParam.OSSAccessID, err = crypto.DecryptWithPrivateKey(createPolicyParam.OSSAccessID) if err != nil { return nil, httpErr.NewBadRequest(err.Error()) @@ -452,6 +455,12 @@ func CreateBackupPolicy(c *gin.Context) (*response.BackupPolicy, error) { if err != nil { return nil, httpErr.NewBadRequest(err.Error()) } + if createPolicyParam.DestType == param.BackupDestCOS && createPolicyParam.AppID == "" { + return nil, httpErr.NewBadRequest("AppID is required for COS type destination") + } + if createPolicyParam.DestType == param.BackupDestS3 && createPolicyParam.Region == "" { + return nil, httpErr.NewBadRequest("Region is required for S3 type destination") + } } if createPolicyParam.BakEncryptionPassword != "" { createPolicyParam.BakEncryptionPassword, err = crypto.DecryptWithPrivateKey(createPolicyParam.BakEncryptionPassword) diff --git a/internal/dashboard/model/common/common.go b/internal/dashboard/model/common/common.go index 827b89a57..1767661cb 100644 --- a/internal/dashboard/model/common/common.go +++ b/internal/dashboard/model/common/common.go @@ -24,7 +24,7 @@ type ResourceSpec struct { type StorageSpec struct { StorageClass string `json:"storageClass"` - SizeGB int64 `json:"size"` + SizeGB int64 `json:"size" binding:"required"` } type SelectorExpression struct { @@ -37,9 +37,10 @@ type AffinityType string type AffinitySpec struct { SelectorExpression `json:",inline"` - Type AffinityType `json:"type"` - Weight int32 `json:"weight,omitempty"` - Preferred bool `json:"preferred,omitempty"` + // Enum: NODE, POD, POD_ANTI + Type AffinityType `json:"type"` + Weight int32 `json:"weight,omitempty"` + Preferred bool `json:"preferred,omitempty"` } type TolerationSpec struct { diff --git a/internal/dashboard/model/param/backup_param.go b/internal/dashboard/model/param/backup_param.go index 7dd776a00..e4fd9e793 100644 --- a/internal/dashboard/model/param/backup_param.go +++ b/internal/dashboard/model/param/backup_param.go @@ -28,7 +28,7 @@ type DaysFieldBase struct { } type BackupPolicyBase struct { - // Enum: NFS, OSS + // Enum: NFS, OSS, COS, S3, S3_COMPATIBLE DestType BackupDestType `json:"destType" binding:"required" example:"NFS"` ArchivePath string `json:"archivePath" binding:"required"` BakDataPath string `json:"bakDataPath" binding:"required"` @@ -39,9 +39,16 @@ type BackupPolicyBase struct { type CreateBackupPolicy struct { BackupPolicyBase `json:",inline"` - OSSAccessID string `json:"ossAccessId,omitempty" example:"encryptedPassword"` - OSSAccessKey string `json:"ossAccessKey,omitempty" example:"encryptedPassword"` BakEncryptionPassword string `json:"bakEncryptionPassword,omitempty" example:"encryptedPassword"` + + // Used for non-NFS + OSSAccessID string `json:"ossAccessId,omitempty" example:"encryptedPassword"` + OSSAccessKey string `json:"ossAccessKey,omitempty" example:"encryptedPassword"` + Host string `json:"host,omitempty" example:"https://oss-cn-hangzhou.aliyuncs.com"` + // Used for S3 + Region string `json:"region,omitempty" example:"cn-hangzhou"` + // Used for COS + AppID string `json:"appId,omitempty" example:"123456"` } type ScheduleDate struct { diff --git a/internal/dashboard/model/param/constant.go b/internal/dashboard/model/param/constant.go new file mode 100644 index 000000000..6130f341b --- /dev/null +++ b/internal/dashboard/model/param/constant.go @@ -0,0 +1,21 @@ +/* +Copyright (c) 2024 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 param + +const ( + BackupDestNFS BackupDestType = "NFS" + BackupDestOSS BackupDestType = "OSS" + BackupDestCOS BackupDestType = "COS" + BackupDestS3 BackupDestType = "S3" + BackupDestS3Compatible BackupDestType = "S3_COMPATIBLE" +) diff --git a/internal/dashboard/model/param/obcluster_param.go b/internal/dashboard/model/param/obcluster_param.go index 95f2ed0c3..c5dfdd63b 100644 --- a/internal/dashboard/model/param/obcluster_param.go +++ b/internal/dashboard/model/param/obcluster_param.go @@ -12,14 +12,17 @@ See the Mulan PSL v2 for more details. package param -import "github.com/oceanbase/ob-operator/internal/dashboard/model/common" +import ( + v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" + "github.com/oceanbase/ob-operator/internal/dashboard/model/common" +) type ZoneTopology struct { - Zone string `json:"zone"` - Replicas int `json:"replicas"` - NodeSelector []common.KVPair `json:"nodeSelector,omitempty"` - Tolerations []common.KVPair `json:"tolerations,omitempty"` - Affinities []common.AffinitySpec `json:"affinities,omitempty"` + Zone string `json:"zone"` + Replicas int `json:"replicas"` + NodeSelector []common.KVPair `json:"nodeSelector,omitempty"` + Tolerations []common.TolerationSpec `json:"tolerations,omitempty"` + Affinities []common.AffinitySpec `json:"affinities,omitempty"` } type OBServerStorageSpec struct { @@ -60,6 +63,11 @@ type CreateOBClusterParam struct { Parameters []common.KVPair `json:"parameters"` BackupVolume *NFSVolumeSpec `json:"backupVolume"` Mode common.ClusterMode `json:"mode"` + + // Enum: express_oltp, express_oltp, olap, kv, htap, express_oltp_perf + Scenario string `json:"scenario"` + DeletionProtection bool `json:"deletionProtection"` + PvcIndependent bool `json:"pvcIndependent"` } type UpgradeOBClusterParam struct { @@ -80,3 +88,20 @@ type OBZoneIdentity struct { Name string `json:"name" uri:"name" binding:"required"` OBZoneName string `json:"obzoneName" uri:"obzoneName" binding:"required"` } + +type PatchOBClusterParam struct { + Resource common.ResourceSpec `json:"resource"` + Storage *OBServerStorageSpec `json:"storage"` + Monitor *MonitorSpec `json:"monitor"` + RemoveMonitor bool `json:"removeMonitor"` + BackupVolume *NFSVolumeSpec `json:"backupVolume"` + RemoveBackupVolume bool `json:"removeBackupVolume"` + + Parameters []common.KVPair `json:"parameters,omitempty"` + AddDeletionProtection bool `json:"addDeletionProtection"` + RemoveDeletionProtection bool `json:"removeDeletionProtection"` +} + +type RestartOBServersParam v1alpha1.RestartOBServersConfig + +type DeleteOBServersParam v1alpha1.DeleteOBServersConfig diff --git a/internal/dashboard/model/param/obtenant_param.go b/internal/dashboard/model/param/obtenant_param.go index 4b49cabf9..158e5b463 100644 --- a/internal/dashboard/model/param/obtenant_param.go +++ b/internal/dashboard/model/param/obtenant_param.go @@ -12,6 +12,10 @@ See the Mulan PSL v2 for more details. package param +import ( + "github.com/oceanbase/ob-operator/internal/dashboard/model/common" +) + type CreateOBTenantParam struct { Name string `json:"name" binding:"required"` Namespace string `json:"namespace" binding:"required"` @@ -28,9 +32,13 @@ type CreateOBTenantParam struct { // Enum: Primary, Standby TenantRole TenantRole `json:"tenantRole,omitempty"` Source *TenantSourceSpec `json:"source,omitempty"` -} -type UpdateOBTenantParam CreateOBTenantParam + // Enum: express_oltp, express_oltp, olap, kv, htap, express_oltp_perf + Scenario string `json:"scenario"` + DeletionProtection bool `json:"deletionProtection"` + Parameters []common.KVPair `json:"parameters"` + Variables []common.KVPair `json:"variables"` +} type ResourcePoolSpec struct { Zone string `json:"zone" binding:"required"` @@ -45,7 +53,7 @@ type TenantSourceSpec struct { } type RestoreSourceSpec struct { - // Enum: OSS, NFS + // Enum: OSS, NFS, COS, S3, S3_COMPATIBLE Type BackupDestType `json:"type" binding:"required"` ArchiveSource string `json:"archiveSource" binding:"required"` BakDataSource string `json:"bakDataSource" binding:"required"` @@ -54,6 +62,9 @@ type RestoreSourceSpec struct { BakEncryptionPassword string `json:"bakEncryptionPassword,omitempty"` Until *RestoreUntilConfig `json:"until,omitempty"` + + AppID string `json:"appId,omitempty"` + Region string `json:"region,omitempty"` } type UnitConfig struct { @@ -93,6 +104,11 @@ type PatchTenant struct { // Deprecated // Description: Deprecated, use PATCH /obtenants/:namespace/:name/pools/:zoneName instead UnitConfig *PatchUnitConfig `json:"unitConfig,omitempty"` + + Parameters []common.KVPair `json:"parameters"` + Variables []common.KVPair `json:"variables"` + AddDeletionProtection bool `json:"addDeletionProtection"` + RemoveDeletionProtection bool `json:"removeDeletionProtection"` } type TenantPoolSpec struct { diff --git a/internal/dashboard/model/response/obcluster.go b/internal/dashboard/model/response/obcluster.go index 5dc586753..36b3d6e0d 100644 --- a/internal/dashboard/model/response/obcluster.go +++ b/internal/dashboard/model/response/obcluster.go @@ -82,12 +82,18 @@ type ResourceSpecRender struct { Memory int64 `json:"memory" binding:"required"` } +type ParameterSpec struct { + Name string `json:"name" binding:"required"` + SpecValue string `json:"specValue" binding:"required"` + Value string `json:"value" binding:"required"` +} + type OBClusterExtra struct { 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"` + Parameters []ParameterSpec `json:"parameters" binding:"required"` Monitor *MonitorSpec `json:"monitor"` BackupVolume *NFSVolumeSpec `json:"backupVolume"` } diff --git a/internal/dashboard/router/v1/obcluster_router.go b/internal/dashboard/router/v1/obcluster_router.go index 0e774bff9..c30692335 100644 --- a/internal/dashboard/router/v1/obcluster_router.go +++ b/internal/dashboard/router/v1/obcluster_router.go @@ -31,4 +31,9 @@ func InitOBClusterRoutes(g *gin.RouterGroup) { g.DELETE("/obclusters/namespace/:namespace/name/:name/obzones/:obzoneName", h.Wrap(h.DeleteOBZone, acbiz.PathGuard("obcluster", ":namespace+:name", "write"))) g.GET("/obclusters/:namespace/:name/resource-usages", h.Wrap(h.ListOBClusterResources, acbiz.PathGuard("obcluster", ":namespace+:name", "read"))) g.GET("/obclusters/:namespace/:name/related-events", h.Wrap(h.ListOBClusterRelatedEvents, acbiz.PathGuard("obcluster", ":namespace+:name", "read"))) + + g.PATCH("/obclusters/namespace/:namespace/name/:name", h.Wrap(h.PatchOBCluster, acbiz.PathGuard("obcluster", ":namespace+:name", "write"))) + g.POST("/obclusters/namespace/:namespace/name/:name/restart", h.Wrap(h.RestartOBServers, acbiz.PathGuard("obcluster", ":namespace+:name", "write"))) + g.DELETE("/obclusters/namespace/:namespace/name/:name/observers", h.Wrap(h.DeleteOBServers, acbiz.PathGuard("obcluster", ":namespace+:name", "write"))) + g.GET("/obclusters/namespace/:namespace/name/:name/parameters", h.Wrap(h.ListOBClusterParameters, acbiz.PathGuard("obcluster", ":namespace+:name", "read"))) } diff --git a/pkg/oceanbase-sdk/const/sql/parameter.go b/pkg/oceanbase-sdk/const/sql/parameter.go index 1df7c28af..57d783980 100644 --- a/pkg/oceanbase-sdk/const/sql/parameter.go +++ b/pkg/oceanbase-sdk/const/sql/parameter.go @@ -20,6 +20,6 @@ const ( ) const ( - ListParametersWithTenantID = "select name, value from GV$OB_PARAMETERS where tenant_id = ?" + ListParametersWithTenantID = "select name, value, data_type, info, section, default_value, isdefault, edit_level, scope from GV$OB_PARAMETERS where tenant_id = ?" SelectCompatibleOfTenants = "select name, value, tenant_id from GV$OB_PARAMETERS where name = 'compatible'" ) diff --git a/pkg/oceanbase-sdk/model/parameter.go b/pkg/oceanbase-sdk/model/parameter.go index f7c913ad0..8209ac7d0 100644 --- a/pkg/oceanbase-sdk/model/parameter.go +++ b/pkg/oceanbase-sdk/model/parameter.go @@ -21,4 +21,10 @@ type Parameter struct { Scope string `json:"scope" db:"scope"` EditLevel string `json:"edit_level" db:"edit_level"` TenantID int64 `json:"tenant_id" db:"tenant_id"` + + DataType string `json:"dataType" db:"data_type"` + Info string `json:"info"` + Section string `json:"section"` + DefaultValue string `json:"defaultValue" db:"default_value"` + IsDefault string `json:"isDefault" db:"isdefault"` }