diff --git a/api/v1alpha1/obresourcerescue_webhook.go b/api/v1alpha1/obresourcerescue_webhook.go index c4495cbf4..1c38b4bf6 100644 --- a/api/v1alpha1/obresourcerescue_webhook.go +++ b/api/v1alpha1/obresourcerescue_webhook.go @@ -31,10 +31,11 @@ import ( var obresourcerescuelog = logf.Log.WithName("obresourcerescue-resource") var rescueTypeMapping = map[string]struct{}{ - "delete": {}, - "reset": {}, - "retry": {}, - "skip": {}, + "delete": {}, + "reset": {}, + "retry": {}, + "skip": {}, + "ignore-deletion": {}, } func (r *OBResourceRescue) SetupWebhookWithManager(mgr ctrl.Manager) error { diff --git a/cmd/generator/task/task-register.go b/cmd/generator/task/task-register.go new file mode 100644 index 000000000..9a0096a06 --- /dev/null +++ b/cmd/generator/task/task-register.go @@ -0,0 +1,102 @@ +/* +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 main + +import ( + "fmt" + "go/ast" + "go/parser" + "go/token" + "log" + "os" + "strings" + "text/template" +) + +const genTemplate = `// Code generated by go generate; DO NOT EDIT. +package {{.PackageName}} + +func init() { +{{- range .Tasks }} + taskMap.Register(t{{.}}, {{.}}) +{{- end }} +} +` + +type Task string + +func main() { + if len(os.Args) != 2 { + log.Fatalf("Usage: %s ", os.Args[0]) + } + sourceFile := os.Args[1] + + fset := token.NewFileSet() + node, err := parser.ParseFile(fset, sourceFile, nil, 0) + if err != nil { + log.Fatalf("Failed to parse source file: %v", err) + } + + taskFuncs := []Task{} + ast.Inspect(node, func(n ast.Node) bool { + fn, ok := n.(*ast.FuncDecl) + if !ok { + return true + } + // Get return type of function and check whether it is a func(resource T) TaskError + if len(fn.Type.Params.List) == 1 && len(fn.Type.Results.List) == 1 { + if strings.HasSuffix(exprToString(fn.Type.Results.List[0].Type), "TaskError") { + taskFuncs = append(taskFuncs, Task(fn.Name.Name)) + } + } + + return true + }) + + tmpl, err := template.New("registration").Parse(genTemplate) + if err != nil { + log.Fatalf("Failed to parse template: %v", err) + } + + outputFile := sourceFile[:len(sourceFile)-3] + "_gen.go" + + f, err := os.Create(outputFile) + if err != nil { + log.Fatalf("Failed to create output file: %v", err) + } + defer f.Close() + + err = tmpl.Execute(f, struct { + PackageName string + Tasks []Task + }{ + PackageName: node.Name.Name, + Tasks: taskFuncs, + }) + if err != nil { + log.Printf("Failed to execute template: %v", err) + } +} + +func exprToString(expr ast.Expr) string { + switch e := expr.(type) { + case *ast.Ident: + return e.Name + case *ast.SelectorExpr: + return exprToString(e.X) + "." + e.Sel.Name + case *ast.StarExpr: + return "*" + exprToString(e.X) + default: + return fmt.Sprintf("unknown(%T)", e) + } +} diff --git a/internal/const/oceanbase/annotations.go b/internal/const/oceanbase/annotations.go new file mode 100644 index 000000000..e246c86ae --- /dev/null +++ b/internal/const/oceanbase/annotations.go @@ -0,0 +1,36 @@ +/* +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 oceanbase + +const ( + AnnotationCalicoValidate = "cni.projectcalico.org/podIP" + AnnotationCalicoIpAddrs = "cni.projectcalico.org/ipAddrs" +) + +const ( + AnnotationsIndependentPVCLifecycle = "oceanbase.oceanbase.com/independent-pvc-lifecycle" + AnnotationsSinglePVC = "oceanbase.oceanbase.com/single-pvc" + AnnotationsMode = "oceanbase.oceanbase.com/mode" + AnnotationsSourceClusterAddress = "oceanbase.oceanbase.com/source-cluster-address" + AnnotationsIgnoreDeletion = "oceanbase.oceanbase.com/ignore-deletion" +) + +const ( + ModeStandalone = "standalone" + ModeService = "service" +) + +const ( + CNICalico = "calico" + CNIUnknown = "unknown" +) diff --git a/internal/resource/obtenantrestore/init.go b/internal/const/oceanbase/labels.go similarity index 52% rename from internal/resource/obtenantrestore/init.go rename to internal/const/oceanbase/labels.go index 809c0dd64..d93f6185a 100644 --- a/internal/resource/obtenantrestore/init.go +++ b/internal/const/oceanbase/labels.go @@ -10,15 +10,19 @@ MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details. */ -package obtenantrestore +package oceanbase -import ( - "github.com/oceanbase/ob-operator/pkg/task" +const ( + LabelRefOBCluster = "ref-obcluster" + LabelRefOBZone = "ref-obzone" + LabelRefOBServer = "ref-observer" + LabelRefUID = "ref-uid" + LabelJobName = "job-name" + LabelRefBackupPolicy = "ref-backuppolicy" ) -func init() { - // tenant-level restore - task.GetRegistry().Register(fStartRestoreFlow, StartRestoreJob) - task.GetRegistry().Register(fRestoreAsPrimaryFlow, RestoreAsPrimary) - task.GetRegistry().Register(fRestoreAsStandbyFlow, RestoreAsStandby) -} +const ( + LabelTenantName = "oceanbase.oceanbase.com/tenant-name" + LabelSecondaryTenant = "oceanbase.oceanbase.com/secondary-tenant" + LabelBackupType = "oceanbase.oceanbase.com/backup-type" +) diff --git a/internal/resource/obparameter/init.go b/internal/const/oceanbase/misc.go similarity index 74% rename from internal/resource/obparameter/init.go rename to internal/const/oceanbase/misc.go index f0c34819f..1658bba97 100644 --- a/internal/resource/obparameter/init.go +++ b/internal/const/oceanbase/misc.go @@ -10,13 +10,11 @@ MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. See the Mulan PSL v2 for more details. */ -package obparameter +package oceanbase -import ( - "github.com/oceanbase/ob-operator/pkg/task" +const ( + LogLevelDefault = 0 + LogLevelInfo = 0 + LogLevelDebug = 1 + LogLevelTrace = 2 ) - -func init() { - // obparameter - task.GetRegistry().Register(fSetOBParameter, SetOBParameter) -} diff --git a/internal/const/oceanbase/oceanbase.go b/internal/const/oceanbase/oceanbase.go index 835854c67..a49f5ff0f 100644 --- a/internal/const/oceanbase/oceanbase.go +++ b/internal/const/oceanbase/oceanbase.go @@ -12,42 +12,9 @@ See the Mulan PSL v2 for more details. package oceanbase -import "k8s.io/apimachinery/pkg/api/resource" - var UpgradeEssentialParameters = [...]string{"server_permanent_offline_time", "enable_rebalance", "enable_rereplication"} var ReservedParameters = [...]string{"cpu_count", "datafile_size", "log_disk_size", "enable_syslog_recycle", "max_syslog_file_count"} -const ( - BootstrapTimeoutSeconds = 300 - LocalityChangeTimeoutSeconds = 3600 - DefaultStateWaitTimeout = 300 - TimeConsumingStateWaitTimeout = 3600 - ServerDeleteTimeoutSeconds = 86400 - GigaConverter = 1 << 30 - MegaConverter = 1 << 20 -) - -const ( - DefaultDiskExpandPercent = 10 - DefaultLogPercent = 80 - InitialDataDiskUsePercent = 20 - DefaultDiskUsePercent = 95 - DefaultMemoryLimitPercent = 90 -) - -const ( - DefaultMemoryLimitSize = "0M" - DefaultDatafileMaxSize = "0M" - DefaultDatafileNextSize = "1G" -) - -var ( - MinMemorySize = resource.MustParse("8Gi") - MinDataDiskSize = resource.MustParse("30Gi") - MinRedoLogDiskSize = resource.MustParse("30Gi") - MinLogDiskSize = resource.MustParse("10Gi") -) - const ( SqlPort = 2881 RpcPort = 2882 @@ -58,38 +25,6 @@ const ( RpcPortName = "rpc" ) -const ( - ProbeCheckPeriodSeconds = 2 - ProbeCheckDelaySeconds = 5 - GetConnectionMaxRetries = 10 - CheckConnectionInterval = 3 - CheckJobInterval = 3 - CheckJobMaxRetries = 100 - CommonCheckInterval = 5 -) - -const ( - AnnotationCalicoValidate = "cni.projectcalico.org/podIP" - AnnotationCalicoIpAddrs = "cni.projectcalico.org/ipAddrs" -) - -const ( - AnnotationsIndependentPVCLifecycle = "oceanbase.oceanbase.com/independent-pvc-lifecycle" - AnnotationsSinglePVC = "oceanbase.oceanbase.com/single-pvc" - AnnotationsMode = "oceanbase.oceanbase.com/mode" - AnnotationsSourceClusterAddress = "oceanbase.oceanbase.com/source-cluster-address" -) - -const ( - ModeStandalone = "standalone" - ModeService = "service" -) - -const ( - CNICalico = "calico" - CNIUnknown = "unknown" -) - const ( ContainerName = "observer" InstallPath = "/home/admin/oceanbase" @@ -127,15 +62,6 @@ const ( DefaultRegion = "default" ) -const ( - LabelRefOBCluster = "ref-obcluster" - LabelRefOBZone = "ref-obzone" - LabelRefOBServer = "ref-observer" - LabelRefUID = "ref-uid" - LabelJobName = "job-name" - LabelRefBackupPolicy = "ref-backuppolicy" -) - const ( OBServerVersionKey = "observer-version" EssentialParametersKey = "essential-parameters" @@ -146,37 +72,10 @@ const ( SelectPrivilege = "select" ) -const ( - LabelTenantName = "oceanbase.oceanbase.com/tenant-name" - LabelSecondaryTenant = "oceanbase.oceanbase.com/secondary-tenant" - LabelBackupType = "oceanbase.oceanbase.com/backup-type" -) - const ( OceanbaseAllScope = "oceanbase.*" ) -const ( - TenantOpRetryTimes = 9 - TenantOpRetryGapSeconds = 9 -) - -const ( - TaskMaxRetryTimes = 99 - TaskRetryBackoffThreshold = 16 -) - -const ( - LogLevelDefault = 0 - LogLevelInfo = 0 - LogLevelDebug = 1 - LogLevelTrace = 2 -) - -const ( - TolerateServerPodNotReadyMinutes = 5 -) - const ( ClusterNameParam = "cluster" ClusterIdParam = "cluster_id" diff --git a/internal/const/oceanbase/resource.go b/internal/const/oceanbase/resource.go new file mode 100644 index 000000000..90c3e2279 --- /dev/null +++ b/internal/const/oceanbase/resource.go @@ -0,0 +1,38 @@ +/* +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 oceanbase + +import "k8s.io/apimachinery/pkg/api/resource" + +const ( + DefaultDiskExpandPercent = 10 + DefaultLogPercent = 80 + InitialDataDiskUsePercent = 20 + DefaultDiskUsePercent = 95 + DefaultMemoryLimitPercent = 90 + GigaConverter = 1 << 30 + MegaConverter = 1 << 20 +) + +const ( + DefaultMemoryLimitSize = "0M" + DefaultDatafileMaxSize = "0M" + DefaultDatafileNextSize = "1G" +) + +var ( + MinMemorySize = resource.MustParse("8Gi") + MinDataDiskSize = resource.MustParse("30Gi") + MinRedoLogDiskSize = resource.MustParse("30Gi") + MinLogDiskSize = resource.MustParse("10Gi") +) diff --git a/internal/const/oceanbase/time.go b/internal/const/oceanbase/time.go new file mode 100644 index 000000000..baf0a8757 --- /dev/null +++ b/internal/const/oceanbase/time.go @@ -0,0 +1,45 @@ +/* +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 oceanbase + +const ( + TenantOpRetryTimes = 9 + TenantOpRetryGapSeconds = 9 +) + +const ( + TaskMaxRetryTimes = 99 + TaskRetryBackoffThreshold = 16 +) + +const ( + ProbeCheckPeriodSeconds = 2 + ProbeCheckDelaySeconds = 5 + GetConnectionMaxRetries = 10 + CheckConnectionInterval = 3 + CheckJobInterval = 3 + CheckJobMaxRetries = 100 + CommonCheckInterval = 5 +) + +const ( + BootstrapTimeoutSeconds = 300 + LocalityChangeTimeoutSeconds = 3600 + DefaultStateWaitTimeout = 300 + TimeConsumingStateWaitTimeout = 3600 + ServerDeleteTimeoutSeconds = 86400 +) + +const ( + TolerateServerPodNotReadyMinutes = 5 +) diff --git a/internal/controller/obresourcerescue_controller.go b/internal/controller/obresourcerescue_controller.go index fac1ee6f0..9a5c58b5a 100644 --- a/internal/controller/obresourcerescue_controller.go +++ b/internal/controller/obresourcerescue_controller.go @@ -33,6 +33,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" "github.com/oceanbase/ob-operator/api/v1alpha1" + oceanbaseconst "github.com/oceanbase/ob-operator/internal/const/oceanbase" ctlconfig "github.com/oceanbase/ob-operator/internal/controller/config" "github.com/oceanbase/ob-operator/internal/telemetry" taskstatus "github.com/oceanbase/ob-operator/pkg/task/const/status" @@ -175,6 +176,18 @@ func (r *OBResourceRescueReconciler) Reconcile(ctx context.Context, req ctrl.Req logger.Error(err, "failed to update status of the target resource") return ctrl.Result{}, err } + case "ignore-deletion": + annotations := uns.GetAnnotations() + if annotations == nil { + annotations = make(map[string]string) + } + annotations[oceanbaseconst.AnnotationsIgnoreDeletion] = "true" + uns.SetAnnotations(annotations) + _, err := r.Dynamic.Resource(mapping.Resource).Namespace(rescue.GetNamespace()).Update(ctx, uns, metav1.UpdateOptions{}) + if err != nil { + logger.Error(err, "failed to update annotations of the target resource") + return ctrl.Result{}, err + } } err = retry.RetryOnConflict(retry.DefaultRetry, func() error { diff --git a/internal/controller/observer_controller.go b/internal/controller/observer_controller.go index 8db1e90d2..46ad5131d 100644 --- a/internal/controller/observer_controller.go +++ b/internal/controller/observer_controller.go @@ -97,7 +97,7 @@ func (r *OBServerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c } } if needExecuteFinalizer { - err = observerManager.DeleteOBServerInCluster() + err = resobserver.DeleteOBServerInCluster(observerManager) if err != nil { logger.Error(err, "delete observer failed") return ctrl.Result{}, errors.Wrapf(err, "delete observer %s failed", observer.Name) diff --git a/internal/resource/obcluster/init.go b/internal/resource/obcluster/init.go deleted file mode 100644 index 31a88fa09..000000000 --- a/internal/resource/obcluster/init.go +++ /dev/null @@ -1,32 +0,0 @@ -/* -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 obcluster - -import ( - "github.com/oceanbase/ob-operator/pkg/task" -) - -func init() { - // obcluster - task.GetRegistry().Register(fBootstrapOBCluster, BootstrapOBCluster) - task.GetRegistry().Register(fMigrateOBClusterFromExisting, MigrateOBClusterFromExisting) - task.GetRegistry().Register(fMaintainOBClusterAfterBootstrap, MaintainOBClusterAfterBootstrap) - task.GetRegistry().Register(fAddOBZone, AddOBZone) - task.GetRegistry().Register(fDeleteOBZone, DeleteOBZone) - task.GetRegistry().Register(fModifyOBZoneReplica, ModifyOBZoneReplica) - task.GetRegistry().Register(fMaintainOBParameter, MaintainOBParameter) - task.GetRegistry().Register(fUpgradeOBCluster, UpgradeOBCluster) - task.GetRegistry().Register(fScaleUpOBZones, ScaleUpOBZones) - task.GetRegistry().Register(fExpandPVC, ResizePVC) - task.GetRegistry().Register(fMountBackupVolume, MountBackupVolume) -} diff --git a/internal/resource/obcluster/obcluster_flow.go b/internal/resource/obcluster/obcluster_flow.go index 82ef9bb70..8925db17b 100644 --- a/internal/resource/obcluster/obcluster_flow.go +++ b/internal/resource/obcluster/obcluster_flow.go @@ -18,7 +18,7 @@ import ( tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) -func MigrateOBClusterFromExisting() *tasktypes.TaskFlow { +func genMigrateOBClusterFromExistingFlow(_ *OBClusterManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fMigrateOBClusterFromExisting, @@ -31,7 +31,7 @@ func MigrateOBClusterFromExisting() *tasktypes.TaskFlow { } } -func BootstrapOBCluster() *tasktypes.TaskFlow { +func genBootstrapOBClusterFlow(_ *OBClusterManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fBootstrapOBCluster, @@ -44,7 +44,7 @@ func BootstrapOBCluster() *tasktypes.TaskFlow { } } -func MaintainOBClusterAfterBootstrap() *tasktypes.TaskFlow { +func genMaintainOBClusterAfterBootstrapFlow(_ *OBClusterManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fMaintainOBClusterAfterBootstrap, @@ -54,7 +54,7 @@ func MaintainOBClusterAfterBootstrap() *tasktypes.TaskFlow { } } -func AddOBZone() *tasktypes.TaskFlow { +func genAddOBZoneFlow(_ *OBClusterManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fAddOBZone, @@ -64,7 +64,7 @@ func AddOBZone() *tasktypes.TaskFlow { } } -func DeleteOBZone() *tasktypes.TaskFlow { +func genDeleteOBZoneFlow(_ *OBClusterManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fDeleteOBZone, @@ -74,7 +74,7 @@ func DeleteOBZone() *tasktypes.TaskFlow { } } -func ModifyOBZoneReplica() *tasktypes.TaskFlow { +func genModifyOBZoneReplicaFlow(_ *OBClusterManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fModifyOBZoneReplica, @@ -84,7 +84,7 @@ func ModifyOBZoneReplica() *tasktypes.TaskFlow { } } -func MaintainOBParameter() *tasktypes.TaskFlow { +func genMaintainOBParameterFlow(_ *OBClusterManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fMaintainOBParameter, @@ -94,7 +94,7 @@ func MaintainOBParameter() *tasktypes.TaskFlow { } } -func UpgradeOBCluster() *tasktypes.TaskFlow { +func genUpgradeOBClusterFlow(_ *OBClusterManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fUpgradeOBCluster, @@ -107,7 +107,7 @@ func UpgradeOBCluster() *tasktypes.TaskFlow { } } -func ScaleUpOBZones() *tasktypes.TaskFlow { +func genScaleUpOBZonesFlow(_ *OBClusterManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fScaleUpOBZones, @@ -117,7 +117,7 @@ func ScaleUpOBZones() *tasktypes.TaskFlow { } } -func ResizePVC() *tasktypes.TaskFlow { +func genExpandPVCFlow(_ *OBClusterManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fExpandPVC, @@ -127,7 +127,7 @@ func ResizePVC() *tasktypes.TaskFlow { } } -func MountBackupVolume() *tasktypes.TaskFlow { +func genMountBackupVolumeFlow(_ *OBClusterManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fMountBackupVolume, diff --git a/internal/resource/obcluster/obcluster_manager.go b/internal/resource/obcluster/obcluster_manager.go index 49a59cf3a..e6d7a8469 100644 --- a/internal/resource/obcluster/obcluster_manager.go +++ b/internal/resource/obcluster/obcluster_manager.go @@ -24,18 +24,17 @@ import ( v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" oceanbaseconst "github.com/oceanbase/ob-operator/internal/const/oceanbase" clusterstatus "github.com/oceanbase/ob-operator/internal/const/status/obcluster" - zonestatus "github.com/oceanbase/ob-operator/internal/const/status/obzone" resourceutils "github.com/oceanbase/ob-operator/internal/resource/utils" "github.com/oceanbase/ob-operator/internal/telemetry" opresource "github.com/oceanbase/ob-operator/pkg/coordinator" - "github.com/oceanbase/ob-operator/pkg/task" taskstatus "github.com/oceanbase/ob-operator/pkg/task/const/status" "github.com/oceanbase/ob-operator/pkg/task/const/strategy" tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) +var _ opresource.ResourceManager = &OBClusterManager{} + type OBClusterManager struct { - opresource.ResourceManager Ctx context.Context OBCluster *v1alpha1.OBCluster Client client.Client @@ -81,42 +80,37 @@ func (m *OBClusterManager) GetTaskFlow() (*tasktypes.TaskFlow, error) { // newly created cluster var taskFlow *tasktypes.TaskFlow - var err error m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Create task flow according to obcluster status") switch m.OBCluster.Status.Status { // create obcluster, return taskFlow to bootstrap obcluster case clusterstatus.MigrateFromExisting: - taskFlow, err = task.GetRegistry().Get(fMigrateOBClusterFromExisting) + taskFlow = genMigrateOBClusterFromExistingFlow(m) case clusterstatus.New: - taskFlow, err = task.GetRegistry().Get(fBootstrapOBCluster) + taskFlow = genBootstrapOBClusterFlow(m) // after obcluster bootstraped, return taskFlow to maintain obcluster after bootstrap case clusterstatus.Bootstrapped: - taskFlow, err = task.GetRegistry().Get(fMaintainOBClusterAfterBootstrap) + taskFlow = genMaintainOBClusterAfterBootstrapFlow(m) case clusterstatus.AddOBZone: - taskFlow, err = task.GetRegistry().Get(fAddOBZone) + taskFlow = genAddOBZoneFlow(m) case clusterstatus.DeleteOBZone: - taskFlow, err = task.GetRegistry().Get(fDeleteOBZone) + taskFlow = genDeleteOBZoneFlow(m) case clusterstatus.ModifyOBZoneReplica: - taskFlow, err = task.GetRegistry().Get(fModifyOBZoneReplica) + taskFlow = genModifyOBZoneReplicaFlow(m) case clusterstatus.Upgrade: - taskFlow, err = task.GetRegistry().Get(fUpgradeOBCluster) + taskFlow = genUpgradeOBClusterFlow(m) case clusterstatus.ModifyOBParameter: - taskFlow, err = task.GetRegistry().Get(fMaintainOBParameter) + taskFlow = genMaintainOBParameterFlow(m) case clusterstatus.ScaleUp: - taskFlow, err = task.GetRegistry().Get(fScaleUpOBZones) + taskFlow = genScaleUpOBZonesFlow(m) case clusterstatus.ExpandPVC: - taskFlow, err = task.GetRegistry().Get(fExpandPVC) + taskFlow = genExpandPVCFlow(m) case clusterstatus.MountBackupVolume: - taskFlow, err = task.GetRegistry().Get(fMountBackupVolume) + taskFlow = genMountBackupVolumeFlow(m) default: m.Logger.V(oceanbaseconst.LogLevelTrace).Info("No need to run anything for obcluster", "obcluster", m.OBCluster.Name) return nil, nil } - if err != nil { - return nil, err - } - if taskFlow.OperationContext.OnFailure.Strategy == "" { taskFlow.OperationContext.OnFailure.Strategy = strategy.StartOver if taskFlow.OperationContext.OnFailure.NextTryStatus == "" { @@ -124,11 +118,12 @@ func (m *OBClusterManager) GetTaskFlow() (*tasktypes.TaskFlow, error) { } } - return taskFlow, err + return taskFlow, nil } func (m *OBClusterManager) IsDeleting() bool { - return !m.OBCluster.ObjectMeta.DeletionTimestamp.IsZero() + ignoreDel, ok := resourceutils.GetAnnotationField(m.OBCluster, oceanbaseconst.AnnotationsIgnoreDeletion) + return !m.OBCluster.ObjectMeta.DeletionTimestamp.IsZero() && (!ok || ignoreDel != "true") } func (m *OBClusterManager) CheckAndUpdateFinalizers() error { @@ -283,64 +278,7 @@ func (m *OBClusterManager) HandleFailure() { } func (m *OBClusterManager) GetTaskFunc(name tasktypes.TaskName) (tasktypes.TaskFunc, error) { - switch name { - case tCheckMigration: - return m.CheckMigration, nil - case tCheckImageReady: - return m.CheckImageReady, nil - case tCheckClusterMode: - return m.CheckClusterMode, nil - case tCheckAndCreateUserSecrets: - return m.CheckAndCreateUserSecrets, nil - case tCreateOBZone: - return m.CreateOBZone, nil - case tDeleteOBZone: - return m.DeleteOBZone, nil - case tModifyOBZoneReplica: - return m.ModifyOBZoneReplica, nil - case tWaitOBZoneTopologyMatch: - return m.WaitOBZoneTopologyMatch, nil - case tWaitOBZoneBootstrapReady: - return m.generateWaitOBZoneStatusFunc(zonestatus.BootstrapReady, oceanbaseconst.DefaultStateWaitTimeout), nil - case tWaitOBZoneRunning: - return m.generateWaitOBZoneStatusFunc(zonestatus.Running, oceanbaseconst.DefaultStateWaitTimeout), nil - case tWaitOBZoneDeleted: - return m.WaitOBZoneDeleted, nil - case tBootstrap: - return m.Bootstrap, nil - case tCreateUsers: - return m.CreateUsers, nil - case tCreateOBClusterService: - return m.CreateServices, nil - case tMaintainOBParameter: - return m.MaintainOBParameter, nil - case tValidateUpgradeInfo: - return m.ValidateUpgradeInfo, nil - case tUpgradeCheck: - return m.UpgradeCheck, nil - case tBackupEssentialParameters: - return m.BackupEssentialParameters, nil - case tBeginUpgrade: - return m.BeginUpgrade, nil - case tRollingUpgradeByZone: - return m.RollingUpgradeByZone, nil - case tFinishUpgrade: - return m.FinishUpgrade, nil - case tRestoreEssentialParameters: - return m.RestoreEssentialParameters, nil - case tCreateServiceForMonitor: - return m.CreateServiceForMonitor, nil - case tModifySysTenantReplica: - return m.ModifySysTenantReplica, nil - case tScaleUpOBZones: - return m.modifyOBZonesAndCheckStatus(m.changeZonesWhenScaling, zonestatus.ScaleUp, oceanbaseconst.DefaultStateWaitTimeout), nil - case tExpandPVC: - return m.modifyOBZonesAndCheckStatus(m.changeZonesWhenExpandingPVC, zonestatus.ExpandPVC, oceanbaseconst.DefaultStateWaitTimeout), nil - case tMountBackupVolume: - return m.rollingUpdateZones(m.changeZonesWhenMountingBackupVolume, zonestatus.MountBackupVolume, zonestatus.Running, oceanbaseconst.DefaultStateWaitTimeout), nil - default: - return nil, errors.New("Can not find a function for task") - } + return taskMap.GetTask(name, m) } func (m *OBClusterManager) PrintErrEvent(err error) { diff --git a/internal/resource/obcluster/obcluster_task.go b/internal/resource/obcluster/obcluster_task.go index 87e4529e5..850519314 100644 --- a/internal/resource/obcluster/obcluster_task.go +++ b/internal/resource/obcluster/obcluster_task.go @@ -9,6 +9,7 @@ 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. */ +//go:generate task-register $GOFILE package obcluster @@ -40,15 +41,18 @@ import ( "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/operation" "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/param" obutil "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/util" + "github.com/oceanbase/ob-operator/pkg/task/builder" tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) -func (m *OBClusterManager) WaitOBZoneTopologyMatch() tasktypes.TaskError { +var taskMap = builder.NewTaskHub[*OBClusterManager]() + +func WaitOBZoneTopologyMatch(_ *OBClusterManager) tasktypes.TaskError { // TODO return nil } -func (m *OBClusterManager) WaitOBZoneDeleted() tasktypes.TaskError { +func WaitOBZoneDeleted(m *OBClusterManager) tasktypes.TaskError { waitSuccess := false for i := 1; i < oceanbaseconst.ServerDeleteTimeoutSeconds; i++ { obcluster, err := m.getOBCluster() @@ -83,32 +87,7 @@ func (m *OBClusterManager) WaitOBZoneDeleted() tasktypes.TaskError { return errors.Errorf("OBCluster %s zone still not deleted when timeout", m.OBCluster.Name) } -func (m *OBClusterManager) generateWaitOBZoneStatusFunc(status string, timeoutSeconds int) func() tasktypes.TaskError { - f := func() tasktypes.TaskError { - for i := 1; i < timeoutSeconds; i++ { - obcluster, err := m.getOBCluster() - if err != nil { - return errors.Wrap(err, "get obcluster failed") - } - allMatched := true - for _, obzoneStatus := range obcluster.Status.OBZoneStatus { - if obzoneStatus.Status != status { - m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Zone status still not matched", "zone", obzoneStatus.Zone, "status", status) - allMatched = false - break - } - } - if allMatched { - return nil - } - time.Sleep(time.Second) - } - return errors.New("Zone status still not matched when timeout") - } - return f -} - -func (m *OBClusterManager) ModifyOBZoneReplica() tasktypes.TaskError { +func ModifyOBZoneReplica(m *OBClusterManager) tasktypes.TaskError { return retry.RetryOnConflict(retry.DefaultRetry, func() error { obzoneList, err := m.listOBZones() if err != nil { @@ -131,7 +110,7 @@ func (m *OBClusterManager) ModifyOBZoneReplica() tasktypes.TaskError { }) } -func (m *OBClusterManager) DeleteOBZone() tasktypes.TaskError { +func DeleteOBZone(m *OBClusterManager) tasktypes.TaskError { zonesToDelete, err := m.getZonesToDelete() if err != nil { return errors.Wrap(err, "Failed to get obzones to delete") @@ -146,7 +125,7 @@ func (m *OBClusterManager) DeleteOBZone() tasktypes.TaskError { return nil } -func (m *OBClusterManager) CreateOBZone() tasktypes.TaskError { +func CreateOBZone(m *OBClusterManager) tasktypes.TaskError { m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Create obzones") blockOwnerDeletion := true ownerReferenceList := make([]metav1.OwnerReference, 0) @@ -222,7 +201,7 @@ func (m *OBClusterManager) CreateOBZone() tasktypes.TaskError { return nil } -func (m *OBClusterManager) Bootstrap() tasktypes.TaskError { +func Bootstrap(m *OBClusterManager) tasktypes.TaskError { obzoneList, err := m.listOBZones() if err != nil { m.Logger.Error(err, "list obzones failed") @@ -291,7 +270,7 @@ func (m *OBClusterManager) Bootstrap() tasktypes.TaskError { } // Use Or for compatibility -func (m *OBClusterManager) CreateUsers() tasktypes.TaskError { +func CreateUsers(m *OBClusterManager) tasktypes.TaskError { err := m.createUser(oceanbaseconst.RootUser, m.OBCluster.Spec.UserSecrets.Root, oceanbaseconst.AllPrivilege) if err != nil { return errors.Wrap(err, "Create root user") @@ -311,7 +290,7 @@ func (m *OBClusterManager) CreateUsers() tasktypes.TaskError { return nil } -func (m *OBClusterManager) MaintainOBParameter() tasktypes.TaskError { +func MaintainOBParameter(m *OBClusterManager) tasktypes.TaskError { parameterMap := make(map[string]apitypes.Parameter) for _, parameter := range m.OBCluster.Status.Parameters { m.Logger.V(oceanbaseconst.LogLevelDebug).Info("Build parameter map", "parameter", parameter.Name) @@ -349,79 +328,7 @@ func (m *OBClusterManager) MaintainOBParameter() tasktypes.TaskError { return nil } -func (m *OBClusterManager) CreateOBParameter(parameter *apitypes.Parameter) error { - m.Logger.Info("Create ob parameters") - ownerReferenceList := make([]metav1.OwnerReference, 0) - ownerReference := metav1.OwnerReference{ - APIVersion: m.OBCluster.APIVersion, - Kind: m.OBCluster.Kind, - Name: m.OBCluster.Name, - UID: m.OBCluster.GetUID(), - } - ownerReferenceList = append(ownerReferenceList, ownerReference) - labels := make(map[string]string) - labels[oceanbaseconst.LabelRefUID] = string(m.OBCluster.GetUID()) - labels[oceanbaseconst.LabelRefOBCluster] = m.OBCluster.Name - parameterName := m.generateParameterName(parameter.Name) - obparameter := &v1alpha1.OBParameter{ - ObjectMeta: metav1.ObjectMeta{ - Name: parameterName, - Namespace: m.OBCluster.Namespace, - OwnerReferences: ownerReferenceList, - Labels: labels, - }, - Spec: v1alpha1.OBParameterSpec{ - ClusterName: m.OBCluster.Spec.ClusterName, - ClusterId: m.OBCluster.Spec.ClusterId, - Parameter: parameter, - }, - } - m.Logger.V(oceanbaseconst.LogLevelDebug).Info("Create obparameter", "parameter", parameterName) - err := m.Client.Create(m.Ctx, obparameter) - if err != nil { - m.Logger.Error(err, "create obparameter failed") - return errors.Wrap(err, "create obparameter") - } - return nil -} - -func (m *OBClusterManager) UpdateOBParameter(parameter *apitypes.Parameter) error { - return retry.RetryOnConflict(retry.DefaultRetry, func() error { - obparameter := &v1alpha1.OBParameter{} - err := m.Client.Get(m.Ctx, types.NamespacedName{ - Namespace: m.OBCluster.Namespace, - Name: m.generateParameterName(parameter.Name), - }, obparameter) - if err != nil { - return errors.Wrap(err, "Get obparameter") - } - obparameter.Spec.Parameter.Value = parameter.Value - err = m.Client.Update(m.Ctx, obparameter) - if err != nil { - return errors.Wrap(err, "Update obparameter") - } - return nil - }) -} - -func (m *OBClusterManager) DeleteOBParameter(parameter *apitypes.Parameter) error { - obparameter := &v1alpha1.OBParameter{} - err := m.Client.Get(m.Ctx, types.NamespacedName{ - Namespace: m.OBCluster.Namespace, - Name: m.generateParameterName(parameter.Name), - }, obparameter) - if err != nil { - return errors.Wrap(err, "Get obparameter") - } - obparameter.Spec.Parameter.Value = parameter.Value - err = m.Client.Delete(m.Ctx, obparameter) - if err != nil { - return errors.Wrap(err, "Delete obparameter") - } - return nil -} - -func (m *OBClusterManager) ValidateUpgradeInfo() tasktypes.TaskError { +func ValidateUpgradeInfo(m *OBClusterManager) tasktypes.TaskError { // Get current obcluster version oceanbaseOperationManager, err := m.getOceanbaseOperationManager() if err != nil { @@ -488,11 +395,11 @@ func (m *OBClusterManager) ValidateUpgradeInfo() tasktypes.TaskError { return nil } -func (m *OBClusterManager) UpgradeCheck() tasktypes.TaskError { +func UpgradeCheck(m *OBClusterManager) tasktypes.TaskError { return resourceutils.ExecuteUpgradeScript(m.Client, m.Logger, m.OBCluster, oceanbaseconst.UpgradeCheckerScriptPath, "") } -func (m *OBClusterManager) BackupEssentialParameters() tasktypes.TaskError { +func BackupEssentialParameters(m *OBClusterManager) tasktypes.TaskError { oceanbaseOperationManager, err := m.getOceanbaseOperationManager() if err != nil { return errors.Wrapf(err, "Failed to get operation manager of obcluster %s", m.OBCluster.Name) @@ -528,39 +435,12 @@ func (m *OBClusterManager) BackupEssentialParameters() tasktypes.TaskError { return nil } -func (m *OBClusterManager) BeginUpgrade() tasktypes.TaskError { +func BeginUpgrade(m *OBClusterManager) tasktypes.TaskError { return resourceutils.ExecuteUpgradeScript(m.Client, m.Logger, m.OBCluster, oceanbaseconst.UpgradePreScriptPath, "") } // TODO: add timeout -func (m *OBClusterManager) WaitOBZoneUpgradeFinished(zoneName string) error { - upgradeFinished := false - for { - zones, err := m.listOBZones() - if err != nil { - return errors.Wrap(err, "Failed to get obzone list") - } - for _, zone := range zones.Items { - if zone.Name != zoneName { - continue - } - m.Logger.Info("Check obzone upgrade status", "obzone", zoneName) - if zone.Status.Status == zonestatus.Running && zone.Status.Image == m.OBCluster.Spec.OBServerTemplate.Image { - upgradeFinished = true - break - } - } - if upgradeFinished { - m.Logger.Info("OBZone upgrade finished", "obzone", zoneName) - break - } - time.Sleep(time.Second * oceanbaseconst.CommonCheckInterval) - } - return nil -} - -// TODO: add timeout -func (m *OBClusterManager) RollingUpgradeByZone() tasktypes.TaskError { +func RollingUpgradeByZone(m *OBClusterManager) tasktypes.TaskError { return retry.RetryOnConflict(retry.DefaultRetry, func() error { zones, err := m.listOBZones() if err != nil { @@ -582,11 +462,11 @@ func (m *OBClusterManager) RollingUpgradeByZone() tasktypes.TaskError { }) } -func (m *OBClusterManager) FinishUpgrade() tasktypes.TaskError { +func FinishUpgrade(m *OBClusterManager) tasktypes.TaskError { return resourceutils.ExecuteUpgradeScript(m.Client, m.Logger, m.OBCluster, oceanbaseconst.UpgradePostScriptPath, "") } -func (m *OBClusterManager) ModifySysTenantReplica() tasktypes.TaskError { +func ModifySysTenantReplica(m *OBClusterManager) tasktypes.TaskError { oceanbaseOperationManager, err := m.getOceanbaseOperationManager() if err != nil { return errors.Wrapf(err, "Failed to get operation manager of obcluster %s", m.OBCluster.Name) @@ -704,7 +584,7 @@ func (m *OBClusterManager) ModifySysTenantReplica() tasktypes.TaskError { }) } -func (m *OBClusterManager) CreateServiceForMonitor() tasktypes.TaskError { +func CreateServiceForMonitor(m *OBClusterManager) tasktypes.TaskError { ownerReferenceList := make([]metav1.OwnerReference, 0) ownerReference := metav1.OwnerReference{ APIVersion: m.OBCluster.APIVersion, @@ -742,7 +622,7 @@ func (m *OBClusterManager) CreateServiceForMonitor() tasktypes.TaskError { return nil } -func (m *OBClusterManager) RestoreEssentialParameters() tasktypes.TaskError { +func RestoreEssentialParameters(m *OBClusterManager) tasktypes.TaskError { oceanbaseOperationManager, err := m.getOceanbaseOperationManager() if err != nil { return errors.Wrapf(err, "Failed to get operation manager of obcluster %s", m.OBCluster.Name) @@ -783,7 +663,7 @@ func (m *OBClusterManager) RestoreEssentialParameters() tasktypes.TaskError { return nil } -func (m *OBClusterManager) CheckAndCreateUserSecrets() tasktypes.TaskError { +func CheckAndCreateUserSecrets(m *OBClusterManager) tasktypes.TaskError { secretList := []string{ m.OBCluster.Spec.UserSecrets.Operator, m.OBCluster.Spec.UserSecrets.Monitor, @@ -815,7 +695,7 @@ func (m *OBClusterManager) CheckAndCreateUserSecrets() tasktypes.TaskError { return nil } -func (m *OBClusterManager) CreateServices() tasktypes.TaskError { +func CreateOBClusterService(m *OBClusterManager) tasktypes.TaskError { modeAnnoVal, modeAnnoExist := resourceutils.GetAnnotationField(m.OBCluster, oceanbaseconst.AnnotationsMode) if modeAnnoExist && modeAnnoVal == oceanbaseconst.ModeStandalone { err := m.Client.Create(m.Ctx, &corev1.Service{ @@ -852,7 +732,7 @@ func (m *OBClusterManager) CreateServices() tasktypes.TaskError { return nil } -func (m *OBClusterManager) CheckImageReady() tasktypes.TaskError { +func CheckImageReady(m *OBClusterManager) tasktypes.TaskError { jobName := "image-pull-ready-" + rand.String(8) var ttl int32 = 120 var backoffLimit int32 = 32 @@ -945,7 +825,7 @@ outerLoop: return nil } -func (m *OBClusterManager) CheckClusterMode() tasktypes.TaskError { +func CheckClusterMode(m *OBClusterManager) tasktypes.TaskError { var err error modeAnnoVal, modeAnnoExist := resourceutils.GetAnnotationField(m.OBCluster, oceanbaseconst.AnnotationsMode) if modeAnnoExist && modeAnnoVal == oceanbaseconst.ModeStandalone { @@ -1011,7 +891,7 @@ func (m *OBClusterManager) CheckClusterMode() tasktypes.TaskError { return nil } -func (m *OBClusterManager) CheckMigration() tasktypes.TaskError { +func CheckMigration(m *OBClusterManager) tasktypes.TaskError { m.Logger.Info("Check before migration") manager, err := m.getOceanbaseOperationManager() if err != nil { @@ -1083,3 +963,23 @@ func (m *OBClusterManager) CheckMigration() tasktypes.TaskError { } return nil } + +func ScaleUpOBZones(m *OBClusterManager) tasktypes.TaskError { + return m.modifyOBZonesAndCheckStatus(m.changeZonesWhenScaling, zonestatus.ScaleUp, oceanbaseconst.DefaultStateWaitTimeout)() +} + +func ExpandPVC(m *OBClusterManager) tasktypes.TaskError { + return m.modifyOBZonesAndCheckStatus(m.changeZonesWhenExpandingPVC, zonestatus.ExpandPVC, oceanbaseconst.DefaultStateWaitTimeout)() +} + +func MountBackupVolume(m *OBClusterManager) tasktypes.TaskError { + return m.modifyOBZonesAndCheckStatus(m.changeZonesWhenMountingBackupVolume, zonestatus.MountBackupVolume, oceanbaseconst.DefaultStateWaitTimeout)() +} + +func WaitOBZoneBootstrapReady(m *OBClusterManager) tasktypes.TaskError { + return m.generateWaitOBZoneStatusFunc(zonestatus.BootstrapReady, oceanbaseconst.DefaultStateWaitTimeout)() +} + +func WaitOBZoneRunning(m *OBClusterManager) tasktypes.TaskError { + return m.generateWaitOBZoneStatusFunc(zonestatus.Running, oceanbaseconst.DefaultStateWaitTimeout)() +} diff --git a/internal/resource/obcluster/obcluster_task_gen.go b/internal/resource/obcluster/obcluster_task_gen.go new file mode 100644 index 000000000..41a124383 --- /dev/null +++ b/internal/resource/obcluster/obcluster_task_gen.go @@ -0,0 +1,32 @@ +// Code generated by go generate; DO NOT EDIT. +package obcluster + +func init() { + taskMap.Register(tWaitOBZoneTopologyMatch, WaitOBZoneTopologyMatch) + taskMap.Register(tWaitOBZoneDeleted, WaitOBZoneDeleted) + taskMap.Register(tModifyOBZoneReplica, ModifyOBZoneReplica) + taskMap.Register(tDeleteOBZone, DeleteOBZone) + taskMap.Register(tCreateOBZone, CreateOBZone) + taskMap.Register(tBootstrap, Bootstrap) + taskMap.Register(tCreateUsers, CreateUsers) + taskMap.Register(tMaintainOBParameter, MaintainOBParameter) + taskMap.Register(tValidateUpgradeInfo, ValidateUpgradeInfo) + taskMap.Register(tUpgradeCheck, UpgradeCheck) + taskMap.Register(tBackupEssentialParameters, BackupEssentialParameters) + taskMap.Register(tBeginUpgrade, BeginUpgrade) + taskMap.Register(tRollingUpgradeByZone, RollingUpgradeByZone) + taskMap.Register(tFinishUpgrade, FinishUpgrade) + taskMap.Register(tModifySysTenantReplica, ModifySysTenantReplica) + taskMap.Register(tCreateServiceForMonitor, CreateServiceForMonitor) + taskMap.Register(tRestoreEssentialParameters, RestoreEssentialParameters) + taskMap.Register(tCheckAndCreateUserSecrets, CheckAndCreateUserSecrets) + taskMap.Register(tCreateOBClusterService, CreateOBClusterService) + taskMap.Register(tCheckImageReady, CheckImageReady) + taskMap.Register(tCheckClusterMode, CheckClusterMode) + taskMap.Register(tCheckMigration, CheckMigration) + taskMap.Register(tScaleUpOBZones, ScaleUpOBZones) + taskMap.Register(tExpandPVC, ExpandPVC) + taskMap.Register(tMountBackupVolume, MountBackupVolume) + taskMap.Register(tWaitOBZoneBootstrapReady, WaitOBZoneBootstrapReady) + taskMap.Register(tWaitOBZoneRunning, WaitOBZoneRunning) +} diff --git a/internal/resource/obcluster/utils.go b/internal/resource/obcluster/utils.go index 8565649c8..278a6a0af 100644 --- a/internal/resource/obcluster/utils.go +++ b/internal/resource/obcluster/utils.go @@ -18,12 +18,15 @@ import ( "time" "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/retry" "sigs.k8s.io/controller-runtime/pkg/client" + apitypes "github.com/oceanbase/ob-operator/api/types" "github.com/oceanbase/ob-operator/api/v1alpha1" oceanbaseconst "github.com/oceanbase/ob-operator/internal/const/oceanbase" + zonestatus "github.com/oceanbase/ob-operator/internal/const/status/obzone" resourceutils "github.com/oceanbase/ob-operator/internal/resource/utils" "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/operation" tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" @@ -276,3 +279,127 @@ func (m *OBClusterManager) rollingUpdateZones(changer obzoneChanger, workingStat return nil } } + +func (m *OBClusterManager) generateWaitOBZoneStatusFunc(status string, timeoutSeconds int) tasktypes.TaskFunc { + f := func() tasktypes.TaskError { + for i := 1; i < timeoutSeconds; i++ { + obcluster, err := m.getOBCluster() + if err != nil { + return errors.Wrap(err, "get obcluster failed") + } + allMatched := true + for _, obzoneStatus := range obcluster.Status.OBZoneStatus { + if obzoneStatus.Status != status { + m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Zone status still not matched", "zone", obzoneStatus.Zone, "status", status) + allMatched = false + break + } + } + if allMatched { + return nil + } + time.Sleep(time.Second) + } + return errors.New("Zone status still not matched when timeout") + } + return f +} + +func (m *OBClusterManager) CreateOBParameter(parameter *apitypes.Parameter) error { + m.Logger.Info("Create ob parameters") + ownerReferenceList := make([]metav1.OwnerReference, 0) + ownerReference := metav1.OwnerReference{ + APIVersion: m.OBCluster.APIVersion, + Kind: m.OBCluster.Kind, + Name: m.OBCluster.Name, + UID: m.OBCluster.GetUID(), + } + ownerReferenceList = append(ownerReferenceList, ownerReference) + labels := make(map[string]string) + labels[oceanbaseconst.LabelRefUID] = string(m.OBCluster.GetUID()) + labels[oceanbaseconst.LabelRefOBCluster] = m.OBCluster.Name + parameterName := m.generateParameterName(parameter.Name) + obparameter := &v1alpha1.OBParameter{ + ObjectMeta: metav1.ObjectMeta{ + Name: parameterName, + Namespace: m.OBCluster.Namespace, + OwnerReferences: ownerReferenceList, + Labels: labels, + }, + Spec: v1alpha1.OBParameterSpec{ + ClusterName: m.OBCluster.Spec.ClusterName, + ClusterId: m.OBCluster.Spec.ClusterId, + Parameter: parameter, + }, + } + m.Logger.V(oceanbaseconst.LogLevelDebug).Info("Create obparameter", "parameter", parameterName) + err := m.Client.Create(m.Ctx, obparameter) + if err != nil { + m.Logger.Error(err, "create obparameter failed") + return errors.Wrap(err, "create obparameter") + } + return nil +} + +func (m *OBClusterManager) UpdateOBParameter(parameter *apitypes.Parameter) error { + return retry.RetryOnConflict(retry.DefaultRetry, func() error { + obparameter := &v1alpha1.OBParameter{} + err := m.Client.Get(m.Ctx, types.NamespacedName{ + Namespace: m.OBCluster.Namespace, + Name: m.generateParameterName(parameter.Name), + }, obparameter) + if err != nil { + return errors.Wrap(err, "Get obparameter") + } + obparameter.Spec.Parameter.Value = parameter.Value + err = m.Client.Update(m.Ctx, obparameter) + if err != nil { + return errors.Wrap(err, "Update obparameter") + } + return nil + }) +} + +func (m *OBClusterManager) DeleteOBParameter(parameter *apitypes.Parameter) error { + obparameter := &v1alpha1.OBParameter{} + err := m.Client.Get(m.Ctx, types.NamespacedName{ + Namespace: m.OBCluster.Namespace, + Name: m.generateParameterName(parameter.Name), + }, obparameter) + if err != nil { + return errors.Wrap(err, "Get obparameter") + } + obparameter.Spec.Parameter.Value = parameter.Value + err = m.Client.Delete(m.Ctx, obparameter) + if err != nil { + return errors.Wrap(err, "Delete obparameter") + } + return nil +} + +// TODO: add timeout +func (m *OBClusterManager) WaitOBZoneUpgradeFinished(zoneName string) error { + upgradeFinished := false + for { + zones, err := m.listOBZones() + if err != nil { + return errors.Wrap(err, "Failed to get obzone list") + } + for _, zone := range zones.Items { + if zone.Name != zoneName { + continue + } + m.Logger.Info("Check obzone upgrade status", "obzone", zoneName) + if zone.Status.Status == zonestatus.Running && zone.Status.Image == m.OBCluster.Spec.OBServerTemplate.Image { + upgradeFinished = true + break + } + } + if upgradeFinished { + m.Logger.Info("OBZone upgrade finished", "obzone", zoneName) + break + } + time.Sleep(time.Second * oceanbaseconst.CommonCheckInterval) + } + return nil +} diff --git a/internal/resource/obparameter/obparameter_flow.go b/internal/resource/obparameter/obparameter_flow.go index c31e13091..4ca7956f5 100644 --- a/internal/resource/obparameter/obparameter_flow.go +++ b/internal/resource/obparameter/obparameter_flow.go @@ -17,7 +17,7 @@ import ( tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) -func SetOBParameter() *tasktypes.TaskFlow { +func genSetOBParameterFlow(_ *OBParameterManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fSetOBParameter, diff --git a/internal/resource/obparameter/obparameter_manager.go b/internal/resource/obparameter/obparameter_manager.go index bb408bbb2..42a05ac11 100644 --- a/internal/resource/obparameter/obparameter_manager.go +++ b/internal/resource/obparameter/obparameter_manager.go @@ -20,8 +20,6 @@ import ( "github.com/go-logr/logr" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/util/retry" "sigs.k8s.io/controller-runtime/pkg/client" apitypes "github.com/oceanbase/ob-operator/api/types" @@ -32,15 +30,14 @@ import ( resourceutils "github.com/oceanbase/ob-operator/internal/resource/utils" "github.com/oceanbase/ob-operator/internal/telemetry" opresource "github.com/oceanbase/ob-operator/pkg/coordinator" - "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/operation" - "github.com/oceanbase/ob-operator/pkg/task" taskstatus "github.com/oceanbase/ob-operator/pkg/task/const/status" "github.com/oceanbase/ob-operator/pkg/task/const/strategy" tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) +var _ opresource.ResourceManager = &OBParameterManager{} + type OBParameterManager struct { - opresource.ResourceManager Ctx context.Context OBParameter *v1alpha1.OBParameter Client client.Client @@ -79,53 +76,34 @@ func (m *OBParameterManager) GetTaskFlow() (*tasktypes.TaskFlow, error) { // return task flow depends on status var taskFlow *tasktypes.TaskFlow - var err error m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Create task flow according to obparameter status") switch m.OBParameter.Status.Status { // only need to handle parameter not match case parameterstatus.NotMatch: - taskFlow, err = task.GetRegistry().Get(fSetOBParameter) + taskFlow = genSetOBParameterFlow(m) default: m.Logger.V(oceanbaseconst.LogLevelTrace).Info("No need to run anything for obparameter") return nil, nil } - if err != nil { - return nil, err - } - if taskFlow.OperationContext.OnFailure.Strategy == "" { taskFlow.OperationContext.OnFailure.Strategy = strategy.StartOver if taskFlow.OperationContext.OnFailure.NextTryStatus == "" { taskFlow.OperationContext.OnFailure.NextTryStatus = parameterstatus.Matched } } - return taskFlow, err + return taskFlow, nil } func (m *OBParameterManager) IsDeleting() bool { - return !m.OBParameter.ObjectMeta.DeletionTimestamp.IsZero() + ignoreDel, ok := resourceutils.GetAnnotationField(m.OBParameter, oceanbaseconst.AnnotationsIgnoreDeletion) + return !m.OBParameter.ObjectMeta.DeletionTimestamp.IsZero() && (!ok || ignoreDel != "true") } func (m *OBParameterManager) CheckAndUpdateFinalizers() error { return nil } -func (m *OBParameterManager) retryUpdateStatus() error { - return retry.RetryOnConflict(retry.DefaultRetry, func() error { - parameter := &v1alpha1.OBParameter{} - err := m.Client.Get(m.Ctx, types.NamespacedName{ - Namespace: m.OBParameter.GetNamespace(), - Name: m.OBParameter.GetName(), - }, parameter) - if err != nil { - return client.IgnoreNotFound(err) - } - parameter.Status = *m.OBParameter.Status.DeepCopy() - return m.Client.Status().Update(m.Ctx, parameter) - }) -} - func (m *OBParameterManager) UpdateStatus() error { obcluster, err := m.getOBCluster() if err != nil { @@ -206,58 +184,13 @@ func (m *OBParameterManager) HandleFailure() { } func (m *OBParameterManager) GetTaskFunc(name tasktypes.TaskName) (tasktypes.TaskFunc, error) { - switch name { - case tSetOBParameter: - return m.SetOBParameter, nil - default: - return nil, errors.New("Can not find a function for task") - } + return taskMap.GetTask(name, m) } func (m *OBParameterManager) PrintErrEvent(err error) { m.Recorder.Event(m.OBParameter, corev1.EventTypeWarning, "Task failed", err.Error()) } -func (m *OBParameterManager) SetOBParameter() tasktypes.TaskError { - operationManager, err := m.getOceanbaseOperationManager() - if err != nil { - m.Logger.Error(err, "Get operation manager failed") - return errors.Wrapf(err, "Get operation manager") - } - err = operationManager.SetParameter(m.OBParameter.Spec.Parameter.Name, m.OBParameter.Spec.Parameter.Value, nil) - if err != nil { - m.Logger.Error(err, "Set parameter failed") - return errors.Wrapf(err, "Set parameter") - } - return nil -} - -func (m *OBParameterManager) generateNamespacedName(name string) types.NamespacedName { - var namespacedName types.NamespacedName - namespacedName.Namespace = m.OBParameter.Namespace - namespacedName.Name = name - return namespacedName -} - -func (m *OBParameterManager) getOBCluster() (*v1alpha1.OBCluster, error) { - // this label always exists - clusterName, _ := m.OBParameter.Labels[oceanbaseconst.LabelRefOBCluster] - obcluster := &v1alpha1.OBCluster{} - err := m.Client.Get(m.Ctx, m.generateNamespacedName(clusterName), obcluster) - if err != nil { - return nil, errors.Wrap(err, "get obcluster") - } - return obcluster, nil -} - -func (m *OBParameterManager) getOceanbaseOperationManager() (*operation.OceanbaseOperationManager, error) { - obcluster, err := m.getOBCluster() - if err != nil { - return nil, errors.Wrap(err, "Get obcluster from K8s") - } - return resourceutils.GetSysOperationClient(m.Client, m.Logger, obcluster) -} - func (m *OBParameterManager) ArchiveResource() { m.Logger.Info("Archive obparameter", "obparameter", m.OBParameter.Name) m.Recorder.Event(m.OBParameter, "Archive", "", "archive obparameter") diff --git a/internal/resource/obparameter/obparameter_task.go b/internal/resource/obparameter/obparameter_task.go new file mode 100644 index 000000000..7940c794c --- /dev/null +++ b/internal/resource/obparameter/obparameter_task.go @@ -0,0 +1,38 @@ +/* +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 obparameter + +import ( + "github.com/pkg/errors" + + "github.com/oceanbase/ob-operator/pkg/task/builder" + tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" +) + +//go:generate task-register $GOFILE + +var taskMap = builder.NewTaskHub[*OBParameterManager]() + +func SetOBParameter(m *OBParameterManager) tasktypes.TaskError { + operationManager, err := m.getOceanbaseOperationManager() + if err != nil { + m.Logger.Error(err, "Get operation manager failed") + return errors.Wrapf(err, "Get operation manager") + } + err = operationManager.SetParameter(m.OBParameter.Spec.Parameter.Name, m.OBParameter.Spec.Parameter.Value, nil) + if err != nil { + m.Logger.Error(err, "Set parameter failed") + return errors.Wrapf(err, "Set parameter") + } + return nil +} diff --git a/internal/resource/obparameter/obparameter_task_gen.go b/internal/resource/obparameter/obparameter_task_gen.go new file mode 100644 index 000000000..1591a7ce0 --- /dev/null +++ b/internal/resource/obparameter/obparameter_task_gen.go @@ -0,0 +1,6 @@ +// Code generated by go generate; DO NOT EDIT. +package obparameter + +func init() { + taskMap.Register(tSetOBParameter, SetOBParameter) +} diff --git a/internal/resource/obparameter/utils.go b/internal/resource/obparameter/utils.go new file mode 100644 index 000000000..608864ad9 --- /dev/null +++ b/internal/resource/obparameter/utils.go @@ -0,0 +1,66 @@ +/* +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 obparameter + +import ( + "github.com/pkg/errors" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/util/retry" + "sigs.k8s.io/controller-runtime/pkg/client" + + v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" + oceanbaseconst "github.com/oceanbase/ob-operator/internal/const/oceanbase" + resourceutils "github.com/oceanbase/ob-operator/internal/resource/utils" + "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/operation" +) + +func (m *OBParameterManager) generateNamespacedName(name string) types.NamespacedName { + var namespacedName types.NamespacedName + namespacedName.Namespace = m.OBParameter.Namespace + namespacedName.Name = name + return namespacedName +} + +func (m *OBParameterManager) getOBCluster() (*v1alpha1.OBCluster, error) { + // this label always exists + clusterName, _ := m.OBParameter.Labels[oceanbaseconst.LabelRefOBCluster] + obcluster := &v1alpha1.OBCluster{} + err := m.Client.Get(m.Ctx, m.generateNamespacedName(clusterName), obcluster) + if err != nil { + return nil, errors.Wrap(err, "get obcluster") + } + return obcluster, nil +} + +func (m *OBParameterManager) getOceanbaseOperationManager() (*operation.OceanbaseOperationManager, error) { + obcluster, err := m.getOBCluster() + if err != nil { + return nil, errors.Wrap(err, "Get obcluster from K8s") + } + return resourceutils.GetSysOperationClient(m.Client, m.Logger, obcluster) +} + +func (m *OBParameterManager) retryUpdateStatus() error { + return retry.RetryOnConflict(retry.DefaultRetry, func() error { + parameter := &v1alpha1.OBParameter{} + err := m.Client.Get(m.Ctx, types.NamespacedName{ + Namespace: m.OBParameter.GetNamespace(), + Name: m.OBParameter.GetName(), + }, parameter) + if err != nil { + return client.IgnoreNotFound(err) + } + parameter.Status = *m.OBParameter.Status.DeepCopy() + return m.Client.Status().Update(m.Ctx, parameter) + }) +} diff --git a/internal/resource/observer/init.go b/internal/resource/observer/init.go deleted file mode 100644 index 69e1a3414..000000000 --- a/internal/resource/observer/init.go +++ /dev/null @@ -1,32 +0,0 @@ -/* -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 observer - -import ( - "github.com/oceanbase/ob-operator/pkg/task" -) - -func init() { - // observer - task.GetRegistry().Register(fCreateOBServer, CreateOBServer) - task.GetRegistry().Register(fPrepareOBServerForBootstrap, PrepareOBServerForBootstrap) - task.GetRegistry().Register(fMaintainOBServerAfterBootstrap, MaintainOBServerAfterBootstrap) - task.GetRegistry().Register(fDeleteOBServerFinalizer, DeleteOBServerFinalizer) - task.GetRegistry().Register(fUpgradeOBServer, UpgradeOBServer) - task.GetRegistry().Register(fRecoverOBServer, RecoverOBServer) - task.GetRegistry().Register(fAnnotateOBServerPod, AnnotateOBServerPod) - task.GetRegistry().Register(fAddServerInOB, AddServerInOB) - task.GetRegistry().Register(fScaleUpOBServer, ScaleUpOBServer) - task.GetRegistry().Register(fExpandPVC, ResizePVC) - task.GetRegistry().Register(fMountBackupVolume, MountBackupVolume) -} diff --git a/internal/resource/observer/observer_flow.go b/internal/resource/observer/observer_flow.go index 0ee2b35e0..89552041e 100644 --- a/internal/resource/observer/observer_flow.go +++ b/internal/resource/observer/observer_flow.go @@ -18,7 +18,7 @@ import ( tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) -func PrepareOBServerForBootstrap() *tasktypes.TaskFlow { +func genPrepareOBServerForBootstrapFlow(_ *OBServerManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fPrepareOBServerForBootstrap, @@ -28,7 +28,7 @@ func PrepareOBServerForBootstrap() *tasktypes.TaskFlow { } } -func MaintainOBServerAfterBootstrap() *tasktypes.TaskFlow { +func genMaintainOBServerAfterBootstrapFlow(_ *OBServerManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fMaintainOBServerAfterBootstrap, @@ -38,7 +38,7 @@ func MaintainOBServerAfterBootstrap() *tasktypes.TaskFlow { } } -func CreateOBServer() *tasktypes.TaskFlow { +func genCreateOBServerFlow(_ *OBServerManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fCreateOBServer, @@ -51,7 +51,7 @@ func CreateOBServer() *tasktypes.TaskFlow { } } -func DeleteOBServerFinalizer() *tasktypes.TaskFlow { +func genDeleteOBServerFinalizerFlow(_ *OBServerManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fDeleteOBServerFinalizer, @@ -61,7 +61,7 @@ func DeleteOBServerFinalizer() *tasktypes.TaskFlow { } } -func UpgradeOBServer() *tasktypes.TaskFlow { +func genUpgradeOBServerFlow(_ *OBServerManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fUpgradeOBServer, @@ -71,7 +71,7 @@ func UpgradeOBServer() *tasktypes.TaskFlow { } } -func RecoverOBServer() *tasktypes.TaskFlow { +func genRecoverOBServerFlow(_ *OBServerManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fRecoverOBServer, @@ -84,7 +84,7 @@ func RecoverOBServer() *tasktypes.TaskFlow { } } -func AddServerInOB() *tasktypes.TaskFlow { +func genAddServerInOBFlow(_ *OBServerManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fAddServerInOB, @@ -94,7 +94,7 @@ func AddServerInOB() *tasktypes.TaskFlow { } } -func AnnotateOBServerPod() *tasktypes.TaskFlow { +func genAnnotateOBServerPodFlow(_ *OBServerManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fAnnotateOBServerPod, @@ -104,7 +104,7 @@ func AnnotateOBServerPod() *tasktypes.TaskFlow { } } -func ScaleUpOBServer() *tasktypes.TaskFlow { +func genScaleUpOBServerFlow(_ *OBServerManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fScaleUpOBServer, @@ -114,7 +114,7 @@ func ScaleUpOBServer() *tasktypes.TaskFlow { } } -func ResizePVC() *tasktypes.TaskFlow { +func genExpandPVCFlow(_ *OBServerManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fExpandPVC, @@ -127,7 +127,7 @@ func ResizePVC() *tasktypes.TaskFlow { } } -func MountBackupVolume() *tasktypes.TaskFlow { +func genMountBackupVolumeFlow(_ *OBServerManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fMountBackupVolume, diff --git a/internal/resource/observer/observer_manager.go b/internal/resource/observer/observer_manager.go index 623788860..b4f0c41a4 100644 --- a/internal/resource/observer/observer_manager.go +++ b/internal/resource/observer/observer_manager.go @@ -29,14 +29,14 @@ import ( resourceutils "github.com/oceanbase/ob-operator/internal/resource/utils" "github.com/oceanbase/ob-operator/internal/telemetry" opresource "github.com/oceanbase/ob-operator/pkg/coordinator" - "github.com/oceanbase/ob-operator/pkg/task" taskstatus "github.com/oceanbase/ob-operator/pkg/task/const/status" "github.com/oceanbase/ob-operator/pkg/task/const/strategy" tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) +var _ opresource.ResourceManager = &OBServerManager{} + type OBServerManager struct { - opresource.ResourceManager Ctx context.Context OBServer *v1alpha1.OBServer Client client.Client @@ -45,46 +45,7 @@ type OBServerManager struct { } func (m *OBServerManager) GetTaskFunc(name tasktypes.TaskName) (tasktypes.TaskFunc, error) { - switch name { - case tCreateOBServerSvc: - return m.CreateOBServerSvc, nil - case tCreateOBPVC: - return m.CreateOBPVC, nil - case tCreateOBPod: - return m.CreateOBPod, nil - case tWaitOBServerReady: - return m.WaitOBServerReady, nil - case tWaitOBClusterBootstrapped: - return m.WaitOBClusterBootstrapped, nil - case tAddServer: - return m.AddServer, nil - case tDeleteOBServerInCluster: - return m.DeleteOBServerInCluster, nil - case tWaitOBServerDeletedInCluster: - return m.WaitOBServerDeletedInCluster, nil - case tWaitOBServerPodReady: - return m.WaitOBServerPodReady, nil - case tWaitOBServerActiveInCluster: - return m.WaitOBServerActiveInCluster, nil - case tUpgradeOBServerImage: - return m.UpgradeOBServerImage, nil - case tAnnotateOBServerPod: - return m.AnnotateOBServerPod, nil - case tDeletePod: - return m.DeletePod, nil - case tWaitForPodDeleted: - return m.WaitForPodDeleted, nil - case tExpandPVC: - return m.ResizePVC, nil - case tWaitForPVCResized: - return m.WaitForPVCResized, nil - case tMountBackupVolume: - return m.MountBackupVolume, nil - case tWaitForBackupVolumeMounted: - return m.WaitForBackupVolumeMounted, nil - default: - return nil, errors.Errorf("Can not find an function for task %s", name) - } + return taskMap.GetTask(name, m) } func (m *OBServerManager) IsNewResource() bool { @@ -198,7 +159,8 @@ func (m *OBServerManager) UpdateStatus() error { } func (m *OBServerManager) IsDeleting() bool { - return !m.OBServer.ObjectMeta.DeletionTimestamp.IsZero() + ignoreDel, ok := resourceutils.GetAnnotationField(m.OBServer, oceanbaseconst.AnnotationsIgnoreDeletion) + return !m.OBServer.ObjectMeta.DeletionTimestamp.IsZero() && (!ok || ignoreDel != "true") } func (m *OBServerManager) CheckAndUpdateFinalizers() error { @@ -238,9 +200,8 @@ func (m *OBServerManager) GetTaskFlow() (*tasktypes.TaskFlow, error) { } // newly created observer var taskFlow *tasktypes.TaskFlow - var err error var obcluster *v1alpha1.OBCluster - + var err error m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Create task flow according to observer status") switch m.OBServer.Status.Status { case serverstatus.New: @@ -251,43 +212,36 @@ func (m *OBServerManager) GetTaskFlow() (*tasktypes.TaskFlow, error) { if obcluster.Status.Status == clusterstatus.New { // created when create obcluster m.Logger.Info("Create observer when create obcluster") - taskFlow, err = task.GetRegistry().Get(fPrepareOBServerForBootstrap) + taskFlow = genPrepareOBServerForBootstrapFlow(m) } else { // created normally m.Logger.Info("Create observer when obcluster already exists") - taskFlow, err = task.GetRegistry().Get(fCreateOBServer) - } - if err != nil { - return nil, errors.Wrap(err, "Get create observer task flow") + taskFlow = genCreateOBServerFlow(m) } case serverstatus.BootstrapReady: - taskFlow, err = task.GetRegistry().Get(fMaintainOBServerAfterBootstrap) + taskFlow = genMaintainOBServerAfterBootstrapFlow(m) case serverstatus.Deleting: - taskFlow, err = task.GetRegistry().Get(fDeleteOBServerFinalizer) + taskFlow = genDeleteOBServerFinalizerFlow(m) case serverstatus.Upgrade: - taskFlow, err = task.GetRegistry().Get(fUpgradeOBServer) + taskFlow = genUpgradeOBServerFlow(m) case serverstatus.Recover: - taskFlow, err = task.GetRegistry().Get(fRecoverOBServer) + taskFlow = genRecoverOBServerFlow(m) case serverstatus.Annotate: - taskFlow, err = task.GetRegistry().Get(fAnnotateOBServerPod) + taskFlow = genAnnotateOBServerPodFlow(m) case serverstatus.AddServer: - taskFlow, err = task.GetRegistry().Get(fAddServerInOB) + taskFlow = genAddServerInOBFlow(m) case serverstatus.ScaleUp: - taskFlow, err = task.GetRegistry().Get(fScaleUpOBServer) + taskFlow = genScaleUpOBServerFlow(m) case serverstatus.ExpandPVC: - taskFlow, err = task.GetRegistry().Get(fExpandPVC) + taskFlow = genExpandPVCFlow(m) case serverstatus.MountBackupVolume: m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Get task flow when observer need to mount backup volume") - taskFlow, err = task.GetRegistry().Get(fMountBackupVolume) + taskFlow = genMountBackupVolumeFlow(m) default: m.Logger.V(oceanbaseconst.LogLevelTrace).Info("No need to run anything for observer") return nil, nil } - if err != nil { - return nil, err - } - if taskFlow.OperationContext.OnFailure.Strategy == "" { taskFlow.OperationContext.OnFailure.Strategy = strategy.StartOver if taskFlow.OperationContext.OnFailure.NextTryStatus == "" { diff --git a/internal/resource/observer/observer_task.go b/internal/resource/observer/observer_task.go index da374dced..d953f76b1 100644 --- a/internal/resource/observer/observer_task.go +++ b/internal/resource/observer/observer_task.go @@ -14,7 +14,6 @@ package observer import ( "fmt" - "strings" "time" "github.com/pkg/errors" @@ -25,20 +24,21 @@ import ( "k8s.io/apimachinery/pkg/util/intstr" apitypes "github.com/oceanbase/ob-operator/api/types" - "github.com/oceanbase/ob-operator/api/v1alpha1" - obagentconst "github.com/oceanbase/ob-operator/internal/const/obagent" oceanbaseconst "github.com/oceanbase/ob-operator/internal/const/oceanbase" podconst "github.com/oceanbase/ob-operator/internal/const/pod" - secretconst "github.com/oceanbase/ob-operator/internal/const/secret" clusterstatus "github.com/oceanbase/ob-operator/internal/const/status/obcluster" resourceutils "github.com/oceanbase/ob-operator/internal/resource/utils" observerstatus "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/const/status/server" "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/model" - "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/operation" + "github.com/oceanbase/ob-operator/pkg/task/builder" tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) -func (m *OBServerManager) WaitOBServerReady() tasktypes.TaskError { +//go:generate task-register $GOFILE + +var taskMap = builder.NewTaskHub[*OBServerManager]() + +func WaitOBServerReady(m *OBServerManager) tasktypes.TaskError { for i := 0; i < podconst.ReadyTimeoutSeconds; i++ { observer, err := m.getOBServer() if err != nil { @@ -53,15 +53,7 @@ func (m *OBServerManager) WaitOBServerReady() tasktypes.TaskError { return errors.New("Timeout to wait pod ready") } -func (m *OBServerManager) getOceanbaseOperationManager() (*operation.OceanbaseOperationManager, error) { - obcluster, err := m.getOBCluster() - if err != nil { - return nil, errors.Wrap(err, "Get obcluster from K8s") - } - return resourceutils.GetSysOperationClient(m.Client, m.Logger, obcluster) -} - -func (m *OBServerManager) AddServer() tasktypes.TaskError { +func AddServer(m *OBServerManager) tasktypes.TaskError { mode, modeAnnoExist := resourceutils.GetAnnotationField(m.OBServer, oceanbaseconst.AnnotationsMode) if modeAnnoExist && mode == oceanbaseconst.ModeStandalone { m.Recorder.Event(m.OBServer, "SkipAddServer", "AddServer", "Skip add server in standalone mode") @@ -89,7 +81,7 @@ func (m *OBServerManager) AddServer() tasktypes.TaskError { return oceanbaseOperationManager.AddServer(serverInfo) } -func (m *OBServerManager) WaitOBClusterBootstrapped() tasktypes.TaskError { +func WaitOBClusterBootstrapped(m *OBServerManager) tasktypes.TaskError { for i := 0; i < oceanbaseconst.BootstrapTimeoutSeconds; i++ { obcluster, err := m.getOBCluster() if err != nil { @@ -104,20 +96,7 @@ func (m *OBServerManager) WaitOBClusterBootstrapped() tasktypes.TaskError { return errors.New("Timeout to wait obcluster bootstrapped") } -func (m *OBServerManager) generateStaticIpAnnotation() map[string]string { - annotations := make(map[string]string) - switch m.OBServer.Status.CNI { - case oceanbaseconst.CNICalico: - if m.OBServer.Status.PodIp != "" { - annotations[oceanbaseconst.AnnotationCalicoIpAddrs] = fmt.Sprintf("[\"%s\"]", m.OBServer.Status.PodIp) - } - default: - m.Logger.Info("static ip not supported, set empty annotation") - } - return annotations -} - -func (m *OBServerManager) CreateOBPod() tasktypes.TaskError { +func CreateOBPod(m *OBServerManager) tasktypes.TaskError { m.Logger.V(oceanbaseconst.LogLevelDebug).Info("create observer pod") obcluster, err := m.getOBCluster() if err != nil { @@ -153,19 +132,7 @@ func (m *OBServerManager) CreateOBPod() tasktypes.TaskError { return nil } -func (m *OBServerManager) generatePVCSpec(storageSpec *apitypes.StorageSpec) corev1.PersistentVolumeClaimSpec { - pvcSpec := &corev1.PersistentVolumeClaimSpec{} - requestsResources := corev1.ResourceList{} - requestsResources["storage"] = storageSpec.Size - storageClassName := storageSpec.StorageClass - pvcSpec.StorageClassName = &(storageClassName) - accessModes := []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce} - pvcSpec.AccessModes = accessModes - pvcSpec.Resources.Requests = requestsResources - return *pvcSpec -} - -func (m *OBServerManager) CreateOBPVC() tasktypes.TaskError { +func CreateOBPVC(m *OBServerManager) tasktypes.TaskError { ownerReferenceList := make([]metav1.OwnerReference, 0) sepVolumeAnnoVal, sepVolumeAnnoExist := resourceutils.GetAnnotationField(m.OBServer, oceanbaseconst.AnnotationsIndependentPVCLifecycle) if !sepVolumeAnnoExist || sepVolumeAnnoVal != "true" { @@ -250,346 +217,7 @@ func (m *OBServerManager) CreateOBPVC() tasktypes.TaskError { return nil } -func (m *OBServerManager) createOBPodSpec(obcluster *v1alpha1.OBCluster) corev1.PodSpec { - containers := make([]corev1.Container, 0) - observerContainer := m.createOBServerContainer(obcluster) - containers = append(containers, observerContainer) - - // TODO, add monitor container - volumes := make([]corev1.Volume, 0) - - singlePvcAnnoVal, singlePvcExist := resourceutils.GetAnnotationField(m.OBServer, oceanbaseconst.AnnotationsSinglePVC) - if singlePvcExist && singlePvcAnnoVal == "true" { - singleVolume := corev1.Volume{} - singleVolumeSource := &corev1.PersistentVolumeClaimVolumeSource{} - singleVolume.Name = m.OBServer.Name - singleVolumeSource.ClaimName = m.OBServer.Name - singleVolume.VolumeSource.PersistentVolumeClaim = singleVolumeSource - - volumes = append(volumes, singleVolume) - } else { - volumeDataFile := corev1.Volume{} - volumeDataFileSource := &corev1.PersistentVolumeClaimVolumeSource{} - volumeDataFile.Name = fmt.Sprintf("%s-%s", m.OBServer.Name, oceanbaseconst.DataVolumeSuffix) - volumeDataFileSource.ClaimName = fmt.Sprintf("%s-%s", m.OBServer.Name, oceanbaseconst.DataVolumeSuffix) - volumeDataFile.VolumeSource.PersistentVolumeClaim = volumeDataFileSource - - volumeDataLog := corev1.Volume{} - volumeDataLog.Name = fmt.Sprintf("%s-%s", m.OBServer.Name, oceanbaseconst.ClogVolumeSuffix) - volumeDataLogSource := &corev1.PersistentVolumeClaimVolumeSource{} - volumeDataLogSource.ClaimName = fmt.Sprintf("%s-%s", m.OBServer.Name, oceanbaseconst.ClogVolumeSuffix) - volumeDataLog.VolumeSource.PersistentVolumeClaim = volumeDataLogSource - - volumeLog := corev1.Volume{} - volumeLog.Name = fmt.Sprintf("%s-%s", m.OBServer.Name, oceanbaseconst.LogVolumeSuffix) - volumeLogSource := &corev1.PersistentVolumeClaimVolumeSource{} - volumeLogSource.ClaimName = fmt.Sprintf("%s-%s", m.OBServer.Name, oceanbaseconst.LogVolumeSuffix) - volumeLog.VolumeSource.PersistentVolumeClaim = volumeLogSource - - volumes = append(volumes, volumeDataFile, volumeDataLog, volumeLog) - } - - if m.OBServer.Spec.BackupVolume != nil { - volumes = append(volumes, *m.OBServer.Spec.BackupVolume.Volume) - } - - if m.OBServer.Spec.MonitorTemplate != nil { - monitorContainer := m.createMonitorContainer(obcluster) - containers = append(containers, monitorContainer) - } - - podSpec := corev1.PodSpec{ - Volumes: volumes, - Containers: containers, - NodeSelector: m.OBServer.Spec.NodeSelector, - Affinity: m.OBServer.Spec.Affinity, - Tolerations: m.OBServer.Spec.Tolerations, - ServiceAccountName: m.OBServer.Spec.ServiceAccount, - } - return podSpec -} - -func (m *OBServerManager) createMonitorContainer(obcluster *v1alpha1.OBCluster) corev1.Container { - // port info - ports := make([]corev1.ContainerPort, 0) - httpPort := corev1.ContainerPort{} - httpPort.Name = obagentconst.HttpPortName - httpPort.ContainerPort = obagentconst.HttpPort - httpPort.Protocol = corev1.ProtocolTCP - pprofPort := corev1.ContainerPort{} - pprofPort.Name = obagentconst.PprofPortName - pprofPort.ContainerPort = obagentconst.PprofPort - pprofPort.Protocol = corev1.ProtocolTCP - ports = append(ports, httpPort) - ports = append(ports, pprofPort) - - // resource info - monagentResource := corev1.ResourceList{} - monagentResource["memory"] = m.OBServer.Spec.MonitorTemplate.Resource.Memory - if !m.OBServer.Spec.MonitorTemplate.Resource.Cpu.IsZero() { - monagentResource["cpu"] = m.OBServer.Spec.MonitorTemplate.Resource.Cpu - } - resources := corev1.ResourceRequirements{ - Limits: monagentResource, - } - - readinessProbeHTTP := corev1.HTTPGetAction{} - readinessProbeHTTP.Port = intstr.FromInt(obagentconst.HttpPort) - readinessProbeHTTP.Path = obagentconst.StatUrl - readinessProbe := corev1.Probe{} - readinessProbe.ProbeHandler.HTTPGet = &readinessProbeHTTP - readinessProbe.PeriodSeconds = obagentconst.ProbeCheckPeriodSeconds - readinessProbe.InitialDelaySeconds = obagentconst.ProbeCheckDelaySeconds - - env := make([]corev1.EnvVar, 0) - envOBModuleStatus := corev1.EnvVar{ - Name: obagentconst.EnvOBMonitorStatus, - Value: obagentconst.ActiveStatus, - } - envClusterName := corev1.EnvVar{ - Name: obagentconst.EnvClusterName, - Value: m.OBServer.Spec.ClusterName, - } - envClusterId := corev1.EnvVar{ - Name: obagentconst.EnvClusterId, - Value: fmt.Sprintf("%d", m.OBServer.Spec.ClusterId), - } - envZoneName := corev1.EnvVar{ - Name: obagentconst.EnvZoneName, - Value: m.OBServer.Spec.Zone, - } - envMonitorUser := corev1.EnvVar{ - Name: obagentconst.EnvMonitorUser, - Value: obagentconst.MonitorUser, - } - envMonitorPassword := corev1.EnvVar{ - Name: obagentconst.EnvMonitorPASSWORD, - ValueFrom: &corev1.EnvVarSource{ - SecretKeyRef: &corev1.SecretKeySelector{ - LocalObjectReference: corev1.LocalObjectReference{ - Name: obcluster.Spec.UserSecrets.Monitor, - }, - Key: secretconst.PasswordKeyName, - }, - }, - } - env = append(env, envOBModuleStatus) - env = append(env, envClusterName) - env = append(env, envClusterId) - env = append(env, envZoneName) - env = append(env, envMonitorUser) - env = append(env, envMonitorPassword) - - container := corev1.Container{ - Name: obagentconst.ContainerName, - Image: m.OBServer.Spec.MonitorTemplate.Image, - ImagePullPolicy: "IfNotPresent", - Ports: ports, - Resources: resources, - ReadinessProbe: &readinessProbe, - WorkingDir: obagentconst.InstallPath, - Env: env, - } - return container -} - -// TODO move hardcoded values to another file -func (m *OBServerManager) createOBServerContainer(obcluster *v1alpha1.OBCluster) corev1.Container { - // port info - ports := make([]corev1.ContainerPort, 0) - mysqlPort := corev1.ContainerPort{} - mysqlPort.Name = oceanbaseconst.SqlPortName - mysqlPort.ContainerPort = oceanbaseconst.SqlPort - mysqlPort.Protocol = corev1.ProtocolTCP - rpcPort := corev1.ContainerPort{} - rpcPort.Name = oceanbaseconst.RpcPortName - rpcPort.ContainerPort = oceanbaseconst.RpcPort - rpcPort.Protocol = corev1.ProtocolTCP - infoPort := corev1.ContainerPort{} - infoPort.Name = "info" - infoPort.ContainerPort = 8080 - infoPort.Protocol = corev1.ProtocolTCP - - ports = append(ports, mysqlPort) - ports = append(ports, rpcPort) - ports = append(ports, infoPort) - - // resource info - observerResource := corev1.ResourceList{} - observerResource["memory"] = m.OBServer.Spec.OBServerTemplate.Resource.Memory - if !m.OBServer.Spec.OBServerTemplate.Resource.Cpu.IsZero() { - observerResource["cpu"] = m.OBServer.Spec.OBServerTemplate.Resource.Cpu - } - resources := corev1.ResourceRequirements{ - Requests: observerResource, - Limits: observerResource, - } - - // volume mounts - volumeMountDataFile := corev1.VolumeMount{} - volumeMountDataFile.MountPath = oceanbaseconst.DataPath - volumeMountDataLog := corev1.VolumeMount{} - volumeMountDataLog.MountPath = oceanbaseconst.ClogPath - volumeMountLog := corev1.VolumeMount{} - volumeMountLog.MountPath = oceanbaseconst.LogPath - - // set subpath - singlePvcAnnoVal, singlePvcExist := resourceutils.GetAnnotationField(m.OBServer, oceanbaseconst.AnnotationsSinglePVC) - if singlePvcExist && singlePvcAnnoVal == "true" { - volumeMountDataFile.Name = m.OBServer.Name - volumeMountDataLog.Name = m.OBServer.Name - volumeMountLog.Name = m.OBServer.Name - volumeMountDataFile.SubPath = oceanbaseconst.DataVolumeSuffix - volumeMountDataLog.SubPath = oceanbaseconst.ClogVolumeSuffix - volumeMountLog.SubPath = oceanbaseconst.LogVolumeSuffix - } else { - volumeMountDataFile.Name = fmt.Sprintf("%s-%s", m.OBServer.Name, oceanbaseconst.DataVolumeSuffix) - volumeMountDataLog.Name = fmt.Sprintf("%s-%s", m.OBServer.Name, oceanbaseconst.ClogVolumeSuffix) - volumeMountLog.Name = fmt.Sprintf("%s-%s", m.OBServer.Name, oceanbaseconst.LogVolumeSuffix) - } - - volumeMounts := make([]corev1.VolumeMount, 0) - volumeMounts = append(volumeMounts, volumeMountDataFile) - volumeMounts = append(volumeMounts, volumeMountDataLog) - volumeMounts = append(volumeMounts, volumeMountLog) - - if m.OBServer.Spec.BackupVolume != nil { - volumeMountBackup := corev1.VolumeMount{} - volumeMountBackup.Name = fmt.Sprintf(m.OBServer.Spec.BackupVolume.Volume.Name) - volumeMountBackup.MountPath = oceanbaseconst.BackupPath - volumeMounts = append(volumeMounts, volumeMountBackup) - } - - readinessProbeTCP := corev1.TCPSocketAction{} - readinessProbeTCP.Port = intstr.FromInt(oceanbaseconst.SqlPort) - readinessProbe := corev1.Probe{} - readinessProbe.ProbeHandler.TCPSocket = &readinessProbeTCP - readinessProbe.PeriodSeconds = oceanbaseconst.ProbeCheckPeriodSeconds - readinessProbe.InitialDelaySeconds = oceanbaseconst.ProbeCheckDelaySeconds - readinessProbe.FailureThreshold = 32 - - startOBServerCmd := "/home/admin/oceanbase/bin/oceanbase-helper start" - - cmds := []string{ - "bash", - "-c", - startOBServerCmd, - } - - env := make([]corev1.EnvVar, 0) - envLib := corev1.EnvVar{ - Name: "LD_LIBRARY_PATH", - Value: "/home/admin/oceanbase/lib", - } - cpuCount := m.OBServer.Spec.OBServerTemplate.Resource.Cpu.Value() - if cpuCount < 16 { - cpuCount = 16 - } - envCpu := corev1.EnvVar{ - Name: "CPU_COUNT", - Value: fmt.Sprintf("%d", cpuCount), - } - - datafileSize, ok := m.OBServer.Spec.OBServerTemplate.Storage.DataStorage.Size.AsInt64() - if !ok { - m.Logger.Error(errors.New("Parse datafile size failed"), "failed to parse datafile size") - } - envDataFile := corev1.EnvVar{ - Name: "DATAFILE_SIZE", - Value: fmt.Sprintf("%dG", datafileSize*oceanbaseconst.InitialDataDiskUsePercent/oceanbaseconst.GigaConverter/100), - } - clogDiskSize, ok := m.OBServer.Spec.OBServerTemplate.Storage.RedoLogStorage.Size.AsInt64() - if !ok { - m.Logger.Error(errors.New("Parse log disk size failed"), "failed to parse log disk size") - } - envLogDisk := corev1.EnvVar{ - Name: "LOG_DISK_SIZE", - Value: fmt.Sprintf("%dG", clogDiskSize*oceanbaseconst.DefaultDiskUsePercent/oceanbaseconst.GigaConverter/100), - } - envClusterName := corev1.EnvVar{ - Name: "CLUSTER_NAME", - Value: m.OBServer.Spec.ClusterName, - } - envClusterId := corev1.EnvVar{ - Name: "CLUSTER_ID", - Value: fmt.Sprintf("%d", m.OBServer.Spec.ClusterId), - } - envZoneName := corev1.EnvVar{ - Name: "ZONE_NAME", - Value: m.OBServer.Spec.Zone, - } - - mode, modeAnnoExist := resourceutils.GetAnnotationField(m.OBServer, oceanbaseconst.AnnotationsMode) - if modeAnnoExist { - switch mode { - case oceanbaseconst.ModeStandalone: - envMode := corev1.EnvVar{ - Name: "STANDALONE", - Value: oceanbaseconst.ModeStandalone, - } - env = append(env, envMode) - case oceanbaseconst.ModeService: - svc, err := m.getSvc() - if err != nil { - if kubeerrors.IsNotFound(err) { - m.Logger.Info("svc not found") - } else { - m.Logger.Error(err, "Failed to get svc") - } - } else { - envSvcIp := corev1.EnvVar{ - Name: "SVC_IP", - Value: svc.Spec.ClusterIP, - } - env = append(env, envSvcIp) - } - } - } - - startupParameters := make([]string, 0) - for _, parameter := range obcluster.Spec.Parameters { - reserved := false - for _, reservedParameter := range oceanbaseconst.ReservedParameters { - if parameter.Name == reservedParameter { - reserved = true - break - } - } - if !reserved { - startupParameters = append(startupParameters, fmt.Sprintf("%s='%s'", parameter.Name, parameter.Value)) - } - } - if len(startupParameters) != 0 { - envExtraOpt := corev1.EnvVar{ - Name: "EXTRA_OPTION", - Value: strings.Join(startupParameters, ","), - } - env = append(env, envExtraOpt) - } - env = append(env, envLib) - env = append(env, envCpu) - env = append(env, envDataFile) - env = append(env, envLogDisk) - env = append(env, envClusterName) - env = append(env, envClusterId) - env = append(env, envZoneName) - - container := corev1.Container{ - Name: oceanbaseconst.ContainerName, - Image: m.OBServer.Spec.OBServerTemplate.Image, - ImagePullPolicy: "IfNotPresent", - Ports: ports, - Resources: resources, - VolumeMounts: volumeMounts, - ReadinessProbe: &readinessProbe, - WorkingDir: oceanbaseconst.InstallPath, - Env: env, - Command: cmds, - } - return container -} - -func (m *OBServerManager) DeleteOBServerInCluster() tasktypes.TaskError { +func DeleteOBServerInCluster(m *OBServerManager) tasktypes.TaskError { m.Logger.V(oceanbaseconst.LogLevelDebug).Info("delete observer in cluster") operationManager, err := m.getOceanbaseOperationManager() if err != nil { @@ -619,7 +247,7 @@ func (m *OBServerManager) DeleteOBServerInCluster() tasktypes.TaskError { return nil } -func (m *OBServerManager) AnnotateOBServerPod() tasktypes.TaskError { +func AnnotateOBServerPod(m *OBServerManager) tasktypes.TaskError { observerPod, err := m.getPod() if err != nil { return errors.Wrapf(err, "Failed to get pod of observer %s", m.OBServer.Name) @@ -635,7 +263,7 @@ func (m *OBServerManager) AnnotateOBServerPod() tasktypes.TaskError { return nil } -func (m *OBServerManager) UpgradeOBServerImage() tasktypes.TaskError { +func UpgradeOBServerImage(m *OBServerManager) tasktypes.TaskError { observerPod, err := m.getPod() if err != nil { return errors.Wrapf(err, "Failed to get pod of observer %s", m.OBServer.Name) @@ -653,7 +281,7 @@ func (m *OBServerManager) UpgradeOBServerImage() tasktypes.TaskError { return nil } -func (m *OBServerManager) WaitOBServerPodReady() tasktypes.TaskError { +func WaitOBServerPodReady(m *OBServerManager) tasktypes.TaskError { observerPodRestarted := false for i := 0; i < oceanbaseconst.DefaultStateWaitTimeout; i++ { observerPod, err := m.getPod() @@ -680,7 +308,7 @@ func (m *OBServerManager) WaitOBServerPodReady() tasktypes.TaskError { return nil } -func (m *OBServerManager) WaitOBServerActiveInCluster() tasktypes.TaskError { +func WaitOBServerActiveInCluster(m *OBServerManager) tasktypes.TaskError { if m.OBServer.SupportStaticIP() { return nil } @@ -715,7 +343,7 @@ func (m *OBServerManager) WaitOBServerActiveInCluster() tasktypes.TaskError { return nil } -func (m *OBServerManager) WaitOBServerDeletedInCluster() tasktypes.TaskError { +func WaitOBServerDeletedInCluster(m *OBServerManager) tasktypes.TaskError { if m.OBServer.SupportStaticIP() { return nil } @@ -749,7 +377,7 @@ func (m *OBServerManager) WaitOBServerDeletedInCluster() tasktypes.TaskError { return nil } -func (m *OBServerManager) DeletePod() tasktypes.TaskError { +func DeletePod(m *OBServerManager) tasktypes.TaskError { m.Logger.Info("Delete observer pod") pod, err := m.getPod() if err != nil { @@ -763,7 +391,7 @@ func (m *OBServerManager) DeletePod() tasktypes.TaskError { return nil } -func (m *OBServerManager) WaitForPodDeleted() tasktypes.TaskError { +func WaitForPodDeleted(m *OBServerManager) tasktypes.TaskError { m.Logger.Info("Wait for observer pod being deleted") for i := 0; i < oceanbaseconst.DefaultStateWaitTimeout; i++ { time.Sleep(time.Second) @@ -775,7 +403,7 @@ func (m *OBServerManager) WaitForPodDeleted() tasktypes.TaskError { return errors.New("Timeout to wait for pod being deleted") } -func (m *OBServerManager) ResizePVC() tasktypes.TaskError { +func ExpandPVC(m *OBServerManager) tasktypes.TaskError { observerPVC, err := m.getPVCs() if err != nil { return errors.Wrapf(err, "Failed to get pvc list of observer %s", m.OBServer.Name) @@ -816,7 +444,7 @@ func (m *OBServerManager) ResizePVC() tasktypes.TaskError { return nil } -func (m *OBServerManager) WaitForPVCResized() tasktypes.TaskError { +func WaitForPVCResized(m *OBServerManager) tasktypes.TaskError { outer: for i := 0; i < oceanbaseconst.DefaultStateWaitTimeout; i++ { time.Sleep(time.Second) @@ -829,17 +457,17 @@ outer: switch pvc.Name { case fmt.Sprintf("%s-%s", m.OBServer.Name, oceanbaseconst.DataVolumeSuffix): if m.OBServer.Spec.OBServerTemplate.Storage.DataStorage.Size.Cmp(pvc.Spec.Resources.Requests[corev1.ResourceStorage]) != 0 { - m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Data pvc not resized", "pvc", pvc.Name) + m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Data pvc not expanded", "pvc", pvc.Name) continue outer } case fmt.Sprintf("%s-%s", m.OBServer.Name, oceanbaseconst.ClogVolumeSuffix): if m.OBServer.Spec.OBServerTemplate.Storage.RedoLogStorage.Size.Cmp(pvc.Spec.Resources.Requests[corev1.ResourceStorage]) != 0 { - m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Data pvc not resized", "pvc", pvc.Name) + m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Data pvc not expanded", "pvc", pvc.Name) continue outer } case fmt.Sprintf("%s-%s", m.OBServer.Name, oceanbaseconst.LogVolumeSuffix): if m.OBServer.Spec.OBServerTemplate.Storage.LogStorage.Size.Cmp(pvc.Spec.Resources.Requests[corev1.ResourceStorage]) != 0 { - m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Data pvc not resized", "pvc", pvc.Name) + m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Data pvc not expanded", "pvc", pvc.Name) continue outer } case m.OBServer.Name: @@ -848,17 +476,18 @@ outer: sum.Add(m.OBServer.Spec.OBServerTemplate.Storage.RedoLogStorage.Size) sum.Add(m.OBServer.Spec.OBServerTemplate.Storage.LogStorage.Size) if sum.Cmp(pvc.Spec.Resources.Requests[corev1.ResourceStorage]) != 0 { - m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Data pvc not resized", "pvc", pvc.Name) + m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Data pvc not expanded", "pvc", pvc.Name) continue outer } } } - // all pvc resized + // all pvc expanded return nil } - return errors.Errorf("Timeout to wait for pvc resized") + return errors.Errorf("Timeout to wait for pvc expanded") } -func (m *OBServerManager) CreateOBServerSvc() tasktypes.TaskError { + +func CreateOBServerSvc(m *OBServerManager) tasktypes.TaskError { mode, modeAnnoExist := resourceutils.GetAnnotationField(m.OBServer, oceanbaseconst.AnnotationsMode) if modeAnnoExist && mode == oceanbaseconst.ModeService { m.Logger.Info("Create observer service") @@ -895,10 +524,10 @@ func (m *OBServerManager) CreateOBServerSvc() tasktypes.TaskError { return nil } -func (m *OBServerManager) MountBackupVolume() tasktypes.TaskError { +func MountBackupVolume(_ *OBServerManager) tasktypes.TaskError { return nil } -func (m *OBServerManager) WaitForBackupVolumeMounted() tasktypes.TaskError { +func WaitForBackupVolumeMounted(_ *OBServerManager) tasktypes.TaskError { return nil } diff --git a/internal/resource/observer/observer_task_gen.go b/internal/resource/observer/observer_task_gen.go new file mode 100644 index 000000000..c3e582a2a --- /dev/null +++ b/internal/resource/observer/observer_task_gen.go @@ -0,0 +1,23 @@ +// Code generated by go generate; DO NOT EDIT. +package observer + +func init() { + taskMap.Register(tWaitOBServerReady, WaitOBServerReady) + taskMap.Register(tAddServer, AddServer) + taskMap.Register(tWaitOBClusterBootstrapped, WaitOBClusterBootstrapped) + taskMap.Register(tCreateOBPod, CreateOBPod) + taskMap.Register(tCreateOBPVC, CreateOBPVC) + taskMap.Register(tDeleteOBServerInCluster, DeleteOBServerInCluster) + taskMap.Register(tAnnotateOBServerPod, AnnotateOBServerPod) + taskMap.Register(tUpgradeOBServerImage, UpgradeOBServerImage) + taskMap.Register(tWaitOBServerPodReady, WaitOBServerPodReady) + taskMap.Register(tWaitOBServerActiveInCluster, WaitOBServerActiveInCluster) + taskMap.Register(tWaitOBServerDeletedInCluster, WaitOBServerDeletedInCluster) + taskMap.Register(tDeletePod, DeletePod) + taskMap.Register(tWaitForPodDeleted, WaitForPodDeleted) + taskMap.Register(tExpandPVC, ExpandPVC) + taskMap.Register(tWaitForPVCResized, WaitForPVCResized) + taskMap.Register(tCreateOBServerSvc, CreateOBServerSvc) + taskMap.Register(tMountBackupVolume, MountBackupVolume) + taskMap.Register(tWaitForBackupVolumeMounted, WaitForBackupVolumeMounted) +} diff --git a/internal/resource/observer/utils.go b/internal/resource/observer/utils.go index 34e206695..30b15358c 100644 --- a/internal/resource/observer/utils.go +++ b/internal/resource/observer/utils.go @@ -13,20 +13,27 @@ See the Mulan PSL v2 for more details. package observer import ( + "fmt" "strings" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" + kubeerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/intstr" "k8s.io/client-go/util/retry" "sigs.k8s.io/controller-runtime/pkg/client" + apitypes "github.com/oceanbase/ob-operator/api/types" v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" + obagentconst "github.com/oceanbase/ob-operator/internal/const/obagent" oceanbaseconst "github.com/oceanbase/ob-operator/internal/const/oceanbase" + secretconst "github.com/oceanbase/ob-operator/internal/const/secret" serverstatus "github.com/oceanbase/ob-operator/internal/const/status/observer" resourceutils "github.com/oceanbase/ob-operator/internal/resource/utils" "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/model" + "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/operation" ) // get observer from K8s api server @@ -179,3 +186,375 @@ func (m *OBServerManager) checkIfBackupVolumeAdded(pod *corev1.Pod) bool { } return false } + +func (m *OBServerManager) generatePVCSpec(storageSpec *apitypes.StorageSpec) corev1.PersistentVolumeClaimSpec { + pvcSpec := &corev1.PersistentVolumeClaimSpec{} + requestsResources := corev1.ResourceList{} + requestsResources["storage"] = storageSpec.Size + storageClassName := storageSpec.StorageClass + pvcSpec.StorageClassName = &(storageClassName) + accessModes := []corev1.PersistentVolumeAccessMode{corev1.ReadWriteOnce} + pvcSpec.AccessModes = accessModes + pvcSpec.Resources.Requests = requestsResources + return *pvcSpec +} + +func (m *OBServerManager) createOBPodSpec(obcluster *v1alpha1.OBCluster) corev1.PodSpec { + containers := make([]corev1.Container, 0) + observerContainer := m.createOBServerContainer(obcluster) + containers = append(containers, observerContainer) + + // TODO, add monitor container + volumes := make([]corev1.Volume, 0) + + singlePvcAnnoVal, singlePvcExist := resourceutils.GetAnnotationField(m.OBServer, oceanbaseconst.AnnotationsSinglePVC) + if singlePvcExist && singlePvcAnnoVal == "true" { + singleVolume := corev1.Volume{} + singleVolumeSource := &corev1.PersistentVolumeClaimVolumeSource{} + singleVolume.Name = m.OBServer.Name + singleVolumeSource.ClaimName = m.OBServer.Name + singleVolume.VolumeSource.PersistentVolumeClaim = singleVolumeSource + + volumes = append(volumes, singleVolume) + } else { + volumeDataFile := corev1.Volume{} + volumeDataFileSource := &corev1.PersistentVolumeClaimVolumeSource{} + volumeDataFile.Name = fmt.Sprintf("%s-%s", m.OBServer.Name, oceanbaseconst.DataVolumeSuffix) + volumeDataFileSource.ClaimName = fmt.Sprintf("%s-%s", m.OBServer.Name, oceanbaseconst.DataVolumeSuffix) + volumeDataFile.VolumeSource.PersistentVolumeClaim = volumeDataFileSource + + volumeDataLog := corev1.Volume{} + volumeDataLog.Name = fmt.Sprintf("%s-%s", m.OBServer.Name, oceanbaseconst.ClogVolumeSuffix) + volumeDataLogSource := &corev1.PersistentVolumeClaimVolumeSource{} + volumeDataLogSource.ClaimName = fmt.Sprintf("%s-%s", m.OBServer.Name, oceanbaseconst.ClogVolumeSuffix) + volumeDataLog.VolumeSource.PersistentVolumeClaim = volumeDataLogSource + + volumeLog := corev1.Volume{} + volumeLog.Name = fmt.Sprintf("%s-%s", m.OBServer.Name, oceanbaseconst.LogVolumeSuffix) + volumeLogSource := &corev1.PersistentVolumeClaimVolumeSource{} + volumeLogSource.ClaimName = fmt.Sprintf("%s-%s", m.OBServer.Name, oceanbaseconst.LogVolumeSuffix) + volumeLog.VolumeSource.PersistentVolumeClaim = volumeLogSource + + volumes = append(volumes, volumeDataFile, volumeDataLog, volumeLog) + } + + if m.OBServer.Spec.BackupVolume != nil { + volumes = append(volumes, *m.OBServer.Spec.BackupVolume.Volume) + } + + if m.OBServer.Spec.MonitorTemplate != nil { + monitorContainer := m.createMonitorContainer(obcluster) + containers = append(containers, monitorContainer) + } + + podSpec := corev1.PodSpec{ + Volumes: volumes, + Containers: containers, + NodeSelector: m.OBServer.Spec.NodeSelector, + Affinity: m.OBServer.Spec.Affinity, + Tolerations: m.OBServer.Spec.Tolerations, + ServiceAccountName: m.OBServer.Spec.ServiceAccount, + } + return podSpec +} + +func (m *OBServerManager) createMonitorContainer(obcluster *v1alpha1.OBCluster) corev1.Container { + // port info + ports := make([]corev1.ContainerPort, 0) + httpPort := corev1.ContainerPort{} + httpPort.Name = obagentconst.HttpPortName + httpPort.ContainerPort = obagentconst.HttpPort + httpPort.Protocol = corev1.ProtocolTCP + pprofPort := corev1.ContainerPort{} + pprofPort.Name = obagentconst.PprofPortName + pprofPort.ContainerPort = obagentconst.PprofPort + pprofPort.Protocol = corev1.ProtocolTCP + ports = append(ports, httpPort) + ports = append(ports, pprofPort) + + // resource info + monagentResource := corev1.ResourceList{} + monagentResource["memory"] = m.OBServer.Spec.MonitorTemplate.Resource.Memory + if !m.OBServer.Spec.MonitorTemplate.Resource.Cpu.IsZero() { + monagentResource["cpu"] = m.OBServer.Spec.MonitorTemplate.Resource.Cpu + } + resources := corev1.ResourceRequirements{ + Limits: monagentResource, + } + + readinessProbeHTTP := corev1.HTTPGetAction{} + readinessProbeHTTP.Port = intstr.FromInt(obagentconst.HttpPort) + readinessProbeHTTP.Path = obagentconst.StatUrl + readinessProbe := corev1.Probe{} + readinessProbe.ProbeHandler.HTTPGet = &readinessProbeHTTP + readinessProbe.PeriodSeconds = obagentconst.ProbeCheckPeriodSeconds + readinessProbe.InitialDelaySeconds = obagentconst.ProbeCheckDelaySeconds + + env := make([]corev1.EnvVar, 0) + envOBModuleStatus := corev1.EnvVar{ + Name: obagentconst.EnvOBMonitorStatus, + Value: obagentconst.ActiveStatus, + } + envClusterName := corev1.EnvVar{ + Name: obagentconst.EnvClusterName, + Value: m.OBServer.Spec.ClusterName, + } + envClusterId := corev1.EnvVar{ + Name: obagentconst.EnvClusterId, + Value: fmt.Sprintf("%d", m.OBServer.Spec.ClusterId), + } + envZoneName := corev1.EnvVar{ + Name: obagentconst.EnvZoneName, + Value: m.OBServer.Spec.Zone, + } + envMonitorUser := corev1.EnvVar{ + Name: obagentconst.EnvMonitorUser, + Value: obagentconst.MonitorUser, + } + envMonitorPassword := corev1.EnvVar{ + Name: obagentconst.EnvMonitorPASSWORD, + ValueFrom: &corev1.EnvVarSource{ + SecretKeyRef: &corev1.SecretKeySelector{ + LocalObjectReference: corev1.LocalObjectReference{ + Name: obcluster.Spec.UserSecrets.Monitor, + }, + Key: secretconst.PasswordKeyName, + }, + }, + } + env = append(env, envOBModuleStatus) + env = append(env, envClusterName) + env = append(env, envClusterId) + env = append(env, envZoneName) + env = append(env, envMonitorUser) + env = append(env, envMonitorPassword) + + container := corev1.Container{ + Name: obagentconst.ContainerName, + Image: m.OBServer.Spec.MonitorTemplate.Image, + ImagePullPolicy: "IfNotPresent", + Ports: ports, + Resources: resources, + ReadinessProbe: &readinessProbe, + WorkingDir: obagentconst.InstallPath, + Env: env, + } + return container +} + +// TODO move hardcoded values to another file +func (m *OBServerManager) createOBServerContainer(obcluster *v1alpha1.OBCluster) corev1.Container { + // port info + ports := make([]corev1.ContainerPort, 0) + mysqlPort := corev1.ContainerPort{} + mysqlPort.Name = oceanbaseconst.SqlPortName + mysqlPort.ContainerPort = oceanbaseconst.SqlPort + mysqlPort.Protocol = corev1.ProtocolTCP + rpcPort := corev1.ContainerPort{} + rpcPort.Name = oceanbaseconst.RpcPortName + rpcPort.ContainerPort = oceanbaseconst.RpcPort + rpcPort.Protocol = corev1.ProtocolTCP + infoPort := corev1.ContainerPort{} + infoPort.Name = "info" + infoPort.ContainerPort = 8080 + infoPort.Protocol = corev1.ProtocolTCP + + ports = append(ports, mysqlPort) + ports = append(ports, rpcPort) + ports = append(ports, infoPort) + + // resource info + observerResource := corev1.ResourceList{} + observerResource["memory"] = m.OBServer.Spec.OBServerTemplate.Resource.Memory + if !m.OBServer.Spec.OBServerTemplate.Resource.Cpu.IsZero() { + observerResource["cpu"] = m.OBServer.Spec.OBServerTemplate.Resource.Cpu + } + resources := corev1.ResourceRequirements{ + Requests: observerResource, + Limits: observerResource, + } + + // volume mounts + volumeMountDataFile := corev1.VolumeMount{} + volumeMountDataFile.MountPath = oceanbaseconst.DataPath + volumeMountDataLog := corev1.VolumeMount{} + volumeMountDataLog.MountPath = oceanbaseconst.ClogPath + volumeMountLog := corev1.VolumeMount{} + volumeMountLog.MountPath = oceanbaseconst.LogPath + + // set subpath + singlePvcAnnoVal, singlePvcExist := resourceutils.GetAnnotationField(m.OBServer, oceanbaseconst.AnnotationsSinglePVC) + if singlePvcExist && singlePvcAnnoVal == "true" { + volumeMountDataFile.Name = m.OBServer.Name + volumeMountDataLog.Name = m.OBServer.Name + volumeMountLog.Name = m.OBServer.Name + volumeMountDataFile.SubPath = oceanbaseconst.DataVolumeSuffix + volumeMountDataLog.SubPath = oceanbaseconst.ClogVolumeSuffix + volumeMountLog.SubPath = oceanbaseconst.LogVolumeSuffix + } else { + volumeMountDataFile.Name = fmt.Sprintf("%s-%s", m.OBServer.Name, oceanbaseconst.DataVolumeSuffix) + volumeMountDataLog.Name = fmt.Sprintf("%s-%s", m.OBServer.Name, oceanbaseconst.ClogVolumeSuffix) + volumeMountLog.Name = fmt.Sprintf("%s-%s", m.OBServer.Name, oceanbaseconst.LogVolumeSuffix) + } + + volumeMounts := make([]corev1.VolumeMount, 0) + volumeMounts = append(volumeMounts, volumeMountDataFile) + volumeMounts = append(volumeMounts, volumeMountDataLog) + volumeMounts = append(volumeMounts, volumeMountLog) + + if m.OBServer.Spec.BackupVolume != nil { + volumeMountBackup := corev1.VolumeMount{} + volumeMountBackup.Name = fmt.Sprintf(m.OBServer.Spec.BackupVolume.Volume.Name) + volumeMountBackup.MountPath = oceanbaseconst.BackupPath + volumeMounts = append(volumeMounts, volumeMountBackup) + } + + readinessProbeTCP := corev1.TCPSocketAction{} + readinessProbeTCP.Port = intstr.FromInt(oceanbaseconst.SqlPort) + readinessProbe := corev1.Probe{} + readinessProbe.ProbeHandler.TCPSocket = &readinessProbeTCP + readinessProbe.PeriodSeconds = oceanbaseconst.ProbeCheckPeriodSeconds + readinessProbe.InitialDelaySeconds = oceanbaseconst.ProbeCheckDelaySeconds + readinessProbe.FailureThreshold = 32 + + startOBServerCmd := "/home/admin/oceanbase/bin/oceanbase-helper start" + + cmds := []string{ + "bash", + "-c", + startOBServerCmd, + } + + env := make([]corev1.EnvVar, 0) + envLib := corev1.EnvVar{ + Name: "LD_LIBRARY_PATH", + Value: "/home/admin/oceanbase/lib", + } + cpuCount := m.OBServer.Spec.OBServerTemplate.Resource.Cpu.Value() + if cpuCount < 16 { + cpuCount = 16 + } + envCpu := corev1.EnvVar{ + Name: "CPU_COUNT", + Value: fmt.Sprintf("%d", cpuCount), + } + + datafileSize, ok := m.OBServer.Spec.OBServerTemplate.Storage.DataStorage.Size.AsInt64() + if !ok { + m.Logger.Error(errors.New("Parse datafile size failed"), "failed to parse datafile size") + } + envDataFile := corev1.EnvVar{ + Name: "DATAFILE_SIZE", + Value: fmt.Sprintf("%dG", datafileSize*oceanbaseconst.InitialDataDiskUsePercent/oceanbaseconst.GigaConverter/100), + } + clogDiskSize, ok := m.OBServer.Spec.OBServerTemplate.Storage.RedoLogStorage.Size.AsInt64() + if !ok { + m.Logger.Error(errors.New("Parse log disk size failed"), "failed to parse log disk size") + } + envLogDisk := corev1.EnvVar{ + Name: "LOG_DISK_SIZE", + Value: fmt.Sprintf("%dG", clogDiskSize*oceanbaseconst.DefaultDiskUsePercent/oceanbaseconst.GigaConverter/100), + } + envClusterName := corev1.EnvVar{ + Name: "CLUSTER_NAME", + Value: m.OBServer.Spec.ClusterName, + } + envClusterId := corev1.EnvVar{ + Name: "CLUSTER_ID", + Value: fmt.Sprintf("%d", m.OBServer.Spec.ClusterId), + } + envZoneName := corev1.EnvVar{ + Name: "ZONE_NAME", + Value: m.OBServer.Spec.Zone, + } + + mode, modeAnnoExist := resourceutils.GetAnnotationField(m.OBServer, oceanbaseconst.AnnotationsMode) + if modeAnnoExist { + switch mode { + case oceanbaseconst.ModeStandalone: + envMode := corev1.EnvVar{ + Name: "STANDALONE", + Value: oceanbaseconst.ModeStandalone, + } + env = append(env, envMode) + case oceanbaseconst.ModeService: + svc, err := m.getSvc() + if err != nil { + if kubeerrors.IsNotFound(err) { + m.Logger.Info("svc not found") + } else { + m.Logger.Error(err, "Failed to get svc") + } + } else { + envSvcIp := corev1.EnvVar{ + Name: "SVC_IP", + Value: svc.Spec.ClusterIP, + } + env = append(env, envSvcIp) + } + } + } + + startupParameters := make([]string, 0) + for _, parameter := range obcluster.Spec.Parameters { + reserved := false + for _, reservedParameter := range oceanbaseconst.ReservedParameters { + if parameter.Name == reservedParameter { + reserved = true + break + } + } + if !reserved { + startupParameters = append(startupParameters, fmt.Sprintf("%s='%s'", parameter.Name, parameter.Value)) + } + } + if len(startupParameters) != 0 { + envExtraOpt := corev1.EnvVar{ + Name: "EXTRA_OPTION", + Value: strings.Join(startupParameters, ","), + } + env = append(env, envExtraOpt) + } + env = append(env, envLib) + env = append(env, envCpu) + env = append(env, envDataFile) + env = append(env, envLogDisk) + env = append(env, envClusterName) + env = append(env, envClusterId) + env = append(env, envZoneName) + + container := corev1.Container{ + Name: oceanbaseconst.ContainerName, + Image: m.OBServer.Spec.OBServerTemplate.Image, + ImagePullPolicy: "IfNotPresent", + Ports: ports, + Resources: resources, + VolumeMounts: volumeMounts, + ReadinessProbe: &readinessProbe, + WorkingDir: oceanbaseconst.InstallPath, + Env: env, + Command: cmds, + } + return container +} + +func (m *OBServerManager) generateStaticIpAnnotation() map[string]string { + annotations := make(map[string]string) + switch m.OBServer.Status.CNI { + case oceanbaseconst.CNICalico: + if m.OBServer.Status.PodIp != "" { + annotations[oceanbaseconst.AnnotationCalicoIpAddrs] = fmt.Sprintf("[\"%s\"]", m.OBServer.Status.PodIp) + } + default: + m.Logger.Info("static ip not supported, set empty annotation") + } + return annotations +} + +func (m *OBServerManager) getOceanbaseOperationManager() (*operation.OceanbaseOperationManager, error) { + obcluster, err := m.getOBCluster() + if err != nil { + return nil, errors.Wrap(err, "Get obcluster from K8s") + } + return resourceutils.GetSysOperationClient(m.Client, m.Logger, obcluster) +} diff --git a/internal/resource/obtenant/init.go b/internal/resource/obtenant/init.go deleted file mode 100644 index d37efa148..000000000 --- a/internal/resource/obtenant/init.go +++ /dev/null @@ -1,35 +0,0 @@ -/* -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 obtenant - -import ( - "github.com/oceanbase/ob-operator/pkg/task" -) - -func init() { - // obtenant - task.GetRegistry().Register(fCreateTenant, CreateTenant) - task.GetRegistry().Register(fMaintainWhiteList, MaintainWhiteList) - task.GetRegistry().Register(fMaintainCharset, MaintainCharset) - task.GetRegistry().Register(fMaintainUnitNum, MaintainUnitNum) - task.GetRegistry().Register(fMaintainPrimaryZone, MaintainPrimaryZone) - task.GetRegistry().Register(fMaintainLocality, MaintainLocality) - task.GetRegistry().Register(fAddPool, AddPool) - task.GetRegistry().Register(fDeletePool, DeletePool) - task.GetRegistry().Register(fMaintainUnitConfig, MaintainUnitConfig) - task.GetRegistry().Register(fDeleteTenant, DeleteTenant) - - task.GetRegistry().Register(fRestoreTenant, RestoreTenant) - task.GetRegistry().Register(fCancelRestoreFlow, CancelRestoreJob) - task.GetRegistry().Register(fCreateEmptyStandbyTenant, CreateEmptyStandbyTenant) -} diff --git a/internal/resource/obtenant/names.go b/internal/resource/obtenant/names.go index ec38068ac..0b7361d21 100644 --- a/internal/resource/obtenant/names.go +++ b/internal/resource/obtenant/names.go @@ -29,31 +29,29 @@ const ( fDeletePool ttypes.FlowName = "delete pool" fDeleteTenant ttypes.FlowName = "delete tenant" fRestoreTenant ttypes.FlowName = "Restore tenant" - fCancelRestoreFlow ttypes.FlowName = "cancel restore" + fCancelRestore ttypes.FlowName = "cancel restore" fCreateEmptyStandbyTenant ttypes.FlowName = "create empty standby tenant" ) const ( tCheckTenant ttypes.TaskName = "create tenant check" - tCheckPoolAndUnitConfig ttypes.TaskName = "create pool and unit config check" - tCreateTenant ttypes.TaskName = "create tenant" - tCreateResourcePoolAndUnitConfig ttypes.TaskName = "create resource pool and unit config" - - tMaintainWhiteList ttypes.TaskName = "maintain white list" - tMaintainCharset ttypes.TaskName = "maintain charset" - tMaintainUnitNum ttypes.TaskName = "maintain unit num" - tMaintainLocality ttypes.TaskName = "maintain locality" - tMaintainPrimaryZone ttypes.TaskName = "maintain primary zone" - tAddResourcePool ttypes.TaskName = "add resource pool" - tDeleteResourcePool ttypes.TaskName = "delete resource pool" - tMaintainUnitConfig ttypes.TaskName = "maintain unit config" - tDeleteTenant ttypes.TaskName = "delete tenant" - - tCreateRestoreJobCR ttypes.TaskName = "create restore job CR" - tWatchRestoreJobToFinish ttypes.TaskName = "watch restore job to finish" - tCancelRestoreJob ttypes.TaskName = "cancel restore job" - tCreateUsersByCredentials ttypes.TaskName = "create users by credentials" - tCheckPrimaryTenantLSIntegrity ttypes.TaskName = "check primary tenant ls integrity" - tCreateEmptyStandbyTenant ttypes.TaskName = "create empty standby tenant" - tUpgradeTenantIfNeeded ttypes.TaskName = "upgrade tenant if needed" + tCheckPoolAndConfig ttypes.TaskName = "create pool and unit config check" + tCreateTenantWithClear ttypes.TaskName = "create tenant" + tCreateResourcePoolAndConfig ttypes.TaskName = "create resource pool and unit config" + tCheckAndApplyWhiteList ttypes.TaskName = "maintain white list" + tCheckAndApplyCharset ttypes.TaskName = "maintain charset" + tCheckAndApplyUnitNum ttypes.TaskName = "maintain unit num" + tCheckAndApplyLocality ttypes.TaskName = "maintain locality" + tCheckAndApplyPrimaryZone ttypes.TaskName = "maintain primary zone" + tAddPool ttypes.TaskName = "add resource pool" + tDeletePool ttypes.TaskName = "delete resource pool" + tMaintainUnitConfig ttypes.TaskName = "maintain unit config" + tDeleteTenant ttypes.TaskName = "delete tenant" + tCreateTenantRestoreJobCR ttypes.TaskName = "create restore job CR" + tWatchRestoreJobToFinish ttypes.TaskName = "watch restore job to finish" + tCancelTenantRestoreJob ttypes.TaskName = "cancel restore job" + tCreateUserWithCredentialSecrets ttypes.TaskName = "create users by credentials" + tCheckPrimaryTenantLSIntegrity ttypes.TaskName = "check primary tenant ls integrity" + tCreateEmptyStandbyTenant ttypes.TaskName = "create empty standby tenant" + tUpgradeTenantIfNeeded ttypes.TaskName = "upgrade tenant if needed" ) diff --git a/internal/resource/obtenant/obtenant_flow.go b/internal/resource/obtenant/obtenant_flow.go index 4f5eda490..973481b65 100644 --- a/internal/resource/obtenant/obtenant_flow.go +++ b/internal/resource/obtenant/obtenant_flow.go @@ -18,16 +18,16 @@ import ( tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) -func CreateTenant() *tasktypes.TaskFlow { +func genCreateTenantFlow(_ *OBTenantManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fCreateTenant, Tasks: []tasktypes.TaskName{ tCheckTenant, - tCheckPoolAndUnitConfig, - tCreateResourcePoolAndUnitConfig, - tCreateTenant, - tCreateUsersByCredentials, + tCheckPoolAndConfig, + tCreateResourcePoolAndConfig, + tCreateTenantWithClear, + tCreateUserWithCredentialSecrets, }, TargetStatus: tenantstatus.Running, OnFailure: tasktypes.FailureRule{ @@ -37,61 +37,61 @@ func CreateTenant() *tasktypes.TaskFlow { } } -func MaintainWhiteList() *tasktypes.TaskFlow { +func genMaintainWhiteListFlow(_ *OBTenantManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fMaintainWhiteList, - Tasks: []tasktypes.TaskName{tMaintainWhiteList}, + Tasks: []tasktypes.TaskName{tCheckAndApplyWhiteList}, TargetStatus: tenantstatus.Running, }, } } -func MaintainCharset() *tasktypes.TaskFlow { +func genMaintainCharsetFlow(_ *OBTenantManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fMaintainCharset, - Tasks: []tasktypes.TaskName{tMaintainCharset}, + Tasks: []tasktypes.TaskName{tCheckAndApplyCharset}, TargetStatus: tenantstatus.Running, }, } } -func MaintainUnitNum() *tasktypes.TaskFlow { +func genMaintainUnitNumFlow(_ *OBTenantManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fMaintainUnitNum, - Tasks: []tasktypes.TaskName{tMaintainUnitNum}, + Tasks: []tasktypes.TaskName{tCheckAndApplyUnitNum}, TargetStatus: tenantstatus.Running, }, } } -func MaintainLocality() *tasktypes.TaskFlow { +func genMaintainLocalityFlow(_ *OBTenantManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fMaintainLocality, - Tasks: []tasktypes.TaskName{tMaintainLocality}, + Tasks: []tasktypes.TaskName{tCheckAndApplyLocality}, TargetStatus: tenantstatus.Running, }, } } -func MaintainPrimaryZone() *tasktypes.TaskFlow { +func genMaintainPrimaryZoneFlow(_ *OBTenantManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fMaintainPrimaryZone, - Tasks: []tasktypes.TaskName{tMaintainPrimaryZone}, + Tasks: []tasktypes.TaskName{tCheckAndApplyPrimaryZone}, TargetStatus: tenantstatus.Running, }, } } -func AddPool() *tasktypes.TaskFlow { +func genAddPoolFlow(_ *OBTenantManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fAddPool, - Tasks: []tasktypes.TaskName{tCheckPoolAndUnitConfig, tAddResourcePool}, + Tasks: []tasktypes.TaskName{tCheckPoolAndConfig, tAddPool}, TargetStatus: tenantstatus.Running, OnFailure: tasktypes.FailureRule{ Strategy: strategy.RetryFromCurrent, @@ -100,17 +100,17 @@ func AddPool() *tasktypes.TaskFlow { } } -func DeletePool() *tasktypes.TaskFlow { +func genDeletePoolFlow(_ *OBTenantManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fDeletePool, - Tasks: []tasktypes.TaskName{tDeleteResourcePool}, + Tasks: []tasktypes.TaskName{tDeletePool}, TargetStatus: tenantstatus.Running, }, } } -func MaintainUnitConfig() *tasktypes.TaskFlow { +func genMaintainUnitConfigFlow(_ *OBTenantManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fMaintainUnitConfig, @@ -120,7 +120,7 @@ func MaintainUnitConfig() *tasktypes.TaskFlow { } } -func DeleteTenant() *tasktypes.TaskFlow { +func genDeleteTenantFlow(_ *OBTenantManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fDeleteTenant, @@ -133,18 +133,18 @@ func DeleteTenant() *tasktypes.TaskFlow { } } -func RestoreTenant() *tasktypes.TaskFlow { +func genRestoreTenantFlow(_ *OBTenantManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fRestoreTenant, Tasks: []tasktypes.TaskName{ tCheckTenant, - tCheckPoolAndUnitConfig, - tCreateResourcePoolAndUnitConfig, - tCreateRestoreJobCR, + tCheckPoolAndConfig, + tCreateResourcePoolAndConfig, + tCreateTenantRestoreJobCR, tWatchRestoreJobToFinish, - tMaintainWhiteList, - tCreateUsersByCredentials, + tCheckAndApplyWhiteList, + tCreateUserWithCredentialSecrets, }, TargetStatus: tenantstatus.Running, OnFailure: tasktypes.FailureRule{ @@ -154,29 +154,29 @@ func RestoreTenant() *tasktypes.TaskFlow { } } -func CancelRestoreJob() *tasktypes.TaskFlow { +func genCancelRestoreFlow(_ *OBTenantManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ - Name: fCancelRestoreFlow, + Name: fCancelRestore, Tasks: []tasktypes.TaskName{ - tCancelRestoreJob, + tCancelTenantRestoreJob, }, TargetStatus: tenantstatus.RestoreCanceled, }, } } -func CreateEmptyStandbyTenant() *tasktypes.TaskFlow { +func genCreateEmptyStandbyTenantFlow(_ *OBTenantManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fCreateEmptyStandbyTenant, Tasks: []tasktypes.TaskName{ tCheckPrimaryTenantLSIntegrity, tCheckTenant, - tCheckPoolAndUnitConfig, - tCreateResourcePoolAndUnitConfig, + tCheckPoolAndConfig, + tCreateResourcePoolAndConfig, tCreateEmptyStandbyTenant, - tMaintainWhiteList, + tCheckAndApplyWhiteList, }, TargetStatus: tenantstatus.Running, OnFailure: tasktypes.FailureRule{ diff --git a/internal/resource/obtenant/obtenant_manager.go b/internal/resource/obtenant/obtenant_manager.go index 1dd8bfd31..cd0ba2cb0 100644 --- a/internal/resource/obtenant/obtenant_manager.go +++ b/internal/resource/obtenant/obtenant_manager.go @@ -37,14 +37,14 @@ import ( "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/const/status/tenant" "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/model" "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/operation" - "github.com/oceanbase/ob-operator/pkg/task" taskstatus "github.com/oceanbase/ob-operator/pkg/task/const/status" "github.com/oceanbase/ob-operator/pkg/task/const/strategy" tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) +var _ opresource.ResourceManager = &OBTenantManager{} + type OBTenantManager struct { - opresource.ResourceManager OBTenant *v1alpha1.OBTenant Ctx context.Context Client client.Client @@ -75,7 +75,8 @@ func (m *OBTenantManager) IsNewResource() bool { } func (m *OBTenantManager) IsDeleting() bool { - return !m.OBTenant.ObjectMeta.DeletionTimestamp.IsZero() + ignoreDel, ok := resourceutils.GetAnnotationField(m.OBTenant, oceanbaseconst.AnnotationsIgnoreDeletion) + return !m.OBTenant.ObjectMeta.DeletionTimestamp.IsZero() && (!ok || ignoreDel != "true") } func (m *OBTenantManager) GetStatus() string { @@ -225,50 +226,7 @@ func (m *OBTenantManager) CheckAndUpdateFinalizers() error { } func (m *OBTenantManager) GetTaskFunc(name tasktypes.TaskName) (tasktypes.TaskFunc, error) { - switch name { - case tCheckTenant: - return m.CheckTenantTask, nil - case tCheckPoolAndUnitConfig: - return m.CheckPoolAndConfigTask, nil - case tCreateTenant: - return m.CreateTenantTaskWithClear, nil - case tCreateResourcePoolAndUnitConfig: - return m.CreateResourcePoolAndConfigTask, nil - case tMaintainCharset: - return m.CheckAndApplyCharset, nil - case tMaintainUnitNum: - return m.CheckAndApplyUnitNum, nil - case tMaintainWhiteList: - return m.CheckAndApplyWhiteList, nil - case tMaintainPrimaryZone: - return m.CheckAndApplyPrimaryZone, nil - case tMaintainLocality: - return m.CheckAndApplyLocality, nil - case tAddResourcePool: - return m.AddPoolTask, nil - case tDeleteResourcePool: - return m.DeletePoolTask, nil - case tMaintainUnitConfig: - return m.MaintainUnitConfigTask, nil - case tDeleteTenant: - return m.DeleteTenantTask, nil - case tCreateEmptyStandbyTenant: - return m.CreateEmptyStandbyTenant, nil - case tCreateUsersByCredentials: - return m.CreateUserWithCredentialSecrets, nil - case tCheckPrimaryTenantLSIntegrity: - return m.CheckPrimaryTenantLSIntegrity, nil - case tCreateRestoreJobCR: - return m.CreateTenantRestoreJobCR, nil - case tWatchRestoreJobToFinish: - return m.WatchRestoreJobToFinish, nil - case tCancelRestoreJob: - return m.CancelTenantRestoreJob, nil - case tUpgradeTenantIfNeeded: - return m.UpgradeTenantIfNeeded, nil - default: - return nil, errors.Errorf("Can not find an function for task %s", name) - } + return taskMap.GetTask(name, m) } func (m *OBTenantManager) GetTaskFlow() (*tasktypes.TaskFlow, error) { @@ -280,58 +238,53 @@ func (m *OBTenantManager) GetTaskFlow() (*tasktypes.TaskFlow, error) { m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Create task flow according to obtenant status") var taskFlow *tasktypes.TaskFlow - var err error switch m.OBTenant.Status.Status { case tenantstatus.CreatingTenant: m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Get task flow when creating tenant") - taskFlow, err = task.GetRegistry().Get(fCreateTenant) + taskFlow = genCreateTenantFlow(m) case tenantstatus.MaintainingWhiteList: m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Get task flow when obtenant maintaining white list") - taskFlow, err = task.GetRegistry().Get(fMaintainWhiteList) + taskFlow = genMaintainWhiteListFlow(m) case tenantstatus.MaintainingCharset: m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Get task flow when obtenant maintaining charset") - taskFlow, err = task.GetRegistry().Get(fMaintainCharset) + taskFlow = genMaintainCharsetFlow(m) case tenantstatus.MaintainingUnitNum: m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Get task flow when obtenant maintaining unit num") - taskFlow, err = task.GetRegistry().Get(fMaintainUnitNum) + taskFlow = genMaintainUnitNumFlow(m) case tenantstatus.MaintainingPrimaryZone: m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Get task flow when obtenant maintaining primary zone") - taskFlow, err = task.GetRegistry().Get(fMaintainPrimaryZone) + taskFlow = genMaintainPrimaryZoneFlow(m) case tenantstatus.MaintainingLocality: m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Get task flow when obtenant maintaining locality") - taskFlow, err = task.GetRegistry().Get(fMaintainLocality) + taskFlow = genMaintainLocalityFlow(m) case tenantstatus.AddingResourcePool: m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Get task flow when obtenant adding pool") - taskFlow, err = task.GetRegistry().Get(fAddPool) + taskFlow = genAddPoolFlow(m) case tenantstatus.DeletingResourcePool: m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Get task flow when obtenant deleting list") - taskFlow, err = task.GetRegistry().Get(fDeletePool) + taskFlow = genDeletePoolFlow(m) case tenantstatus.MaintainingUnitConfig: m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Get task flow when obtenant maintaining unit config") - taskFlow, err = task.GetRegistry().Get(fMaintainUnitConfig) + taskFlow = genMaintainUnitConfigFlow(m) case tenantstatus.DeletingTenant: m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Get task flow when deleting tenant") - taskFlow, err = task.GetRegistry().Get(fDeleteTenant) + taskFlow = genDeleteTenantFlow(m) case tenantstatus.PausingReconcile: m.Logger.Error(errors.New("Obtenant pause reconcile"), "obtenant pause reconcile, please set status to running after manually resolving problem") return nil, nil case tenantstatus.Restoring: - taskFlow, err = task.GetRegistry().Get(fRestoreTenant) + taskFlow = genRestoreTenantFlow(m) case tenantstatus.CancelingRestore: - taskFlow, err = task.GetRegistry().Get(fCancelRestoreFlow) + taskFlow = genCancelRestoreFlow(m) case tenantstatus.CreatingEmptyStandby: - taskFlow, err = task.GetRegistry().Get(fCreateEmptyStandbyTenant) + taskFlow = genCreateEmptyStandbyTenantFlow(m) default: m.Logger.V(oceanbaseconst.LogLevelTrace).Info("No need to run anything for obtenant") return nil, nil } - if err != nil { - return nil, err - } - if taskFlow.OperationContext.OnFailure.Strategy == "" { taskFlow.OperationContext.OnFailure.Strategy = strategy.StartOver if taskFlow.OperationContext.OnFailure.NextTryStatus == "" { diff --git a/internal/resource/obtenant/obtenant_task.go b/internal/resource/obtenant/obtenant_task.go index 726733dfd..1f9649710 100644 --- a/internal/resource/obtenant/obtenant_task.go +++ b/internal/resource/obtenant/obtenant_task.go @@ -3,30 +3,26 @@ 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 + + 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 obtenant import ( "fmt" "reflect" - "sort" - "strconv" - "strings" "time" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" - kubeerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" - "k8s.io/apimachinery/pkg/util/rand" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/oceanbase/ob-operator/api/constants" @@ -36,62 +32,15 @@ import ( "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/const/config" "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/const/status/tenant" "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/model" - "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/operation" + "github.com/oceanbase/ob-operator/pkg/task/builder" tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) -// ---------- task entry point ---------- - -func (m *OBTenantManager) CreateTenantTaskWithClear() tasktypes.TaskError { - err := m.CreateTenantTask() - // clean created resource, restore to the initial state - if err != nil { - err := m.DeleteTenantTask() - if err != nil { - err = errors.Wrapf(err, "delete tenant when creating tenant") - return err - } - } - return err -} - -func (m *OBTenantManager) CreateResourcePoolAndConfigTaskWithClear() tasktypes.TaskError { - err := m.CreateResourcePoolAndConfigTask() - // clean created resource, restore to the initial state - if err != nil { - err := m.DeleteTenantTask() - if err != nil { - err = errors.Wrapf(err, "delete tenant when creating tenant") - return err - } - } - return err -} - -func (m *OBTenantManager) CreateTenantTask() tasktypes.TaskError { - tenantName := m.OBTenant.Spec.TenantName - err := m.createTenant() - if err != nil { - m.Logger.Error(err, "Create Tenant failed", "tenantName", tenantName) - return err - } - return nil -} - -func (m *OBTenantManager) CreateResourcePoolAndConfigTask() tasktypes.TaskError { - tenantName := m.OBTenant.Spec.TenantName +//go:generate task-register $GOFILE - for _, pool := range m.OBTenant.Spec.Pools { - err := m.createUnitAndPoolV4(pool) - if err != nil { - m.Logger.Error(err, "Create Tenant failed", "tenantName", tenantName) - return err - } - } - return nil -} +var taskMap = builder.NewTaskHub[*OBTenantManager]() -func (m *OBTenantManager) CheckTenantTask() tasktypes.TaskError { +func CheckTenant(m *OBTenantManager) tasktypes.TaskError { tenantName := m.OBTenant.Spec.TenantName tenantExist, err := m.tenantExist(tenantName) if err != nil { @@ -106,7 +55,7 @@ func (m *OBTenantManager) CheckTenantTask() tasktypes.TaskError { return nil } -func (m *OBTenantManager) CheckPoolAndConfigTask() tasktypes.TaskError { +func CheckPoolAndConfig(m *OBTenantManager) tasktypes.TaskError { tenantName := m.OBTenant.Spec.TenantName client, err := m.getClusterSysClient() if err != nil { @@ -119,1100 +68,150 @@ func (m *OBTenantManager) CheckPoolAndConfigTask() tasktypes.TaskError { if len(params) == 0 { return errors.New("Getting parameter __min_full_resource_pool_memory returns empty result") } - minPoolMemory := params[0].Value - minPoolMemoryQuant, err := resource.ParseQuantity(minPoolMemory) - if err != nil { - return errors.Wrap(err, fmt.Sprintf("Parse quantity when checking and applying tenant '%s' pool and config", tenantName)) - } - for _, pool := range m.OBTenant.Spec.Pools { - unitName := m.generateUnitName(pool.Zone) - poolName := m.generatePoolName(pool.Zone) - poolExist, err := m.poolExist(poolName) - if err != nil { - m.Logger.Error(err, "Check resource pool exist", "tenantName", tenantName, "poolName", poolName) - return err - } - if poolExist { - return err - } - - unitExist, err := m.unitConfigV4Exist(unitName) - if err != nil { - m.Logger.Error(err, "Check unit config exist failed", "tenantName", tenantName, "unitName", unitName) - return err - } - if unitExist { - return err - } - if pool.UnitConfig.MemorySize.Cmp(minPoolMemoryQuant) < 0 { - err = errors.New("pool memory size is less than min_full_resource_pool_memory") - m.Logger.Error(err, "Check pool memory size", "tenantName", tenantName, "poolName", poolName) - return err - } - } - return nil -} - -func (m *OBTenantManager) MaintainWhiteListTask() tasktypes.TaskError { - tenantName := m.OBTenant.Spec.TenantName - err := m.CheckAndApplyWhiteList() - if err != nil { - m.Logger.Error(err, "maintain tenant, check and set whitelist (tcp invited node)", "tenantName", tenantName) - return err - } - return nil -} - -func (m *OBTenantManager) AddPoolTask() tasktypes.TaskError { - // handle add pool - poolSpecs := m.getPoolsForAdd() - for _, addPool := range poolSpecs { - err := m.tenantAddPool(addPool) - if err != nil { - return err - } - } - return nil -} - -func (m *OBTenantManager) DeletePoolTask() tasktypes.TaskError { - // handle delete pool - poolStatuses := m.getPoolsForDelete() - for _, poolStatus := range poolStatuses { - err := m.TenantDeletePool(poolStatus) - if err != nil { - return err - } - } - return nil -} - -func (m *OBTenantManager) MaintainUnitConfigTask() tasktypes.TaskError { - tenantName := m.OBTenant.Spec.TenantName - - version, err := m.getOBVersion() - if err != nil { - m.Logger.Error(err, "maintain tenant failed, check and apply unitConfigV4", "tenantName", tenantName) - return err - } - if string(version[0]) == tenant.Version4 { - return m.CheckAndApplyUnitConfigV4() - } - return errors.New("no match version for check and set unit config") -} - -func (m *OBTenantManager) DeleteTenantTask() tasktypes.TaskError { - var err error - tenantName := m.OBTenant.Spec.TenantName - m.Logger.Info("Delete Tenant", "tenantName", tenantName) - err = m.deleteTenant() - if err != nil { - return err - } - m.Logger.Info("Delete Pool", "tenantName", tenantName) - err = m.deletePool() - if err != nil { - return err - } - m.Logger.Info("Delete Unit", "tenantName", tenantName) - err = m.deleteUnitConfig() - if err != nil { - return err - } - m.Logger.Info("Delete Tenant Success", "tenantName", tenantName) - return nil -} - -func (m *OBTenantManager) AddFinalizerTask() tasktypes.TaskError { - return nil -} - -// ---------- Check And Apply function ---------- - -func (m *OBTenantManager) CheckAndApplyWhiteList() tasktypes.TaskError { - tenantName := m.OBTenant.Spec.TenantName - oceanbaseOperationManager, err := m.getClusterSysClient() - if err != nil { - return errors.Wrap(err, fmt.Sprint("Get Sql Operator When Checking And Applying ob_tcp_invited_nodes For Tenant ", tenantName)) - } - - specWhiteList := m.OBTenant.Spec.ConnectWhiteList - statusWhiteList := m.OBTenant.Status.TenantRecordInfo.ConnectWhiteList - - if specWhiteList == "" { - specWhiteList = tenant.DefaultOBTcpInvitedNodes - } - if statusWhiteList != specWhiteList { - m.Logger.Info("Found specWhiteList didn't match", "tenantName", tenantName, - "statusWhiteList", statusWhiteList, "specWhiteList", specWhiteList) - variableList := m.generateWhiteListInVariableForm(specWhiteList) - err = oceanbaseOperationManager.SetTenantVariable(tenantName, variableList) - if err != nil { - return err - } - // TODO: get whitelist variable by tenant account - // Because getting a whitelist requires specifying a tenant , temporary use .Status.TenantRecordInfo.ConnectWhiteList as value in db - tenantWhiteListMap.Store(tenantName, specWhiteList) - } - return nil -} - -func (m *OBTenantManager) CheckAndApplyUnitConfigV4() tasktypes.TaskError { - tenantName := m.OBTenant.Spec.TenantName - specUnitConfigMap := m.generateSpecUnitConfigV4Map(m.OBTenant.Spec) - statusUnitConfigMap := m.GenerateStatusUnitConfigV4Map(m.OBTenant.Status) - for _, pool := range m.OBTenant.Spec.Pools { - match := true - specUnitConfig := specUnitConfigMap[pool.Zone] - statusUnitConfig, statusExist := statusUnitConfigMap[pool.Zone] - - // If status does not exist, Continue to check UnitConfig of the next ResourcePool - // while Add and delete a pool in the CheckAndApplyResourcePool - if !statusExist { - continue - } - - if !IsUnitConfigV4Equal(specUnitConfig, statusUnitConfig) { - m.Logger.Info("Found unit config v4 didn't match", "tenantName", tenantName, "zoneName", pool.Zone, - "statusUnitConfig", FormatUnitConfigV4(statusUnitConfigMap[pool.Zone]), "specUnitConfig", FormatUnitConfigV4(specUnitConfigMap[pool.Zone])) - match = false - } - if !match { - unitName := m.generateUnitName(pool.Zone) - err := m.setUnitConfigV4(unitName, specUnitConfigMap[pool.Zone]) - if err != nil { - m.Logger.Error(err, "Set Tenant Unit failed", "tenantName", tenantName, "unitName", unitName) - return err - } - } - } - return nil -} - -func (m *OBTenantManager) CheckAndApplyUnitNum() tasktypes.TaskError { - tenantName := m.OBTenant.Spec.TenantName - oceanbaseOperationManager, err := m.getClusterSysClient() - if err != nil { - return errors.Wrap(err, fmt.Sprint("Get Sql Operator When Checking And Applying Tenant UnitNum", tenantName)) - } - - if m.OBTenant.Spec.UnitNumber != m.OBTenant.Status.TenantRecordInfo.UnitNumber { - err = oceanbaseOperationManager.SetTenantUnitNum(tenantName, m.OBTenant.Spec.UnitNumber) - if err != nil { - return err - } - } - return nil -} - -func (m *OBTenantManager) CheckAndApplyPrimaryZone() tasktypes.TaskError { - tenantName := m.OBTenant.Spec.TenantName - oceanbaseOperationManager, err := m.getClusterSysClient() - if err != nil { - return errors.Wrap(err, fmt.Sprintf("Get Sql Operator When Prcoessing Tenant '%s' Priority ", tenantName)) - } - - specPrimaryZone := m.generateSpecPrimaryZone(m.OBTenant.Spec.Pools) - specPrimaryZoneMap := m.generatePrimaryZoneMap(specPrimaryZone) - statusPrimaryZone := m.generateStatusPrimaryZone(m.OBTenant.Status.Pools) - statusPrimaryZoneMap := m.generatePrimaryZoneMap(statusPrimaryZone) - if !reflect.DeepEqual(specPrimaryZoneMap, statusPrimaryZoneMap) { - tenantSQLParam := model.TenantSQLParam{ - TenantName: tenantName, - PrimaryZone: specPrimaryZone, - } - err = oceanbaseOperationManager.SetTenant(tenantSQLParam) - if err != nil { - return err - } - } - return nil -} - -func (m *OBTenantManager) CheckAndApplyLocality() tasktypes.TaskError { - tenantName := m.OBTenant.Spec.TenantName - oceanbaseOperationManager, err := m.getClusterSysClient() - if err != nil { - return errors.Wrap(err, fmt.Sprintf("Get Sql Operator When Prcoessing Tenant '%s' Locality ", tenantName)) - } - specLocalityMap := m.generateSpecLocalityMap(m.OBTenant.Spec.Pools) - statusLocalityMap := m.generateStatusLocalityMap(m.OBTenant.Status.Pools) - if !reflect.DeepEqual(specLocalityMap, statusLocalityMap) { - locality := m.generateLocality(m.OBTenant.Spec.Pools) - tenantSQLParam := model.TenantSQLParam{ - TenantName: tenantName, - Locality: locality, - } - err = oceanbaseOperationManager.SetTenant(tenantSQLParam) - if err != nil { - return err - } - } - m.Logger.V(oceanbaseconst.LogLevelDebug).Info("Wait For Tenant 'ALTER_TENANT' Job for addPool Finished", "tenantName", tenantName) - for { - exist, err := oceanbaseOperationManager.CheckRsJobExistByTenantID(m.OBTenant.Status.TenantRecordInfo.TenantID) - if err != nil { - return errors.Wrap(err, fmt.Sprintf("Get RsJob %s", tenantName)) - } - if !exist { - break - } - time.Sleep(config.PollingJobSleepTime) - } - m.Logger.V(oceanbaseconst.LogLevelDebug).Info("'ALTER_TENANT' Job for addPool successes", "tenantName", tenantName) - return nil -} - -func (m *OBTenantManager) CheckAndApplyCharset() tasktypes.TaskError { - tenantName := m.OBTenant.Spec.TenantName - oceanbaseOperationManager, err := m.getClusterSysClient() - if err != nil { - return errors.Wrap(err, fmt.Sprintf("Get Sql Operator When Checking and Applying Tenant '%s' Charset ", tenantName)) - } - specCharset := m.OBTenant.Spec.Charset - if specCharset == "" { - specCharset = tenant.Charset - } - if specCharset != m.OBTenant.Status.TenantRecordInfo.Charset { - tenantSQLParam := model.TenantSQLParam{ - TenantName: tenantName, - Charset: specCharset, - } - err = oceanbaseOperationManager.SetTenant(tenantSQLParam) - if err != nil { - return err - } - } - return nil -} - -// ---------- action function ---------- - -func (m *OBTenantManager) createTenant() tasktypes.TaskError { - tenantName := m.OBTenant.Spec.TenantName - pools := m.OBTenant.Spec.Pools - m.Logger.Info("Create Tenant", "tenantName", tenantName) - oceanbaseOperationManager, err := m.getClusterSysClient() - if err != nil { - return errors.Wrap(err, "Get Sql Operator Error When Creating Tenant") - } - - tenantSQLParam := model.TenantSQLParam{ - TenantName: tenantName, - PrimaryZone: m.generateSpecPrimaryZone(pools), - VariableList: m.generateWhiteListInVariableForm(m.OBTenant.Spec.ConnectWhiteList), - Charset: m.OBTenant.Spec.Charset, - PoolList: m.generateSpecPoolList(pools), - Locality: m.generateLocality(pools), - Collate: m.OBTenant.Spec.Collate, - } - if tenantSQLParam.Charset == "" { - tenantSQLParam.Charset = tenant.Charset - } - - err = oceanbaseOperationManager.AddTenant(tenantSQLParam) - if err != nil { - m.Recorder.Event(m.OBTenant, corev1.EventTypeWarning, "failed to create OBTenant", err.Error()) - return err - } - tenantWhiteListMap.Store(tenantName, m.OBTenant.Spec.ConnectWhiteList) - // Create user or change password of root, do not return error - m.Recorder.Event(m.OBTenant, "Create", "", "create OBTenant successfully") - return nil -} - -func (m *OBTenantManager) createUnitConfigV4(unitName string, unitConfig *v1alpha1.UnitConfig) error { - tenantName := m.OBTenant.Spec.TenantName - m.Logger.Info("Create UnitConfig", "tenantName", tenantName, "unitName", unitName) - unitModel := m.generateModelUnitConfigV4SQLParam(unitName, m.generateModelUnitConfigV4(unitConfig)) - if unitModel.MemorySize == 0 { - err := errors.New("unit memorySize is empty") - m.Logger.Error(err, "unit memorySize cannot be zero", "tenantName", tenantName, "unitName", unitName) - return err - } - oceanbaseOperationManager, err := m.getClusterSysClient() - if err != nil { - return errors.Wrap(err, "Get Sql Operator Error When Creating Resource UnitConfigV4") - } - - return oceanbaseOperationManager.AddUnitConfigV4(unitModel) -} - -func (m *OBTenantManager) setUnitConfigV4(unitName string, unitConfig *model.UnitConfigV4) error { - tenantName := m.OBTenant.Spec.TenantName - oceanbaseOperationManager, err := m.getClusterSysClient() - unitModel := m.generateModelUnitConfigV4SQLParam(unitName, unitConfig) - if err != nil { - return errors.Wrap(err, fmt.Sprint("Get Sql Operator When Checking And Setting Unit Config For Tenant ", tenantName)) - } - return oceanbaseOperationManager.SetUnitConfigV4(unitModel) -} - -func (m *OBTenantManager) getPoolsForAdd() []v1alpha1.ResourcePoolSpec { - var pools []v1alpha1.ResourcePoolSpec - for _, specZone := range m.OBTenant.Spec.Pools { - exist := false - for _, statusZone := range m.OBTenant.Status.Pools { - if statusZone.ZoneList == specZone.Zone { - exist = true - } - } - if !exist { - pools = append(pools, specZone) - } - } - return pools -} - -func (m *OBTenantManager) getPoolsForDelete() []v1alpha1.ResourcePoolStatus { - var poolStatuses []v1alpha1.ResourcePoolStatus - for _, statusPool := range m.OBTenant.Status.Pools { - exist := false - for _, specPool := range m.OBTenant.Spec.Pools { - if statusPool.ZoneList == specPool.Zone { - exist = true - } - } - if !exist { - poolStatuses = append(poolStatuses, statusPool) - } - } - return poolStatuses -} - -func (m *OBTenantManager) tenantAddPool(poolAdd v1alpha1.ResourcePoolSpec) error { - tenantName := m.OBTenant.Spec.TenantName - oceanbaseOperationManager, err := m.getClusterSysClient() - if err != nil { - return errors.Wrap(err, fmt.Sprintf("Get Sql Operator When Prcoessing Tenant '%s' -- Add Pool", tenantName)) - } - - // step 1: create unit and poolAdd - err = m.createUnitAndPoolV4(poolAdd) - if err != nil { - return err - } - - // step 2.1: update locality and resource poolAdd list - poolStatusAdd := v1alpha1.ResourcePoolStatus{ - ZoneList: poolAdd.Zone, - Type: poolAdd.Type, - UnitNumber: m.OBTenant.Spec.UnitNumber, - } - resourcePoolStatusList := m.OBTenant.Status.Pools - resourcePoolStatusList = append(resourcePoolStatusList, poolStatusAdd) - statusLocalityMap := m.generateStatusLocalityMap(resourcePoolStatusList) - localityList := m.generateLocalityList(statusLocalityMap) - poolList := m.generateStatusPoolList(resourcePoolStatusList) - specPrimaryZone := m.generateSpecPrimaryZone(m.OBTenant.Spec.Pools) - - tenantSQLParam := model.TenantSQLParam{ - TenantName: tenantName, - Locality: strings.Join(localityList, ","), - PoolList: poolList, - PrimaryZone: specPrimaryZone, - } - err = oceanbaseOperationManager.SetTenant(tenantSQLParam) - if err != nil { - return err - } - - // step 2.2: Wait for task finished - m.Logger.V(oceanbaseconst.LogLevelDebug).Info("Wait For Tenant 'ALTER_TENANT' Job for addPool Finished", "tenantName", tenantName) - for { - exist, err := oceanbaseOperationManager.CheckRsJobExistByTenantID(m.OBTenant.Status.TenantRecordInfo.TenantID) - if err != nil { - return errors.Wrap(err, fmt.Sprintf("Get RsJob %s", tenantName)) - } - if !exist { - break - } - time.Sleep(config.PollingJobSleepTime) - } - m.Logger.V(oceanbaseconst.LogLevelDebug).Info("'ALTER_TENANT' Job for addPool successes", "tenantName", tenantName) - - m.Logger.V(oceanbaseconst.LogLevelDebug).Info("Succeed add poolAdd", "deleted poolName", poolAdd.Zone) - return nil -} - -func (m *OBTenantManager) TenantDeletePool(poolDelete v1alpha1.ResourcePoolStatus) error { - tenantName := m.OBTenant.Spec.TenantName - poolName := m.generatePoolName(poolDelete.ZoneList) - unitName := m.generateUnitName(poolDelete.ZoneList) - - oceanbaseOperationManager, err := m.getClusterSysClient() - if err != nil { - return errors.Wrap(err, fmt.Sprintf("Get Sql Operator When Prcoessing Tenant '%s' -- Delete Pool ", tenantName)) - } - var zoneList []v1alpha1.ResourcePoolStatus - for _, zone := range m.OBTenant.Status.Pools { - if zone.ZoneList != poolDelete.ZoneList { - zoneList = append(zoneList, zone) - } - } - - statusLocalityMap := m.generateStatusLocalityMap(zoneList) - localityList := m.generateLocalityList(statusLocalityMap) - poolList := m.generateStatusPoolList(zoneList) - specPrimaryZone := m.generateSpecPrimaryZone(m.OBTenant.Spec.Pools) - - // step 1.1: update locality - // note:this operator is async in oceanbase, polling until update locality task success - tenantSQLParam := model.TenantSQLParam{ - TenantName: tenantName, - Locality: strings.Join(localityList, ","), - } - err = oceanbaseOperationManager.SetTenant(tenantSQLParam) - if err != nil { - m.Logger.Error(err, "Modify Tenant, update locality", "tenantName", tenantName) - return err - } - - m.Logger.V(oceanbaseconst.LogLevelDebug).Info("Wait For Tenant 'ALTER_TENANT' Job for deletePool Finished", "tenantName", tenantName) - - for { - exist, err := oceanbaseOperationManager.CheckRsJobExistByTenantID(m.OBTenant.Status.TenantRecordInfo.TenantID) - if err != nil { - return errors.Wrap(err, fmt.Sprintf("Get RsJob %s", tenantName)) - } - if !exist { - break - } - time.Sleep(config.PollingJobSleepTime) - } - - // step 1.2: update resource pool list - tenantSQLParam = model.TenantSQLParam{ - TenantName: tenantName, - PoolList: poolList, - PrimaryZone: specPrimaryZone, - } - err = oceanbaseOperationManager.SetTenant(tenantSQLParam) - if err != nil { - m.Logger.Error(err, "Modify Tenant, update poolList", "tenantName", tenantName) - return err - } - // step 2: delete resource pool - poolExist, err := m.poolExist(poolName) - if err != nil { - m.Logger.Error(err, "Check ResourcePool exist", "poolName", poolName) - return err - } - if poolExist { - err = oceanbaseOperationManager.DeletePool(poolName) - if err != nil { - return err - } - } - - // step 3: delete unit - unitExist, err := m.unitConfigV4Exist(unitName) - if err != nil { - m.Logger.Error(err, "Check UnitConfigV4 Exist", "unitName", unitName) - return err - } - if unitExist { - err = oceanbaseOperationManager.DeleteUnitConfig(unitName) - if err != nil { - return err - } - } - m.Logger.Info("Succeed delete pool", "deleted poolName", poolDelete.ZoneList) - return nil -} - -// ---------- compare helper function ---------- - -func IsUnitConfigV4Equal(specUnitConfig *model.UnitConfigV4, statusUnitConfig *model.UnitConfigV4) bool { - if specUnitConfig.MaxCPU == statusUnitConfig.MaxCPU && - specUnitConfig.MemorySize == statusUnitConfig.MemorySize { - if (specUnitConfig.MinIops != 0 && specUnitConfig.MinIops != statusUnitConfig.MinIops) || - (specUnitConfig.MaxIops != 0 && specUnitConfig.MaxIops != statusUnitConfig.MaxIops) || - (specUnitConfig.MinCPU != 0 && specUnitConfig.MinCPU != statusUnitConfig.MinCPU) || - (specUnitConfig.LogDiskSize != 0 && specUnitConfig.LogDiskSize != statusUnitConfig.LogDiskSize) || - (specUnitConfig.IopsWeight != 0 && specUnitConfig.IopsWeight != statusUnitConfig.IopsWeight) { - return false - } - return true - } - return false -} - -func FormatUnitConfigV4(unit *model.UnitConfigV4) string { - return fmt.Sprintf("MaxCPU: %f MinCPU:%f MemorySize:%d MaxIops:%d MinIops:%d IopsWeight:%d LogDiskSize:%d", - unit.MaxCPU, unit.MinCPU, unit.MemorySize, unit.MaxIops, unit.MinIops, unit.IopsWeight, unit.LogDiskSize) -} - -// ---------- generate "zoneName-value" map function ---------- - -func (m *OBTenantManager) generatePrimaryZoneMap(str string) map[int][]string { - res := make(map[int][]string, 0) - levelCuts := strings.Split(str, ";") - for idx, levelCut := range levelCuts { - cut := strings.Split(levelCut, ",") - res[idx] = cut - sort.Strings(res[idx]) - } - return res -} - -func (m *OBTenantManager) generateSpecUnitConfigV4Map(spec v1alpha1.OBTenantSpec) map[string]*model.UnitConfigV4 { - var unitConfigMap = make(map[string]*model.UnitConfigV4, 0) - for _, pool := range spec.Pools { - unitConfigMap[pool.Zone] = m.generateModelUnitConfigV4(pool.UnitConfig) - } - return unitConfigMap -} - -func (m *OBTenantManager) GenerateStatusUnitConfigV4Map(status v1alpha1.OBTenantStatus) map[string]*model.UnitConfigV4 { - var unitConfigMap = make(map[string]*model.UnitConfigV4, 0) - for _, pool := range status.Pools { - unitConfigMap[pool.ZoneList] = m.generateModelUnitConfigV4(pool.UnitConfig) - } - return unitConfigMap -} - -func (m *OBTenantManager) generateModelUnitConfigV4(unitConfig *v1alpha1.UnitConfig) *model.UnitConfigV4 { - var modelUnitConfigV4 model.UnitConfigV4 - modelUnitConfigV4.MaxCPU = unitConfig.MaxCPU.AsApproximateFloat64() - modelUnitConfigV4.MinCPU = unitConfig.MinCPU.AsApproximateFloat64() - modelUnitConfigV4.MaxIops = int64(unitConfig.MaxIops) - modelUnitConfigV4.MinIops = int64(unitConfig.MinIops) - modelUnitConfigV4.IopsWeight = int64(unitConfig.IopsWeight) - modelUnitConfigV4.MemorySize = unitConfig.MemorySize.Value() - modelUnitConfigV4.LogDiskSize = unitConfig.LogDiskSize.Value() - return &modelUnitConfigV4 -} - -func (m *OBTenantManager) generateModelUnitConfigV4SQLParam(unitConfigName string, unitConfig *model.UnitConfigV4) *model.UnitConfigV4SQLParam { - var modelUnitConfigV4 model.UnitConfigV4SQLParam - modelUnitConfigV4.UnitConfigName = unitConfigName - modelUnitConfigV4.MaxCPU = unitConfig.MaxCPU - modelUnitConfigV4.MinCPU = unitConfig.MinCPU - modelUnitConfigV4.MaxIops = unitConfig.MaxIops - modelUnitConfigV4.MinIops = unitConfig.MinIops - modelUnitConfigV4.IopsWeight = unitConfig.IopsWeight - modelUnitConfigV4.MemorySize = unitConfig.MemorySize - modelUnitConfigV4.LogDiskSize = unitConfig.LogDiskSize - return &modelUnitConfigV4 -} - -func (m *OBTenantManager) generateSpecUnitNumMap(spec v1alpha1.OBTenantSpec) map[string]int { - var unitNumMap = make(map[string]int, 0) - for _, zone := range spec.Pools { - unitNumMap[zone.Zone] = spec.UnitNumber - } - return unitNumMap -} - -func (m *OBTenantManager) generateSpecLocalityMap(pools []v1alpha1.ResourcePoolSpec) map[string]*v1alpha1.LocalityType { - localityMap := make(map[string]*v1alpha1.LocalityType, 0) - for _, pool := range pools { - localityMap[pool.Zone] = &v1alpha1.LocalityType{ - Name: strings.ToUpper(pool.Type.Name), // locality type in DB is Upper - Replica: pool.Type.Replica, - IsActive: pool.Type.IsActive, - } - } - return localityMap -} - -func (m *OBTenantManager) generateStatusLocalityMap(pools []v1alpha1.ResourcePoolStatus) map[string]*v1alpha1.LocalityType { - localityMap := make(map[string]*v1alpha1.LocalityType, 0) - for _, pool := range pools { - localityMap[pool.ZoneList] = pool.Type - } - return localityMap -} - -func (m *OBTenantManager) generateStatusUnitNumMap(zones []v1alpha1.ResourcePoolSpec) (map[string]int, error) { - unitNumMap := make(map[string]int, 0) - oceanbaseOperationManager, err := m.getClusterSysClient() - if err != nil { - return unitNumMap, errors.Wrap(err, "Get Sql Operator Error When Building Resource Unit From DB") - } - poolList, err := oceanbaseOperationManager.GetPoolList() - if err != nil { - return unitNumMap, errors.Wrap(err, "Get sql error when get pool list") - } - for _, zone := range zones { - poolName := m.generatePoolName(zone.Zone) - for _, pool := range poolList { - if pool.Name == poolName { - unitNumMap[zone.Zone] = int(pool.UnitNum) - } - } - } - return unitNumMap, nil -} - -func (m *OBTenantManager) generateLocalityList(localityMap map[string]*v1alpha1.LocalityType) []string { - var locality []string - var zoneSortList []string - for k := range localityMap { - zoneSortList = append(zoneSortList, k) - } - sort.Sort(sort.Reverse(sort.StringSlice(zoneSortList))) - for _, zoneList := range zoneSortList { - zoneType := localityMap[zoneList] - if zoneType.IsActive { - locality = append(locality, fmt.Sprintf("%s{%d}@%s", zoneType.Name, zoneType.Replica, zoneList)) - } - } - return locality -} - -func (m *OBTenantManager) generateSpecZoneList(pools []v1alpha1.ResourcePoolSpec) []string { - var zoneList []string - for _, pool := range pools { - zoneList = append(zoneList, pool.Zone) - } - return zoneList -} - -func (m *OBTenantManager) generateStatusZoneList(pools []v1alpha1.ResourcePoolStatus) []string { - var zoneList []string - for _, pool := range pools { - zoneList = append(zoneList, pool.ZoneList) - } - return zoneList -} - -func (m *OBTenantManager) generateSpecPoolList(pools []v1alpha1.ResourcePoolSpec) []string { - var poolList []string - for _, pool := range pools { - poolName := m.generatePoolName(pool.Zone) - poolList = append(poolList, poolName) - } - return poolList -} - -func (m *OBTenantManager) generateStatusPoolList(pools []v1alpha1.ResourcePoolStatus) []string { - var poolList []string - for _, pool := range pools { - poolName := m.generatePoolName(pool.ZoneList) - poolList = append(poolList, poolName) - } - return poolList -} - -func (m *OBTenantManager) generateSpecPrimaryZone(pools []v1alpha1.ResourcePoolSpec) string { - var primaryZone string - zoneMap := make(map[int][]string, 0) - var priorityList []int - for _, pool := range pools { - if pool.Type.IsActive { - zones := zoneMap[pool.Priority] - zones = append(zones, pool.Zone) - zoneMap[pool.Priority] = zones - } - } - for k := range zoneMap { - priorityList = append(priorityList, k) - } - sort.Sort(sort.Reverse(sort.IntSlice(priorityList))) - for _, priority := range priorityList { - zones := zoneMap[priority] - primaryZone = fmt.Sprint(primaryZone, strings.Join(zones, ","), ";") - } - return primaryZone -} - -func (m *OBTenantManager) generateStatusPrimaryZone(pools []v1alpha1.ResourcePoolStatus) string { - var primaryZone string - zoneMap := make(map[int][]string, 0) - var priorityList []int - for _, pool := range pools { - if pool.Type.IsActive { - zones := zoneMap[pool.Priority] - zones = append(zones, pool.ZoneList) - zoneMap[pool.Priority] = zones - } - } - for k := range zoneMap { - priorityList = append(priorityList, k) - } - sort.Sort(sort.Reverse(sort.IntSlice(priorityList))) - for _, priority := range priorityList { - zones := zoneMap[priority] - primaryZone = fmt.Sprint(primaryZone, strings.Join(zones, ","), ";") - } - return primaryZone -} - -func (m *OBTenantManager) generateLocality(zones []v1alpha1.ResourcePoolSpec) string { - specLocalityMap := m.generateSpecLocalityMap(zones) - localityList := m.generateLocalityList(specLocalityMap) - return strings.Join(localityList, ",") -} - -func (m *OBTenantManager) generateWhiteListInVariableForm(whiteList string) string { - if whiteList == "" { - return fmt.Sprintf("%s = '%s'", tenant.OBTcpInvitedNodes, tenant.DefaultOBTcpInvitedNodes) - } - return fmt.Sprintf("%s = '%s'", tenant.OBTcpInvitedNodes, whiteList) -} - -func (m *OBTenantManager) generateStatusTypeMapFromLocalityStr(locality string) map[string]v1alpha1.LocalityType { - typeMap := make(map[string]v1alpha1.LocalityType, 0) - typeList := strings.Split(locality, ", ") - for _, type1 := range typeList { - split1 := strings.Split(type1, "@") - typeName := strings.Split(split1[0], "{")[0] - typeReplica := type1[strings.Index(type1, "{")+1 : strings.Index(type1, "}")] - replicaInt, _ := strconv.Atoi(typeReplica) - typeMap[split1[1]] = v1alpha1.LocalityType{ - Name: typeName, - Replica: replicaInt, - IsActive: true, - } - } - return typeMap -} - -func (m *OBTenantManager) generateStatusPriorityMap(primaryZone string) map[string]int { - priorityMap := make(map[string]int, 0) - cutZones := strings.Split(primaryZone, ";") - priority := len(cutZones) - for _, cutZone := range cutZones { - zoneList := strings.Split(cutZone, ",") - for _, zone := range zoneList { - priorityMap[zone] = priority - } - priority-- - } - return priorityMap -} - -func (m *OBTenantManager) generateUnitName(zoneList string) string { - tenantName := m.OBTenant.Spec.TenantName - unitName := fmt.Sprintf("unitconfig_%s_%s", tenantName, zoneList) - return unitName -} - -func (m *OBTenantManager) generatePoolName(zoneList string) string { - tenantName := m.OBTenant.Spec.TenantName - poolName := fmt.Sprintf("pool_%s_%s", tenantName, zoneList) - return poolName -} - -// ---------- sql operator wrap ---------- - -func (m *OBTenantManager) getOBVersion() (string, error) { - tenantName := m.OBTenant.Spec.TenantName - oceanbaseOperationManager, err := m.getClusterSysClient() - if err != nil { - return "", errors.Wrap(err, "Get Sql Operator Error When Get OB Version") - } - version, err := oceanbaseOperationManager.GetVersion() - if err != nil { - return "", errors.Wrapf(err, "Tenant '%s' get ob version from db failed", tenantName) - } - return version.Version, nil -} - -// sql wrap function - -func (m *OBTenantManager) getCharset() (string, error) { - oceanbaseOperationManager, err := m.getClusterSysClient() - if err != nil { - return "", errors.Wrap(err, "Get Sql Operator Error When Getting Charset") - } - charset, err := oceanbaseOperationManager.GetCharset() - if err != nil { - return "", errors.Wrap(err, "Get sql error when get charset") - } - return charset.Charset, nil -} - -func (m *OBTenantManager) getVariable(variableName string) (string, error) { - oceanbaseOperationManager, err := m.getClusterSysClient() - if err != nil { - return "", errors.Wrap(err, "Get Sql Operator Error When Getting Variable") - } - variable, err := oceanbaseOperationManager.GetVariable(variableName) - if err != nil { - return "", errors.Wrap(err, "Get sql error when get variable") - } - return variable.Value, nil -} - -func (m *OBTenantManager) getTenantByName(tenantName string) (*model.OBTenant, error) { - oceanbaseOperationManager, err := m.getClusterSysClient() - if err != nil { - return nil, errors.Wrap(err, "Get Sql Operator Error When Getting Tenant") - } - tenant, err := oceanbaseOperationManager.GetTenantByName(tenantName) - if err != nil { - return nil, err - } - return tenant, nil -} - -func (m *OBTenantManager) getPoolByName(poolName string) (*model.Pool, error) { - oceanbaseOperationManager, err := m.getClusterSysClient() - if err != nil { - return nil, errors.Wrap(err, "Get Sql Operator Error When Getting Pool by poolName") - } - pool, err := oceanbaseOperationManager.GetPoolByName(poolName) - if err != nil { - return nil, err - } - return pool, nil -} - -func (m *OBTenantManager) getUnitConfigV4ByName(unitName string) (*model.UnitConfigV4, error) { - oceanbaseOperationManager, err := m.getClusterSysClient() - if err != nil { - return nil, errors.Wrap(err, "Get Sql Operator Error When Getting UnitConfigV4 By unitConfig name") - } - unit, err := oceanbaseOperationManager.GetUnitConfigV4ByName(unitName) - if err != nil { - return nil, err - } - return unit, nil -} - -func (m *OBTenantManager) tenantExist(tenantName string) (bool, error) { - oceanbaseOperationManager, err := m.getClusterSysClient() - if err != nil { - return false, errors.Wrap(err, "Get Sql Operator Error When Check whether tenant exist") - } - isExist, err := oceanbaseOperationManager.CheckTenantExistByName(tenantName) - if err != nil { - return false, err - } - return isExist, nil -} - -func (m *OBTenantManager) poolExist(poolName string) (bool, error) { - oceanbaseOperationManager, err := m.getClusterSysClient() - if err != nil { - return false, errors.Wrap(err, "Get Sql Operator Error When Check whether pool exist") - } - isExist, err := oceanbaseOperationManager.CheckPoolExistByName(poolName) - if err != nil { - return false, err - } - return isExist, nil -} - -func (m *OBTenantManager) unitConfigV4Exist(unitConfigName string) (bool, error) { - oceanbaseOperationManager, err := m.getClusterSysClient() - if err != nil { - return false, errors.Wrap(err, "Get Sql Operator Error When Check whether UnitConfigV4 exist") - } - isExist, err := oceanbaseOperationManager.CheckUnitConfigExistByName(unitConfigName) - if err != nil { - return false, err - } - return isExist, nil -} - -func (m *OBTenantManager) createPool(poolName, unitName string, pool v1alpha1.ResourcePoolSpec) error { - tenantName := m.OBTenant.Spec.TenantName - m.Logger.Info("Create Resource Pool", "tenantName", tenantName, "poolName", poolName) - oceanbaseOperationManager, err := m.getClusterSysClient() - if err != nil { - return errors.Wrap(err, "Get Sql Operator Error When Creating Resource Pool") - } - poolSQLParam := model.PoolSQLParam{ - PoolName: poolName, - UnitName: unitName, - UnitNum: int64(m.OBTenant.Spec.UnitNumber), - ZoneList: pool.Zone, - } - return oceanbaseOperationManager.AddPool(poolSQLParam) -} - -func (m *OBTenantManager) createUnitAndPoolV4(pool v1alpha1.ResourcePoolSpec) error { - tenantName := m.OBTenant.Spec.TenantName - unitName := m.generateUnitName(pool.Zone) - poolName := m.generatePoolName(pool.Zone) - - err := m.createUnitConfigV4(unitName, pool.UnitConfig) - if err != nil { - m.Logger.Error(err, "Create UnitConfigV4 Failed", "tenantName", tenantName, "unitName", unitName) - return err - } - err = m.createPool(poolName, unitName, pool) - if err != nil { - m.Logger.Error(err, "Create Tenant Failed", "tenantName", tenantName, "poolName", poolName) - return err - } - return nil -} - -func (m *OBTenantManager) deleteTenant() tasktypes.TaskError { - tenantName := m.OBTenant.Spec.TenantName - oceanbaseOperationManager, err := m.getClusterSysClient() - if err != nil { - return errors.Wrap(err, fmt.Sprint("Get Sql Operator When Deleting Tenant ", tenantName)) - } - - tenantExist, err := m.tenantExist(tenantName) - if err != nil { - m.Logger.Error(err, "Check Whether The Tenant Exists Failed", "tenantName", tenantName) - return err - } - if tenantExist { - return oceanbaseOperationManager.DeleteTenant(tenantName, m.OBTenant.Spec.ForceDelete) - } - return nil -} - -func (m *OBTenantManager) deletePool() tasktypes.TaskError { - tenantName := m.OBTenant.Spec.TenantName - oceanbaseOperationManager, err := m.getClusterSysClient() + minPoolMemory := params[0].Value + minPoolMemoryQuant, err := resource.ParseQuantity(minPoolMemory) if err != nil { - return errors.Wrap(err, fmt.Sprint("Get Sql Operator When Deleting Pool", tenantName)) + return errors.Wrap(err, fmt.Sprintf("Parse quantity when checking and applying tenant '%s' pool and config", tenantName)) } - for _, zone := range m.OBTenant.Spec.Pools { - poolName := m.generatePoolName(zone.Zone) + for _, pool := range m.OBTenant.Spec.Pools { + unitName := m.generateUnitName(pool.Zone) + poolName := m.generatePoolName(pool.Zone) poolExist, err := m.poolExist(poolName) if err != nil { - m.Logger.Error(err, "Check Whether The Resource Pool Exists Failed") + m.Logger.Error(err, "Check resource pool exist", "tenantName", tenantName, "poolName", poolName) return err } if poolExist { - err = oceanbaseOperationManager.DeletePool(poolName) - if err != nil { - return err - } + return err } - } - return nil -} -func (m *OBTenantManager) deleteUnitConfig() tasktypes.TaskError { - tenantName := m.OBTenant.Spec.TenantName - oceanbaseOperationManager, err := m.getClusterSysClient() - if err != nil { - return errors.Wrap(err, fmt.Sprint("Get Sql Operator When Deleting Unit", tenantName)) - } - for _, zone := range m.OBTenant.Spec.Pools { - unitName := m.generateUnitName(zone.Zone) unitExist, err := m.unitConfigV4Exist(unitName) if err != nil { - m.Logger.Error(err, "Check Whether The Resource Unit Exists Failed", "tenantName", tenantName, "unitName", unitName) + m.Logger.Error(err, "Check unit config exist failed", "tenantName", tenantName, "unitName", unitName) return err } if unitExist { - err = oceanbaseOperationManager.DeleteUnitConfig(unitName) - if err != nil { - return err - } + return err + } + if pool.UnitConfig.MemorySize.Cmp(minPoolMemoryQuant) < 0 { + err = errors.New("pool memory size is less than min_full_resource_pool_memory") + m.Logger.Error(err, "Check pool memory size", "tenantName", tenantName, "poolName", poolName) + return err } } return nil } -func (m *OBTenantManager) CreateUserWithCredentialSecrets() tasktypes.TaskError { - if m.OBTenant.Spec.TenantRole == constants.TenantRoleStandby { - // standby tenant can not create users - return nil - } - err := m.CreateUserWithCredentials() +func CreateTenantWithClear(m *OBTenantManager) tasktypes.TaskError { + err := CreateTenantTask(m) + // clean created resource, restore to the initial state if err != nil { - m.Recorder.Event(m.OBTenant, corev1.EventTypeWarning, "Failed to create user or change password", err.Error()) - m.Logger.Error(err, "Failed to create user or change password, please check the credential secrets") + err := DeleteTenant(m) + if err != nil { + err = errors.Wrapf(err, "delete tenant when creating tenant") + return err + } } - - return nil + return err } -func (m *OBTenantManager) CreateUserWithCredentials() tasktypes.TaskError { - var con *operation.OceanbaseOperationManager - var err error +func CreateResourcePoolAndConfig(m *OBTenantManager) tasktypes.TaskError { + tenantName := m.OBTenant.Spec.TenantName - creds := m.OBTenant.Spec.Credentials - if creds.Root != "" { - con, err = m.getTenantClient() + for _, pool := range m.OBTenant.Spec.Pools { + err := m.createUnitAndPoolV4(pool) if err != nil { + m.Logger.Error(err, "Create Tenant failed", "tenantName", tenantName) return err } - rootPwd, err := resourceutils.ReadPassword(m.Client, m.OBTenant.Namespace, creds.Root) + } + return nil +} + +func AddPool(m *OBTenantManager) tasktypes.TaskError { + // handle add pool + poolSpecs := m.getPoolsForAdd() + for _, addPool := range poolSpecs { + err := m.tenantAddPool(addPool) if err != nil { - if client.IgnoreNotFound(err) != nil { - m.Logger.Error(err, "Failed to get root password secret") - return err - } - } else if rootPwd != "" { - err = con.ChangeTenantUserPassword(oceanbaseconst.RootUser, rootPwd) - if err != nil { - m.Logger.Error(err, "Failed to change root password") - return err - } + return err } } + return nil +} - if creds.StandbyRO != "" { - var standbyROPwd string - secret := &corev1.Secret{} - err = m.Client.Get(m.Ctx, types.NamespacedName{ - Namespace: m.OBTenant.GetNamespace(), - Name: creds.StandbyRO, - }, secret) +func DeletePool(m *OBTenantManager) tasktypes.TaskError { + // handle delete pool + poolStatuses := m.getPoolsForDelete() + for _, poolStatus := range poolStatuses { + err := m.TenantDeletePool(poolStatus) if err != nil { - if kubeerrors.IsNotFound(err) { - secret.Name = creds.StandbyRO - secret.Namespace = m.OBTenant.GetNamespace() - secret.SetOwnerReferences([]metav1.OwnerReference{{ - APIVersion: m.OBTenant.APIVersion, - Kind: m.OBTenant.Kind, - Name: m.OBTenant.GetName(), - UID: m.OBTenant.GetUID(), - }}) - standbyROPwd = rand.String(16) - secret.StringData = map[string]string{ - "password": standbyROPwd, - } - err = m.Client.Create(m.Ctx, secret) - if err != nil { - m.Logger.Error(err, "Failed to create standbyRO password secret") - return err - } - } else { - m.Logger.Error(err, "Failed to get standbyRO password secret") - return err - } + return err } + } + return nil +} - if standbyROPwd == "" && secret != nil { - standbyROPwd = string(secret.Data["password"]) - } +func MaintainUnitConfig(m *OBTenantManager) tasktypes.TaskError { + tenantName := m.OBTenant.Spec.TenantName - if con == nil { - con, err = m.getTenantClient() - if err != nil { - return err - } - } + version, err := m.getOBVersion() + if err != nil { + m.Logger.Error(err, "maintain tenant failed, check and apply unitConfigV4", "tenantName", tenantName) + return err + } + if string(version[0]) == tenant.Version4 { + return m.CheckAndApplyUnitConfigV4() + } + return errors.New("no match version for check and set unit config") +} - if standbyROPwd != "" { - err = con.CreateUserWithPwd(oceanbaseconst.StandbyROUser, standbyROPwd) - if err != nil { - m.Logger.Error(err, "Failed to create standbyRO user with password") - return err - } - } else { - err = con.CreateUser(oceanbaseconst.StandbyROUser) - if err != nil { - m.Logger.Error(err, "Failed to create standbyRO user") - return err - } - } +func DeleteTenant(m *OBTenantManager) tasktypes.TaskError { + var err error + tenantName := m.OBTenant.Spec.TenantName + m.Logger.Info("Delete Tenant", "tenantName", tenantName) + err = m.deleteTenant() + if err != nil { + return err + } + m.Logger.Info("Delete Pool", "tenantName", tenantName) + err = m.deletePool() + if err != nil { + return err + } + m.Logger.Info("Delete Unit", "tenantName", tenantName) + err = m.deleteUnitConfig() + if err != nil { + return err + } + m.Logger.Info("Delete Tenant Success", "tenantName", tenantName) + return nil +} - err = con.GrantPrivilege(oceanbaseconst.SelectPrivilege, oceanbaseconst.OceanbaseAllScope, oceanbaseconst.StandbyROUser) +func CheckAndApplyCharset(m *OBTenantManager) tasktypes.TaskError { + tenantName := m.OBTenant.Spec.TenantName + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return errors.Wrap(err, fmt.Sprintf("Get Sql Operator When Checking and Applying Tenant '%s' Charset ", tenantName)) + } + specCharset := m.OBTenant.Spec.Charset + if specCharset == "" { + specCharset = tenant.Charset + } + if specCharset != m.OBTenant.Status.TenantRecordInfo.Charset { + tenantSQLParam := model.TenantSQLParam{ + TenantName: tenantName, + Charset: specCharset, + } + err = oceanbaseOperationManager.SetTenant(tenantSQLParam) if err != nil { - m.Logger.Error(err, "Failed to grant privilege to standbyRO") return err } } return nil } -func (m *OBTenantManager) CreateEmptyStandbyTenant() tasktypes.TaskError { +func CreateEmptyStandbyTenant(m *OBTenantManager) tasktypes.TaskError { if m.OBTenant.Spec.Source == nil || m.OBTenant.Spec.Source.Tenant == nil { return errors.New("Empty standby tenant must have source tenant") } @@ -1241,7 +240,7 @@ func (m *OBTenantManager) CreateEmptyStandbyTenant() tasktypes.TaskError { return nil } -func (m *OBTenantManager) CheckPrimaryTenantLSIntegrity() tasktypes.TaskError { +func CheckPrimaryTenantLSIntegrity(m *OBTenantManager) tasktypes.TaskError { var err error if m.OBTenant.Spec.Source == nil || m.OBTenant.Spec.Source.Tenant == nil { return errors.New("Primary tenant must have source tenant") @@ -1282,16 +281,7 @@ func (m *OBTenantManager) CheckPrimaryTenantLSIntegrity() tasktypes.TaskError { return nil } -// OBTenantManager tasks completion - -func (m *OBTenantManager) generateRestoreOption() string { - poolList := m.generateSpecPoolList(m.OBTenant.Spec.Pools) - primaryZone := m.generateSpecPrimaryZone(m.OBTenant.Spec.Pools) - locality := m.generateLocality(m.OBTenant.Spec.Pools) - return fmt.Sprintf("pool_list=%s&primary_zone=%s&locality=%s", strings.Join(poolList, ","), primaryZone, locality) -} - -func (m *OBTenantManager) CreateTenantRestoreJobCR() tasktypes.TaskError { +func CreateTenantRestoreJobCR(m *OBTenantManager) tasktypes.TaskError { var existingJobs v1alpha1.OBTenantRestoreList var err error @@ -1341,7 +331,7 @@ func (m *OBTenantManager) CreateTenantRestoreJobCR() tasktypes.TaskError { return nil } -func (m *OBTenantManager) WatchRestoreJobToFinish() tasktypes.TaskError { +func WatchRestoreJobToFinish(m *OBTenantManager) tasktypes.TaskError { var err error for { runningRestore := &v1alpha1.OBTenantRestore{} @@ -1365,7 +355,7 @@ func (m *OBTenantManager) WatchRestoreJobToFinish() tasktypes.TaskError { return nil } -func (m *OBTenantManager) CancelTenantRestoreJob() tasktypes.TaskError { +func CancelTenantRestoreJob(m *OBTenantManager) tasktypes.TaskError { con, err := m.getClusterSysClient() if err != nil { return err @@ -1399,7 +389,7 @@ func (m *OBTenantManager) CancelTenantRestoreJob() tasktypes.TaskError { return nil } -func (m *OBTenantManager) UpgradeTenantIfNeeded() tasktypes.TaskError { +func UpgradeTenantIfNeeded(m *OBTenantManager) tasktypes.TaskError { con, err := m.getClusterSysClient() if err != nil { return err @@ -1441,3 +431,119 @@ func (m *OBTenantManager) UpgradeTenantIfNeeded() tasktypes.TaskError { } return nil } + +func CheckAndApplyUnitNum(m *OBTenantManager) tasktypes.TaskError { + tenantName := m.OBTenant.Spec.TenantName + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return errors.Wrap(err, fmt.Sprint("Get Sql Operator When Checking And Applying Tenant UnitNum", tenantName)) + } + + if m.OBTenant.Spec.UnitNumber != m.OBTenant.Status.TenantRecordInfo.UnitNumber { + err = oceanbaseOperationManager.SetTenantUnitNum(tenantName, m.OBTenant.Spec.UnitNumber) + if err != nil { + return err + } + } + return nil +} + +func CheckAndApplyWhiteList(m *OBTenantManager) tasktypes.TaskError { + tenantName := m.OBTenant.Spec.TenantName + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return errors.Wrap(err, fmt.Sprint("Get Sql Operator When Checking And Applying ob_tcp_invited_nodes For Tenant ", tenantName)) + } + + specWhiteList := m.OBTenant.Spec.ConnectWhiteList + statusWhiteList := m.OBTenant.Status.TenantRecordInfo.ConnectWhiteList + + if specWhiteList == "" { + specWhiteList = tenant.DefaultOBTcpInvitedNodes + } + if statusWhiteList != specWhiteList { + m.Logger.Info("Found specWhiteList didn't match", "tenantName", tenantName, + "statusWhiteList", statusWhiteList, "specWhiteList", specWhiteList) + variableList := m.generateWhiteListInVariableForm(specWhiteList) + err = oceanbaseOperationManager.SetTenantVariable(tenantName, variableList) + if err != nil { + return err + } + // TODO: get whitelist variable by tenant account + // Because getting a whitelist requires specifying a tenant , temporary use .Status.TenantRecordInfo.ConnectWhiteList as value in db + tenantWhiteListMap.Store(tenantName, specWhiteList) + } + return nil +} + +func CheckAndApplyPrimaryZone(m *OBTenantManager) tasktypes.TaskError { + tenantName := m.OBTenant.Spec.TenantName + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return errors.Wrap(err, fmt.Sprintf("Get Sql Operator When Prcoessing Tenant '%s' Priority ", tenantName)) + } + + specPrimaryZone := m.generateSpecPrimaryZone(m.OBTenant.Spec.Pools) + specPrimaryZoneMap := m.generatePrimaryZoneMap(specPrimaryZone) + statusPrimaryZone := m.generateStatusPrimaryZone(m.OBTenant.Status.Pools) + statusPrimaryZoneMap := m.generatePrimaryZoneMap(statusPrimaryZone) + if !reflect.DeepEqual(specPrimaryZoneMap, statusPrimaryZoneMap) { + tenantSQLParam := model.TenantSQLParam{ + TenantName: tenantName, + PrimaryZone: specPrimaryZone, + } + err = oceanbaseOperationManager.SetTenant(tenantSQLParam) + if err != nil { + return err + } + } + return nil +} + +func CheckAndApplyLocality(m *OBTenantManager) tasktypes.TaskError { + tenantName := m.OBTenant.Spec.TenantName + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return errors.Wrap(err, fmt.Sprintf("Get Sql Operator When Prcoessing Tenant '%s' Locality ", tenantName)) + } + specLocalityMap := m.generateSpecLocalityMap(m.OBTenant.Spec.Pools) + statusLocalityMap := m.generateStatusLocalityMap(m.OBTenant.Status.Pools) + if !reflect.DeepEqual(specLocalityMap, statusLocalityMap) { + locality := m.generateLocality(m.OBTenant.Spec.Pools) + tenantSQLParam := model.TenantSQLParam{ + TenantName: tenantName, + Locality: locality, + } + err = oceanbaseOperationManager.SetTenant(tenantSQLParam) + if err != nil { + return err + } + } + m.Logger.V(oceanbaseconst.LogLevelDebug).Info("Wait For Tenant 'ALTER_TENANT' Job for addPool Finished", "tenantName", tenantName) + for { + exist, err := oceanbaseOperationManager.CheckRsJobExistByTenantID(m.OBTenant.Status.TenantRecordInfo.TenantID) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("Get RsJob %s", tenantName)) + } + if !exist { + break + } + time.Sleep(config.PollingJobSleepTime) + } + m.Logger.V(oceanbaseconst.LogLevelDebug).Info("'ALTER_TENANT' Job for addPool successes", "tenantName", tenantName) + return nil +} + +func CreateUserWithCredentialSecrets(m *OBTenantManager) tasktypes.TaskError { + if m.OBTenant.Spec.TenantRole == constants.TenantRoleStandby { + // standby tenant can not create users + return nil + } + err := CreateUserWithCredentials(m) + if err != nil { + m.Recorder.Event(m.OBTenant, corev1.EventTypeWarning, "Failed to create user or change password", err.Error()) + m.Logger.Error(err, "Failed to create user or change password, please check the credential secrets") + } + + return nil +} diff --git a/internal/resource/obtenant/obtenant_task_gen.go b/internal/resource/obtenant/obtenant_task_gen.go new file mode 100644 index 000000000..41514ad2d --- /dev/null +++ b/internal/resource/obtenant/obtenant_task_gen.go @@ -0,0 +1,25 @@ +// Code generated by go generate; DO NOT EDIT. +package obtenant + +func init() { + taskMap.Register(tCheckTenant, CheckTenant) + taskMap.Register(tCheckPoolAndConfig, CheckPoolAndConfig) + taskMap.Register(tCreateTenantWithClear, CreateTenantWithClear) + taskMap.Register(tCreateResourcePoolAndConfig, CreateResourcePoolAndConfig) + taskMap.Register(tAddPool, AddPool) + taskMap.Register(tDeletePool, DeletePool) + taskMap.Register(tMaintainUnitConfig, MaintainUnitConfig) + taskMap.Register(tDeleteTenant, DeleteTenant) + taskMap.Register(tCheckAndApplyCharset, CheckAndApplyCharset) + taskMap.Register(tCreateEmptyStandbyTenant, CreateEmptyStandbyTenant) + taskMap.Register(tCheckPrimaryTenantLSIntegrity, CheckPrimaryTenantLSIntegrity) + taskMap.Register(tCreateTenantRestoreJobCR, CreateTenantRestoreJobCR) + taskMap.Register(tWatchRestoreJobToFinish, WatchRestoreJobToFinish) + taskMap.Register(tCancelTenantRestoreJob, CancelTenantRestoreJob) + taskMap.Register(tUpgradeTenantIfNeeded, UpgradeTenantIfNeeded) + taskMap.Register(tCheckAndApplyUnitNum, CheckAndApplyUnitNum) + taskMap.Register(tCheckAndApplyWhiteList, CheckAndApplyWhiteList) + taskMap.Register(tCheckAndApplyPrimaryZone, CheckAndApplyPrimaryZone) + taskMap.Register(tCheckAndApplyLocality, CheckAndApplyLocality) + taskMap.Register(tCreateUserWithCredentialSecrets, CreateUserWithCredentialSecrets) +} diff --git a/internal/resource/obtenant/utils.go b/internal/resource/obtenant/utils.go new file mode 100644 index 000000000..2ea63fc59 --- /dev/null +++ b/internal/resource/obtenant/utils.go @@ -0,0 +1,924 @@ +/* +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 obtenant + +import ( + "fmt" + "sort" + "strconv" + "strings" + "time" + + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + kubeerrors "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/rand" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/oceanbase/ob-operator/api/v1alpha1" + oceanbaseconst "github.com/oceanbase/ob-operator/internal/const/oceanbase" + resourceutils "github.com/oceanbase/ob-operator/internal/resource/utils" + "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/const/config" + "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/const/status/tenant" + "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/model" + "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/operation" + tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" +) + +// ---------- action function ---------- + +func (m *OBTenantManager) createTenant() tasktypes.TaskError { + tenantName := m.OBTenant.Spec.TenantName + pools := m.OBTenant.Spec.Pools + m.Logger.Info("Create Tenant", "tenantName", tenantName) + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return errors.Wrap(err, "Get Sql Operator Error When Creating Tenant") + } + + tenantSQLParam := model.TenantSQLParam{ + TenantName: tenantName, + PrimaryZone: m.generateSpecPrimaryZone(pools), + VariableList: m.generateWhiteListInVariableForm(m.OBTenant.Spec.ConnectWhiteList), + Charset: m.OBTenant.Spec.Charset, + PoolList: m.generateSpecPoolList(pools), + Locality: m.generateLocality(pools), + Collate: m.OBTenant.Spec.Collate, + } + if tenantSQLParam.Charset == "" { + tenantSQLParam.Charset = tenant.Charset + } + + err = oceanbaseOperationManager.AddTenant(tenantSQLParam) + if err != nil { + m.Recorder.Event(m.OBTenant, corev1.EventTypeWarning, "failed to create OBTenant", err.Error()) + return err + } + tenantWhiteListMap.Store(tenantName, m.OBTenant.Spec.ConnectWhiteList) + // Create user or change password of root, do not return error + m.Recorder.Event(m.OBTenant, "Create", "", "create OBTenant successfully") + return nil +} + +func (m *OBTenantManager) createUnitConfigV4(unitName string, unitConfig *v1alpha1.UnitConfig) error { + tenantName := m.OBTenant.Spec.TenantName + m.Logger.Info("Create UnitConfig", "tenantName", tenantName, "unitName", unitName) + unitModel := m.generateModelUnitConfigV4SQLParam(unitName, m.generateModelUnitConfigV4(unitConfig)) + if unitModel.MemorySize == 0 { + err := errors.New("unit memorySize is empty") + m.Logger.Error(err, "unit memorySize cannot be zero", "tenantName", tenantName, "unitName", unitName) + return err + } + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return errors.Wrap(err, "Get Sql Operator Error When Creating Resource UnitConfigV4") + } + + return oceanbaseOperationManager.AddUnitConfigV4(unitModel) +} + +func (m *OBTenantManager) setUnitConfigV4(unitName string, unitConfig *model.UnitConfigV4) error { + tenantName := m.OBTenant.Spec.TenantName + oceanbaseOperationManager, err := m.getClusterSysClient() + unitModel := m.generateModelUnitConfigV4SQLParam(unitName, unitConfig) + if err != nil { + return errors.Wrap(err, fmt.Sprint("Get Sql Operator When Checking And Setting Unit Config For Tenant ", tenantName)) + } + return oceanbaseOperationManager.SetUnitConfigV4(unitModel) +} + +func (m *OBTenantManager) getPoolsForAdd() []v1alpha1.ResourcePoolSpec { + var pools []v1alpha1.ResourcePoolSpec + for _, specZone := range m.OBTenant.Spec.Pools { + exist := false + for _, statusZone := range m.OBTenant.Status.Pools { + if statusZone.ZoneList == specZone.Zone { + exist = true + } + } + if !exist { + pools = append(pools, specZone) + } + } + return pools +} + +func (m *OBTenantManager) getPoolsForDelete() []v1alpha1.ResourcePoolStatus { + var poolStatuses []v1alpha1.ResourcePoolStatus + for _, statusPool := range m.OBTenant.Status.Pools { + exist := false + for _, specPool := range m.OBTenant.Spec.Pools { + if statusPool.ZoneList == specPool.Zone { + exist = true + } + } + if !exist { + poolStatuses = append(poolStatuses, statusPool) + } + } + return poolStatuses +} + +func (m *OBTenantManager) tenantAddPool(poolAdd v1alpha1.ResourcePoolSpec) error { + tenantName := m.OBTenant.Spec.TenantName + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return errors.Wrap(err, fmt.Sprintf("Get Sql Operator When Prcoessing Tenant '%s' -- Add Pool", tenantName)) + } + + // step 1: create unit and poolAdd + err = m.createUnitAndPoolV4(poolAdd) + if err != nil { + return err + } + + // step 2.1: update locality and resource poolAdd list + poolStatusAdd := v1alpha1.ResourcePoolStatus{ + ZoneList: poolAdd.Zone, + Type: poolAdd.Type, + UnitNumber: m.OBTenant.Spec.UnitNumber, + } + resourcePoolStatusList := m.OBTenant.Status.Pools + resourcePoolStatusList = append(resourcePoolStatusList, poolStatusAdd) + statusLocalityMap := m.generateStatusLocalityMap(resourcePoolStatusList) + localityList := m.generateLocalityList(statusLocalityMap) + poolList := m.generateStatusPoolList(resourcePoolStatusList) + specPrimaryZone := m.generateSpecPrimaryZone(m.OBTenant.Spec.Pools) + + tenantSQLParam := model.TenantSQLParam{ + TenantName: tenantName, + Locality: strings.Join(localityList, ","), + PoolList: poolList, + PrimaryZone: specPrimaryZone, + } + err = oceanbaseOperationManager.SetTenant(tenantSQLParam) + if err != nil { + return err + } + + // step 2.2: Wait for task finished + m.Logger.V(oceanbaseconst.LogLevelDebug).Info("Wait For Tenant 'ALTER_TENANT' Job for addPool Finished", "tenantName", tenantName) + for { + exist, err := oceanbaseOperationManager.CheckRsJobExistByTenantID(m.OBTenant.Status.TenantRecordInfo.TenantID) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("Get RsJob %s", tenantName)) + } + if !exist { + break + } + time.Sleep(config.PollingJobSleepTime) + } + m.Logger.V(oceanbaseconst.LogLevelDebug).Info("'ALTER_TENANT' Job for addPool successes", "tenantName", tenantName) + + m.Logger.V(oceanbaseconst.LogLevelDebug).Info("Succeed add poolAdd", "deleted poolName", poolAdd.Zone) + return nil +} + +// ---------- generate "zoneName-value" map function ---------- + +func (m *OBTenantManager) generatePrimaryZoneMap(str string) map[int][]string { + res := make(map[int][]string, 0) + levelCuts := strings.Split(str, ";") + for idx, levelCut := range levelCuts { + cut := strings.Split(levelCut, ",") + res[idx] = cut + sort.Strings(res[idx]) + } + return res +} + +func (m *OBTenantManager) generateSpecUnitConfigV4Map(spec v1alpha1.OBTenantSpec) map[string]*model.UnitConfigV4 { + var unitConfigMap = make(map[string]*model.UnitConfigV4, 0) + for _, pool := range spec.Pools { + unitConfigMap[pool.Zone] = m.generateModelUnitConfigV4(pool.UnitConfig) + } + return unitConfigMap +} + +func (m *OBTenantManager) GenerateStatusUnitConfigV4Map(status v1alpha1.OBTenantStatus) map[string]*model.UnitConfigV4 { + var unitConfigMap = make(map[string]*model.UnitConfigV4, 0) + for _, pool := range status.Pools { + unitConfigMap[pool.ZoneList] = m.generateModelUnitConfigV4(pool.UnitConfig) + } + return unitConfigMap +} + +func (m *OBTenantManager) generateModelUnitConfigV4(unitConfig *v1alpha1.UnitConfig) *model.UnitConfigV4 { + var modelUnitConfigV4 model.UnitConfigV4 + modelUnitConfigV4.MaxCPU = unitConfig.MaxCPU.AsApproximateFloat64() + modelUnitConfigV4.MinCPU = unitConfig.MinCPU.AsApproximateFloat64() + modelUnitConfigV4.MaxIops = int64(unitConfig.MaxIops) + modelUnitConfigV4.MinIops = int64(unitConfig.MinIops) + modelUnitConfigV4.IopsWeight = int64(unitConfig.IopsWeight) + modelUnitConfigV4.MemorySize = unitConfig.MemorySize.Value() + modelUnitConfigV4.LogDiskSize = unitConfig.LogDiskSize.Value() + return &modelUnitConfigV4 +} + +func (m *OBTenantManager) generateModelUnitConfigV4SQLParam(unitConfigName string, unitConfig *model.UnitConfigV4) *model.UnitConfigV4SQLParam { + var modelUnitConfigV4 model.UnitConfigV4SQLParam + modelUnitConfigV4.UnitConfigName = unitConfigName + modelUnitConfigV4.MaxCPU = unitConfig.MaxCPU + modelUnitConfigV4.MinCPU = unitConfig.MinCPU + modelUnitConfigV4.MaxIops = unitConfig.MaxIops + modelUnitConfigV4.MinIops = unitConfig.MinIops + modelUnitConfigV4.IopsWeight = unitConfig.IopsWeight + modelUnitConfigV4.MemorySize = unitConfig.MemorySize + modelUnitConfigV4.LogDiskSize = unitConfig.LogDiskSize + return &modelUnitConfigV4 +} + +func (m *OBTenantManager) generateSpecUnitNumMap(spec v1alpha1.OBTenantSpec) map[string]int { + var unitNumMap = make(map[string]int, 0) + for _, zone := range spec.Pools { + unitNumMap[zone.Zone] = spec.UnitNumber + } + return unitNumMap +} + +func (m *OBTenantManager) generateSpecLocalityMap(pools []v1alpha1.ResourcePoolSpec) map[string]*v1alpha1.LocalityType { + localityMap := make(map[string]*v1alpha1.LocalityType, 0) + for _, pool := range pools { + localityMap[pool.Zone] = &v1alpha1.LocalityType{ + Name: strings.ToUpper(pool.Type.Name), // locality type in DB is Upper + Replica: pool.Type.Replica, + IsActive: pool.Type.IsActive, + } + } + return localityMap +} + +func (m *OBTenantManager) generateStatusLocalityMap(pools []v1alpha1.ResourcePoolStatus) map[string]*v1alpha1.LocalityType { + localityMap := make(map[string]*v1alpha1.LocalityType, 0) + for _, pool := range pools { + localityMap[pool.ZoneList] = pool.Type + } + return localityMap +} + +func (m *OBTenantManager) generateStatusUnitNumMap(zones []v1alpha1.ResourcePoolSpec) (map[string]int, error) { + unitNumMap := make(map[string]int, 0) + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return unitNumMap, errors.Wrap(err, "Get Sql Operator Error When Building Resource Unit From DB") + } + poolList, err := oceanbaseOperationManager.GetPoolList() + if err != nil { + return unitNumMap, errors.Wrap(err, "Get sql error when get pool list") + } + for _, zone := range zones { + poolName := m.generatePoolName(zone.Zone) + for _, pool := range poolList { + if pool.Name == poolName { + unitNumMap[zone.Zone] = int(pool.UnitNum) + } + } + } + return unitNumMap, nil +} + +func (m *OBTenantManager) generateLocalityList(localityMap map[string]*v1alpha1.LocalityType) []string { + var locality []string + var zoneSortList []string + for k := range localityMap { + zoneSortList = append(zoneSortList, k) + } + sort.Sort(sort.Reverse(sort.StringSlice(zoneSortList))) + for _, zoneList := range zoneSortList { + zoneType := localityMap[zoneList] + if zoneType.IsActive { + locality = append(locality, fmt.Sprintf("%s{%d}@%s", zoneType.Name, zoneType.Replica, zoneList)) + } + } + return locality +} + +func (m *OBTenantManager) generateSpecZoneList(pools []v1alpha1.ResourcePoolSpec) []string { + var zoneList []string + for _, pool := range pools { + zoneList = append(zoneList, pool.Zone) + } + return zoneList +} + +func (m *OBTenantManager) generateStatusZoneList(pools []v1alpha1.ResourcePoolStatus) []string { + var zoneList []string + for _, pool := range pools { + zoneList = append(zoneList, pool.ZoneList) + } + return zoneList +} + +func (m *OBTenantManager) generateSpecPoolList(pools []v1alpha1.ResourcePoolSpec) []string { + var poolList []string + for _, pool := range pools { + poolName := m.generatePoolName(pool.Zone) + poolList = append(poolList, poolName) + } + return poolList +} + +func (m *OBTenantManager) generateStatusPoolList(pools []v1alpha1.ResourcePoolStatus) []string { + var poolList []string + for _, pool := range pools { + poolName := m.generatePoolName(pool.ZoneList) + poolList = append(poolList, poolName) + } + return poolList +} + +func (m *OBTenantManager) generateSpecPrimaryZone(pools []v1alpha1.ResourcePoolSpec) string { + var primaryZone string + zoneMap := make(map[int][]string, 0) + var priorityList []int + for _, pool := range pools { + if pool.Type.IsActive { + zones := zoneMap[pool.Priority] + zones = append(zones, pool.Zone) + zoneMap[pool.Priority] = zones + } + } + for k := range zoneMap { + priorityList = append(priorityList, k) + } + sort.Sort(sort.Reverse(sort.IntSlice(priorityList))) + for _, priority := range priorityList { + zones := zoneMap[priority] + primaryZone = fmt.Sprint(primaryZone, strings.Join(zones, ","), ";") + } + return primaryZone +} + +func (m *OBTenantManager) generateStatusPrimaryZone(pools []v1alpha1.ResourcePoolStatus) string { + var primaryZone string + zoneMap := make(map[int][]string, 0) + var priorityList []int + for _, pool := range pools { + if pool.Type.IsActive { + zones := zoneMap[pool.Priority] + zones = append(zones, pool.ZoneList) + zoneMap[pool.Priority] = zones + } + } + for k := range zoneMap { + priorityList = append(priorityList, k) + } + sort.Sort(sort.Reverse(sort.IntSlice(priorityList))) + for _, priority := range priorityList { + zones := zoneMap[priority] + primaryZone = fmt.Sprint(primaryZone, strings.Join(zones, ","), ";") + } + return primaryZone +} + +func (m *OBTenantManager) generateLocality(zones []v1alpha1.ResourcePoolSpec) string { + specLocalityMap := m.generateSpecLocalityMap(zones) + localityList := m.generateLocalityList(specLocalityMap) + return strings.Join(localityList, ",") +} + +func (m *OBTenantManager) generateWhiteListInVariableForm(whiteList string) string { + if whiteList == "" { + return fmt.Sprintf("%s = '%s'", tenant.OBTcpInvitedNodes, tenant.DefaultOBTcpInvitedNodes) + } + return fmt.Sprintf("%s = '%s'", tenant.OBTcpInvitedNodes, whiteList) +} + +func (m *OBTenantManager) generateStatusTypeMapFromLocalityStr(locality string) map[string]v1alpha1.LocalityType { + typeMap := make(map[string]v1alpha1.LocalityType, 0) + typeList := strings.Split(locality, ", ") + for _, type1 := range typeList { + split1 := strings.Split(type1, "@") + typeName := strings.Split(split1[0], "{")[0] + typeReplica := type1[strings.Index(type1, "{")+1 : strings.Index(type1, "}")] + replicaInt, _ := strconv.Atoi(typeReplica) + typeMap[split1[1]] = v1alpha1.LocalityType{ + Name: typeName, + Replica: replicaInt, + IsActive: true, + } + } + return typeMap +} + +func (m *OBTenantManager) generateStatusPriorityMap(primaryZone string) map[string]int { + priorityMap := make(map[string]int, 0) + cutZones := strings.Split(primaryZone, ";") + priority := len(cutZones) + for _, cutZone := range cutZones { + zoneList := strings.Split(cutZone, ",") + for _, zone := range zoneList { + priorityMap[zone] = priority + } + priority-- + } + return priorityMap +} + +func (m *OBTenantManager) generateUnitName(zoneList string) string { + tenantName := m.OBTenant.Spec.TenantName + unitName := fmt.Sprintf("unitconfig_%s_%s", tenantName, zoneList) + return unitName +} + +func (m *OBTenantManager) generatePoolName(zoneList string) string { + tenantName := m.OBTenant.Spec.TenantName + poolName := fmt.Sprintf("pool_%s_%s", tenantName, zoneList) + return poolName +} + +// ---------- sql operator wrap ---------- + +func (m *OBTenantManager) getOBVersion() (string, error) { + tenantName := m.OBTenant.Spec.TenantName + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return "", errors.Wrap(err, "Get Sql Operator Error When Get OB Version") + } + version, err := oceanbaseOperationManager.GetVersion() + if err != nil { + return "", errors.Wrapf(err, "Tenant '%s' get ob version from db failed", tenantName) + } + return version.Version, nil +} + +// sql wrap function + +func (m *OBTenantManager) getCharset() (string, error) { + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return "", errors.Wrap(err, "Get Sql Operator Error When Getting Charset") + } + charset, err := oceanbaseOperationManager.GetCharset() + if err != nil { + return "", errors.Wrap(err, "Get sql error when get charset") + } + return charset.Charset, nil +} + +func (m *OBTenantManager) getVariable(variableName string) (string, error) { + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return "", errors.Wrap(err, "Get Sql Operator Error When Getting Variable") + } + variable, err := oceanbaseOperationManager.GetVariable(variableName) + if err != nil { + return "", errors.Wrap(err, "Get sql error when get variable") + } + return variable.Value, nil +} + +func (m *OBTenantManager) getTenantByName(tenantName string) (*model.OBTenant, error) { + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return nil, errors.Wrap(err, "Get Sql Operator Error When Getting Tenant") + } + tenant, err := oceanbaseOperationManager.GetTenantByName(tenantName) + if err != nil { + return nil, err + } + return tenant, nil +} + +func (m *OBTenantManager) getPoolByName(poolName string) (*model.Pool, error) { + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return nil, errors.Wrap(err, "Get Sql Operator Error When Getting Pool by poolName") + } + pool, err := oceanbaseOperationManager.GetPoolByName(poolName) + if err != nil { + return nil, err + } + return pool, nil +} + +func (m *OBTenantManager) getUnitConfigV4ByName(unitName string) (*model.UnitConfigV4, error) { + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return nil, errors.Wrap(err, "Get Sql Operator Error When Getting UnitConfigV4 By unitConfig name") + } + unit, err := oceanbaseOperationManager.GetUnitConfigV4ByName(unitName) + if err != nil { + return nil, err + } + return unit, nil +} + +func (m *OBTenantManager) tenantExist(tenantName string) (bool, error) { + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return false, errors.Wrap(err, "Get Sql Operator Error When Check whether tenant exist") + } + isExist, err := oceanbaseOperationManager.CheckTenantExistByName(tenantName) + if err != nil { + return false, err + } + return isExist, nil +} + +func (m *OBTenantManager) poolExist(poolName string) (bool, error) { + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return false, errors.Wrap(err, "Get Sql Operator Error When Check whether pool exist") + } + isExist, err := oceanbaseOperationManager.CheckPoolExistByName(poolName) + if err != nil { + return false, err + } + return isExist, nil +} + +func (m *OBTenantManager) unitConfigV4Exist(unitConfigName string) (bool, error) { + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return false, errors.Wrap(err, "Get Sql Operator Error When Check whether UnitConfigV4 exist") + } + isExist, err := oceanbaseOperationManager.CheckUnitConfigExistByName(unitConfigName) + if err != nil { + return false, err + } + return isExist, nil +} + +func (m *OBTenantManager) createPool(poolName, unitName string, pool v1alpha1.ResourcePoolSpec) error { + tenantName := m.OBTenant.Spec.TenantName + m.Logger.Info("Create Resource Pool", "tenantName", tenantName, "poolName", poolName) + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return errors.Wrap(err, "Get Sql Operator Error When Creating Resource Pool") + } + poolSQLParam := model.PoolSQLParam{ + PoolName: poolName, + UnitName: unitName, + UnitNum: int64(m.OBTenant.Spec.UnitNumber), + ZoneList: pool.Zone, + } + return oceanbaseOperationManager.AddPool(poolSQLParam) +} + +func (m *OBTenantManager) createUnitAndPoolV4(pool v1alpha1.ResourcePoolSpec) error { + tenantName := m.OBTenant.Spec.TenantName + unitName := m.generateUnitName(pool.Zone) + poolName := m.generatePoolName(pool.Zone) + + err := m.createUnitConfigV4(unitName, pool.UnitConfig) + if err != nil { + m.Logger.Error(err, "Create UnitConfigV4 Failed", "tenantName", tenantName, "unitName", unitName) + return err + } + err = m.createPool(poolName, unitName, pool) + if err != nil { + m.Logger.Error(err, "Create Tenant Failed", "tenantName", tenantName, "poolName", poolName) + return err + } + return nil +} + +func (m *OBTenantManager) deleteTenant() tasktypes.TaskError { + tenantName := m.OBTenant.Spec.TenantName + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return errors.Wrap(err, fmt.Sprint("Get Sql Operator When Deleting Tenant ", tenantName)) + } + + tenantExist, err := m.tenantExist(tenantName) + if err != nil { + m.Logger.Error(err, "Check Whether The Tenant Exists Failed", "tenantName", tenantName) + return err + } + if tenantExist { + return oceanbaseOperationManager.DeleteTenant(tenantName, m.OBTenant.Spec.ForceDelete) + } + return nil +} + +func (m *OBTenantManager) deletePool() tasktypes.TaskError { + tenantName := m.OBTenant.Spec.TenantName + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return errors.Wrap(err, fmt.Sprint("Get Sql Operator When Deleting Pool", tenantName)) + } + for _, zone := range m.OBTenant.Spec.Pools { + poolName := m.generatePoolName(zone.Zone) + poolExist, err := m.poolExist(poolName) + if err != nil { + m.Logger.Error(err, "Check Whether The Resource Pool Exists Failed") + return err + } + if poolExist { + err = oceanbaseOperationManager.DeletePool(poolName) + if err != nil { + return err + } + } + } + return nil +} + +func (m *OBTenantManager) deleteUnitConfig() tasktypes.TaskError { + tenantName := m.OBTenant.Spec.TenantName + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return errors.Wrap(err, fmt.Sprint("Get Sql Operator When Deleting Unit", tenantName)) + } + for _, zone := range m.OBTenant.Spec.Pools { + unitName := m.generateUnitName(zone.Zone) + unitExist, err := m.unitConfigV4Exist(unitName) + if err != nil { + m.Logger.Error(err, "Check Whether The Resource Unit Exists Failed", "tenantName", tenantName, "unitName", unitName) + return err + } + if unitExist { + err = oceanbaseOperationManager.DeleteUnitConfig(unitName) + if err != nil { + return err + } + } + } + return nil +} + +// OBTenantManager tasks completion + +func (m *OBTenantManager) generateRestoreOption() string { + poolList := m.generateSpecPoolList(m.OBTenant.Spec.Pools) + primaryZone := m.generateSpecPrimaryZone(m.OBTenant.Spec.Pools) + locality := m.generateLocality(m.OBTenant.Spec.Pools) + return fmt.Sprintf("pool_list=%s&primary_zone=%s&locality=%s", strings.Join(poolList, ","), primaryZone, locality) +} + +// ---------- compare helper function ---------- + +func IsUnitConfigV4Equal(specUnitConfig *model.UnitConfigV4, statusUnitConfig *model.UnitConfigV4) bool { + if specUnitConfig.MaxCPU == statusUnitConfig.MaxCPU && + specUnitConfig.MemorySize == statusUnitConfig.MemorySize { + if (specUnitConfig.MinIops != 0 && specUnitConfig.MinIops != statusUnitConfig.MinIops) || + (specUnitConfig.MaxIops != 0 && specUnitConfig.MaxIops != statusUnitConfig.MaxIops) || + (specUnitConfig.MinCPU != 0 && specUnitConfig.MinCPU != statusUnitConfig.MinCPU) || + (specUnitConfig.LogDiskSize != 0 && specUnitConfig.LogDiskSize != statusUnitConfig.LogDiskSize) || + (specUnitConfig.IopsWeight != 0 && specUnitConfig.IopsWeight != statusUnitConfig.IopsWeight) { + return false + } + return true + } + return false +} + +func FormatUnitConfigV4(unit *model.UnitConfigV4) string { + return fmt.Sprintf("MaxCPU: %f MinCPU:%f MemorySize:%d MaxIops:%d MinIops:%d IopsWeight:%d LogDiskSize:%d", + unit.MaxCPU, unit.MinCPU, unit.MemorySize, unit.MaxIops, unit.MinIops, unit.IopsWeight, unit.LogDiskSize) +} + +func (m *OBTenantManager) TenantDeletePool(poolDelete v1alpha1.ResourcePoolStatus) error { + tenantName := m.OBTenant.Spec.TenantName + poolName := m.generatePoolName(poolDelete.ZoneList) + unitName := m.generateUnitName(poolDelete.ZoneList) + + oceanbaseOperationManager, err := m.getClusterSysClient() + if err != nil { + return errors.Wrap(err, fmt.Sprintf("Get Sql Operator When Prcoessing Tenant '%s' -- Delete Pool ", tenantName)) + } + var zoneList []v1alpha1.ResourcePoolStatus + for _, zone := range m.OBTenant.Status.Pools { + if zone.ZoneList != poolDelete.ZoneList { + zoneList = append(zoneList, zone) + } + } + + statusLocalityMap := m.generateStatusLocalityMap(zoneList) + localityList := m.generateLocalityList(statusLocalityMap) + poolList := m.generateStatusPoolList(zoneList) + specPrimaryZone := m.generateSpecPrimaryZone(m.OBTenant.Spec.Pools) + + // step 1.1: update locality + // note:this operator is async in oceanbase, polling until update locality task success + tenantSQLParam := model.TenantSQLParam{ + TenantName: tenantName, + Locality: strings.Join(localityList, ","), + } + err = oceanbaseOperationManager.SetTenant(tenantSQLParam) + if err != nil { + m.Logger.Error(err, "Modify Tenant, update locality", "tenantName", tenantName) + return err + } + + m.Logger.V(oceanbaseconst.LogLevelDebug).Info("Wait For Tenant 'ALTER_TENANT' Job for deletePool Finished", "tenantName", tenantName) + + for { + exist, err := oceanbaseOperationManager.CheckRsJobExistByTenantID(m.OBTenant.Status.TenantRecordInfo.TenantID) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("Get RsJob %s", tenantName)) + } + if !exist { + break + } + time.Sleep(config.PollingJobSleepTime) + } + + // step 1.2: update resource pool list + tenantSQLParam = model.TenantSQLParam{ + TenantName: tenantName, + PoolList: poolList, + PrimaryZone: specPrimaryZone, + } + err = oceanbaseOperationManager.SetTenant(tenantSQLParam) + if err != nil { + m.Logger.Error(err, "Modify Tenant, update poolList", "tenantName", tenantName) + return err + } + // step 2: delete resource pool + poolExist, err := m.poolExist(poolName) + if err != nil { + m.Logger.Error(err, "Check ResourcePool exist", "poolName", poolName) + return err + } + if poolExist { + err = oceanbaseOperationManager.DeletePool(poolName) + if err != nil { + return err + } + } + + // step 3: delete unit + unitExist, err := m.unitConfigV4Exist(unitName) + if err != nil { + m.Logger.Error(err, "Check UnitConfigV4 Exist", "unitName", unitName) + return err + } + if unitExist { + err = oceanbaseOperationManager.DeleteUnitConfig(unitName) + if err != nil { + return err + } + } + m.Logger.Info("Succeed delete pool", "deleted poolName", poolDelete.ZoneList) + return nil +} + +func (m *OBTenantManager) CheckAndApplyUnitConfigV4() tasktypes.TaskError { + tenantName := m.OBTenant.Spec.TenantName + specUnitConfigMap := m.generateSpecUnitConfigV4Map(m.OBTenant.Spec) + statusUnitConfigMap := m.GenerateStatusUnitConfigV4Map(m.OBTenant.Status) + for _, pool := range m.OBTenant.Spec.Pools { + match := true + specUnitConfig := specUnitConfigMap[pool.Zone] + statusUnitConfig, statusExist := statusUnitConfigMap[pool.Zone] + + // If status does not exist, Continue to check UnitConfig of the next ResourcePool + // while Add and delete a pool in the CheckAndApplyResourcePool + if !statusExist { + continue + } + + if !IsUnitConfigV4Equal(specUnitConfig, statusUnitConfig) { + m.Logger.Info("Found unit config v4 didn't match", "tenantName", tenantName, "zoneName", pool.Zone, + "statusUnitConfig", FormatUnitConfigV4(statusUnitConfigMap[pool.Zone]), "specUnitConfig", FormatUnitConfigV4(specUnitConfigMap[pool.Zone])) + match = false + } + if !match { + unitName := m.generateUnitName(pool.Zone) + err := m.setUnitConfigV4(unitName, specUnitConfigMap[pool.Zone]) + if err != nil { + m.Logger.Error(err, "Set Tenant Unit failed", "tenantName", tenantName, "unitName", unitName) + return err + } + } + } + return nil +} + +func CreateResourcePoolAndConfigTaskWithClear(m *OBTenantManager) error { + err := CreateResourcePoolAndConfig(m) + // clean created resource, restore to the initial state + if err != nil { + err := DeleteTenant(m) + if err != nil { + err = errors.Wrapf(err, "delete tenant when creating tenant") + return err + } + } + return err +} + +func CreateTenantTask(m *OBTenantManager) error { + tenantName := m.OBTenant.Spec.TenantName + err := m.createTenant() + if err != nil { + m.Logger.Error(err, "Create Tenant failed", "tenantName", tenantName) + return err + } + return nil +} + +func MaintainWhiteListTask(m *OBTenantManager) error { + tenantName := m.OBTenant.Spec.TenantName + err := CheckAndApplyWhiteList(m) + if err != nil { + m.Logger.Error(err, "maintain tenant, check and set whitelist (tcp invited node)", "tenantName", tenantName) + return err + } + return nil +} + +func CreateUserWithCredentials(m *OBTenantManager) error { + var con *operation.OceanbaseOperationManager + var err error + + creds := m.OBTenant.Spec.Credentials + if creds.Root != "" { + con, err = m.getTenantClient() + if err != nil { + return err + } + rootPwd, err := resourceutils.ReadPassword(m.Client, m.OBTenant.Namespace, creds.Root) + if err != nil { + if client.IgnoreNotFound(err) != nil { + m.Logger.Error(err, "Failed to get root password secret") + return err + } + } else if rootPwd != "" { + err = con.ChangeTenantUserPassword(oceanbaseconst.RootUser, rootPwd) + if err != nil { + m.Logger.Error(err, "Failed to change root password") + return err + } + } + } + + if creds.StandbyRO != "" { + var standbyROPwd string + secret := &corev1.Secret{} + err = m.Client.Get(m.Ctx, types.NamespacedName{ + Namespace: m.OBTenant.GetNamespace(), + Name: creds.StandbyRO, + }, secret) + if err != nil { + if kubeerrors.IsNotFound(err) { + secret.Name = creds.StandbyRO + secret.Namespace = m.OBTenant.GetNamespace() + secret.SetOwnerReferences([]metav1.OwnerReference{{ + APIVersion: m.OBTenant.APIVersion, + Kind: m.OBTenant.Kind, + Name: m.OBTenant.GetName(), + UID: m.OBTenant.GetUID(), + }}) + standbyROPwd = rand.String(16) + secret.StringData = map[string]string{ + "password": standbyROPwd, + } + err = m.Client.Create(m.Ctx, secret) + if err != nil { + m.Logger.Error(err, "Failed to create standbyRO password secret") + return err + } + } else { + m.Logger.Error(err, "Failed to get standbyRO password secret") + return err + } + } + + if standbyROPwd == "" && secret != nil { + standbyROPwd = string(secret.Data["password"]) + } + + if con == nil { + con, err = m.getTenantClient() + if err != nil { + return err + } + } + + if standbyROPwd != "" { + err = con.CreateUserWithPwd(oceanbaseconst.StandbyROUser, standbyROPwd) + if err != nil { + m.Logger.Error(err, "Failed to create standbyRO user with password") + return err + } + } else { + err = con.CreateUser(oceanbaseconst.StandbyROUser) + if err != nil { + m.Logger.Error(err, "Failed to create standbyRO user") + return err + } + } + + err = con.GrantPrivilege(oceanbaseconst.SelectPrivilege, oceanbaseconst.OceanbaseAllScope, oceanbaseconst.StandbyROUser) + if err != nil { + m.Logger.Error(err, "Failed to grant privilege to standbyRO") + return err + } + } + return nil +} diff --git a/internal/resource/obtenantbackup/init.go b/internal/resource/obtenantbackup/init.go deleted file mode 100644 index 314204d91..000000000 --- a/internal/resource/obtenantbackup/init.go +++ /dev/null @@ -1,22 +0,0 @@ -/* -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 obtenantbackup - -import ( - "github.com/oceanbase/ob-operator/pkg/task" -) - -func init() { - // obtenantbackup - task.GetRegistry().Register(fCreateBackupJobInDB, CreateBackupJobInDB) -} diff --git a/internal/resource/obtenantbackup/names.go b/internal/resource/obtenantbackup/names.go index 3adac3003..b96c435fe 100644 --- a/internal/resource/obtenantbackup/names.go +++ b/internal/resource/obtenantbackup/names.go @@ -17,9 +17,9 @@ import ( ) const ( - fCreateBackupJobInDB ttypes.FlowName = "create backup job in db" + fCreateBackupJobInOB ttypes.FlowName = "create backup job in db" ) const ( - tCreateBackupJobInDB ttypes.TaskName = "create backup job in db" + tCreateBackupJobInOB ttypes.TaskName = "create backup job in db" ) diff --git a/internal/resource/obtenantbackup/obtenantbackup_flow.go b/internal/resource/obtenantbackup/obtenantbackup_flow.go index 0bd854b5e..af336722a 100644 --- a/internal/resource/obtenantbackup/obtenantbackup_flow.go +++ b/internal/resource/obtenantbackup/obtenantbackup_flow.go @@ -18,11 +18,11 @@ import ( tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) -func CreateBackupJobInDB() *tasktypes.TaskFlow { +func genCreateBackupJobInDBFlow(_ *OBTenantBackupManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ - Name: fCreateBackupJobInDB, - Tasks: []tasktypes.TaskName{tCreateBackupJobInDB}, + Name: fCreateBackupJobInOB, + Tasks: []tasktypes.TaskName{tCreateBackupJobInOB}, TargetStatus: string(constants.BackupPolicyStatusRunning), OnFailure: tasktypes.FailureRule{ Strategy: strategy.StartOver, diff --git a/internal/resource/obtenantbackup/obtenantbackup_manager.go b/internal/resource/obtenantbackup/obtenantbackup_manager.go index d255dfd9a..a8b7450af 100644 --- a/internal/resource/obtenantbackup/obtenantbackup_manager.go +++ b/internal/resource/obtenantbackup/obtenantbackup_manager.go @@ -26,20 +26,20 @@ import ( "github.com/oceanbase/ob-operator/api/constants" apitypes "github.com/oceanbase/ob-operator/api/types" v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" + oceanbaseconst "github.com/oceanbase/ob-operator/internal/const/oceanbase" resourceutils "github.com/oceanbase/ob-operator/internal/resource/utils" "github.com/oceanbase/ob-operator/internal/telemetry" opresource "github.com/oceanbase/ob-operator/pkg/coordinator" "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/model" "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/operation" - "github.com/oceanbase/ob-operator/pkg/task" taskstatus "github.com/oceanbase/ob-operator/pkg/task/const/status" "github.com/oceanbase/ob-operator/pkg/task/const/strategy" tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) -type OBTenantBackupManager struct { - opresource.ResourceManager +var _ opresource.ResourceManager = &OBTenantBackupManager{} +type OBTenantBackupManager struct { Ctx context.Context Resource *v1alpha1.OBTenantBackup Client client.Client @@ -56,7 +56,8 @@ func (m *OBTenantBackupManager) GetStatus() string { } func (m *OBTenantBackupManager) IsDeleting() bool { - return m.Resource.GetDeletionTimestamp() != nil + ignoreDel, ok := resourceutils.GetAnnotationField(m.Resource, oceanbaseconst.AnnotationsIgnoreDeletion) + return !m.Resource.ObjectMeta.DeletionTimestamp.IsZero() && (!ok || ignoreDel != "true") } func (m *OBTenantBackupManager) CheckAndUpdateFinalizers() error { @@ -129,11 +130,7 @@ func (m *OBTenantBackupManager) UpdateStatus() error { } func (m *OBTenantBackupManager) GetTaskFunc(name tasktypes.TaskName) (tasktypes.TaskFunc, error) { - if name == tCreateBackupJobInDB { - return m.CreateBackupJobInOB, nil - } - - return nil, nil + return taskMap.GetTask(name, m) } func (m *OBTenantBackupManager) GetTaskFlow() (*tasktypes.TaskFlow, error) { @@ -142,14 +139,13 @@ func (m *OBTenantBackupManager) GetTaskFlow() (*tasktypes.TaskFlow, error) { return tasktypes.NewTaskFlow(m.Resource.Status.OperationContext), nil } var taskFlow *tasktypes.TaskFlow - var err error if m.Resource.Status.Status == constants.BackupJobStatusInitializing { switch m.Resource.Spec.Type { case constants.BackupJobTypeFull, constants.BackupJobTypeIncr: - taskFlow, err = task.GetRegistry().Get(fCreateBackupJobInDB) + taskFlow = genCreateBackupJobInDBFlow(m) } } - return taskFlow, err + return taskFlow, nil } func (m *OBTenantBackupManager) PrintErrEvent(err error) { @@ -163,38 +159,6 @@ func (m *OBTenantBackupManager) ArchiveResource() { m.Resource.Status.OperationContext = nil } -func (m *OBTenantBackupManager) CreateBackupJobInOB() tasktypes.TaskError { - job := m.Resource - con, err := m.getObOperationClient() - if err != nil { - m.Logger.Error(err, "failed to get ob operation client") - return err - } - if job.Spec.EncryptionSecret != "" { - password, err := resourceutils.ReadPassword(m.Client, job.Namespace, job.Spec.EncryptionSecret) - if err != nil { - m.Logger.Error(err, "failed to read backup encryption secret") - m.Recorder.Event(job, "Warning", "ReadBackupEncryptionSecretFailed", err.Error()) - } else if password != "" { - err = con.SetBackupPassword(password) - if err != nil { - m.Logger.Error(err, "failed to set backup password") - m.Recorder.Event(job, "Warning", "SetBackupPasswordFailed", err.Error()) - } - } - } - _, err = con.CreateAndReturnBackupJob(job.Spec.Type) - if err != nil { - m.Logger.Error(err, "failed to create and return backup job") - m.Recorder.Event(job, "Warning", "CreateAndReturnBackupJobFailed", err.Error()) - return err - } - - // job.Status.BackupJob = latest - m.Recorder.Event(job, "Create", "", "create backup job successfully") - return nil -} - func (m *OBTenantBackupManager) getObOperationClient() (*operation.OceanbaseOperationManager, error) { var err error job := m.Resource diff --git a/internal/resource/obtenantbackup/obtenantbackup_task.go b/internal/resource/obtenantbackup/obtenantbackup_task.go new file mode 100644 index 000000000..8f7e395ee --- /dev/null +++ b/internal/resource/obtenantbackup/obtenantbackup_task.go @@ -0,0 +1,55 @@ +/* +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 obtenantbackup + +import ( + resourceutils "github.com/oceanbase/ob-operator/internal/resource/utils" + "github.com/oceanbase/ob-operator/pkg/task/builder" + tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" +) + +//go:generate task-register $GOFILE + +var taskMap = builder.NewTaskHub[*OBTenantBackupManager]() + +func CreateBackupJobInOB(m *OBTenantBackupManager) tasktypes.TaskError { + job := m.Resource + con, err := m.getObOperationClient() + if err != nil { + m.Logger.Error(err, "failed to get ob operation client") + return err + } + if job.Spec.EncryptionSecret != "" { + password, err := resourceutils.ReadPassword(m.Client, job.Namespace, job.Spec.EncryptionSecret) + if err != nil { + m.Logger.Error(err, "failed to read backup encryption secret") + m.Recorder.Event(job, "Warning", "ReadBackupEncryptionSecretFailed", err.Error()) + } else if password != "" { + err = con.SetBackupPassword(password) + if err != nil { + m.Logger.Error(err, "failed to set backup password") + m.Recorder.Event(job, "Warning", "SetBackupPasswordFailed", err.Error()) + } + } + } + _, err = con.CreateAndReturnBackupJob(job.Spec.Type) + if err != nil { + m.Logger.Error(err, "failed to create and return backup job") + m.Recorder.Event(job, "Warning", "CreateAndReturnBackupJobFailed", err.Error()) + return err + } + + // job.Status.BackupJob = latest + m.Recorder.Event(job, "Create", "", "create backup job successfully") + return nil +} diff --git a/internal/resource/obtenantbackup/obtenantbackup_task_gen.go b/internal/resource/obtenantbackup/obtenantbackup_task_gen.go new file mode 100644 index 000000000..9126d3efd --- /dev/null +++ b/internal/resource/obtenantbackup/obtenantbackup_task_gen.go @@ -0,0 +1,6 @@ +// Code generated by go generate; DO NOT EDIT. +package obtenantbackup + +func init() { + taskMap.Register(tCreateBackupJobInOB, CreateBackupJobInOB) +} diff --git a/internal/resource/obtenantbackuppolicy/init.go b/internal/resource/obtenantbackuppolicy/init.go deleted file mode 100644 index 22083c61b..000000000 --- a/internal/resource/obtenantbackuppolicy/init.go +++ /dev/null @@ -1,27 +0,0 @@ -/* -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 obtenantbackuppolicy - -import ( - "github.com/oceanbase/ob-operator/pkg/task" -) - -func init() { - // obtenantbackuppolicy - task.GetRegistry().Register(fPrepareBackupPolicy, PrepareBackupPolicy) - task.GetRegistry().Register(fStartBackupJob, StartBackupJob) - task.GetRegistry().Register(fStopBackupPolicy, StopBackupPolicy) - task.GetRegistry().Register(fMaintainRunningPolicy, MaintainRunningPolicy) - task.GetRegistry().Register(fPauseBackup, PauseBackup) - task.GetRegistry().Register(fResumeBackup, ResumeBackup) -} diff --git a/internal/resource/obtenantbackuppolicy/names.go b/internal/resource/obtenantbackuppolicy/names.go index 525068bbc..5ed817fe2 100644 --- a/internal/resource/obtenantbackuppolicy/names.go +++ b/internal/resource/obtenantbackuppolicy/names.go @@ -28,8 +28,8 @@ const ( const ( tConfigureServerForBackup ttypes.TaskName = "configure server for backup" tCheckAndSpawnJobs ttypes.TaskName = "check and spawn jobs" - tStartBackupJob ttypes.TaskName = "start backup job" - tStopBackupPolicy ttypes.TaskName = "stop backup policy" + tStartBackup ttypes.TaskName = "start backup job" + tStopBackup ttypes.TaskName = "stop backup policy" tCleanOldBackupJobs ttypes.TaskName = "clean old backup jobs" tPauseBackup ttypes.TaskName = "pause backup" tResumeBackup ttypes.TaskName = "resume backup" diff --git a/internal/resource/obtenantbackuppolicy/obtenantbackuppolicy_flow.go b/internal/resource/obtenantbackuppolicy/obtenantbackuppolicy_flow.go index bf870be88..c0ca87c22 100644 --- a/internal/resource/obtenantbackuppolicy/obtenantbackuppolicy_flow.go +++ b/internal/resource/obtenantbackuppolicy/obtenantbackuppolicy_flow.go @@ -17,7 +17,7 @@ import ( tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) -func PrepareBackupPolicy() *tasktypes.TaskFlow { +func genPrepareBackupPolicyFlow(_ *ObTenantBackupPolicyManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fPrepareBackupPolicy, @@ -30,11 +30,11 @@ func PrepareBackupPolicy() *tasktypes.TaskFlow { } } -func StartBackupJob() *tasktypes.TaskFlow { +func genStartBackupJobFlow(_ *ObTenantBackupPolicyManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fStartBackupJob, - Tasks: []tasktypes.TaskName{tStartBackupJob}, + Tasks: []tasktypes.TaskName{tStartBackup}, TargetStatus: string(constants.BackupPolicyStatusRunning), OnFailure: tasktypes.FailureRule{ NextTryStatus: string(constants.BackupPolicyStatusFailed), @@ -43,17 +43,17 @@ func StartBackupJob() *tasktypes.TaskFlow { } } -func StopBackupPolicy() *tasktypes.TaskFlow { +func genStopBackupPolicyFlow(_ *ObTenantBackupPolicyManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fStopBackupPolicy, - Tasks: []tasktypes.TaskName{tStopBackupPolicy}, + Tasks: []tasktypes.TaskName{tStopBackup}, TargetStatus: string(constants.BackupPolicyStatusStopped), }, } } -func MaintainRunningPolicy() *tasktypes.TaskFlow { +func genMaintainRunningPolicyFlow(_ *ObTenantBackupPolicyManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fMaintainRunningPolicy, @@ -66,7 +66,7 @@ func MaintainRunningPolicy() *tasktypes.TaskFlow { } } -func PauseBackup() *tasktypes.TaskFlow { +func genPauseBackupFlow(_ *ObTenantBackupPolicyManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fPauseBackup, @@ -76,7 +76,7 @@ func PauseBackup() *tasktypes.TaskFlow { } } -func ResumeBackup() *tasktypes.TaskFlow { +func genResumeBackupFlow(_ *ObTenantBackupPolicyManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fResumeBackup, diff --git a/internal/resource/obtenantbackuppolicy/obtenantbackuppolicy_manager.go b/internal/resource/obtenantbackuppolicy/obtenantbackuppolicy_manager.go index 86ffc2b61..6f2451132 100644 --- a/internal/resource/obtenantbackuppolicy/obtenantbackuppolicy_manager.go +++ b/internal/resource/obtenantbackuppolicy/obtenantbackuppolicy_manager.go @@ -35,14 +35,14 @@ import ( "github.com/oceanbase/ob-operator/internal/telemetry" opresource "github.com/oceanbase/ob-operator/pkg/coordinator" "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/operation" - "github.com/oceanbase/ob-operator/pkg/task" taskstatus "github.com/oceanbase/ob-operator/pkg/task/const/status" "github.com/oceanbase/ob-operator/pkg/task/const/strategy" tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) +var _ opresource.ResourceManager = &ObTenantBackupPolicyManager{} + type ObTenantBackupPolicyManager struct { - opresource.ResourceManager Ctx context.Context BackupPolicy *v1alpha1.OBTenantBackupPolicy Client client.Client @@ -57,7 +57,8 @@ func (m *ObTenantBackupPolicyManager) IsNewResource() bool { } func (m *ObTenantBackupPolicyManager) IsDeleting() bool { - return !m.BackupPolicy.ObjectMeta.DeletionTimestamp.IsZero() + ignoreDel, ok := resourceutils.GetAnnotationField(m.BackupPolicy, oceanbaseconst.AnnotationsIgnoreDeletion) + return !m.BackupPolicy.ObjectMeta.DeletionTimestamp.IsZero() && (!ok || ignoreDel != "true") } func (m *ObTenantBackupPolicyManager) GetStatus() string { @@ -276,24 +277,7 @@ func (m *ObTenantBackupPolicyManager) UpdateStatus() error { } func (m *ObTenantBackupPolicyManager) GetTaskFunc(name tasktypes.TaskName) (tasktypes.TaskFunc, error) { - switch name { - case tConfigureServerForBackup: - return m.ConfigureServerForBackup, nil - case tStartBackupJob: - return m.StartBackup, nil - case tStopBackupPolicy: - return m.StopBackup, nil - case tCheckAndSpawnJobs: - return m.CheckAndSpawnJobs, nil - case tCleanOldBackupJobs: - return m.CleanOldBackupJobs, nil - case tPauseBackup: - return m.PauseBackup, nil - case tResumeBackup: - return m.ResumeBackup, nil - default: - return nil, errors.Errorf("unknown task name %s", name) - } + return taskMap.GetTask(name, m) } func (m *ObTenantBackupPolicyManager) GetTaskFlow() (*tasktypes.TaskFlow, error) { @@ -302,29 +286,25 @@ func (m *ObTenantBackupPolicyManager) GetTaskFlow() (*tasktypes.TaskFlow, error) return tasktypes.NewTaskFlow(m.BackupPolicy.Status.OperationContext), nil } var taskFlow *tasktypes.TaskFlow - var err error status := m.BackupPolicy.Status.Status // get task flow depending on BackupPolicy status switch status { case constants.BackupPolicyStatusPreparing: - taskFlow, err = task.GetRegistry().Get(fPrepareBackupPolicy) + taskFlow = genPrepareBackupPolicyFlow(m) case constants.BackupPolicyStatusPrepared: - taskFlow, err = task.GetRegistry().Get(fStartBackupJob) + taskFlow = genStartBackupJobFlow(m) case constants.BackupPolicyStatusMaintaining: - taskFlow, err = task.GetRegistry().Get(fMaintainRunningPolicy) + taskFlow = genMaintainRunningPolicyFlow(m) case constants.BackupPolicyStatusPausing: - taskFlow, err = task.GetRegistry().Get(fPauseBackup) + taskFlow = genPauseBackupFlow(m) case constants.BackupPolicyStatusResuming: - taskFlow, err = task.GetRegistry().Get(fResumeBackup) + taskFlow = genResumeBackupFlow(m) case constants.BackupPolicyStatusDeleting: - taskFlow, err = task.GetRegistry().Get(fStopBackupPolicy) + taskFlow = genStopBackupPolicyFlow(m) default: // Paused, Stopped or Failed return nil, nil } - if err != nil { - return nil, err - } if taskFlow.OperationContext.OnFailure.Strategy == "" { taskFlow.OperationContext.OnFailure.Strategy = strategy.StartOver diff --git a/internal/resource/obtenantbackuppolicy/obtenantbackuppolicy_task.go b/internal/resource/obtenantbackuppolicy/obtenantbackuppolicy_task.go index f1e032a6e..bfdfe1750 100644 --- a/internal/resource/obtenantbackuppolicy/obtenantbackuppolicy_task.go +++ b/internal/resource/obtenantbackuppolicy/obtenantbackuppolicy_task.go @@ -14,32 +14,28 @@ package obtenantbackuppolicy import ( "fmt" - "path" "strconv" "strings" "time" - "github.com/pkg/errors" cron "github.com/robfig/cron/v3" v1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" - "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" - apitypes "github.com/oceanbase/ob-operator/api/types" - constants "github.com/oceanbase/ob-operator/api/constants" "github.com/oceanbase/ob-operator/api/v1alpha1" oceanbaseconst "github.com/oceanbase/ob-operator/internal/const/oceanbase" - resourceutils "github.com/oceanbase/ob-operator/internal/resource/utils" - "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/model" - "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/operation" + "github.com/oceanbase/ob-operator/pkg/task/builder" tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) -func (m *ObTenantBackupPolicyManager) ConfigureServerForBackup() tasktypes.TaskError { +//go:generate task-register $GOFILE + +var taskMap = builder.NewTaskHub[*ObTenantBackupPolicyManager]() + +func ConfigureServerForBackup(m *ObTenantBackupPolicyManager) tasktypes.TaskError { m.Logger.V(oceanbaseconst.LogLevelDebug).Info("Configure Server For Backup") con, err := m.getOperationManager() if err != nil { @@ -149,7 +145,7 @@ func (m *ObTenantBackupPolicyManager) ConfigureServerForBackup() tasktypes.TaskE return nil } -func (m *ObTenantBackupPolicyManager) StartBackup() tasktypes.TaskError { +func StartBackup(m *ObTenantBackupPolicyManager) tasktypes.TaskError { con, err := m.getOperationManager() if err != nil { return err @@ -188,7 +184,7 @@ func (m *ObTenantBackupPolicyManager) StartBackup() tasktypes.TaskError { return m.createBackupJobIfNotExists(constants.BackupJobTypeFull) } -func (m *ObTenantBackupPolicyManager) StopBackup() tasktypes.TaskError { +func StopBackup(m *ObTenantBackupPolicyManager) tasktypes.TaskError { con, err := m.getOperationManager() if err != nil { return err @@ -220,7 +216,7 @@ func (m *ObTenantBackupPolicyManager) StopBackup() tasktypes.TaskError { return nil } -func (m *ObTenantBackupPolicyManager) CheckAndSpawnJobs() tasktypes.TaskError { +func CheckAndSpawnJobs(m *ObTenantBackupPolicyManager) tasktypes.TaskError { var backupPath string if m.BackupPolicy.Spec.DataBackup.Destination.Type == constants.BackupDestTypeOSS { backupPath = m.BackupPolicy.Spec.DataBackup.Destination.Path @@ -316,7 +312,7 @@ func (m *ObTenantBackupPolicyManager) CheckAndSpawnJobs() tasktypes.TaskError { return nil } -func (m *ObTenantBackupPolicyManager) CleanOldBackupJobs() tasktypes.TaskError { +func CleanOldBackupJobs(m *ObTenantBackupPolicyManager) tasktypes.TaskError { // JobKeepWindow is not set, do nothing if m.BackupPolicy.Spec.JobKeepWindow == "" { return nil @@ -366,7 +362,7 @@ func (m *ObTenantBackupPolicyManager) CleanOldBackupJobs() tasktypes.TaskError { return nil } -func (m *ObTenantBackupPolicyManager) PauseBackup() tasktypes.TaskError { +func PauseBackup(m *ObTenantBackupPolicyManager) tasktypes.TaskError { con, err := m.getOperationManager() if err != nil { return err @@ -393,7 +389,7 @@ func (m *ObTenantBackupPolicyManager) PauseBackup() tasktypes.TaskError { return nil } -func (m *ObTenantBackupPolicyManager) ResumeBackup() tasktypes.TaskError { +func ResumeBackup(m *ObTenantBackupPolicyManager) tasktypes.TaskError { con, err := m.getOperationManager() if err != nil { return err @@ -420,298 +416,3 @@ func (m *ObTenantBackupPolicyManager) ResumeBackup() tasktypes.TaskError { m.Recorder.Event(m.BackupPolicy, v1.EventTypeNormal, "ResumeBackup", "Resume backup policy") return m.createBackupJobIfNotExists(constants.BackupJobTypeFull) } - -func (m *ObTenantBackupPolicyManager) syncLatestJobs() error { - con, err := m.getOperationManager() - if err != nil { - return err - } - latestArchiveJob, err := con.GetLatestArchiveLogJob() - if err != nil { - return err - } - latestCleanJob, err := con.GetLatestBackupCleanJob() - if err != nil { - return err - } - m.BackupPolicy.Status.LatestArchiveLogJob = latestArchiveJob - m.BackupPolicy.Status.LatestBackupCleanJob = latestCleanJob - return nil -} - -func (m *ObTenantBackupPolicyManager) getLatestBackupJob(jobType apitypes.BackupJobType) (*model.OBBackupJob, error) { - con, err := m.getOperationManager() - if err != nil { - return nil, err - } - return con.GetLatestBackupJobOfType(jobType) -} - -func (m *ObTenantBackupPolicyManager) getLatestBackupJobOfTypeAndPath(jobType apitypes.BackupJobType, path string) (*model.OBBackupJob, error) { - con, err := m.getOperationManager() - if err != nil { - return nil, err - } - return con.GetLatestBackupJobOfTypeAndPath(jobType, path) -} - -// get operation manager to exec sql -func (m *ObTenantBackupPolicyManager) getOperationManager() (*operation.OceanbaseOperationManager, error) { - if m.con != nil { - return m.con, nil - } - var con *operation.OceanbaseOperationManager - var err error - obcluster := &v1alpha1.OBCluster{} - err = m.Client.Get(m.Ctx, types.NamespacedName{ - Namespace: m.BackupPolicy.Namespace, - Name: m.BackupPolicy.Spec.ObClusterName, - }, obcluster) - if err != nil { - return nil, errors.Wrap(err, "get obcluster") - } - if m.BackupPolicy.Spec.TenantName != "" && m.BackupPolicy.Spec.TenantSecret != "" { - con, err = resourceutils.GetTenantRootOperationClient(m.Client, m.Logger, obcluster, m.BackupPolicy.Spec.TenantName, m.BackupPolicy.Spec.TenantSecret) - if err != nil { - return nil, errors.Wrap(err, "get oceanbase operation manager") - } - } else if m.BackupPolicy.Spec.TenantCRName != "" { - tenantCR := &v1alpha1.OBTenant{} - err = m.Client.Get(m.Ctx, types.NamespacedName{ - Namespace: m.BackupPolicy.Namespace, - Name: m.BackupPolicy.Spec.TenantCRName, - }, tenantCR) - if err != nil { - return nil, err - } - - con, err = resourceutils.GetTenantRootOperationClient(m.Client, m.Logger, obcluster, tenantCR.Spec.TenantName, tenantCR.Status.Credentials.Root) - if err != nil { - return nil, errors.Wrap(err, "get oceanbase operation manager") - } - } - m.con = con - return con, nil -} - -func (m *ObTenantBackupPolicyManager) getArchiveDestPath() string { - targetDest := m.BackupPolicy.Spec.LogArchive.Destination - if targetDest.Type == constants.BackupDestTypeNFS || resourceutils.IsZero(targetDest.Type) { - return "file://" + path.Join(oceanbaseconst.BackupPath, targetDest.Path) - } else if targetDest.Type == constants.BackupDestTypeOSS && targetDest.OSSAccessSecret != "" { - secret := &v1.Secret{} - err := m.Client.Get(m.Ctx, types.NamespacedName{ - Namespace: m.BackupPolicy.GetNamespace(), - Name: targetDest.OSSAccessSecret, - }, secret) - if err != nil { - m.PrintErrEvent(err) - return "" - } - return strings.Join([]string{targetDest.Path, "access_id=" + string(secret.Data["accessId"]), "access_key=" + string(secret.Data["accessKey"])}, "&") - } - return targetDest.Path -} - -func (m *ObTenantBackupPolicyManager) getArchiveDestSettingValue() string { - path := m.getArchiveDestPath() - archiveSpec := m.BackupPolicy.Spec.LogArchive - if archiveSpec.SwitchPieceInterval != "" { - path += fmt.Sprintf(" PIECE_SWITCH_INTERVAL=%s", archiveSpec.SwitchPieceInterval) - } - if archiveSpec.Binding != "" { - path += fmt.Sprintf(" BINDING=%s", archiveSpec.Binding) - } - return "LOCATION=" + path -} - -func (m *ObTenantBackupPolicyManager) getBackupDestPath() string { - targetDest := m.BackupPolicy.Spec.DataBackup.Destination - if targetDest.Type == constants.BackupDestTypeNFS || resourceutils.IsZero(targetDest.Type) { - return "file://" + path.Join(oceanbaseconst.BackupPath, targetDest.Path) - } else if targetDest.Type == constants.BackupDestTypeOSS && targetDest.OSSAccessSecret != "" { - secret := &v1.Secret{} - err := m.Client.Get(m.Ctx, types.NamespacedName{ - Namespace: m.BackupPolicy.GetNamespace(), - Name: targetDest.OSSAccessSecret, - }, secret) - if err != nil { - m.PrintErrEvent(err) - return "" - } - return strings.Join([]string{targetDest.Path, "access_id=" + string(secret.Data["accessId"]), "access_key=" + string(secret.Data["accessKey"])}, "&") - } - return targetDest.Path -} - -func (m *ObTenantBackupPolicyManager) createBackupJob(jobType apitypes.BackupJobType) error { - var path string - switch jobType { - case constants.BackupJobTypeClean: - fallthrough - case constants.BackupJobTypeIncr: - fallthrough - case constants.BackupJobTypeFull: - path = m.getBackupDestPath() - - case constants.BackupJobTypeArchive: - path = m.getArchiveDestPath() - } - var tenantRecordName string - var tenantSecret string - if m.BackupPolicy.Spec.TenantName != "" { - tenantRecordName = m.BackupPolicy.Spec.TenantName - tenantSecret = m.BackupPolicy.Spec.TenantSecret - } else { - tenant, err := m.getOBTenantCR() - if err != nil { - return err - } - tenantRecordName = tenant.Spec.TenantName - tenantSecret = tenant.Status.Credentials.Root - } - - backupJob := &v1alpha1.OBTenantBackup{ - ObjectMeta: metav1.ObjectMeta{ - Name: m.BackupPolicy.Name + "-" + strings.ToLower(string(jobType)) + "-" + time.Now().Format("20060102150405"), - Namespace: m.BackupPolicy.Namespace, - OwnerReferences: []metav1.OwnerReference{{ - APIVersion: m.BackupPolicy.APIVersion, - Kind: m.BackupPolicy.Kind, - Name: m.BackupPolicy.Name, - UID: m.BackupPolicy.GetUID(), - BlockOwnerDeletion: resourceutils.GetRef(true), - }}, - Labels: map[string]string{ - oceanbaseconst.LabelRefOBCluster: m.BackupPolicy.Labels[oceanbaseconst.LabelRefOBCluster], - oceanbaseconst.LabelRefBackupPolicy: m.BackupPolicy.Name, - oceanbaseconst.LabelRefUID: string(m.BackupPolicy.GetUID()), - oceanbaseconst.LabelBackupType: string(jobType), - }, - }, - Spec: v1alpha1.OBTenantBackupSpec{ - Path: path, - Type: jobType, - TenantName: tenantRecordName, - TenantSecret: tenantSecret, - ObClusterName: m.BackupPolicy.Spec.ObClusterName, - EncryptionSecret: m.BackupPolicy.Spec.DataBackup.EncryptionSecret, - }, - } - return m.Client.Create(m.Ctx, backupJob) -} - -func (m *ObTenantBackupPolicyManager) createBackupJobIfNotExists(jobType apitypes.BackupJobType) error { - noRunningJobs, err := m.noRunningJobs(jobType) - if err != nil { - m.Logger.Error(err, "Failed to check if there is running backup job") - return nil - } - if noRunningJobs { - return m.createBackupJob(jobType) - } - return nil -} - -func (m *ObTenantBackupPolicyManager) noRunningJobs(jobType apitypes.BackupJobType) (bool, error) { - var runningJobs v1alpha1.OBTenantBackupList - err := m.Client.List(m.Ctx, &runningJobs, - client.MatchingLabels{ - oceanbaseconst.LabelRefBackupPolicy: m.BackupPolicy.Name, - oceanbaseconst.LabelBackupType: string(jobType), - }, - client.InNamespace(m.BackupPolicy.Namespace)) - if err != nil { - return false, err - } - for _, item := range runningJobs.Items { - if item.Spec.Type == jobType { - switch item.Status.Status { - case "": - fallthrough - case constants.BackupJobStatusInitializing: - fallthrough - case constants.BackupJobStatusRunning: - return false, nil - } - } - } - return true, nil -} - -// getTenantRecord return tenant info from status if exists, otherwise query from database view -func (m *ObTenantBackupPolicyManager) getTenantRecord(useCache bool) (*model.OBTenant, error) { - if useCache && m.BackupPolicy.Status.TenantInfo != nil { - return m.BackupPolicy.Status.TenantInfo, nil - } - con, err := m.getOperationManager() - if err != nil { - return nil, err - } - var tenantRecordName string - if m.BackupPolicy.Spec.TenantName != "" { - tenantRecordName = m.BackupPolicy.Spec.TenantName - } else { - tenantRecordName, err = m.getTenantRecordName() - if err != nil { - return nil, err - } - } - tenants, err := con.ListTenantWithName(tenantRecordName) - if err != nil { - return nil, err - } - if len(tenants) == 0 { - return nil, errors.Errorf("tenant %s not found", tenantRecordName) - } - return tenants[0], nil -} - -func (m *ObTenantBackupPolicyManager) configureBackupCleanPolicy() error { - con, err := m.getOperationManager() - if err != nil { - return err - } - cleanConfig := &m.BackupPolicy.Spec.DataClean - cleanPolicy, err := con.ListBackupCleanPolicy() - if err != nil { - return err - } - policyName := "default" - if len(cleanPolicy) == 0 { - err = con.AddCleanBackupPolicy(policyName, cleanConfig.RecoveryWindow) - if err != nil { - return err - } - } else { - for _, policy := range cleanPolicy { - if policy.RecoveryWindow != cleanConfig.RecoveryWindow { - err = con.RemoveCleanBackupPolicy(policy.PolicyName) - if err != nil { - return err - } - err = con.AddCleanBackupPolicy(policyName, cleanConfig.RecoveryWindow) - if err != nil { - return err - } - break - } - } - } - return nil -} - -func (m *ObTenantBackupPolicyManager) getTenantRecordName() (string, error) { - if m.BackupPolicy.Status.TenantCR != nil { - return m.BackupPolicy.Status.TenantCR.Spec.TenantName, nil - } - tenant := &v1alpha1.OBTenant{} - err := m.Client.Get(m.Ctx, types.NamespacedName{ - Namespace: m.BackupPolicy.Namespace, - Name: m.BackupPolicy.Spec.TenantCRName, - }, tenant) - if err != nil { - return "", err - } - return tenant.Spec.TenantName, nil -} diff --git a/internal/resource/obtenantbackuppolicy/obtenantbackuppolicy_task_gen.go b/internal/resource/obtenantbackuppolicy/obtenantbackuppolicy_task_gen.go new file mode 100644 index 000000000..372a03e24 --- /dev/null +++ b/internal/resource/obtenantbackuppolicy/obtenantbackuppolicy_task_gen.go @@ -0,0 +1,12 @@ +// Code generated by go generate; DO NOT EDIT. +package obtenantbackuppolicy + +func init() { + taskMap.Register(tConfigureServerForBackup, ConfigureServerForBackup) + taskMap.Register(tStartBackup, StartBackup) + taskMap.Register(tStopBackup, StopBackup) + taskMap.Register(tCheckAndSpawnJobs, CheckAndSpawnJobs) + taskMap.Register(tCleanOldBackupJobs, CleanOldBackupJobs) + taskMap.Register(tPauseBackup, PauseBackup) + taskMap.Register(tResumeBackup, ResumeBackup) +} diff --git a/internal/resource/obtenantbackuppolicy/utils.go b/internal/resource/obtenantbackuppolicy/utils.go new file mode 100644 index 000000000..b6e11924f --- /dev/null +++ b/internal/resource/obtenantbackuppolicy/utils.go @@ -0,0 +1,329 @@ +/* +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 obtenantbackuppolicy + +import ( + "fmt" + "path" + "strings" + "time" + + "github.com/pkg/errors" + v1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/oceanbase/ob-operator/api/constants" + apitypes "github.com/oceanbase/ob-operator/api/types" + v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" + oceanbaseconst "github.com/oceanbase/ob-operator/internal/const/oceanbase" + resourceutils "github.com/oceanbase/ob-operator/internal/resource/utils" + "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/model" + "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/operation" +) + +func (m *ObTenantBackupPolicyManager) syncLatestJobs() error { + con, err := m.getOperationManager() + if err != nil { + return err + } + latestArchiveJob, err := con.GetLatestArchiveLogJob() + if err != nil { + return err + } + latestCleanJob, err := con.GetLatestBackupCleanJob() + if err != nil { + return err + } + m.BackupPolicy.Status.LatestArchiveLogJob = latestArchiveJob + m.BackupPolicy.Status.LatestBackupCleanJob = latestCleanJob + return nil +} + +func (m *ObTenantBackupPolicyManager) getLatestBackupJob(jobType apitypes.BackupJobType) (*model.OBBackupJob, error) { + con, err := m.getOperationManager() + if err != nil { + return nil, err + } + return con.GetLatestBackupJobOfType(jobType) +} + +func (m *ObTenantBackupPolicyManager) getLatestBackupJobOfTypeAndPath(jobType apitypes.BackupJobType, path string) (*model.OBBackupJob, error) { + con, err := m.getOperationManager() + if err != nil { + return nil, err + } + return con.GetLatestBackupJobOfTypeAndPath(jobType, path) +} + +// get operation manager to exec sql +func (m *ObTenantBackupPolicyManager) getOperationManager() (*operation.OceanbaseOperationManager, error) { + if m.con != nil { + return m.con, nil + } + var con *operation.OceanbaseOperationManager + var err error + obcluster := &v1alpha1.OBCluster{} + err = m.Client.Get(m.Ctx, types.NamespacedName{ + Namespace: m.BackupPolicy.Namespace, + Name: m.BackupPolicy.Spec.ObClusterName, + }, obcluster) + if err != nil { + return nil, errors.Wrap(err, "get obcluster") + } + if m.BackupPolicy.Spec.TenantName != "" && m.BackupPolicy.Spec.TenantSecret != "" { + con, err = resourceutils.GetTenantRootOperationClient(m.Client, m.Logger, obcluster, m.BackupPolicy.Spec.TenantName, m.BackupPolicy.Spec.TenantSecret) + if err != nil { + return nil, errors.Wrap(err, "get oceanbase operation manager") + } + } else if m.BackupPolicy.Spec.TenantCRName != "" { + tenantCR := &v1alpha1.OBTenant{} + err = m.Client.Get(m.Ctx, types.NamespacedName{ + Namespace: m.BackupPolicy.Namespace, + Name: m.BackupPolicy.Spec.TenantCRName, + }, tenantCR) + if err != nil { + return nil, err + } + + con, err = resourceutils.GetTenantRootOperationClient(m.Client, m.Logger, obcluster, tenantCR.Spec.TenantName, tenantCR.Status.Credentials.Root) + if err != nil { + return nil, errors.Wrap(err, "get oceanbase operation manager") + } + } + m.con = con + return con, nil +} + +func (m *ObTenantBackupPolicyManager) getArchiveDestPath() string { + targetDest := m.BackupPolicy.Spec.LogArchive.Destination + if targetDest.Type == constants.BackupDestTypeNFS || resourceutils.IsZero(targetDest.Type) { + return "file://" + path.Join(oceanbaseconst.BackupPath, targetDest.Path) + } else if targetDest.Type == constants.BackupDestTypeOSS && targetDest.OSSAccessSecret != "" { + secret := &v1.Secret{} + err := m.Client.Get(m.Ctx, types.NamespacedName{ + Namespace: m.BackupPolicy.GetNamespace(), + Name: targetDest.OSSAccessSecret, + }, secret) + if err != nil { + m.PrintErrEvent(err) + return "" + } + return strings.Join([]string{targetDest.Path, "access_id=" + string(secret.Data["accessId"]), "access_key=" + string(secret.Data["accessKey"])}, "&") + } + return targetDest.Path +} + +func (m *ObTenantBackupPolicyManager) getArchiveDestSettingValue() string { + path := m.getArchiveDestPath() + archiveSpec := m.BackupPolicy.Spec.LogArchive + if archiveSpec.SwitchPieceInterval != "" { + path += fmt.Sprintf(" PIECE_SWITCH_INTERVAL=%s", archiveSpec.SwitchPieceInterval) + } + if archiveSpec.Binding != "" { + path += fmt.Sprintf(" BINDING=%s", archiveSpec.Binding) + } + return "LOCATION=" + path +} + +func (m *ObTenantBackupPolicyManager) getBackupDestPath() string { + targetDest := m.BackupPolicy.Spec.DataBackup.Destination + if targetDest.Type == constants.BackupDestTypeNFS || resourceutils.IsZero(targetDest.Type) { + return "file://" + path.Join(oceanbaseconst.BackupPath, targetDest.Path) + } else if targetDest.Type == constants.BackupDestTypeOSS && targetDest.OSSAccessSecret != "" { + secret := &v1.Secret{} + err := m.Client.Get(m.Ctx, types.NamespacedName{ + Namespace: m.BackupPolicy.GetNamespace(), + Name: targetDest.OSSAccessSecret, + }, secret) + if err != nil { + m.PrintErrEvent(err) + return "" + } + return strings.Join([]string{targetDest.Path, "access_id=" + string(secret.Data["accessId"]), "access_key=" + string(secret.Data["accessKey"])}, "&") + } + return targetDest.Path +} + +func (m *ObTenantBackupPolicyManager) createBackupJob(jobType apitypes.BackupJobType) error { + var path string + switch jobType { + case constants.BackupJobTypeClean: + fallthrough + case constants.BackupJobTypeIncr: + fallthrough + case constants.BackupJobTypeFull: + path = m.getBackupDestPath() + + case constants.BackupJobTypeArchive: + path = m.getArchiveDestPath() + } + var tenantRecordName string + var tenantSecret string + if m.BackupPolicy.Spec.TenantName != "" { + tenantRecordName = m.BackupPolicy.Spec.TenantName + tenantSecret = m.BackupPolicy.Spec.TenantSecret + } else { + tenant, err := m.getOBTenantCR() + if err != nil { + return err + } + tenantRecordName = tenant.Spec.TenantName + tenantSecret = tenant.Status.Credentials.Root + } + + backupJob := &v1alpha1.OBTenantBackup{ + ObjectMeta: metav1.ObjectMeta{ + Name: m.BackupPolicy.Name + "-" + strings.ToLower(string(jobType)) + "-" + time.Now().Format("20060102150405"), + Namespace: m.BackupPolicy.Namespace, + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: m.BackupPolicy.APIVersion, + Kind: m.BackupPolicy.Kind, + Name: m.BackupPolicy.Name, + UID: m.BackupPolicy.GetUID(), + BlockOwnerDeletion: resourceutils.GetRef(true), + }}, + Labels: map[string]string{ + oceanbaseconst.LabelRefOBCluster: m.BackupPolicy.Labels[oceanbaseconst.LabelRefOBCluster], + oceanbaseconst.LabelRefBackupPolicy: m.BackupPolicy.Name, + oceanbaseconst.LabelRefUID: string(m.BackupPolicy.GetUID()), + oceanbaseconst.LabelBackupType: string(jobType), + }, + }, + Spec: v1alpha1.OBTenantBackupSpec{ + Path: path, + Type: jobType, + TenantName: tenantRecordName, + TenantSecret: tenantSecret, + ObClusterName: m.BackupPolicy.Spec.ObClusterName, + EncryptionSecret: m.BackupPolicy.Spec.DataBackup.EncryptionSecret, + }, + } + return m.Client.Create(m.Ctx, backupJob) +} + +func (m *ObTenantBackupPolicyManager) createBackupJobIfNotExists(jobType apitypes.BackupJobType) error { + noRunningJobs, err := m.noRunningJobs(jobType) + if err != nil { + m.Logger.Error(err, "Failed to check if there is running backup job") + return nil + } + if noRunningJobs { + return m.createBackupJob(jobType) + } + return nil +} + +func (m *ObTenantBackupPolicyManager) noRunningJobs(jobType apitypes.BackupJobType) (bool, error) { + var runningJobs v1alpha1.OBTenantBackupList + err := m.Client.List(m.Ctx, &runningJobs, + client.MatchingLabels{ + oceanbaseconst.LabelRefBackupPolicy: m.BackupPolicy.Name, + oceanbaseconst.LabelBackupType: string(jobType), + }, + client.InNamespace(m.BackupPolicy.Namespace)) + if err != nil { + return false, err + } + for _, item := range runningJobs.Items { + if item.Spec.Type == jobType { + switch item.Status.Status { + case "": + fallthrough + case constants.BackupJobStatusInitializing: + fallthrough + case constants.BackupJobStatusRunning: + return false, nil + } + } + } + return true, nil +} + +// getTenantRecord return tenant info from status if exists, otherwise query from database view +func (m *ObTenantBackupPolicyManager) getTenantRecord(useCache bool) (*model.OBTenant, error) { + if useCache && m.BackupPolicy.Status.TenantInfo != nil { + return m.BackupPolicy.Status.TenantInfo, nil + } + con, err := m.getOperationManager() + if err != nil { + return nil, err + } + var tenantRecordName string + if m.BackupPolicy.Spec.TenantName != "" { + tenantRecordName = m.BackupPolicy.Spec.TenantName + } else { + tenantRecordName, err = m.getTenantRecordName() + if err != nil { + return nil, err + } + } + tenants, err := con.ListTenantWithName(tenantRecordName) + if err != nil { + return nil, err + } + if len(tenants) == 0 { + return nil, errors.Errorf("tenant %s not found", tenantRecordName) + } + return tenants[0], nil +} + +func (m *ObTenantBackupPolicyManager) configureBackupCleanPolicy() error { + con, err := m.getOperationManager() + if err != nil { + return err + } + cleanConfig := &m.BackupPolicy.Spec.DataClean + cleanPolicy, err := con.ListBackupCleanPolicy() + if err != nil { + return err + } + policyName := "default" + if len(cleanPolicy) == 0 { + err = con.AddCleanBackupPolicy(policyName, cleanConfig.RecoveryWindow) + if err != nil { + return err + } + } else { + for _, policy := range cleanPolicy { + if policy.RecoveryWindow != cleanConfig.RecoveryWindow { + err = con.RemoveCleanBackupPolicy(policy.PolicyName) + if err != nil { + return err + } + err = con.AddCleanBackupPolicy(policyName, cleanConfig.RecoveryWindow) + if err != nil { + return err + } + break + } + } + } + return nil +} + +func (m *ObTenantBackupPolicyManager) getTenantRecordName() (string, error) { + if m.BackupPolicy.Status.TenantCR != nil { + return m.BackupPolicy.Status.TenantCR.Spec.TenantName, nil + } + tenant := &v1alpha1.OBTenant{} + err := m.Client.Get(m.Ctx, types.NamespacedName{ + Namespace: m.BackupPolicy.Namespace, + Name: m.BackupPolicy.Spec.TenantCRName, + }, tenant) + if err != nil { + return "", err + } + return tenant.Spec.TenantName, nil +} diff --git a/internal/resource/obtenantoperation/init.go b/internal/resource/obtenantoperation/init.go deleted file mode 100644 index f85607a90..000000000 --- a/internal/resource/obtenantoperation/init.go +++ /dev/null @@ -1,27 +0,0 @@ -/* -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 obtenantoperation - -import ( - "github.com/oceanbase/ob-operator/pkg/task" -) - -func init() { - // tenant operation - task.GetRegistry().Register(fChangeTenantRootPasswordFlow, ChangeTenantRootPassword) - task.GetRegistry().Register(fActivateStandbyTenantFlow, ActivateStandbyTenantOp) - task.GetRegistry().Register(fSwitchoverTenantsFlow, SwitchoverTenants) - task.GetRegistry().Register(fRevertSwitchoverTenantsFlow, RevertSwitchoverTenants) - task.GetRegistry().Register(fOpReplayLog, ReplayLogOfStandby) - task.GetRegistry().Register(fOpUpgradeTenant, UpgradeTenant) -} diff --git a/internal/resource/obtenantoperation/obtenantoperation_flow.go b/internal/resource/obtenantoperation/obtenantoperation_flow.go index 5b8861a47..5952ce6b5 100644 --- a/internal/resource/obtenantoperation/obtenantoperation_flow.go +++ b/internal/resource/obtenantoperation/obtenantoperation_flow.go @@ -17,7 +17,7 @@ import ( tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) -func ChangeTenantRootPassword() *tasktypes.TaskFlow { +func genChangeTenantRootPasswordFlow(_ *ObTenantOperationManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fChangeTenantRootPasswordFlow, @@ -32,7 +32,7 @@ func ChangeTenantRootPassword() *tasktypes.TaskFlow { } } -func ActivateStandbyTenantOp() *tasktypes.TaskFlow { +func genActivateStandbyTenantOpFlow(_ *ObTenantOperationManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fActivateStandbyTenantFlow, @@ -48,7 +48,7 @@ func ActivateStandbyTenantOp() *tasktypes.TaskFlow { } } -func SwitchoverTenants() *tasktypes.TaskFlow { +func genSwitchoverTenantsFlow(_ *ObTenantOperationManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fSwitchoverTenantsFlow, @@ -64,7 +64,7 @@ func SwitchoverTenants() *tasktypes.TaskFlow { } } -func RevertSwitchoverTenants() *tasktypes.TaskFlow { +func genRevertSwitchoverTenantsFlow(_ *ObTenantOperationManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fRevertSwitchoverTenantsFlow, @@ -79,7 +79,7 @@ func RevertSwitchoverTenants() *tasktypes.TaskFlow { } } -func UpgradeTenant() *tasktypes.TaskFlow { +func genUpgradeTenantFlow(_ *ObTenantOperationManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fOpUpgradeTenant, @@ -94,7 +94,7 @@ func UpgradeTenant() *tasktypes.TaskFlow { } } -func ReplayLogOfStandby() *tasktypes.TaskFlow { +func genReplayLogOfStandbyFlow(_ *ObTenantOperationManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fOpReplayLog, diff --git a/internal/resource/obtenantoperation/obtenantoperation_manager.go b/internal/resource/obtenantoperation/obtenantoperation_manager.go index 86df8f04f..0fe091d60 100644 --- a/internal/resource/obtenantoperation/obtenantoperation_manager.go +++ b/internal/resource/obtenantoperation/obtenantoperation_manager.go @@ -27,18 +27,18 @@ import ( apitypes "github.com/oceanbase/ob-operator/api/types" v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" oceanbaseconst "github.com/oceanbase/ob-operator/internal/const/oceanbase" + resourceutils "github.com/oceanbase/ob-operator/internal/resource/utils" "github.com/oceanbase/ob-operator/internal/telemetry" opresource "github.com/oceanbase/ob-operator/pkg/coordinator" "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/operation" - "github.com/oceanbase/ob-operator/pkg/task" taskstatus "github.com/oceanbase/ob-operator/pkg/task/const/status" "github.com/oceanbase/ob-operator/pkg/task/const/strategy" tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) -type ObTenantOperationManager struct { - opresource.ResourceManager +var _ opresource.ResourceManager = &ObTenantOperationManager{} +type ObTenantOperationManager struct { Ctx context.Context Resource *v1alpha1.OBTenantOperation Client client.Client @@ -57,7 +57,8 @@ func (m *ObTenantOperationManager) GetStatus() string { } func (m *ObTenantOperationManager) IsDeleting() bool { - return m.Resource.GetDeletionTimestamp() != nil + ignoreDel, ok := resourceutils.GetAnnotationField(m.Resource, oceanbaseconst.AnnotationsIgnoreDeletion) + return !m.Resource.ObjectMeta.DeletionTimestamp.IsZero() && (!ok || ignoreDel != "true") } func (m *ObTenantOperationManager) CheckAndUpdateFinalizers() error { @@ -175,24 +176,7 @@ func (m *ObTenantOperationManager) ArchiveResource() { } func (m *ObTenantOperationManager) GetTaskFunc(name tasktypes.TaskName) (tasktypes.TaskFunc, error) { - switch name { - case tOpChangeTenantRootPassword: - return m.ChangeTenantRootPassword, nil - case tOpActivateStandby: - return m.ActivateStandbyTenant, nil - case tOpCreateUsersForActivatedStandby: - return m.CreateUsersForActivatedStandby, nil - case tOpSwitchTenantsRole: - return m.SwitchTenantsRole, nil - case tOpSetTenantLogRestoreSource: - return m.SetTenantLogRestoreSource, nil - case tOpUpgradeTenant: - return m.UpgradeTenant, nil - case tOpReplayLog: - return m.ReplayLogOfStandby, nil - default: - return nil, errors.New("Task name not registered") - } + return taskMap.GetTask(name, m) } func (m *ObTenantOperationManager) GetTaskFlow() (*tasktypes.TaskFlow, error) { @@ -204,24 +188,24 @@ func (m *ObTenantOperationManager) GetTaskFlow() (*tasktypes.TaskFlow, error) { status := m.Resource.Status.Status switch status { case constants.TenantOpStarting: - // taskFlow, err = task.GetRegistry().Get(flow.CheckTenantCRExistenceFlow) + // taskFlow = low.CheckTenantCRExistenceFlow() case constants.TenantOpRunning: switch m.Resource.Spec.Type { case constants.TenantOpChangePwd: - taskFlow, err = task.GetRegistry().Get(fChangeTenantRootPasswordFlow) + taskFlow = genChangeTenantRootPasswordFlow(m) case constants.TenantOpFailover: - taskFlow, err = task.GetRegistry().Get(fActivateStandbyTenantFlow) + taskFlow = genActivateStandbyTenantOpFlow(m) case constants.TenantOpSwitchover: - taskFlow, err = task.GetRegistry().Get(fSwitchoverTenantsFlow) + taskFlow = genSwitchoverTenantsFlow(m) case constants.TenantOpUpgrade: - taskFlow, err = task.GetRegistry().Get(fOpUpgradeTenant) + taskFlow = genUpgradeTenantFlow(m) case constants.TenantOpReplayLog: - taskFlow, err = task.GetRegistry().Get(fOpReplayLog) + taskFlow = genReplayLogOfStandbyFlow(m) } case constants.TenantOpReverting: switch m.Resource.Spec.Type { case constants.TenantOpSwitchover: - taskFlow, err = task.GetRegistry().Get(fRevertSwitchoverTenantsFlow) + taskFlow = genRevertSwitchoverTenantsFlow(m) default: err = errors.New("unsupported operation type") } diff --git a/internal/resource/obtenantoperation/obtenantoperation_task.go b/internal/resource/obtenantoperation/obtenantoperation_task.go index 318461b2f..f1b6156d0 100644 --- a/internal/resource/obtenantoperation/obtenantoperation_task.go +++ b/internal/resource/obtenantoperation/obtenantoperation_task.go @@ -17,7 +17,6 @@ import ( "time" "github.com/pkg/errors" - "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/retry" @@ -28,9 +27,14 @@ import ( resourceutils "github.com/oceanbase/ob-operator/internal/resource/utils" "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/operation" "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/param" + "github.com/oceanbase/ob-operator/pkg/task/builder" tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) +//go:generate task-register $GOFILE + +var taskMap = builder.NewTaskHub[*ObTenantOperationManager]() + func (m *ObTenantOperationManager) ChangeTenantRootPassword() tasktypes.TaskError { con, err := m.getTenantRootClient(m.Resource.Spec.ChangePwd.Tenant) if err != nil { @@ -127,7 +131,7 @@ func (m *ObTenantOperationManager) CreateUsersForActivatedStandby() tasktypes.Ta // Hack: tenantManager.OBTenant.ObjectMeta.SetNamespace(m.Resource.Namespace) // Just reuse the logic of creating users for new coming tenant - _ = tenantManager.CreateUserWithCredentials() + _ = obtenantresource.CreateUserWithCredentials(tenantManager) return nil } @@ -235,7 +239,7 @@ func (m *ObTenantOperationManager) SetTenantLogRestoreSource() tasktypes.TaskErr Logger: m.Logger, OBTenant: originStandby, } - err = tenantManager.CreateUserWithCredentials() + err = obtenantresource.CreateUserWithCredentials(tenantManager) if err != nil { return err } diff --git a/internal/resource/obtenantoperation/obtenantoperation_task_gen.go b/internal/resource/obtenantoperation/obtenantoperation_task_gen.go new file mode 100644 index 000000000..b95f17c11 --- /dev/null +++ b/internal/resource/obtenantoperation/obtenantoperation_task_gen.go @@ -0,0 +1,5 @@ +// Code generated by go generate; DO NOT EDIT. +package obtenantoperation + +func init() { +} diff --git a/internal/resource/obtenantrestore/names.go b/internal/resource/obtenantrestore/names.go index 499c6807f..7ee966a55 100644 --- a/internal/resource/obtenantrestore/names.go +++ b/internal/resource/obtenantrestore/names.go @@ -23,7 +23,7 @@ const ( ) const ( - tStartRestoreJob ttypes.TaskName = "start restore job" - tStartLogReplay ttypes.TaskName = "start log replay" - tActivateStandby ttypes.TaskName = "activate standby" + tStartRestoreJobInOB ttypes.TaskName = "start restore job" + tStartLogReplay ttypes.TaskName = "start log replay" + tActivateStandby ttypes.TaskName = "activate standby" ) diff --git a/internal/resource/obtenantrestore/obtenantrestore_flow.go b/internal/resource/obtenantrestore/obtenantrestore_flow.go index d710499e5..454d38069 100644 --- a/internal/resource/obtenantrestore/obtenantrestore_flow.go +++ b/internal/resource/obtenantrestore/obtenantrestore_flow.go @@ -17,11 +17,11 @@ import ( tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) -func StartRestoreJob() *tasktypes.TaskFlow { +func genStartRestoreJobFlow(_ *ObTenantRestoreManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fStartRestoreFlow, - Tasks: []tasktypes.TaskName{tStartRestoreJob}, + Tasks: []tasktypes.TaskName{tStartRestoreJobInOB}, TargetStatus: string(constants.RestoreJobRunning), OnFailure: tasktypes.FailureRule{ NextTryStatus: string(constants.RestoreJobFailed), @@ -30,7 +30,7 @@ func StartRestoreJob() *tasktypes.TaskFlow { } } -func RestoreAsPrimary() *tasktypes.TaskFlow { +func genRestoreAsPrimaryFlow(_ *ObTenantRestoreManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fRestoreAsPrimaryFlow, @@ -43,7 +43,7 @@ func RestoreAsPrimary() *tasktypes.TaskFlow { } } -func RestoreAsStandby() *tasktypes.TaskFlow { +func genRestoreAsStandbyFlow(_ *ObTenantRestoreManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fRestoreAsStandbyFlow, diff --git a/internal/resource/obtenantrestore/obtenantrestore_manager.go b/internal/resource/obtenantrestore/obtenantrestore_manager.go index 0699557e9..e52397696 100644 --- a/internal/resource/obtenantrestore/obtenantrestore_manager.go +++ b/internal/resource/obtenantrestore/obtenantrestore_manager.go @@ -16,7 +16,6 @@ import ( "context" "github.com/go-logr/logr" - "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/util/retry" @@ -25,19 +24,20 @@ import ( "github.com/oceanbase/ob-operator/api/constants" apitypes "github.com/oceanbase/ob-operator/api/types" v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" + oceanbaseconst "github.com/oceanbase/ob-operator/internal/const/oceanbase" + resourceutils "github.com/oceanbase/ob-operator/internal/resource/utils" "github.com/oceanbase/ob-operator/internal/telemetry" opresource "github.com/oceanbase/ob-operator/pkg/coordinator" "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/model" "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/operation" - "github.com/oceanbase/ob-operator/pkg/task" taskstatus "github.com/oceanbase/ob-operator/pkg/task/const/status" "github.com/oceanbase/ob-operator/pkg/task/const/strategy" tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) -type ObTenantRestoreManager struct { - opresource.ResourceManager +var _ opresource.ResourceManager = &ObTenantRestoreManager{} +type ObTenantRestoreManager struct { Ctx context.Context Resource *v1alpha1.OBTenantRestore Client client.Client @@ -47,7 +47,7 @@ type ObTenantRestoreManager struct { con *operation.OceanbaseOperationManager } -func (m ObTenantRestoreManager) IsNewResource() bool { +func (m *ObTenantRestoreManager) IsNewResource() bool { return m.Resource.Status.Status == "" } @@ -55,33 +55,34 @@ func (m *ObTenantRestoreManager) GetStatus() string { return string(m.Resource.Status.Status) } -func (m ObTenantRestoreManager) IsDeleting() bool { - return m.Resource.GetDeletionTimestamp() != nil +func (m *ObTenantRestoreManager) IsDeleting() bool { + ignoreDel, ok := resourceutils.GetAnnotationField(m.Resource, oceanbaseconst.AnnotationsIgnoreDeletion) + return !m.Resource.ObjectMeta.DeletionTimestamp.IsZero() && (!ok || ignoreDel != "true") } -func (m ObTenantRestoreManager) CheckAndUpdateFinalizers() error { +func (m *ObTenantRestoreManager) CheckAndUpdateFinalizers() error { return nil } -func (m ObTenantRestoreManager) InitStatus() { +func (m *ObTenantRestoreManager) InitStatus() { m.Resource.Status.Status = constants.RestoreJobStarting } -func (m ObTenantRestoreManager) SetOperationContext(c *tasktypes.OperationContext) { +func (m *ObTenantRestoreManager) SetOperationContext(c *tasktypes.OperationContext) { m.Resource.Status.OperationContext = c } -func (m ObTenantRestoreManager) ClearTaskInfo() { +func (m *ObTenantRestoreManager) ClearTaskInfo() { m.Resource.Status.Status = constants.RestoreJobRunning m.Resource.Status.OperationContext = nil } -func (m ObTenantRestoreManager) FinishTask() { +func (m *ObTenantRestoreManager) FinishTask() { m.Resource.Status.Status = apitypes.RestoreJobStatus(m.Resource.Status.OperationContext.TargetStatus) m.Resource.Status.OperationContext = nil } -func (m ObTenantRestoreManager) HandleFailure() { +func (m *ObTenantRestoreManager) HandleFailure() { if m.IsDeleting() { m.Resource.Status.OperationContext = nil } else { @@ -162,7 +163,7 @@ func (m *ObTenantRestoreManager) checkRestoreProgress() error { return nil } -func (m ObTenantRestoreManager) UpdateStatus() error { +func (m *ObTenantRestoreManager) UpdateStatus() error { var err error if m.Resource.Status.Status == constants.RestoreJobRunning { err = m.checkRestoreProgress() @@ -173,34 +174,24 @@ func (m ObTenantRestoreManager) UpdateStatus() error { return m.retryUpdateStatus() } -func (m ObTenantRestoreManager) GetTaskFunc(name tasktypes.TaskName) (tasktypes.TaskFunc, error) { - switch name { - case tStartRestoreJob: - return m.StartRestoreJobInOB, nil - case tStartLogReplay: - return m.StartLogReplay, nil - case tActivateStandby: - return m.ActivateStandby, nil - default: - return nil, errors.New("Task name not registered") - } +func (m *ObTenantRestoreManager) GetTaskFunc(name tasktypes.TaskName) (tasktypes.TaskFunc, error) { + return taskMap.GetTask(name, m) } -func (m ObTenantRestoreManager) GetTaskFlow() (*tasktypes.TaskFlow, error) { +func (m *ObTenantRestoreManager) GetTaskFlow() (*tasktypes.TaskFlow, error) { if m.Resource.Status.OperationContext != nil { return tasktypes.NewTaskFlow(m.Resource.Status.OperationContext), nil } var taskFlow *tasktypes.TaskFlow - var err error status := m.Resource.Status.Status // get task flow depending on BackupPolicy status switch status { case constants.RestoreJobStarting: - taskFlow, err = task.GetRegistry().Get(fStartRestoreFlow) + taskFlow = genStartRestoreJobFlow(m) case constants.RestoreJobStatusActivating: - taskFlow, err = task.GetRegistry().Get(fRestoreAsPrimaryFlow) + taskFlow = genRestoreAsPrimaryFlow(m) case constants.RestoreJobStatusReplaying: - taskFlow, err = task.GetRegistry().Get(fRestoreAsStandbyFlow) + taskFlow = genRestoreAsStandbyFlow(m) case constants.RestoreJobRunning: fallthrough case constants.RestoreJobCanceled: @@ -213,10 +204,6 @@ func (m ObTenantRestoreManager) GetTaskFlow() (*tasktypes.TaskFlow, error) { return nil, nil } - if err != nil { - return nil, err - } - if taskFlow.OperationContext.OnFailure.Strategy == "" { taskFlow.OperationContext.OnFailure.Strategy = strategy.StartOver if taskFlow.OperationContext.OnFailure.NextTryStatus == "" { @@ -227,7 +214,7 @@ func (m ObTenantRestoreManager) GetTaskFlow() (*tasktypes.TaskFlow, error) { return taskFlow, nil } -func (m ObTenantRestoreManager) PrintErrEvent(err error) { +func (m *ObTenantRestoreManager) PrintErrEvent(err error) { m.Recorder.Event(m.Resource, corev1.EventTypeWarning, "Task failed", err.Error()) } diff --git a/internal/resource/obtenantrestore/obtenantrestore_task.go b/internal/resource/obtenantrestore/obtenantrestore_task.go index 1971075df..dab893413 100644 --- a/internal/resource/obtenantrestore/obtenantrestore_task.go +++ b/internal/resource/obtenantrestore/obtenantrestore_task.go @@ -14,20 +14,13 @@ package obtenantrestore import ( "fmt" - "path" - "strings" "github.com/pkg/errors" - v1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/types" - "github.com/oceanbase/ob-operator/api/constants" - "github.com/oceanbase/ob-operator/api/v1alpha1" - oceanbaseconst "github.com/oceanbase/ob-operator/internal/const/oceanbase" resourceutils "github.com/oceanbase/ob-operator/internal/resource/utils" - "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/operation" "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/param" + "github.com/oceanbase/ob-operator/pkg/task/builder" tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) @@ -41,7 +34,11 @@ import ( // OBTenantRestore tasks -func (m *ObTenantRestoreManager) StartRestoreJobInOB() tasktypes.TaskError { +//go:generate task-register $GOFILE + +var taskMap = builder.NewTaskHub[*ObTenantRestoreManager]() + +func StartRestoreJobInOB(m *ObTenantRestoreManager) tasktypes.TaskError { con, err := m.getClusterSysClient() if err != nil { return err @@ -88,7 +85,7 @@ func (m *ObTenantRestoreManager) StartRestoreJobInOB() tasktypes.TaskError { return nil } -func (m *ObTenantRestoreManager) StartLogReplay() tasktypes.TaskError { +func StartLogReplay(m *ObTenantRestoreManager) tasktypes.TaskError { con, err := m.getClusterSysClient() if err != nil { return err @@ -120,77 +117,10 @@ func (m *ObTenantRestoreManager) StartLogReplay() tasktypes.TaskError { return err } -func (m *ObTenantRestoreManager) ActivateStandby() tasktypes.TaskError { +func ActivateStandby(m *ObTenantRestoreManager) tasktypes.TaskError { con, err := m.getClusterSysClient() if err != nil { return err } return con.ActivateStandby(m.Resource.Spec.TargetTenant) } - -func (m *ObTenantRestoreManager) getClusterSysClient() (*operation.OceanbaseOperationManager, error) { - if m.con != nil { - return m.con, nil - } - obcluster := &v1alpha1.OBCluster{} - err := m.Client.Get(m.Ctx, types.NamespacedName{ - Namespace: m.Resource.Namespace, - Name: m.Resource.Spec.TargetCluster, - }, obcluster) - if err != nil { - return nil, errors.Wrap(err, "get obcluster") - } - con, err := resourceutils.GetSysOperationClient(m.Client, m.Logger, obcluster) - if err != nil { - return nil, errors.Wrap(err, "get oceanbase operation manager") - } - m.con = con - return con, nil -} - -func (m *ObTenantRestoreManager) getSourceUri() (string, error) { - source := m.Resource.Spec.Source - if source.SourceUri != "" { - return source.SourceUri, nil - } - var bakPath, archivePath string - if source.BakDataSource != nil && source.BakDataSource.Type == constants.BackupDestTypeOSS { - accessId, accessKey, err := m.readAccessCredentials(source.BakDataSource.OSSAccessSecret) - if err != nil { - return "", err - } - bakPath = strings.Join([]string{source.BakDataSource.Path, "access_id=" + accessId, "access_key=" + accessKey}, "&") - } else { - bakPath = "file://" + path.Join(oceanbaseconst.BackupPath, source.BakDataSource.Path) - } - - if source.ArchiveSource != nil && source.ArchiveSource.Type == constants.BackupDestTypeOSS { - accessId, accessKey, err := m.readAccessCredentials(source.ArchiveSource.OSSAccessSecret) - if err != nil { - return "", err - } - archivePath = strings.Join([]string{source.ArchiveSource.Path, "access_id=" + accessId, "access_key=" + accessKey}, "&") - } else { - archivePath = "file://" + path.Join(oceanbaseconst.BackupPath, source.ArchiveSource.Path) - } - - if bakPath == "" || archivePath == "" { - return "", errors.New("Unexpected error: both bakPath and archivePath must be set") - } - - return strings.Join([]string{bakPath, archivePath}, ","), nil -} - -func (m *ObTenantRestoreManager) readAccessCredentials(secretName string) (accessId, accessKey string, err error) { - secret := &v1.Secret{} - err = m.Client.Get(m.Ctx, types.NamespacedName{ - Namespace: m.Resource.Namespace, - Name: secretName, - }, secret) - if err != nil { - return "", "", err - } - accessId = string(secret.Data["accessId"]) - accessKey = string(secret.Data["accessKey"]) - return accessId, accessKey, nil -} diff --git a/internal/resource/obtenantrestore/obtenantrestore_task_gen.go b/internal/resource/obtenantrestore/obtenantrestore_task_gen.go new file mode 100644 index 000000000..48329260a --- /dev/null +++ b/internal/resource/obtenantrestore/obtenantrestore_task_gen.go @@ -0,0 +1,8 @@ +// Code generated by go generate; DO NOT EDIT. +package obtenantrestore + +func init() { + taskMap.Register(tStartRestoreJobInOB, StartRestoreJobInOB) + taskMap.Register(tStartLogReplay, StartLogReplay) + taskMap.Register(tActivateStandby, ActivateStandby) +} diff --git a/internal/resource/obtenantrestore/utils.go b/internal/resource/obtenantrestore/utils.go new file mode 100644 index 000000000..b34f32ae9 --- /dev/null +++ b/internal/resource/obtenantrestore/utils.go @@ -0,0 +1,95 @@ +/* +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 obtenantrestore + +import ( + "path" + "strings" + + "github.com/pkg/errors" + v1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" + + "github.com/oceanbase/ob-operator/api/constants" + v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" + oceanbaseconst "github.com/oceanbase/ob-operator/internal/const/oceanbase" + resourceutils "github.com/oceanbase/ob-operator/internal/resource/utils" + "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/operation" +) + +func (m *ObTenantRestoreManager) getClusterSysClient() (*operation.OceanbaseOperationManager, error) { + if m.con != nil { + return m.con, nil + } + obcluster := &v1alpha1.OBCluster{} + err := m.Client.Get(m.Ctx, types.NamespacedName{ + Namespace: m.Resource.Namespace, + Name: m.Resource.Spec.TargetCluster, + }, obcluster) + if err != nil { + return nil, errors.Wrap(err, "get obcluster") + } + con, err := resourceutils.GetSysOperationClient(m.Client, m.Logger, obcluster) + if err != nil { + return nil, errors.Wrap(err, "get oceanbase operation manager") + } + m.con = con + return con, nil +} + +func (m *ObTenantRestoreManager) getSourceUri() (string, error) { + source := m.Resource.Spec.Source + if source.SourceUri != "" { + return source.SourceUri, nil + } + var bakPath, archivePath string + if source.BakDataSource != nil && source.BakDataSource.Type == constants.BackupDestTypeOSS { + accessId, accessKey, err := m.readAccessCredentials(source.BakDataSource.OSSAccessSecret) + if err != nil { + return "", err + } + bakPath = strings.Join([]string{source.BakDataSource.Path, "access_id=" + accessId, "access_key=" + accessKey}, "&") + } else { + bakPath = "file://" + path.Join(oceanbaseconst.BackupPath, source.BakDataSource.Path) + } + + if source.ArchiveSource != nil && source.ArchiveSource.Type == constants.BackupDestTypeOSS { + accessId, accessKey, err := m.readAccessCredentials(source.ArchiveSource.OSSAccessSecret) + if err != nil { + return "", err + } + archivePath = strings.Join([]string{source.ArchiveSource.Path, "access_id=" + accessId, "access_key=" + accessKey}, "&") + } else { + archivePath = "file://" + path.Join(oceanbaseconst.BackupPath, source.ArchiveSource.Path) + } + + if bakPath == "" || archivePath == "" { + return "", errors.New("Unexpected error: both bakPath and archivePath must be set") + } + + return strings.Join([]string{bakPath, archivePath}, ","), nil +} + +func (m *ObTenantRestoreManager) readAccessCredentials(secretName string) (accessId, accessKey string, err error) { + secret := &v1.Secret{} + err = m.Client.Get(m.Ctx, types.NamespacedName{ + Namespace: m.Resource.Namespace, + Name: secretName, + }, secret) + if err != nil { + return "", "", err + } + accessId = string(secret.Data["accessId"]) + accessKey = string(secret.Data["accessKey"]) + return accessId, accessKey, nil +} diff --git a/internal/resource/obzone/init.go b/internal/resource/obzone/init.go deleted file mode 100644 index d626aceb2..000000000 --- a/internal/resource/obzone/init.go +++ /dev/null @@ -1,33 +0,0 @@ -/* -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 obzone - -import ( - "github.com/oceanbase/ob-operator/pkg/task" -) - -func init() { - // obzone - task.GetRegistry().Register(fCreateOBZone, CreateOBZone) - task.GetRegistry().Register(fMigrateOBZoneFromExisting, MigrateOBZoneFromExisting) - task.GetRegistry().Register(fAddOBServer, AddOBServer) - task.GetRegistry().Register(fDeleteOBServer, DeleteOBServer) - task.GetRegistry().Register(fPrepareOBZoneForBootstrap, PrepareOBZoneForBootstrap) - task.GetRegistry().Register(fUpgradeOBZone, UpgradeOBZone) - task.GetRegistry().Register(fForceUpgradeOBZone, ForceUpgradeOBZone) - task.GetRegistry().Register(fMaintainOBZoneAfterBootstrap, MaintainOBZoneAfterBootstrap) - task.GetRegistry().Register(fDeleteOBZoneFinalizer, DeleteOBZoneFinalizer) - task.GetRegistry().Register(fScaleUpOBServers, ScaleUpOBServers) - task.GetRegistry().Register(fExpandPVC, ResizePVC) - task.GetRegistry().Register(fMountBackupVolume, MountBackupVolume) -} diff --git a/internal/resource/obzone/obzone_flow.go b/internal/resource/obzone/obzone_flow.go index 1f8b429e2..013ce57fd 100644 --- a/internal/resource/obzone/obzone_flow.go +++ b/internal/resource/obzone/obzone_flow.go @@ -18,7 +18,7 @@ import ( tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) -func MigrateOBZoneFromExisting() *tasktypes.TaskFlow { +func genMigrateOBZoneFromExistingFlow(_ *OBZoneManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fMigrateOBZoneFromExisting, @@ -28,7 +28,7 @@ func MigrateOBZoneFromExisting() *tasktypes.TaskFlow { } } -func PrepareOBZoneForBootstrap() *tasktypes.TaskFlow { +func genPrepareOBZoneForBootstrapFlow(_ *OBZoneManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fPrepareOBZoneForBootstrap, @@ -38,7 +38,7 @@ func PrepareOBZoneForBootstrap() *tasktypes.TaskFlow { } } -func MaintainOBZoneAfterBootstrap() *tasktypes.TaskFlow { +func genMaintainOBZoneAfterBootstrapFlow(_ *OBZoneManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fMaintainOBZoneAfterBootstrap, @@ -48,7 +48,7 @@ func MaintainOBZoneAfterBootstrap() *tasktypes.TaskFlow { } } -func CreateOBZone() *tasktypes.TaskFlow { +func genCreateOBZoneFlow(_ *OBZoneManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fCreateOBZone, @@ -58,7 +58,7 @@ func CreateOBZone() *tasktypes.TaskFlow { } } -func AddOBServer() *tasktypes.TaskFlow { +func genAddOBServerFlow(_ *OBZoneManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fAddOBServer, @@ -68,7 +68,7 @@ func AddOBServer() *tasktypes.TaskFlow { } } -func DeleteOBServer() *tasktypes.TaskFlow { +func genDeleteOBServerFlow(_ *OBZoneManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fDeleteOBServer, @@ -81,7 +81,7 @@ func DeleteOBServer() *tasktypes.TaskFlow { } } -func DeleteOBZoneFinalizer() *tasktypes.TaskFlow { +func genDeleteOBZoneFinalizerFlow(_ *OBZoneManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fDeleteOBZoneFinalizer, @@ -91,7 +91,7 @@ func DeleteOBZoneFinalizer() *tasktypes.TaskFlow { } } -func UpgradeOBZone() *tasktypes.TaskFlow { +func genUpgradeOBZoneFlow(_ *OBZoneManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fUpgradeOBZone, @@ -101,7 +101,7 @@ func UpgradeOBZone() *tasktypes.TaskFlow { } } -func ForceUpgradeOBZone() *tasktypes.TaskFlow { +func genForceUpgradeOBZoneFlow(_ *OBZoneManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fForceUpgradeOBZone, @@ -111,7 +111,7 @@ func ForceUpgradeOBZone() *tasktypes.TaskFlow { } } -func ScaleUpOBServers() *tasktypes.TaskFlow { +func genScaleUpOBServersFlow(_ *OBZoneManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fScaleUpOBServers, @@ -121,7 +121,7 @@ func ScaleUpOBServers() *tasktypes.TaskFlow { } } -func ResizePVC() *tasktypes.TaskFlow { +func FlowExpandPVC(_ *OBZoneManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fExpandPVC, @@ -131,7 +131,7 @@ func ResizePVC() *tasktypes.TaskFlow { } } -func MountBackupVolume() *tasktypes.TaskFlow { +func genMountBackupVolumeFlow(_ *OBZoneManager) *tasktypes.TaskFlow { return &tasktypes.TaskFlow{ OperationContext: &tasktypes.OperationContext{ Name: fMountBackupVolume, diff --git a/internal/resource/obzone/obzone_manager.go b/internal/resource/obzone/obzone_manager.go index 0aeae7285..f765d661c 100644 --- a/internal/resource/obzone/obzone_manager.go +++ b/internal/resource/obzone/obzone_manager.go @@ -30,14 +30,14 @@ import ( resourceutils "github.com/oceanbase/ob-operator/internal/resource/utils" "github.com/oceanbase/ob-operator/internal/telemetry" opresource "github.com/oceanbase/ob-operator/pkg/coordinator" - "github.com/oceanbase/ob-operator/pkg/task" taskstatus "github.com/oceanbase/ob-operator/pkg/task/const/status" "github.com/oceanbase/ob-operator/pkg/task/const/strategy" tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) +var _ opresource.ResourceManager = &OBZoneManager{} + type OBZoneManager struct { - opresource.ResourceManager Ctx context.Context OBZone *v1alpha1.OBZone Client client.Client @@ -92,50 +92,44 @@ func (m *OBZoneManager) GetTaskFlow() (*tasktypes.TaskFlow, error) { if obcluster.Status.Status == clusterstatus.New { // created when create obcluster m.Logger.Info("Create obzone when create obcluster") - taskFlow, err = task.GetRegistry().Get(fPrepareOBZoneForBootstrap) + taskFlow = genPrepareOBZoneForBootstrapFlow(m) } else { // created normally m.Logger.Info("Create obzone when obcluster already exists") - taskFlow, err = task.GetRegistry().Get(fCreateOBZone) - } - if err != nil { - return nil, errors.Wrap(err, "Get create obzone task flow") + taskFlow = genCreateOBZoneFlow(m) } case zonestatus.MigrateFromExisting: - taskFlow, err = task.GetRegistry().Get(fMigrateOBZoneFromExisting) + taskFlow = genMigrateOBZoneFromExistingFlow(m) case zonestatus.BootstrapReady: - taskFlow, err = task.GetRegistry().Get(fMaintainOBZoneAfterBootstrap) + taskFlow = genMaintainOBZoneAfterBootstrapFlow(m) case zonestatus.AddOBServer: - taskFlow, err = task.GetRegistry().Get(fAddOBServer) + taskFlow = genAddOBServerFlow(m) case zonestatus.DeleteOBServer: - taskFlow, err = task.GetRegistry().Get(fDeleteOBServer) + taskFlow = genDeleteOBServerFlow(m) case zonestatus.Deleting: - taskFlow, err = task.GetRegistry().Get(fDeleteOBZoneFinalizer) + taskFlow = genDeleteOBZoneFinalizerFlow(m) case zonestatus.ScaleUp: - taskFlow, err = task.GetRegistry().Get(fScaleUpOBServers) + taskFlow = genScaleUpOBServersFlow(m) case zonestatus.ExpandPVC: - taskFlow, err = task.GetRegistry().Get(fExpandPVC) + taskFlow = FlowExpandPVC(m) case zonestatus.MountBackupVolume: - taskFlow, err = task.GetRegistry().Get(fMountBackupVolume) + taskFlow = genMountBackupVolumeFlow(m) case zonestatus.Upgrade: obcluster, err = m.getOBCluster() if err != nil { return nil, errors.Wrap(err, "Get obcluster") } if len(obcluster.Status.OBZoneStatus) >= 3 { - return task.GetRegistry().Get(fUpgradeOBZone) + taskFlow = genUpgradeOBZoneFlow(m) + } else { + taskFlow = genForceUpgradeOBZoneFlow(m) } - return task.GetRegistry().Get(fForceUpgradeOBZone) // TODO upgrade default: m.Logger.V(oceanbaseconst.LogLevelTrace).Info("No need to run anything for obzone") return nil, nil } - if err != nil { - return nil, err - } - if taskFlow.OperationContext.OnFailure.Strategy == "" { taskFlow.OperationContext.OnFailure.Strategy = strategy.StartOver if taskFlow.OperationContext.OnFailure.NextTryStatus == "" { @@ -146,7 +140,8 @@ func (m *OBZoneManager) GetTaskFlow() (*tasktypes.TaskFlow, error) { } func (m *OBZoneManager) IsDeleting() bool { - return !m.OBZone.ObjectMeta.DeletionTimestamp.IsZero() + ignoreDel, ok := resourceutils.GetAnnotationField(m.OBZone, oceanbaseconst.AnnotationsIgnoreDeletion) + return !m.OBZone.ObjectMeta.DeletionTimestamp.IsZero() && (!ok || ignoreDel != "true") } func (m *OBZoneManager) CheckAndUpdateFinalizers() error { @@ -300,54 +295,7 @@ func (m *OBZoneManager) FinishTask() { } func (m *OBZoneManager) GetTaskFunc(name tasktypes.TaskName) (tasktypes.TaskFunc, error) { - switch name { - case tCreateOBServer: - return m.CreateOBServer, nil - case tWaitOBServerBootstrapReady: - return m.generateWaitOBServerStatusFunc(serverstatus.BootstrapReady, oceanbaseconst.DefaultStateWaitTimeout), nil - case tWaitOBServerRunning: - return m.generateWaitOBServerStatusFunc(serverstatus.Running, oceanbaseconst.DefaultStateWaitTimeout), nil - case tWaitForOBServerScalingUp: - return m.generateWaitOBServerStatusFunc(serverstatus.ScaleUp, oceanbaseconst.DefaultStateWaitTimeout), nil - case tWaitForOBServerExpandingPVC: - return m.generateWaitOBServerStatusFunc(serverstatus.ExpandPVC, oceanbaseconst.DefaultStateWaitTimeout), nil - case tWaitForOBServerMounting: - return m.generateWaitOBServerStatusFunc(serverstatus.MountBackupVolume, oceanbaseconst.DefaultStateWaitTimeout), nil - case tAddZone: - return m.AddZone, nil - case tStartOBZone: - return m.StartOBZone, nil - case tDeleteOBServer: - return m.DeleteOBServer, nil - case tDeleteAllOBServer: - return m.DeleteAllOBServer, nil - case tWaitReplicaMatch: - return m.WaitReplicaMatch, nil - case tWaitOBServerDeleted: - return m.WaitOBServerDeleted, nil - case tStopOBZone: - return m.StopOBZone, nil - case tDeleteOBZoneInCluster: - return m.DeleteOBZoneInCluster, nil - case tOBClusterHealthCheck: - return m.OBClusterHealthCheck, nil - case tOBZoneHealthCheck: - return m.OBZoneHealthCheck, nil - case tUpgradeOBServer: - return m.UpgradeOBServer, nil - case tWaitOBServerUpgraded: - return m.WaitOBServerUpgraded, nil - case tScaleUpOBServers: - return m.ScaleUpOBServer, nil - case tExpandPVC: - return m.ResizePVC, nil - case tDeleteLegacyOBServers: - return m.DeleteLegacyOBServer, nil - case tMountBackupVolume: - return m.MountBackupVolume, nil - default: - return nil, errors.Errorf("Can not find an function for %s", name) - } + return taskMap.GetTask(name, m) } func (m *OBZoneManager) PrintErrEvent(err error) { diff --git a/internal/resource/obzone/obzone_task.go b/internal/resource/obzone/obzone_task.go index 3a4d4ab37..2eb53b497 100644 --- a/internal/resource/obzone/obzone_task.go +++ b/internal/resource/obzone/obzone_task.go @@ -18,7 +18,6 @@ import ( "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/rand" "k8s.io/client-go/util/retry" v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" @@ -26,23 +25,15 @@ import ( serverstatus "github.com/oceanbase/ob-operator/internal/const/status/observer" resourceutils "github.com/oceanbase/ob-operator/internal/resource/utils" "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/model" - "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/operation" + "github.com/oceanbase/ob-operator/pkg/task/builder" tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) -func (m *OBZoneManager) getOceanbaseOperationManager() (*operation.OceanbaseOperationManager, error) { - obcluster, err := m.getOBCluster() - if err != nil { - return nil, errors.Wrap(err, "Get obcluster from K8s") - } - return resourceutils.GetSysOperationClient(m.Client, m.Logger, obcluster) -} +//go:generate task-register $GOFILE -func (m *OBZoneManager) generateServerName() string { - return fmt.Sprintf("%s-%d-%s-%s", m.OBZone.Spec.ClusterName, m.OBZone.Spec.ClusterId, m.OBZone.Spec.Topology.Zone, rand.String(6)) -} +var taskMap = builder.NewTaskHub[*OBZoneManager]() -func (m *OBZoneManager) AddZone() tasktypes.TaskError { +func AddZone(m *OBZoneManager) tasktypes.TaskError { oceanbaseOperationManager, err := m.getOceanbaseOperationManager() if err != nil { m.Logger.Error(err, "Get oceanbase operation manager failed") @@ -51,7 +42,7 @@ func (m *OBZoneManager) AddZone() tasktypes.TaskError { return oceanbaseOperationManager.AddZone(m.OBZone.Spec.Topology.Zone) } -func (m *OBZoneManager) StartOBZone() tasktypes.TaskError { +func StartOBZone(m *OBZoneManager) tasktypes.TaskError { oceanbaseOperationManager, err := m.getOceanbaseOperationManager() if err != nil { m.Logger.Error(err, "Get oceanbase operation manager failed") @@ -60,32 +51,7 @@ func (m *OBZoneManager) StartOBZone() tasktypes.TaskError { return oceanbaseOperationManager.StartZone(m.OBZone.Spec.Topology.Zone) } -func (m *OBZoneManager) generateWaitOBServerStatusFunc(status string, timeoutSeconds int) tasktypes.TaskFunc { - f := func() tasktypes.TaskError { - for i := 1; i < timeoutSeconds; i++ { - obzone, err := m.getOBZone() - if err != nil { - return errors.Wrap(err, "get obzoen failed") - } - allMatched := true - for _, observerStatus := range obzone.Status.OBServerStatus { - if observerStatus.Status != status && observerStatus.Status != serverstatus.Unrecoverable { - m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Server status still not matched", "server", observerStatus.Server, "status", status) - allMatched = false - break - } - } - if allMatched { - return nil - } - time.Sleep(time.Second) - } - return errors.New("all server still not bootstrap ready when timeout") - } - return f -} - -func (m *OBZoneManager) CreateOBServer() tasktypes.TaskError { +func CreateOBServer(m *OBZoneManager) tasktypes.TaskError { m.Logger.Info("Create observers") blockOwnerDeletion := true ownerReferenceList := make([]metav1.OwnerReference, 0) @@ -161,7 +127,7 @@ func (m *OBZoneManager) CreateOBServer() tasktypes.TaskError { return nil } -func (m *OBZoneManager) DeleteOBServer() tasktypes.TaskError { +func DeleteOBServer(m *OBZoneManager) tasktypes.TaskError { m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Delete observers") observerList, err := m.listOBServers() if err != nil { @@ -189,7 +155,7 @@ func (m *OBZoneManager) DeleteOBServer() tasktypes.TaskError { } // TODO refactor Delete observer method together -func (m *OBZoneManager) DeleteAllOBServer() tasktypes.TaskError { +func DeleteAllOBServer(m *OBZoneManager) tasktypes.TaskError { m.Logger.Info("Delete all observers") observerList, err := m.listOBServers() if err != nil { @@ -206,7 +172,7 @@ func (m *OBZoneManager) DeleteAllOBServer() tasktypes.TaskError { return nil } -func (m *OBZoneManager) WaitReplicaMatch() tasktypes.TaskError { +func WaitReplicaMatch(m *OBZoneManager) tasktypes.TaskError { matched := false for i := 0; i < oceanbaseconst.ServerDeleteTimeoutSeconds; i++ { obzone, err := m.getOBZone() @@ -227,7 +193,7 @@ func (m *OBZoneManager) WaitReplicaMatch() tasktypes.TaskError { return nil } -func (m *OBZoneManager) WaitOBServerDeleted() tasktypes.TaskError { +func WaitOBServerDeleted(m *OBZoneManager) tasktypes.TaskError { matched := false for i := 0; i < oceanbaseconst.ServerDeleteTimeoutSeconds; i++ { obzone, err := m.getOBZone() @@ -247,7 +213,7 @@ func (m *OBZoneManager) WaitOBServerDeleted() tasktypes.TaskError { return nil } -func (m *OBZoneManager) StopOBZone() tasktypes.TaskError { +func StopOBZone(m *OBZoneManager) tasktypes.TaskError { operationManager, err := m.getOceanbaseOperationManager() if err != nil { return errors.Wrapf(err, "OBZone %s get oceanbase operation manager", m.OBZone.Name) @@ -259,7 +225,7 @@ func (m *OBZoneManager) StopOBZone() tasktypes.TaskError { return nil } -func (m *OBZoneManager) OBClusterHealthCheck() tasktypes.TaskError { +func OBClusterHealthCheck(m *OBZoneManager) tasktypes.TaskError { obcluster, err := m.getOBCluster() if err != nil { return errors.Wrap(err, "Get obcluster from K8s") @@ -268,7 +234,7 @@ func (m *OBZoneManager) OBClusterHealthCheck() tasktypes.TaskError { return nil } -func (m *OBZoneManager) OBZoneHealthCheck() tasktypes.TaskError { +func OBZoneHealthCheck(m *OBZoneManager) tasktypes.TaskError { obcluster, err := m.getOBCluster() if err != nil { return errors.Wrap(err, "Get obcluster from K8s") @@ -278,7 +244,7 @@ func (m *OBZoneManager) OBZoneHealthCheck() tasktypes.TaskError { return nil } -func (m *OBZoneManager) UpgradeOBServer() tasktypes.TaskError { +func UpgradeOBServer(m *OBZoneManager) tasktypes.TaskError { return retry.RetryOnConflict(retry.DefaultRetry, func() error { observerList, err := m.listOBServers() if err != nil { @@ -297,7 +263,7 @@ func (m *OBZoneManager) UpgradeOBServer() tasktypes.TaskError { }) } -func (m *OBZoneManager) WaitOBServerUpgraded() tasktypes.TaskError { +func WaitOBServerUpgraded(m *OBZoneManager) tasktypes.TaskError { for i := 0; i < oceanbaseconst.TimeConsumingStateWaitTimeout; i++ { observerList, err := m.listOBServers() if err != nil { @@ -321,7 +287,7 @@ func (m *OBZoneManager) WaitOBServerUpgraded() tasktypes.TaskError { return errors.New("Wait all server upgraded timeout") } -func (m *OBZoneManager) DeleteOBZoneInCluster() tasktypes.TaskError { +func DeleteOBZoneInCluster(m *OBZoneManager) tasktypes.TaskError { operationManager, err := m.getOceanbaseOperationManager() if err != nil { return errors.Wrapf(err, "OBZone %s get oceanbase operation manager", m.OBZone.Name) @@ -333,7 +299,7 @@ func (m *OBZoneManager) DeleteOBZoneInCluster() tasktypes.TaskError { return nil } -func (m *OBZoneManager) ScaleUpOBServer() tasktypes.TaskError { +func ScaleUpOBServers(m *OBZoneManager) tasktypes.TaskError { observerList, err := m.listOBServers() if err != nil { return err @@ -355,7 +321,7 @@ func (m *OBZoneManager) ScaleUpOBServer() tasktypes.TaskError { return nil } -func (m *OBZoneManager) ResizePVC() tasktypes.TaskError { +func ExpandPVC(m *OBZoneManager) tasktypes.TaskError { observerList, err := m.listOBServers() if err != nil { return err @@ -379,7 +345,7 @@ func (m *OBZoneManager) ResizePVC() tasktypes.TaskError { return nil } -func (m *OBZoneManager) MountBackupVolume() tasktypes.TaskError { +func MountBackupVolume(m *OBZoneManager) tasktypes.TaskError { observerList, err := m.listOBServers() if err != nil { return err @@ -399,7 +365,7 @@ func (m *OBZoneManager) MountBackupVolume() tasktypes.TaskError { return nil } -func (m *OBZoneManager) DeleteLegacyOBServer() tasktypes.TaskError { +func DeleteLegacyOBServers(m *OBZoneManager) tasktypes.TaskError { operationManager, err := m.getOceanbaseOperationManager() if err != nil { return errors.Wrapf(err, "OBZone %s get oceanbase operation manager", m.OBZone.Name) @@ -435,3 +401,23 @@ func (m *OBZoneManager) DeleteLegacyOBServer() tasktypes.TaskError { } return nil } + +func WaitOBServerBootstrapReady(m *OBZoneManager) tasktypes.TaskError { + return m.generateWaitOBServerStatusFunc(serverstatus.BootstrapReady, oceanbaseconst.DefaultStateWaitTimeout)() +} + +func WaitOBServerRunning(m *OBZoneManager) tasktypes.TaskError { + return m.generateWaitOBServerStatusFunc(serverstatus.Running, oceanbaseconst.DefaultStateWaitTimeout)() +} + +func WaitForOBServerScalingUp(m *OBZoneManager) tasktypes.TaskError { + return m.generateWaitOBServerStatusFunc(serverstatus.ScaleUp, oceanbaseconst.DefaultStateWaitTimeout)() +} + +func WaitForOBServerExpandingPVC(m *OBZoneManager) tasktypes.TaskError { + return m.generateWaitOBServerStatusFunc(serverstatus.ExpandPVC, oceanbaseconst.DefaultStateWaitTimeout)() +} + +func WaitForOBServerMounting(m *OBZoneManager) tasktypes.TaskError { + return m.generateWaitOBServerStatusFunc(serverstatus.MountBackupVolume, oceanbaseconst.DefaultStateWaitTimeout)() +} diff --git a/internal/resource/obzone/obzone_task_gen.go b/internal/resource/obzone/obzone_task_gen.go new file mode 100644 index 000000000..b23658a92 --- /dev/null +++ b/internal/resource/obzone/obzone_task_gen.go @@ -0,0 +1,27 @@ +// Code generated by go generate; DO NOT EDIT. +package obzone + +func init() { + taskMap.Register(tAddZone, AddZone) + taskMap.Register(tStartOBZone, StartOBZone) + taskMap.Register(tCreateOBServer, CreateOBServer) + taskMap.Register(tDeleteOBServer, DeleteOBServer) + taskMap.Register(tDeleteAllOBServer, DeleteAllOBServer) + taskMap.Register(tWaitReplicaMatch, WaitReplicaMatch) + taskMap.Register(tWaitOBServerDeleted, WaitOBServerDeleted) + taskMap.Register(tStopOBZone, StopOBZone) + taskMap.Register(tOBClusterHealthCheck, OBClusterHealthCheck) + taskMap.Register(tOBZoneHealthCheck, OBZoneHealthCheck) + taskMap.Register(tUpgradeOBServer, UpgradeOBServer) + taskMap.Register(tWaitOBServerUpgraded, WaitOBServerUpgraded) + taskMap.Register(tDeleteOBZoneInCluster, DeleteOBZoneInCluster) + taskMap.Register(tScaleUpOBServers, ScaleUpOBServers) + taskMap.Register(tExpandPVC, ExpandPVC) + taskMap.Register(tMountBackupVolume, MountBackupVolume) + taskMap.Register(tDeleteLegacyOBServers, DeleteLegacyOBServers) + taskMap.Register(tWaitOBServerBootstrapReady, WaitOBServerBootstrapReady) + taskMap.Register(tWaitOBServerRunning, WaitOBServerRunning) + taskMap.Register(tWaitForOBServerScalingUp, WaitForOBServerScalingUp) + taskMap.Register(tWaitForOBServerExpandingPVC, WaitForOBServerExpandingPVC) + taskMap.Register(tWaitForOBServerMounting, WaitForOBServerMounting) +} diff --git a/internal/resource/obzone/utils.go b/internal/resource/obzone/utils.go index 3067beada..9e28a6e03 100644 --- a/internal/resource/obzone/utils.go +++ b/internal/resource/obzone/utils.go @@ -13,15 +13,35 @@ See the Mulan PSL v2 for more details. package obzone import ( + "fmt" + "time" + "github.com/pkg/errors" "k8s.io/apimachinery/pkg/types" + "k8s.io/apimachinery/pkg/util/rand" "k8s.io/client-go/util/retry" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/oceanbase/ob-operator/api/v1alpha1" oceanbaseconst "github.com/oceanbase/ob-operator/internal/const/oceanbase" + serverstatus "github.com/oceanbase/ob-operator/internal/const/status/observer" + resourceutils "github.com/oceanbase/ob-operator/internal/resource/utils" + "github.com/oceanbase/ob-operator/pkg/oceanbase-sdk/operation" + tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" ) +func (m *OBZoneManager) getOceanbaseOperationManager() (*operation.OceanbaseOperationManager, error) { + obcluster, err := m.getOBCluster() + if err != nil { + return nil, errors.Wrap(err, "Get obcluster from K8s") + } + return resourceutils.GetSysOperationClient(m.Client, m.Logger, obcluster) +} + +func (m *OBZoneManager) generateServerName() string { + return fmt.Sprintf("%s-%d-%s-%s", m.OBZone.Spec.ClusterName, m.OBZone.Spec.ClusterId, m.OBZone.Spec.Topology.Zone, rand.String(6)) +} + func (m *OBZoneManager) checkIfStorageSizeExpand(observer *v1alpha1.OBServer) bool { return observer.Spec.OBServerTemplate.Storage.DataStorage.Size.Cmp(m.OBZone.Spec.OBServerTemplate.Storage.DataStorage.Size) < 0 || observer.Spec.OBServerTemplate.Storage.LogStorage.Size.Cmp(m.OBZone.Spec.OBServerTemplate.Storage.LogStorage.Size) < 0 || @@ -87,3 +107,28 @@ func (m *OBZoneManager) getOBCluster() (*v1alpha1.OBCluster, error) { } return obcluster, nil } + +func (m *OBZoneManager) generateWaitOBServerStatusFunc(status string, timeoutSeconds int) tasktypes.TaskFunc { + f := func() tasktypes.TaskError { + for i := 1; i < timeoutSeconds; i++ { + obzone, err := m.getOBZone() + if err != nil { + return errors.Wrap(err, "get obzoen failed") + } + allMatched := true + for _, observerStatus := range obzone.Status.OBServerStatus { + if observerStatus.Status != status && observerStatus.Status != serverstatus.Unrecoverable { + m.Logger.V(oceanbaseconst.LogLevelTrace).Info("Server status still not matched", "server", observerStatus.Server, "status", status) + allMatched = false + break + } + } + if allMatched { + return nil + } + time.Sleep(time.Second) + } + return errors.New("all server still not bootstrap ready when timeout") + } + return f +} diff --git a/make/deps.mk b/make/deps.mk index 56053890d..2076339d6 100644 --- a/make/deps.mk +++ b/make/deps.mk @@ -40,3 +40,7 @@ install-delve: ## Install delve, a debugger for the Go programming language. Mor .PHONY: tools tools: kustomize controller-gen envtest install-delve ## Download all tools + +.PHONY: init-generator +init-generator: ## Install generator tools + go install cmd/generator/task/task-register.go diff --git a/make/development.mk b/make/development.mk index f662456d2..8c4cd62a7 100644 --- a/make/development.mk +++ b/make/development.mk @@ -7,8 +7,9 @@ manifests: controller-gen ## Generate WebhookConfiguration, ClusterRole and Cust $(CONTROLLER_GEN) rbac:roleName=manager-role crd webhook paths="./..." output:crd:artifacts:config=config/crd/bases .PHONY: generate -generate: controller-gen ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations. +generate: controller-gen init-generator ## Generate code containing DeepCopy, DeepCopyInto, and DeepCopyObject method implementations; Generate task registrations for resource manager. $(CONTROLLER_GEN) object:headerFile="hack/boilerplate.go.txt" paths="./..." + go generate ./... .PHONY: fmt fmt: ## Run go fmt against code. diff --git a/pkg/task/builder/flow_builder.go b/pkg/task/builder/flow_builder.go new file mode 100644 index 000000000..c298bf245 --- /dev/null +++ b/pkg/task/builder/flow_builder.go @@ -0,0 +1,149 @@ +/* +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 builder + +import ( + "github.com/oceanbase/ob-operator/pkg/task/const/strategy" + tasktypes "github.com/oceanbase/ob-operator/pkg/task/types" +) + +type FlowBuilder interface { + BuildFlow() *tasktypes.TaskFlow + Step(task tasktypes.TaskName) FlowBuilder + Steps(tasks ...tasktypes.TaskName) FlowBuilder + To(status string) FlowBuilder + FailedTo(status string) FlowBuilder + RetryStrategy(strategy tasktypes.TaskFailureStrategy) FlowBuilder + MaxRetry(max int) FlowBuilder +} + +type flowBuilder struct { + operationContext *tasktypes.OperationContext +} + +func NewFlowBuilder(name tasktypes.FlowName) FlowBuilder { + return &flowBuilder{ + operationContext: &tasktypes.OperationContext{ + Name: name, + Tasks: []tasktypes.TaskName{}, + }, + } +} + +func (b *flowBuilder) BuildFlow() *tasktypes.TaskFlow { + if b.operationContext.TargetStatus == "" { + b.operationContext.TargetStatus = "running" + } + if b.operationContext.OnFailure.Strategy == "" { + b.operationContext.OnFailure.Strategy = strategy.StartOver + } + if b.operationContext.OnFailure.MaxRetry == 0 { + b.operationContext.OnFailure.MaxRetry = 32 + } + if b.operationContext.OnFailure.NextTryStatus == "" { + b.operationContext.OnFailure.NextTryStatus = "failed" + } + return &tasktypes.TaskFlow{ + OperationContext: b.operationContext, + } +} + +func (b *flowBuilder) Step(task tasktypes.TaskName) FlowBuilder { + b.operationContext.Tasks = append(b.operationContext.Tasks, task) + return b +} + +func (b *flowBuilder) To(status string) FlowBuilder { + b.operationContext.TargetStatus = status + return b +} + +func (b *flowBuilder) FailedTo(status string) FlowBuilder { + b.operationContext.OnFailure.NextTryStatus = status + return b +} + +func (b *flowBuilder) RetryStrategy(strategy tasktypes.TaskFailureStrategy) FlowBuilder { + b.operationContext.OnFailure.Strategy = strategy + return b +} + +func (b *flowBuilder) MaxRetry(max int) FlowBuilder { + b.operationContext.OnFailure.MaxRetry = max + return b +} + +func (b *flowBuilder) Steps(tasks ...tasktypes.TaskName) FlowBuilder { + b.operationContext.Tasks = append(b.operationContext.Tasks, tasks...) + return b +} + +// FlowGenerator generate a flow for a given type +type FlowGenerator[T any] func(T) *tasktypes.TaskFlow + +// FlowGeneratorBuilder build a flow generator with a given type +type FlowGeneratorBuilder[T any] interface { + FlowBuilder + + BuildGenerator() FlowGenerator[T] + NamedTaskStep(named NamedTask[T]) FlowGeneratorBuilder[T] + NamedTaskSteps(named ...NamedTask[T]) FlowGeneratorBuilder[T] + GenFunc(gen func(T) *tasktypes.TaskFlow) FlowGeneratorBuilder[T] +} + +type flowGeneratorBuilder[T any] struct { + *flowBuilder + gen func(T) *tasktypes.TaskFlow +} + +// BuildGenerator build a generator function for the flow +func (b *flowGeneratorBuilder[T]) BuildGenerator() FlowGenerator[T] { + if b.gen != nil { + return b.gen + } + return func(T) *tasktypes.TaskFlow { + return b.BuildFlow() + } +} + +// NamedTaskStep add a named task to the flow +func (b *flowGeneratorBuilder[T]) NamedTaskStep(named NamedTask[T]) FlowGeneratorBuilder[T] { + b.Step(named.Name()) + return b +} + +// NamedTaskSteps add named tasks to the flow +func (b *flowGeneratorBuilder[T]) NamedTaskSteps(named ...NamedTask[T]) FlowGeneratorBuilder[T] { + for _, n := range named { + b.Step(n.Name()) + } + return b +} + +// GenFunc set the generator function for the flow +func (b *flowGeneratorBuilder[T]) GenFunc(gen func(T) *tasktypes.TaskFlow) FlowGeneratorBuilder[T] { + b.gen = gen + return b +} + +// NewFlowGenerator create a new flow generator builder +func NewFlowGenerator[T any](name tasktypes.FlowName) FlowGeneratorBuilder[T] { + return &flowGeneratorBuilder[T]{ + flowBuilder: &flowBuilder{ + operationContext: &tasktypes.OperationContext{ + Name: name, + Tasks: []tasktypes.TaskName{}, + }, + }, + } +} diff --git a/pkg/task/builder/task_builder.go b/pkg/task/builder/task_builder.go new file mode 100644 index 000000000..100474cb4 --- /dev/null +++ b/pkg/task/builder/task_builder.go @@ -0,0 +1,80 @@ +/* +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 builder + +import ( + "errors" + + tt "github.com/oceanbase/ob-operator/pkg/task/types" +) + +type TypedTaskFunc[T any] func(T) tt.TaskError + +type NamedTask[T any] struct { + taskName tt.TaskName + fn TypedTaskFunc[T] +} + +func (t NamedTask[T]) Name() tt.TaskName { + return t.taskName +} + +func (t NamedTask[T]) Run(resource T) tt.TaskError { + return t.fn(resource) +} + +func (t NamedTask[T]) Func() TypedTaskFunc[T] { + return t.fn +} + +type TaskHub[T any] interface { + Register(tt.TaskName, TypedTaskFunc[T]) + GetTask(tt.TaskName, T) (tt.TaskFunc, error) + GetTypedTask(tt.TaskName) (TypedTaskFunc[T], error) + Build(tt.TaskName, TypedTaskFunc[T]) NamedTask[T] +} + +type taskMap[T any] struct { + m map[tt.TaskName]TypedTaskFunc[T] +} + +func NewTaskHub[T any]() TaskHub[T] { + return &taskMap[T]{m: make(map[tt.TaskName]TypedTaskFunc[T])} +} + +func (t *taskMap[T]) Build(name tt.TaskName, taskFunc TypedTaskFunc[T]) NamedTask[T] { + t.Register(name, taskFunc) + return NamedTask[T]{taskName: name, fn: taskFunc} +} + +func (t taskMap[T]) Register(name tt.TaskName, taskFunc TypedTaskFunc[T]) { + t.m[name] = taskFunc +} + +func (t taskMap[T]) GetTask(name tt.TaskName, resource T) (tt.TaskFunc, error) { + task, err := t.GetTypedTask(name) + if err != nil { + return nil, err + } + return func() tt.TaskError { + return task(resource) + }, nil +} + +func (t taskMap[T]) GetTypedTask(name tt.TaskName) (TypedTaskFunc[T], error) { + task, ok := t.m[name] + if !ok { + return nil, errors.New("Task not found: " + string(name)) + } + return task, nil +}