From 012dde3f3b449892045fc1dddbc9dd1ad6e09085 Mon Sep 17 00:00:00 2001 From: yuyi Date: Wed, 20 Sep 2023 20:40:56 +0800 Subject: [PATCH 01/19] chore: tests for tenant restore progress; definition of API types --- PROJECT | 9 + api/constants/tenant.go | 37 ++ api/v1alpha1/obtenant_types.go | 67 ++++ api/v1alpha1/obtenantoperation_types.go | 84 +++++ api/v1alpha1/obtenantrestore_types.go | 33 +- api/v1alpha1/zz_generated.deepcopy.go | 241 +++++++++++-- cmd/main.go | 8 + ....oceanbase.com_obtenantbackuppolicies.yaml | 1 - ...eanbase.oceanbase.com_obtenantbackups.yaml | 1 - ...base.oceanbase.com_obtenantoperations.yaml | 133 +++++++ ...anbase.oceanbase.com_obtenantrestores.yaml | 252 ++++++------- .../oceanbase.oceanbase.com_obtenants.yaml | 136 +++++++ config/crd/kustomization.yaml | 3 + .../cainjection_in_obtenantoperations.yaml | 7 + .../webhook_in_obtenantoperations.yaml | 16 + .../rbac/obtenantoperation_editor_role.yaml | 31 ++ .../rbac/obtenantoperation_viewer_role.yaml | 27 ++ config/rbac/role.yaml | 26 ++ config/samples/kustomization.yaml | 1 + .../oceanbase_v1alpha1_obtenantoperation.yaml | 12 + pkg/controller/observer_controller.go | 5 +- pkg/controller/obtenantbackup_controller.go | 4 +- .../obtenantoperation_controller.go | 62 ++++ pkg/oceanbase/const/sql/restore.go | 16 +- pkg/oceanbase/model/backup.go | 48 +-- pkg/oceanbase/model/restore.go | 42 +-- pkg/oceanbase/operation/restore.go | 49 ++- pkg/oceanbase/test/backup_test.go | 2 +- pkg/oceanbase/test/restore_test.go | 334 ++++++++++++++++++ pkg/resource/obtenant_task.go | 11 +- 30 files changed, 1441 insertions(+), 257 deletions(-) create mode 100644 api/constants/tenant.go create mode 100644 api/v1alpha1/obtenantoperation_types.go create mode 100644 config/crd/bases/oceanbase.oceanbase.com_obtenantoperations.yaml create mode 100644 config/crd/patches/cainjection_in_obtenantoperations.yaml create mode 100644 config/crd/patches/webhook_in_obtenantoperations.yaml create mode 100644 config/rbac/obtenantoperation_editor_role.yaml create mode 100644 config/rbac/obtenantoperation_viewer_role.yaml create mode 100644 config/samples/oceanbase_v1alpha1_obtenantoperation.yaml create mode 100644 pkg/controller/obtenantoperation_controller.go create mode 100644 pkg/oceanbase/test/restore_test.go diff --git a/PROJECT b/PROJECT index eca9eb1ad..f6b0e1855 100644 --- a/PROJECT +++ b/PROJECT @@ -123,4 +123,13 @@ resources: defaulting: true validation: true webhookVersion: v1 +- api: + crdVersion: v1 + namespaced: true + controller: true + domain: oceanbase.com + group: oceanbase + kind: OBTenantOperation + path: github.com/oceanbase/ob-operator/api/v1alpha1 + version: v1alpha1 version: "3" diff --git a/api/constants/tenant.go b/api/constants/tenant.go new file mode 100644 index 000000000..d7b816435 --- /dev/null +++ b/api/constants/tenant.go @@ -0,0 +1,37 @@ +/* +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 constants + +type TenantRole string + +const ( + TenantRolePrimary TenantRole = "PRIMARY" + TenantRoleStandby TenantRole = "STANDBY" +) + +type TenantOperationType string + +const ( + TenantOpSwitchover TenantOperationType = "SWITCHOVER" + TenantOpFailover = "FAILOVER" + TenantOpChangePwd = "CHANGE_PASSWORD" +) + +type TenantOperationStatus string + +const ( + TenantOpStarting TenantOperationStatus = "STARTING" + TenantOpRunning = "RUNNING" + TenantOpSuccessful = "SUCCESSFUL" + TenantOpFailed = "FAILED" +) diff --git a/api/v1alpha1/obtenant_types.go b/api/v1alpha1/obtenant_types.go index 42c737a70..7fde60890 100644 --- a/api/v1alpha1/obtenant_types.go +++ b/api/v1alpha1/obtenant_types.go @@ -17,6 +17,9 @@ limitations under the License. package v1alpha1 import ( + "github.com/oceanbase/ob-operator/api/constants" + "github.com/oceanbase/ob-operator/pkg/oceanbase/model" + corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -42,6 +45,24 @@ type OBTenantSpec struct { ConnectWhiteList string `json:"connectWhiteList,omitempty"` Pools []ResourcePoolSpec `json:"pools"` + + //+kubebuilder:default=PRIMARY + TenantRole constants.TenantRole `json:"tenantRole,omitempty"` + Source *TenantSourceSpec `json:"source,omitempty"` + Credentials []corev1.SecretReference `json:"credentials,omitempty"` +} + +// Source for restoring or creating standby +type TenantSourceSpec struct { + Tenant *string `json:"tenant,omitempty"` + Restore *RestoreSource `json:"restore,omitempty"` +} + +type RestoreSource struct { + SourceUri string `json:"sourceUri"` + Until string `json:"until"` + Description *string `json:"description,omitempty"` + ReplayLogUntil *string `json:"replayLogUntil,omitempty"` } type ResourcePoolSpec struct { @@ -71,6 +92,7 @@ type UnitConfig struct { LogDiskSize resource.Quantity `json:"logDiskSize,omitempty"` } +// +kubebuilder:object:generate=false // OBTenantStatus defines the observed state of OBTenant type OBTenantStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster @@ -79,6 +101,51 @@ type OBTenantStatus struct { Pools []ResourcePoolStatus `json:"resourcePool"` OperationContext *OperationContext `json:"operationContext,omitempty"` TenantRecordInfo TenantRecordInfo `json:"tenantRecordInfo,omitempty"` + + TenantRole constants.TenantRole `json:"tenantRole,omitempty"` + Source *TenantSourceStatus `json:"source,omitempty"` +} + +// +kubebuilder:object:generate=false +type TenantSourceStatus struct { + Tenant *string `json:"tenant,omitempty"` + Restore *model.RestoreHistory `json:"restore,omitempty"` +} + +func (in *OBTenantStatus) DeepCopyInto(out *OBTenantStatus) { + *out = *in + if in.Pools != nil { + in, out := &in.Pools, &out.Pools + *out = make([]ResourcePoolStatus, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.OperationContext != nil { + in, out := &in.OperationContext, &out.OperationContext + *out = new(OperationContext) + (*in).DeepCopyInto(*out) + } + out.TenantRecordInfo = in.TenantRecordInfo + if in.Source != nil { + in, out := &in.Source, &out.Source + *out = new(TenantSourceStatus) + (*in).DeepCopyInto(*out) + } +} + +func (in *TenantSourceStatus) DeepCopyInto(out *TenantSourceStatus) { + *out = *in + if in.Tenant != nil { + in, out := &in.Tenant, &out.Tenant + *out = new(string) + **out = **in + } + if in.Restore != nil { + in, out := &in.Restore, &out.Restore + *out = new(model.RestoreHistory) + **out = **in + } } type ResourcePoolStatus struct { diff --git a/api/v1alpha1/obtenantoperation_types.go b/api/v1alpha1/obtenantoperation_types.go new file mode 100644 index 000000000..3b9c68027 --- /dev/null +++ b/api/v1alpha1/obtenantoperation_types.go @@ -0,0 +1,84 @@ +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + "github.com/oceanbase/ob-operator/api/constants" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! +// NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. + +// OBTenantOperationSpec defines the desired state of OBTenantOperation +type OBTenantOperationSpec struct { + // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster + // Important: Run "make" to regenerate code after modifying this file + + Type constants.TenantOperationType `json:"type"` + Switchover *OBTenantOpSwitchoverSpec `json:"switchover,omitempty"` + Failover *OBTenantOpFailoverSpec `json:"failover,omitempty"` + ChangePwd *OBTenantOpChangePwdSpec `json:"changePwd,omitempty"` +} + +type OBTenantOpSwitchoverSpec struct { + PrimaryTenant string `json:"primaryTenant"` + StandbyTenant string `json:"standbyTenant"` +} + +type OBTenantOpFailoverSpec struct { + StandbyTenant string `json:"standbyTenant"` +} + +type OBTenantOpChangePwdSpec struct { + Tenant string `json:"tenant"` + SecretRef corev1.SecretReference `json:"secretRef"` +} + +// OBTenantOperationStatus defines the observed state of OBTenantOperation +type OBTenantOperationStatus struct { + // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster + // Important: Run "make" to regenerate code after modifying this file + Status constants.TenantOperationStatus `json:"status"` + OperationContext *OperationContext `json:"operationContext,omitempty"` +} + +//+kubebuilder:object:root=true +//+kubebuilder:subresource:status + +// OBTenantOperation is the Schema for the obtenantoperations API +type OBTenantOperation struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec OBTenantOperationSpec `json:"spec,omitempty"` + Status OBTenantOperationStatus `json:"status,omitempty"` +} + +//+kubebuilder:object:root=true + +// OBTenantOperationList contains a list of OBTenantOperation +type OBTenantOperationList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + Items []OBTenantOperation `json:"items"` +} + +func init() { + SchemeBuilder.Register(&OBTenantOperation{}, &OBTenantOperationList{}) +} diff --git a/api/v1alpha1/obtenantrestore_types.go b/api/v1alpha1/obtenantrestore_types.go index 0467a3e57..9d4384d90 100644 --- a/api/v1alpha1/obtenantrestore_types.go +++ b/api/v1alpha1/obtenantrestore_types.go @@ -13,7 +13,8 @@ See the Mulan PSL v2 for more details. package v1alpha1 import ( - batchv1 "k8s.io/api/batch/v1" + "github.com/oceanbase/ob-operator/api/constants" + "github.com/oceanbase/ob-operator/pkg/oceanbase/model" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -25,21 +26,33 @@ type OBTenantRestoreSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - ObClusterName string `json:"obClusterName"` - RestoreTenantName string `json:"restoreTenantName"` - Type string `json:"type"` - SourceUri string `json:"sourceUri"` - Until string `json:"until,omitempty"` + TargetTenant string `json:"targetTenant"` + RestoreRole constants.TenantRole `json:"restoreRole"` + Source TenantSourceSpec `json:"source"` } +// +kubebuilder:object:generate=false // OBTenantRestoreStatus defines the observed state of OBTenantRestore type OBTenantRestoreStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "make" to regenerate code after modifying this file - Status RestoreJobStatus `json:"status"` - JobStatus batchv1.JobStatus `json:"jobStatus"` - Progress string `json:"progress"` - OperationContext *OperationContext `json:"operationContext,omitempty"` + Status RestoreJobStatus `json:"status"` + RestoreProgress *model.RestoreHistory `json:"restoreProgress,omitempty"` + OperationContext *OperationContext `json:"operationContext,omitempty"` +} + +func (in *OBTenantRestoreStatus) DeepCopyInto(out *OBTenantRestoreStatus) { + *out = *in + if in.RestoreProgress != nil { + in, out := &in.RestoreProgress, &out.RestoreProgress + *out = new(model.RestoreHistory) + **out = **in + } + if in.OperationContext != nil { + in, out := &in.OperationContext, &out.OperationContext + *out = new(OperationContext) + (*in).DeepCopyInto(*out) + } } //+kubebuilder:object:root=true diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index fc9a8b6c4..80cf58f39 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -932,26 +932,72 @@ func (in *OBTenantList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OBTenantRestore) DeepCopyInto(out *OBTenantRestore) { +func (in *OBTenantOpChangePwdSpec) DeepCopyInto(out *OBTenantOpChangePwdSpec) { + *out = *in + out.SecretRef = in.SecretRef +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OBTenantOpChangePwdSpec. +func (in *OBTenantOpChangePwdSpec) DeepCopy() *OBTenantOpChangePwdSpec { + if in == nil { + return nil + } + out := new(OBTenantOpChangePwdSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OBTenantOpFailoverSpec) DeepCopyInto(out *OBTenantOpFailoverSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OBTenantOpFailoverSpec. +func (in *OBTenantOpFailoverSpec) DeepCopy() *OBTenantOpFailoverSpec { + if in == nil { + return nil + } + out := new(OBTenantOpFailoverSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OBTenantOpSwitchoverSpec) DeepCopyInto(out *OBTenantOpSwitchoverSpec) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OBTenantOpSwitchoverSpec. +func (in *OBTenantOpSwitchoverSpec) DeepCopy() *OBTenantOpSwitchoverSpec { + if in == nil { + return nil + } + out := new(OBTenantOpSwitchoverSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OBTenantOperation) DeepCopyInto(out *OBTenantOperation) { *out = *in out.TypeMeta = in.TypeMeta in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) - out.Spec = in.Spec + in.Spec.DeepCopyInto(&out.Spec) in.Status.DeepCopyInto(&out.Status) } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OBTenantRestore. -func (in *OBTenantRestore) DeepCopy() *OBTenantRestore { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OBTenantOperation. +func (in *OBTenantOperation) DeepCopy() *OBTenantOperation { if in == nil { return nil } - out := new(OBTenantRestore) + out := new(OBTenantOperation) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *OBTenantRestore) DeepCopyObject() runtime.Object { +func (in *OBTenantOperation) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -959,31 +1005,31 @@ func (in *OBTenantRestore) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OBTenantRestoreList) DeepCopyInto(out *OBTenantRestoreList) { +func (in *OBTenantOperationList) DeepCopyInto(out *OBTenantOperationList) { *out = *in out.TypeMeta = in.TypeMeta in.ListMeta.DeepCopyInto(&out.ListMeta) if in.Items != nil { in, out := &in.Items, &out.Items - *out = make([]OBTenantRestore, len(*in)) + *out = make([]OBTenantOperation, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OBTenantRestoreList. -func (in *OBTenantRestoreList) DeepCopy() *OBTenantRestoreList { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OBTenantOperationList. +func (in *OBTenantOperationList) DeepCopy() *OBTenantOperationList { if in == nil { return nil } - out := new(OBTenantRestoreList) + out := new(OBTenantOperationList) in.DeepCopyInto(out) return out } // DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. -func (in *OBTenantRestoreList) DeepCopyObject() runtime.Object { +func (in *OBTenantOperationList) DeepCopyObject() runtime.Object { if c := in.DeepCopy(); c != nil { return c } @@ -991,24 +1037,38 @@ func (in *OBTenantRestoreList) DeepCopyObject() runtime.Object { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OBTenantRestoreSpec) DeepCopyInto(out *OBTenantRestoreSpec) { +func (in *OBTenantOperationSpec) DeepCopyInto(out *OBTenantOperationSpec) { *out = *in + if in.Switchover != nil { + in, out := &in.Switchover, &out.Switchover + *out = new(OBTenantOpSwitchoverSpec) + **out = **in + } + if in.Failover != nil { + in, out := &in.Failover, &out.Failover + *out = new(OBTenantOpFailoverSpec) + **out = **in + } + if in.ChangePwd != nil { + in, out := &in.ChangePwd, &out.ChangePwd + *out = new(OBTenantOpChangePwdSpec) + **out = **in + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OBTenantRestoreSpec. -func (in *OBTenantRestoreSpec) DeepCopy() *OBTenantRestoreSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OBTenantOperationSpec. +func (in *OBTenantOperationSpec) DeepCopy() *OBTenantOperationSpec { if in == nil { return nil } - out := new(OBTenantRestoreSpec) + out := new(OBTenantOperationSpec) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OBTenantRestoreStatus) DeepCopyInto(out *OBTenantRestoreStatus) { +func (in *OBTenantOperationStatus) DeepCopyInto(out *OBTenantOperationStatus) { *out = *in - in.JobStatus.DeepCopyInto(&out.JobStatus) if in.OperationContext != nil { in, out := &in.OperationContext, &out.OperationContext *out = new(OperationContext) @@ -1016,62 +1076,119 @@ func (in *OBTenantRestoreStatus) DeepCopyInto(out *OBTenantRestoreStatus) { } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OBTenantRestoreStatus. -func (in *OBTenantRestoreStatus) DeepCopy() *OBTenantRestoreStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OBTenantOperationStatus. +func (in *OBTenantOperationStatus) DeepCopy() *OBTenantOperationStatus { if in == nil { return nil } - out := new(OBTenantRestoreStatus) + out := new(OBTenantOperationStatus) in.DeepCopyInto(out) return out } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OBTenantSpec) DeepCopyInto(out *OBTenantSpec) { +func (in *OBTenantRestore) DeepCopyInto(out *OBTenantRestore) { *out = *in - if in.Pools != nil { - in, out := &in.Pools, &out.Pools - *out = make([]ResourcePoolSpec, len(*in)) + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Spec.DeepCopyInto(&out.Spec) + in.Status.DeepCopyInto(&out.Status) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OBTenantRestore. +func (in *OBTenantRestore) DeepCopy() *OBTenantRestore { + if in == nil { + return nil + } + out := new(OBTenantRestore) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OBTenantRestore) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OBTenantRestoreList) DeepCopyInto(out *OBTenantRestoreList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]OBTenantRestore, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OBTenantSpec. -func (in *OBTenantSpec) DeepCopy() *OBTenantSpec { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OBTenantRestoreList. +func (in *OBTenantRestoreList) DeepCopy() *OBTenantRestoreList { if in == nil { return nil } - out := new(OBTenantSpec) + out := new(OBTenantRestoreList) in.DeepCopyInto(out) return out } +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *OBTenantRestoreList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *OBTenantStatus) DeepCopyInto(out *OBTenantStatus) { +func (in *OBTenantRestoreSpec) DeepCopyInto(out *OBTenantRestoreSpec) { + *out = *in + in.Source.DeepCopyInto(&out.Source) +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OBTenantRestoreSpec. +func (in *OBTenantRestoreSpec) DeepCopy() *OBTenantRestoreSpec { + if in == nil { + return nil + } + out := new(OBTenantRestoreSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *OBTenantSpec) DeepCopyInto(out *OBTenantSpec) { *out = *in if in.Pools != nil { in, out := &in.Pools, &out.Pools - *out = make([]ResourcePoolStatus, len(*in)) + *out = make([]ResourcePoolSpec, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } } - if in.OperationContext != nil { - in, out := &in.OperationContext, &out.OperationContext - *out = new(OperationContext) + if in.Source != nil { + in, out := &in.Source, &out.Source + *out = new(TenantSourceSpec) (*in).DeepCopyInto(*out) } - out.TenantRecordInfo = in.TenantRecordInfo + if in.Credentials != nil { + in, out := &in.Credentials, &out.Credentials + *out = make([]v1.SecretReference, len(*in)) + copy(*out, *in) + } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OBTenantStatus. -func (in *OBTenantStatus) DeepCopy() *OBTenantStatus { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OBTenantSpec. +func (in *OBTenantSpec) DeepCopy() *OBTenantSpec { if in == nil { return nil } - out := new(OBTenantStatus) + out := new(OBTenantSpec) in.DeepCopyInto(out) return out } @@ -1520,6 +1637,31 @@ func (in *ResourceSpec) DeepCopy() *ResourceSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RestoreSource) DeepCopyInto(out *RestoreSource) { + *out = *in + if in.Description != nil { + in, out := &in.Description, &out.Description + *out = new(string) + **out = **in + } + if in.ReplayLogUntil != nil { + in, out := &in.ReplayLogUntil, &out.ReplayLogUntil + *out = new(string) + **out = **in + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreSource. +func (in *RestoreSource) DeepCopy() *RestoreSource { + if in == nil { + return nil + } + out := new(RestoreSource) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *StorageSpec) DeepCopyInto(out *StorageSpec) { *out = *in @@ -1551,6 +1693,31 @@ func (in *TenantRecordInfo) DeepCopy() *TenantRecordInfo { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TenantSourceSpec) DeepCopyInto(out *TenantSourceSpec) { + *out = *in + if in.Tenant != nil { + in, out := &in.Tenant, &out.Tenant + *out = new(string) + **out = **in + } + if in.Restore != nil { + in, out := &in.Restore, &out.Restore + *out = new(RestoreSource) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantSourceSpec. +func (in *TenantSourceSpec) DeepCopy() *TenantSourceSpec { + if in == nil { + return nil + } + out := new(TenantSourceSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UnitConfig) DeepCopyInto(out *UnitConfig) { *out = *in diff --git a/cmd/main.go b/cmd/main.go index b2652fb48..bd5b92e27 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -48,6 +48,7 @@ func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(v1alpha1.AddToScheme(scheme)) + utilruntime.Must(oceanbasev1alpha1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } @@ -214,6 +215,13 @@ func main() { setupLog.Error(err, "unable to create webhook", "webhook", "OBTenantBackupPolicy") os.Exit(1) } + if err = (&controller.OBTenantOperationReconciler{ + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + }).SetupWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create controller", "controller", "OBTenantOperation") + os.Exit(1) + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/config/crd/bases/oceanbase.oceanbase.com_obtenantbackuppolicies.yaml b/config/crd/bases/oceanbase.oceanbase.com_obtenantbackuppolicies.yaml index 9e2771bf3..3c505f136 100644 --- a/config/crd/bases/oceanbase.oceanbase.com_obtenantbackuppolicies.yaml +++ b/config/crd/bases/oceanbase.oceanbase.com_obtenantbackuppolicies.yaml @@ -210,7 +210,6 @@ spec: - piece_switch_interval - round_id - start_scn - - start_scn_display - status - tenant_id - used_piece_id diff --git a/config/crd/bases/oceanbase.oceanbase.com_obtenantbackups.yaml b/config/crd/bases/oceanbase.oceanbase.com_obtenantbackups.yaml index 468616726..e6100539c 100644 --- a/config/crd/bases/oceanbase.oceanbase.com_obtenantbackups.yaml +++ b/config/crd/bases/oceanbase.oceanbase.com_obtenantbackups.yaml @@ -163,7 +163,6 @@ spec: - piece_switch_interval - round_id - start_scn - - start_scn_display - status - tenant_id - used_piece_id diff --git a/config/crd/bases/oceanbase.oceanbase.com_obtenantoperations.yaml b/config/crd/bases/oceanbase.oceanbase.com_obtenantoperations.yaml new file mode 100644 index 000000000..af6becb86 --- /dev/null +++ b/config/crd/bases/oceanbase.oceanbase.com_obtenantoperations.yaml @@ -0,0 +1,133 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.13.0 + name: obtenantoperations.oceanbase.oceanbase.com +spec: + group: oceanbase.oceanbase.com + names: + kind: OBTenantOperation + listKind: OBTenantOperationList + plural: obtenantoperations + singular: obtenantoperation + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: OBTenantOperation is the Schema for the obtenantoperations API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: OBTenantOperationSpec defines the desired state of OBTenantOperation + properties: + changePwd: + properties: + secretRef: + description: SecretReference represents a Secret Reference. It + has enough information to retrieve secret in any namespace + properties: + name: + description: name is unique within a namespace to reference + a secret resource. + type: string + namespace: + description: namespace defines the space within which the + secret name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + tenant: + type: string + required: + - secretRef + - tenant + type: object + failover: + properties: + standbyTenant: + type: string + required: + - standbyTenant + type: object + switchover: + properties: + primaryTenant: + type: string + standbyTenant: + type: string + required: + - primaryTenant + - standbyTenant + type: object + type: + type: string + required: + - type + type: object + status: + description: OBTenantOperationStatus defines the observed state of OBTenantOperation + properties: + operationContext: + properties: + failureRule: + properties: + failureStatus: + type: string + failureStrategy: + type: string + required: + - failureStatus + - failureStrategy + type: object + idx: + type: integer + name: + type: string + targetStatus: + type: string + task: + type: string + taskId: + type: string + taskStatus: + type: string + tasks: + items: + type: string + type: array + required: + - idx + - name + - targetStatus + - task + - taskId + - taskStatus + - tasks + type: object + status: + description: 'INSERT ADDITIONAL STATUS FIELD - define observed state + of cluster Important: Run "make" to regenerate code after modifying + this file' + type: string + required: + - status + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/oceanbase.oceanbase.com_obtenantrestores.yaml b/config/crd/bases/oceanbase.oceanbase.com_obtenantrestores.yaml index 3fd064e95..7f2a4815e 100644 --- a/config/crd/bases/oceanbase.oceanbase.com_obtenantrestores.yaml +++ b/config/crd/bases/oceanbase.oceanbase.com_obtenantrestores.yaml @@ -35,146 +35,38 @@ spec: spec: description: OBTenantRestoreSpec defines the desired state of OBTenantRestore properties: - obClusterName: + restoreRole: type: string - restoreTenantName: - type: string - sourceUri: - type: string - type: - type: string - until: + source: + description: Source for restoring or creating standby + properties: + restore: + properties: + description: + type: string + replayLogUntil: + type: string + sourceUri: + type: string + until: + type: string + required: + - sourceUri + - until + type: object + tenant: + type: string + type: object + targetTenant: type: string required: - - obClusterName - - restoreTenantName - - sourceUri - - type + - restoreRole + - source + - targetTenant type: object status: description: OBTenantRestoreStatus defines the observed state of OBTenantRestore properties: - jobStatus: - description: JobStatus represents the current state of a Job. - properties: - active: - description: The number of pending and running pods. - format: int32 - type: integer - completedIndexes: - description: completedIndexes holds the completed indexes when - .spec.completionMode = "Indexed" in a text format. The indexes - are represented as decimal integers separated by commas. The - numbers are listed in increasing order. Three or more consecutive - numbers are compressed and represented by the first and last - element of the series, separated by a hyphen. For example, if - the completed indexes are 1, 3, 4, 5 and 7, they are represented - as "1,3-5,7". - type: string - completionTime: - description: Represents time when the job was completed. It is - not guaranteed to be set in happens-before order across separate - operations. It is represented in RFC3339 form and is in UTC. - The completion time is only set when the job finishes successfully. - format: date-time - type: string - conditions: - description: 'The latest available observations of an object''s - current state. When a Job fails, one of the conditions will - have type "Failed" and status true. When a Job is suspended, - one of the conditions will have type "Suspended" and status - true; when the Job is resumed, the status of this condition - will become false. When a Job is completed, one of the conditions - will have type "Complete" and status true. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/' - items: - description: JobCondition describes current state of a job. - properties: - lastProbeTime: - description: Last time the condition was checked. - format: date-time - type: string - lastTransitionTime: - description: Last time the condition transit from one status - to another. - format: date-time - type: string - message: - description: Human readable message indicating details about - last transition. - type: string - reason: - description: (brief) reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, - Unknown. - type: string - type: - description: Type of job condition, Complete or Failed. - type: string - required: - - status - - type - type: object - type: array - x-kubernetes-list-type: atomic - failed: - description: The number of pods which reached phase Failed. - format: int32 - type: integer - ready: - description: "The number of pods which have a Ready condition. - \n This field is beta-level. The job controller populates the - field when the feature gate JobReadyPods is enabled (enabled - by default)." - format: int32 - type: integer - startTime: - description: Represents time when the job controller started processing - a job. When a Job is created in the suspended state, this field - is not set until the first time it is resumed. This field is - reset every time a Job is resumed from suspension. It is represented - in RFC3339 form and is in UTC. - format: date-time - type: string - succeeded: - description: The number of pods which reached phase Succeeded. - format: int32 - type: integer - uncountedTerminatedPods: - description: "uncountedTerminatedPods holds the UIDs of Pods that - have terminated but the job controller hasn't yet accounted - for in the status counters. \n The job controller creates pods - with a finalizer. When a pod terminates (succeeded or failed), - the controller does three steps to account for it in the job - status: \n 1. Add the pod UID to the arrays in this field. 2. - Remove the pod finalizer. 3. Remove the pod UID from the arrays - while increasing the corresponding counter. \n Old jobs might - not be tracked using this field, in which case the field remains - null." - properties: - failed: - description: failed holds UIDs of failed Pods. - items: - description: UID is a type that holds unique ID values, - including UUIDs. Because we don't ONLY use UUIDs, this - is an alias to string. Being a type captures intent and - helps make sure that UIDs and names do not get conflated. - type: string - type: array - x-kubernetes-list-type: set - succeeded: - description: succeeded holds UIDs of succeeded Pods. - items: - description: UID is a type that holds unique ID values, - including UUIDs. Because we don't ONLY use UUIDs, this - is an alias to string. Being a type captures intent and - helps make sure that UIDs and names do not get conflated. - type: string - type: array - x-kubernetes-list-type: set - type: object - type: object operationContext: properties: failureRule: @@ -212,16 +104,102 @@ spec: - taskStatus - tasks type: object - progress: - type: string + restoreProgress: + description: RestoreHistory is the history of restore job, matches + view CDB_OB_RESTORE_HISTORY + properties: + backup_cluster_name: + type: string + backup_cluster_version: + type: string + backup_dest: + type: string + backup_piece_list: + type: string + backup_set_list: + type: string + backup_tenant_id: + format: int64 + type: integer + backup_tenant_name: + type: string + description: + type: string + finish_bytes: + format: int64 + type: integer + finish_bytes_display: + type: string + finish_ls_count: + format: int64 + type: integer + finish_tablet_count: + format: int64 + type: integer + finish_timestamp: + type: string + job_id: + format: int64 + type: integer + ls_count: + format: int64 + type: integer + restore_option: + type: string + restore_scn: + format: int64 + type: integer + restore_scn_display: + type: string + restore_tenant_id: + format: int64 + type: integer + restore_tenant_name: + type: string + start_timestamp: + type: string + status: + type: string + tablet_count: + format: int64 + type: integer + tenant_id: + format: int64 + type: integer + total_bytes: + format: int64 + type: integer + total_bytes_display: + type: string + required: + - backup_cluster_name + - backup_cluster_version + - backup_dest + - backup_piece_list + - backup_set_list + - backup_tenant_id + - backup_tenant_name + - finish_ls_count + - finish_tablet_count + - finish_timestamp + - job_id + - ls_count + - restore_option + - restore_scn + - restore_scn_display + - restore_tenant_id + - restore_tenant_name + - start_timestamp + - status + - tablet_count + - tenant_id + type: object status: description: 'INSERT ADDITIONAL STATUS FIELD - define observed state of cluster Important: Run "make" to regenerate code after modifying this file' type: string required: - - jobStatus - - progress - status type: object type: object diff --git a/config/crd/bases/oceanbase.oceanbase.com_obtenants.yaml b/config/crd/bases/oceanbase.oceanbase.com_obtenants.yaml index ccbe0157e..d607cb2d9 100644 --- a/config/crd/bases/oceanbase.oceanbase.com_obtenants.yaml +++ b/config/crd/bases/oceanbase.oceanbase.com_obtenants.yaml @@ -68,6 +68,22 @@ spec: connectWhiteList: default: '%' type: string + credentials: + items: + description: SecretReference represents a Secret Reference. It has + enough information to retrieve secret in any namespace + properties: + name: + description: name is unique within a namespace to reference + a secret resource. + type: string + namespace: + description: namespace defines the space within which the secret + name must be unique. + type: string + type: object + x-kubernetes-map-type: atomic + type: array forceDelete: default: false type: boolean @@ -141,8 +157,31 @@ spec: - zone type: object type: array + source: + description: Source for restoring or creating standby + properties: + restore: + properties: + description: + type: string + replayLogUntil: + type: string + sourceUri: + type: string + until: + type: string + required: + - sourceUri + - until + type: object + tenant: + type: string + type: object tenantName: type: string + tenantRole: + default: PRIMARY + type: string unitNum: type: integer required: @@ -292,6 +331,101 @@ spec: - zoneList type: object type: array + source: + properties: + restore: + description: RestoreHistory is the history of restore job, matches + view CDB_OB_RESTORE_HISTORY + properties: + backup_cluster_name: + type: string + backup_cluster_version: + type: string + backup_dest: + type: string + backup_piece_list: + type: string + backup_set_list: + type: string + backup_tenant_id: + format: int64 + type: integer + backup_tenant_name: + type: string + description: + type: string + finish_bytes: + format: int64 + type: integer + finish_bytes_display: + type: string + finish_ls_count: + format: int64 + type: integer + finish_tablet_count: + format: int64 + type: integer + finish_timestamp: + type: string + job_id: + format: int64 + type: integer + ls_count: + format: int64 + type: integer + restore_option: + type: string + restore_scn: + format: int64 + type: integer + restore_scn_display: + type: string + restore_tenant_id: + format: int64 + type: integer + restore_tenant_name: + type: string + start_timestamp: + type: string + status: + type: string + tablet_count: + format: int64 + type: integer + tenant_id: + format: int64 + type: integer + total_bytes: + format: int64 + type: integer + total_bytes_display: + type: string + required: + - backup_cluster_name + - backup_cluster_version + - backup_dest + - backup_piece_list + - backup_set_list + - backup_tenant_id + - backup_tenant_name + - finish_ls_count + - finish_tablet_count + - finish_timestamp + - job_id + - ls_count + - restore_option + - restore_scn + - restore_scn_display + - restore_tenant_id + - restore_tenant_name + - start_timestamp + - status + - tablet_count + - tenant_id + type: object + tenant: + type: string + type: object status: description: 'INSERT ADDITIONAL STATUS FIELD - define observed state of cluster Important: Run "make" to regenerate code after modifying @@ -323,6 +457,8 @@ spec: - primaryZone - tenantID type: object + tenantRole: + type: string required: - resourcePool - status diff --git a/config/crd/kustomization.yaml b/config/crd/kustomization.yaml index 54fe86808..06ddfafef 100644 --- a/config/crd/kustomization.yaml +++ b/config/crd/kustomization.yaml @@ -13,6 +13,7 @@ resources: - bases/oceanbase.oceanbase.com_obclusterrestores.yaml - bases/oceanbase.oceanbase.com_obtenantrestores.yaml - bases/oceanbase.oceanbase.com_obtenantbackuppolicies.yaml +- bases/oceanbase.oceanbase.com_obtenantoperations.yaml #+kubebuilder:scaffold:crdkustomizeresource patchesStrategicMerge: @@ -29,6 +30,7 @@ patchesStrategicMerge: # - patches/webhook_in_obclusterrestores.yaml # - patches/webhook_in_obtenantrestores.yaml - patches/webhook_in_obtenantbackuppolicies.yaml +#- path: patches/webhook_in_obtenantoperations.yaml #+kubebuilder:scaffold:crdkustomizewebhookpatch # [CERTMANAGER] To enable cert-manager, uncomment all the sections with [CERTMANAGER] prefix. @@ -44,6 +46,7 @@ patchesStrategicMerge: # - patches/cainjection_in_obclusterrestores.yaml # - patches/cainjection_in_obtenantrestores.yaml - patches/cainjection_in_obtenantbackuppolicies.yaml +#- path: patches/cainjection_in_obtenantoperations.yaml #+kubebuilder:scaffold:crdkustomizecainjectionpatch # the following config is for teaching kustomize how to do kustomization for CRDs. diff --git a/config/crd/patches/cainjection_in_obtenantoperations.yaml b/config/crd/patches/cainjection_in_obtenantoperations.yaml new file mode 100644 index 000000000..60072f6d6 --- /dev/null +++ b/config/crd/patches/cainjection_in_obtenantoperations.yaml @@ -0,0 +1,7 @@ +# The following patch adds a directive for certmanager to inject CA into the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: CERTIFICATE_NAMESPACE/CERTIFICATE_NAME + name: obtenantoperations.oceanbase.oceanbase.com diff --git a/config/crd/patches/webhook_in_obtenantoperations.yaml b/config/crd/patches/webhook_in_obtenantoperations.yaml new file mode 100644 index 000000000..0edabedaf --- /dev/null +++ b/config/crd/patches/webhook_in_obtenantoperations.yaml @@ -0,0 +1,16 @@ +# The following patch enables a conversion webhook for the CRD +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: obtenantoperations.oceanbase.oceanbase.com +spec: + conversion: + strategy: Webhook + webhook: + clientConfig: + service: + namespace: system + name: webhook-service + path: /convert + conversionReviewVersions: + - v1 diff --git a/config/rbac/obtenantoperation_editor_role.yaml b/config/rbac/obtenantoperation_editor_role.yaml new file mode 100644 index 000000000..1d1bac614 --- /dev/null +++ b/config/rbac/obtenantoperation_editor_role.yaml @@ -0,0 +1,31 @@ +# permissions for end users to edit obtenantoperations. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: obtenantoperation-editor-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: ob-operator + app.kubernetes.io/part-of: ob-operator + app.kubernetes.io/managed-by: kustomize + name: obtenantoperation-editor-role +rules: +- apiGroups: + - oceanbase.oceanbase.com + resources: + - obtenantoperations + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - oceanbase.oceanbase.com + resources: + - obtenantoperations/status + verbs: + - get diff --git a/config/rbac/obtenantoperation_viewer_role.yaml b/config/rbac/obtenantoperation_viewer_role.yaml new file mode 100644 index 000000000..52a0d32f5 --- /dev/null +++ b/config/rbac/obtenantoperation_viewer_role.yaml @@ -0,0 +1,27 @@ +# permissions for end users to view obtenantoperations. +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + labels: + app.kubernetes.io/name: clusterrole + app.kubernetes.io/instance: obtenantoperation-viewer-role + app.kubernetes.io/component: rbac + app.kubernetes.io/created-by: ob-operator + app.kubernetes.io/part-of: ob-operator + app.kubernetes.io/managed-by: kustomize + name: obtenantoperation-viewer-role +rules: +- apiGroups: + - oceanbase.oceanbase.com + resources: + - obtenantoperations + verbs: + - get + - list + - watch +- apiGroups: + - oceanbase.oceanbase.com + resources: + - obtenantoperations/status + verbs: + - get diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 861fb073d..2f9df2198 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -357,6 +357,32 @@ rules: - get - patch - update +- apiGroups: + - oceanbase.oceanbase.com + resources: + - obtenantoperations + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - oceanbase.oceanbase.com + resources: + - obtenantoperations/finalizers + verbs: + - update +- apiGroups: + - oceanbase.oceanbase.com + resources: + - obtenantoperations/status + verbs: + - get + - patch + - update - apiGroups: - oceanbase.oceanbase.com resources: diff --git a/config/samples/kustomization.yaml b/config/samples/kustomization.yaml index cd1de9d0c..52d7ac1c8 100644 --- a/config/samples/kustomization.yaml +++ b/config/samples/kustomization.yaml @@ -11,4 +11,5 @@ resources: - oceanbase.oceanbase.com_v2alpha1_obclusterrestore.yaml - oceanbase.oceanbase.com_v2alpha1_obtenantrestore.yaml - oceanbase_v1alpha1_obtenantbackuppolicy.yaml +- oceanbase_v1alpha1_obtenantoperation.yaml #+kubebuilder:scaffold:manifestskustomizesamples diff --git a/config/samples/oceanbase_v1alpha1_obtenantoperation.yaml b/config/samples/oceanbase_v1alpha1_obtenantoperation.yaml new file mode 100644 index 000000000..39efbf793 --- /dev/null +++ b/config/samples/oceanbase_v1alpha1_obtenantoperation.yaml @@ -0,0 +1,12 @@ +apiVersion: oceanbase.oceanbase.com/v1alpha1 +kind: OBTenantOperation +metadata: + labels: + app.kubernetes.io/name: obtenantoperation + app.kubernetes.io/instance: obtenantoperation-sample + app.kubernetes.io/part-of: ob-operator + app.kubernetes.io/managed-by: kustomize + app.kubernetes.io/created-by: ob-operator + name: obtenantoperation-sample +spec: + # TODO(user): Add fields here diff --git a/pkg/controller/observer_controller.go b/pkg/controller/observer_controller.go index dbfabb5ca..624209324 100644 --- a/pkg/controller/observer_controller.go +++ b/pkg/controller/observer_controller.go @@ -19,6 +19,7 @@ package controller import ( "context" "fmt" + "time" "github.com/pkg/errors" kubeerrors "k8s.io/apimachinery/pkg/api/errors" @@ -102,7 +103,9 @@ func (r *OBServerReconciler) Reconcile(ctx context.Context, req ctrl.Request) (c } coordinator := resource.NewCoordinator(observerManager, &logger) err = coordinator.Coordinate() - return ctrl.Result{}, err + return ctrl.Result{ + RequeueAfter: 5 * time.Second, + }, err } // SetupWithManager sets up the controller with the Manager. diff --git a/pkg/controller/obtenantbackup_controller.go b/pkg/controller/obtenantbackup_controller.go index 175881683..7e2391dc2 100644 --- a/pkg/controller/obtenantbackup_controller.go +++ b/pkg/controller/obtenantbackup_controller.go @@ -216,7 +216,9 @@ func (r *OBTenantBackupReconciler) maintainRunningArchiveLogJob(ctx context.Cont } if latest != nil { job.Status.ArchiveLogJob = latest - job.Status.StartedAt = latest.StartScnDisplay + if latest.StartScnDisplay != nil { + job.Status.StartedAt = *latest.StartScnDisplay + } job.Status.EndedAt = latest.CheckpointScnDisplay switch latest.Status { case "STOP": diff --git a/pkg/controller/obtenantoperation_controller.go b/pkg/controller/obtenantoperation_controller.go new file mode 100644 index 000000000..aaad06062 --- /dev/null +++ b/pkg/controller/obtenantoperation_controller.go @@ -0,0 +1,62 @@ +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "context" + + "k8s.io/apimachinery/pkg/runtime" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/log" + + oceanbasev1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" +) + +// OBTenantOperationReconciler reconciles a OBTenantOperation object +type OBTenantOperationReconciler struct { + client.Client + Scheme *runtime.Scheme +} + +//+kubebuilder:rbac:groups=oceanbase.oceanbase.com,resources=obtenantoperations,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=oceanbase.oceanbase.com,resources=obtenantoperations/status,verbs=get;update;patch +//+kubebuilder:rbac:groups=oceanbase.oceanbase.com,resources=obtenantoperations/finalizers,verbs=update + +// Reconcile is part of the main kubernetes reconciliation loop which aims to +// move the current state of the cluster closer to the desired state. +// TODO(user): Modify the Reconcile function to compare the state specified by +// the OBTenantOperation object against the actual cluster state, and then +// perform operations to make the cluster state reflect the state specified by +// the user. +// +// For more details, check Reconcile and its Result here: +// - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.15.0/pkg/reconcile +func (r *OBTenantOperationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = log.FromContext(ctx) + + // TODO(user): your logic here + + return ctrl.Result{}, nil +} + +// SetupWithManager sets up the controller with the Manager. +func (r *OBTenantOperationReconciler) SetupWithManager(mgr ctrl.Manager) error { + return ctrl.NewControllerManagedBy(mgr). + For(&oceanbasev1alpha1.OBTenantOperation{}). + Complete(r) +} diff --git a/pkg/oceanbase/const/sql/restore.go b/pkg/oceanbase/const/sql/restore.go index fd66f7710..f7c87b18a 100644 --- a/pkg/oceanbase/const/sql/restore.go +++ b/pkg/oceanbase/const/sql/restore.go @@ -12,15 +12,23 @@ See the Mulan PSL v2 for more details. package sql +const ( + restoreProgressFields = "tenant_id, job_id, restore_tenant_name, restore_tenant_id, restore_tenant_id, backup_tenant_name, backup_tenant_id, backup_cluster_name, backup_dest, restore_option, restore_scn, restore_scn_display, status, start_timestamp, backup_set_list, backup_piece_list, total_bytes, total_bytes_display, finish_bytes, finish_bytes_display, description" + restoreHistoryFields = restoreProgressFields + ", finish_timestamp, backup_cluster_version, ls_count, finish_ls_count, tablet_count, finish_tablet_count" +) + const ( SetRestorePassword = "SET DECRYPTION IDENTIFIED BY ?" // tenant_name, uri, Time/SCN, restore_option - StartRestoreWithLimit = "ALTER SYSTEM RESTORE ? FROM ? UNTIL %s=? WITH ?" + StartRestoreWithLimit = "ALTER SYSTEM RESTORE %s FROM ? UNTIL %s=? WITH ?" // tenant_name, uri, restore_option - StartRestoreUnlimited = "ALTER SYSTEM RESTORE ? FROM ? WITH ?" + StartRestoreUnlimited = "ALTER SYSTEM RESTORE %s FROM ? WITH ?" CancelRestore = "ALTER SYSTEM CANCEL RESTORE ?" ReplayStandbyLog = "ALTER SYSTEM RECOVER STANDBY TENANT ? UNTIL %s" ActivateStandby = "ALTER SYSTEM ACTIVATE STANDBY TENANT ?" - QueryRestoreProgress = "SELECT * FROM CDB_OB_RESTORE_PROGRESS" - QueryRestoreHistory = "SELECT * FROM CDB_OB_RESTORE_HISTORY" + QueryRestoreProgress = "SELECT " + restoreProgressFields + " FROM CDB_OB_RESTORE_PROGRESS" + QueryRestoreHistory = "SELECT " + restoreHistoryFields + " FROM CDB_OB_RESTORE_HISTORY" + + GetLatestRestoreProgress = QueryRestoreProgress + " WHERE restore_tenant_name = ? ORDER BY JOB_ID DESC LIMIT 1" + GetLatestRestoreHistory = QueryRestoreHistory + " WHERE restore_tenant_name = ? ORDER BY JOB_ID DESC LIMIT 1" ) diff --git a/pkg/oceanbase/model/backup.go b/pkg/oceanbase/model/backup.go index e9e6edb3a..26df8659d 100644 --- a/pkg/oceanbase/model/backup.go +++ b/pkg/oceanbase/model/backup.go @@ -27,30 +27,30 @@ type OBBackupParameter struct { // OBArchiveLogSummary matches view DBA_OB_ARCHIVELOG_SUMMARY type OBArchiveLogSummary struct { - TenantId int64 `json:"tenant_id" db:"tenant_id"` - DestId int64 `json:"dest_id" db:"dest_id"` - RoundId int64 `json:"round_id" db:"round_id"` - DestNo int64 `json:"dest_no" db:"dest_no"` - Status string `json:"status" db:"status"` - StartScn int64 `json:"start_scn" db:"start_scn"` - StartScnDisplay string `json:"start_scn_display" db:"start_scn_display"` - CheckpointScn int64 `json:"checkpoint_scn" db:"checkpoint_scn"` - CheckpointScnDisplay string `json:"checkpoint_scn_display" db:"checkpoint_scn_display"` - Compatible string `json:"compatible" db:"compatible"` - BasePieceId int64 `json:"base_piece_id" db:"base_piece_id"` - UsedPieceId int64 `json:"used_piece_id" db:"used_piece_id"` - PieceSwitchInterval string `json:"piece_switch_interval" db:"piece_switch_interval"` - InputBytes int64 `json:"input_bytes" db:"input_bytes"` - InputBytesDisplay string `json:"input_bytes_display" db:"input_bytes_display"` - OutputBytes int64 `json:"output_bytes" db:"output_bytes"` - OutputBytesDisplay string `json:"output_bytes_display" db:"output_bytes_display"` - CompressionRatio string `json:"compression_ratio" db:"compression_ratio"` - DeletedInputBytes int64 `json:"deleted_input_bytes" db:"deleted_input_bytes"` - DeletedInputBytesDisplay string `json:"deleted_input_bytes_display" db:"deleted_input_bytes_display"` - DeletedOutputBytes int64 `json:"deleted_output_bytes" db:"deleted_output_bytes"` - DeletedOutputBytesDisplay string `json:"deleted_output_bytes_display" db:"deleted_output_bytes_display"` - Comment string `json:"comment" db:"comment"` - Path string `json:"path" db:"path"` + TenantId int64 `json:"tenant_id" db:"tenant_id"` + DestId int64 `json:"dest_id" db:"dest_id"` + RoundId int64 `json:"round_id" db:"round_id"` + DestNo int64 `json:"dest_no" db:"dest_no"` + Status string `json:"status" db:"status"` + StartScn int64 `json:"start_scn" db:"start_scn"` + StartScnDisplay *string `json:"start_scn_display,omitempty" db:"start_scn_display"` + CheckpointScn int64 `json:"checkpoint_scn" db:"checkpoint_scn"` + CheckpointScnDisplay string `json:"checkpoint_scn_display" db:"checkpoint_scn_display"` + Compatible string `json:"compatible" db:"compatible"` + BasePieceId int64 `json:"base_piece_id" db:"base_piece_id"` + UsedPieceId int64 `json:"used_piece_id" db:"used_piece_id"` + PieceSwitchInterval string `json:"piece_switch_interval" db:"piece_switch_interval"` + InputBytes int64 `json:"input_bytes" db:"input_bytes"` + InputBytesDisplay string `json:"input_bytes_display" db:"input_bytes_display"` + OutputBytes int64 `json:"output_bytes" db:"output_bytes"` + OutputBytesDisplay string `json:"output_bytes_display" db:"output_bytes_display"` + CompressionRatio string `json:"compression_ratio" db:"compression_ratio"` + DeletedInputBytes int64 `json:"deleted_input_bytes" db:"deleted_input_bytes"` + DeletedInputBytesDisplay string `json:"deleted_input_bytes_display" db:"deleted_input_bytes_display"` + DeletedOutputBytes int64 `json:"deleted_output_bytes" db:"deleted_output_bytes"` + DeletedOutputBytesDisplay string `json:"deleted_output_bytes_display" db:"deleted_output_bytes_display"` + Comment string `json:"comment" db:"comment"` + Path string `json:"path" db:"path"` } // OBArchiveLogJob is equal to OBArchiveLogSummary, but match view DBA_OB_ARCHIVELOG_JOBS diff --git a/pkg/oceanbase/model/restore.go b/pkg/oceanbase/model/restore.go index b52112b80..a9c9b3155 100644 --- a/pkg/oceanbase/model/restore.go +++ b/pkg/oceanbase/model/restore.go @@ -14,33 +14,33 @@ package model // RestoreProgress is the progress of restore job, matches view CDB_OB_RESTORE_PROGRESS type RestoreProgress struct { - TenantId int64 `json:"tenant_id" db:"tenant_id"` - JobId int64 `json:"job_id" db:"job_id"` - RestoreTenantName string `json:"restore_tenant_name" db:"restore_tenant_name"` - RestoreTenantId int64 `json:"restore_tenant_id" db:"restore_tenant_id"` - BackupTenantName string `json:"backup_tenant_name" db:"backup_tenant_name"` - BackupTenantId int64 `json:"backup_tenant_id" db:"backup_tenant_id"` - BackupClusterName string `json:"backup_cluster_name" db:"backup_cluster_name"` - BackupDest string `json:"backup_dest" db:"backup_dest"` - RestoreOption string `json:"restore_option" db:"restore_option"` - RestoreScn int64 `json:"restore_scn" db:"restore_scn"` - RestoreScnDisplay string `json:"restore_scn_display" db:"restore_scn_display"` - Status string `json:"status" db:"status"` - StartTimestamp int64 `json:"start_timestamp" db:"start_timestamp"` - BackupSetList string `json:"backup_set_list" db:"backup_set_list"` - BackupPieceList string `json:"backup_piece_list" db:"backup_piece_list"` - TotalBytes int64 `json:"total_bytes" db:"total_bytes"` - TotalBytesDisplay string `json:"total_bytes_display" db:"total_bytes_display"` - FinishBytes int64 `json:"finish_bytes" db:"finish_bytes"` - FinishBytesDisplay string `json:"finish_bytes_display" db:"finish_bytes_display"` - Description string `json:"description" db:"description"` + TenantId int64 `json:"tenant_id" db:"tenant_id"` + JobId int64 `json:"job_id" db:"job_id"` + RestoreTenantName string `json:"restore_tenant_name" db:"restore_tenant_name"` + RestoreTenantId int64 `json:"restore_tenant_id" db:"restore_tenant_id"` + BackupTenantName string `json:"backup_tenant_name" db:"backup_tenant_name"` + BackupTenantId int64 `json:"backup_tenant_id" db:"backup_tenant_id"` + BackupClusterName string `json:"backup_cluster_name" db:"backup_cluster_name"` + BackupDest string `json:"backup_dest" db:"backup_dest"` + RestoreOption string `json:"restore_option" db:"restore_option"` + RestoreScn int64 `json:"restore_scn" db:"restore_scn"` + RestoreScnDisplay string `json:"restore_scn_display" db:"restore_scn_display"` + Status string `json:"status" db:"status"` + StartTimestamp string `json:"start_timestamp" db:"start_timestamp"` + BackupSetList string `json:"backup_set_list" db:"backup_set_list"` + BackupPieceList string `json:"backup_piece_list" db:"backup_piece_list"` + TotalBytes *int64 `json:"total_bytes,omitempty" db:"total_bytes"` + TotalBytesDisplay *string `json:"total_bytes_display,omitempty" db:"total_bytes_display"` + FinishBytes *int64 `json:"finish_bytes,omitempty" db:"finish_bytes"` + FinishBytesDisplay *string `json:"finish_bytes_display,omitempty" db:"finish_bytes_display"` + Description *string `json:"description,omitempty" db:"description"` } // RestoreHistory is the history of restore job, matches view CDB_OB_RESTORE_HISTORY type RestoreHistory struct { RestoreProgress `json:",inline" db:",inline"` - FinishTimestamp int64 `json:"finish_timestamp" db:"finish_timestamp"` + FinishTimestamp string `json:"finish_timestamp" db:"finish_timestamp"` BackupClusterVersion string `json:"backup_cluster_version" db:"backup_cluster_version"` LsCount int64 `json:"ls_count" db:"ls_count"` FinishLsCount int64 `json:"finish_ls_count" db:"finish_ls_count"` diff --git a/pkg/oceanbase/operation/restore.go b/pkg/oceanbase/operation/restore.go index e58ac1227..323f5f44b 100644 --- a/pkg/oceanbase/operation/restore.go +++ b/pkg/oceanbase/operation/restore.go @@ -14,6 +14,7 @@ package operation import ( "fmt" + "time" "github.com/oceanbase/ob-operator/pkg/oceanbase/const/sql" "github.com/oceanbase/ob-operator/pkg/oceanbase/model" @@ -30,8 +31,8 @@ func (m *OceanbaseOperationManager) SetRestorePassword(password string) error { } func (m *OceanbaseOperationManager) StartRestoreWithLimit(tenantName, uri, limitKey, restoreOption string, limitValue interface{}) error { - sqlStatement := fmt.Sprintf(sql.StartRestoreWithLimit, limitKey) - err := m.ExecWithDefaultTimeout(sqlStatement, tenantName, uri, limitValue, restoreOption) + sqlStatement := fmt.Sprintf(sql.StartRestoreWithLimit, tenantName, limitKey) + err := m.ExecWithDefaultTimeout(sqlStatement, uri, limitValue, restoreOption) if err != nil { m.Logger.Error(err, "Got exception when start restore with limit") return errors.Wrap(err, "Start restore with limit") @@ -40,7 +41,7 @@ func (m *OceanbaseOperationManager) StartRestoreWithLimit(tenantName, uri, limit } func (m *OceanbaseOperationManager) StartRestoreUnlimited(tenantName, uri, restoreOption string) error { - err := m.ExecWithDefaultTimeout(sql.StartRestoreUnlimited, tenantName, uri, restoreOption) + err := m.ExecWithTimeout(600*time.Second, fmt.Sprintf(sql.StartRestoreUnlimited, tenantName), uri, restoreOption) if err != nil { m.Logger.Error(err, "Got exception when start restore unlimited") return errors.Wrap(err, "Start restore unlimited") @@ -76,28 +77,48 @@ func (m *OceanbaseOperationManager) ActivateStandby(tenantName string) error { return nil } -func (m *OceanbaseOperationManager) QueryRestoreProgress() ([]*model.RestoreProgress, error) { +func (m *OceanbaseOperationManager) ListRestoreProgress() ([]*model.RestoreProgress, error) { progressInfos := make([]*model.RestoreProgress, 0) - err := m.QueryList(&progressInfos, sql.QueryBackupCleanJobs) + err := m.QueryList(&progressInfos, sql.QueryRestoreProgress) if err != nil { m.Logger.Error(err, "Got exception when query restore progress") - return nil, errors.Wrap(err, "Query restore progress") - } - if len(progressInfos) == 0 { - return nil, errors.Errorf("No restore progress found") + return nil, errors.Wrap(err, "List restore progress") } return progressInfos, nil } -func (m *OceanbaseOperationManager) QueryRestoreHistory() ([]*model.RestoreHistory, error) { +func (m *OceanbaseOperationManager) ListRestoreHistory() ([]*model.RestoreHistory, error) { restoreHistory := make([]*model.RestoreHistory, 0) err := m.QueryList(&restoreHistory, sql.QueryRestoreHistory) if err != nil { m.Logger.Error(err, "Got exception when query restore history") - return nil, errors.Wrap(err, "Query restore history") - } - if len(restoreHistory) == 0 { - return nil, errors.Errorf("No restore history found") + return nil, errors.Wrap(err, "List restore history") } return restoreHistory, nil } + +func (m *OceanbaseOperationManager) GetLatestRestoreProgressOfTenant(tenant string) (*model.RestoreProgress, error) { + latest := make([]*model.RestoreProgress, 0) + err := m.QueryList(&latest, sql.GetLatestRestoreProgress, tenant) + if err != nil { + m.Logger.Error(err, "Got exception when query latest restore progress") + return nil, errors.Wrap(err, "Get latest restore progress") + } + if len(latest) == 0 { + return nil, nil + } + return latest[0], nil +} + +func (m *OceanbaseOperationManager) GetLatestRestoreHistoryOfTenant(tenant string) (*model.RestoreHistory, error) { + latest := make([]*model.RestoreHistory, 0) + err := m.QueryList(&latest, sql.GetLatestRestoreHistory, tenant) + if err != nil { + m.Logger.Error(err, "Got exception when query latest restore history") + return nil, errors.Wrap(err, "Get latest restore history") + } + if len(latest) == 0 { + return nil, nil + } + return latest[0], nil +} diff --git a/pkg/oceanbase/test/backup_test.go b/pkg/oceanbase/test/backup_test.go index e544c0d93..7238fccf9 100644 --- a/pkg/oceanbase/test/backup_test.go +++ b/pkg/oceanbase/test/backup_test.go @@ -24,7 +24,7 @@ import ( . "github.com/onsi/gomega" ) -var _ = Describe("Test Backup Operation", func() { +var _ = Describe("Test Backup Operation", Label("backup"), func() { var con *operation.OceanbaseOperationManager diff --git a/pkg/oceanbase/test/restore_test.go b/pkg/oceanbase/test/restore_test.go new file mode 100644 index 000000000..b3ba1d153 --- /dev/null +++ b/pkg/oceanbase/test/restore_test.go @@ -0,0 +1,334 @@ +/* +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 test + +import ( + "fmt" + "strings" + "time" + + "github.com/go-logr/logr" + "github.com/oceanbase/ob-operator/api/constants" + "github.com/oceanbase/ob-operator/pkg/oceanbase/connector" + "github.com/oceanbase/ob-operator/pkg/oceanbase/model" + "github.com/oceanbase/ob-operator/pkg/oceanbase/operation" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" +) + +var _ = Describe("Test Restore Operation", Serial, Label("restore"), func() { + var con *operation.OceanbaseOperationManager + var standbyName string + var _ = BeforeEach(func() { + var err error + logger := logr.Discard() + ds := connector.NewOceanBaseDataSource(host, port, sysUser, "sys", sysPassword, database) + con, err = operation.GetOceanbaseOperationManager(ds) + Expect(err).To(BeNil()) + con.Logger = &logger + standbyName = tenant + "_standby" + }) + + var _ = AfterEach(func() { + Expect(con).NotTo(BeNil()) + err := con.Close() + Expect(err).To(BeNil()) + }) + + It("Create Unit, Resource pool and Tenants", func() { + By("Check if tenant exists") + tenants, err := con.ListTenantWithName(tenant) + Expect(err).To(BeNil()) + if len(tenants) > 0 { + Skip("tenant already exists") + } + By("Create unit") + unitList, err := con.GetUnitConfigV4List() + Expect(err).To(BeNil()) + exists := false + for _, unit := range unitList { + if unit.Name == "unit_test" { + exists = true + break + } + } + if !exists { + err = con.AddUnitConfigV4(&model.UnitConfigV4SQLParam{ + UnitConfigName: "unit_test", + MinCPU: 2, + MaxCPU: 2, + MemorySize: 2048000000, + MaxIops: 1024, + LogDiskSize: 2048000000, + MinIops: 1024, + }) + Expect(err).To(BeNil()) + } + By("Create resource pool") + poolList, err := con.GetPoolList() + Expect(err).To(BeNil()) + exists = false + for _, pool := range poolList { + if pool.Name == "pool_test1" { + exists = true + break + } + } + if !exists { + for _, v := range []int{1, 2, 3} { + err = con.AddPool(model.PoolSQLParam{ + UnitNum: 1, + PoolName: fmt.Sprintf("pool_test%d", v), + ZoneList: fmt.Sprintf("zone%d", v), + UnitName: "unit_test", + }) + Expect(err).To(BeNil()) + } + } + + By("Create tenant") + exists, err = con.CheckTenantExistByName(tenant) + Expect(err).To(BeNil()) + if !exists { + err = con.AddTenant(model.TenantSQLParam{ + TenantName: tenant, + Charset: "utf8mb4", + PrimaryZone: "zone1", + PoolList: []string{"pool_test1", "pool_test2", "pool_test3"}, + UnitNum: 1, + VariableList: "ob_tcp_invited_nodes='%'", + }) + Expect(err).To(BeNil()) + } + }) + + It("Write some data to tenant", Label("prepare_data"), func() { + var err error + logger := logr.Discard() + ds := connector.NewOceanBaseDataSource(host, port, user, tenant, "", "") + tenantCon, err := operation.GetOceanbaseOperationManager(ds) + Expect(err).To(BeNil()) + tenantCon.Logger = &logger + + By("Write some data to tenant") + err = tenantCon.ExecWithDefaultTimeout("create table if not exists test.test (id int, name varchar(20))") + Expect(err).To(BeNil()) + err = tenantCon.ExecWithDefaultTimeout("insert into test.test values (1, 'test')") + Expect(err).To(BeNil()) + err = tenantCon.ExecWithDefaultTimeout("insert into test.test values (2, 'test')") + Expect(err).To(BeNil()) + err = tenantCon.ExecWithDefaultTimeout("insert into test.test values (3, 'test'), (4, 'test'), (5, 'test')") + Expect(err).To(BeNil()) + + Expect(tenantCon.Close()).To(BeNil()) + }) + + It("Backup primary tenant", Label("prepare_backup"), func() { + var err error + logger := logr.Discard() + ds := connector.NewOceanBaseDataSource(host, port, user, tenant, "", database) + tenantCon, err := operation.GetOceanbaseOperationManager(ds) + Expect(err).To(BeNil()) + tenantCon.Logger = &logger + backupDest := "file:///ob-backup/" + tenant + "/backup" + archiveDest := "location=file:///ob-backup/" + tenant + "/archive" + + By("Backup primary tenant") + err = tenantCon.SetLogArchiveDestForTenant(archiveDest) + Expect(err).To(BeNil()) + + By("Set archive log retention") + err = tenantCon.EnableArchiveLogForTenant() + Expect(err).To(BeNil()) + + By("Wait for archive doing") + for { + time.Sleep(5 * time.Second) + latestArchive, err := tenantCon.GetLatestArchiveLogJob() + Expect(err).To(BeNil()) + if latestArchive != nil && latestArchive.Status == "DOING" { + break + } + } + + By("Set backup destination") + err = tenantCon.SetDataBackupDestForTenant(backupDest) + Expect(err).To(BeNil()) + + By("Create full backup job") + err = tenantCon.CreateBackupFull() + Expect(err).To(BeNil()) + + By("Wait for backup done") + for { + time.Sleep(3 * time.Second) + backupJob, err := tenantCon.GetLatestBackupJobOfTypeAndPath(constants.BackupJobTypeFull, backupDest) + Expect(err).To(BeNil()) + if backupJob != nil && backupJob.Status == "COMPLETED" { + break + } + } + By("Finish backup of primary tenant") + }) + + It("Write some data to tenant", Label("prepare_data2"), func() { + var err error + logger := logr.Discard() + ds := connector.NewOceanBaseDataSource(host, port, user, tenant, "", "") + tenantCon, err := operation.GetOceanbaseOperationManager(ds) + Expect(err).To(BeNil()) + tenantCon.Logger = &logger + + By("Write some data to tenant") + err = tenantCon.ExecWithDefaultTimeout("insert into test.test values (101, 'test_after')") + Expect(err).To(BeNil()) + err = tenantCon.ExecWithDefaultTimeout("insert into test.test values (102, 'test_after')") + Expect(err).To(BeNil()) + err = tenantCon.ExecWithDefaultTimeout("insert into test.test values (103, 'test_after')") + Expect(err).To(BeNil()) + By("Wait for a moment") + time.Sleep(10 * time.Second) + Expect(tenantCon.Close()).To(BeNil()) + }) + + It("Checking backup progress", Label("check_backup"), func() { + var err error + logger := logr.Discard() + ds := connector.NewOceanBaseDataSource(host, port, user, tenant, "", database) + tenantCon, err := operation.GetOceanbaseOperationManager(ds) + Expect(err).To(BeNil()) + tenantCon.Logger = &logger + backupDest := "file:///ob-backup/" + tenant + "/backup" + + By("Wait for backup done") + for { + time.Sleep(3 * time.Second) + backupJob, err := tenantCon.GetLatestBackupJobOfTypeAndPath(constants.BackupJobTypeFull, backupDest) + Expect(err).To(BeNil()) + if backupJob != nil && backupJob.Status == "COMPLETED" { + break + } + } + By("Finish backup of primary tenant") + }) + + It("Wait for 3 mins", func() { + // Avoid the case that the standby tenant is created too fast + // ERROR 4018 (HY000): No enough log to restore + time.Sleep(1 * time.Minute) + }) + + It("Restore standby tenant", Label("restore_standby"), func() { + By("Check target tenant's existence") + exists, err := con.CheckTenantExistByName(standbyName) + Expect(err).To(BeNil()) + if exists { + Skip("Target standby tenant exists") + } + + By("Create resource pool") + poolList, err := con.GetPoolList() + Expect(err).To(BeNil()) + exists = false + for _, pool := range poolList { + if pool.Name == "pool_test_standby1" { + exists = true + break + } + } + if !exists { + for _, v := range []int{1, 2, 3} { + err = con.AddPool(model.PoolSQLParam{ + UnitNum: 1, + PoolName: fmt.Sprintf("pool_test_standby%d", v), + ZoneList: fmt.Sprintf("zone%d", v), + UnitName: "unit_test", + }) + Expect(err).To(BeNil()) + } + } + + By("Trigger restoration of standby tenant") + backupDest := "file:///ob-backup/" + tenant + "/backup" + archiveDest := "file:///ob-backup/" + tenant + "/archive" + err = con.StartRestoreUnlimited(standbyName, strings.Join([]string{backupDest, archiveDest}, ","), "pool_list=pool_test_standby1,pool_test_standby2,pool_test_standby3") + Expect(err).To(BeNil()) + }) + + It("Query restore progress", Label("query_restore"), func() { + + By("Check restoration progress") + for { + time.Sleep(5 * time.Second) + restoreJob, err := con.GetLatestRestoreProgressOfTenant(standbyName) + Expect(err).To(BeNil()) + printObject(restoreJob, "restoreJob") + if restoreJob != nil && restoreJob.Status == "SUCCESS" { + break + } + if restoreJob == nil { + restoreHistory, err := con.GetLatestRestoreHistoryOfTenant(standbyName) + Expect(err).To(BeNil()) + if restoreHistory != nil { + printObject(restoreHistory, "restoreHistory") + if restoreHistory.Status == "SUCCESS" { + break + } + } + } + } + By("Restore finished") + }) + + It("Cancel restoring", Label("cancel_restore"), func() { + Skip("") + Expect(con.CancelCleanBackup()).To(BeNil()) + }) + + It("Replay", Label("replay"), func() { + // Not repeatable + err := con.ReplayStandbyLog(standbyName, "UNLIMITED") + Expect(err).To(BeNil()) + time.Sleep(3 * time.Second) + }) + + It("Activate", Label("activate"), func() { + // Repeatable + err := con.ActivateStandby(standbyName) + Expect(err).To(BeNil()) + time.Sleep(3 * time.Second) + }) + + It("Delete Tenants", Label("delete_tenants"), func() { + By("Deleting primary tenant") + exists, err := con.CheckTenantExistByName(tenant) + Expect(err).To(BeNil()) + if exists { + Expect(con.DeleteTenant(tenant, true)).To(BeNil()) + } + By("Deleting standby tenants") + exists, err = con.CheckTenantExistByName(standbyName) + Expect(err).To(BeNil()) + if exists { + Expect(con.DeleteTenant(standbyName, true)).To(BeNil()) + } + By("Deleting resource pools") + for _, pool := range []string{"pool_test1", "pool_test2", "pool_test3", "pool_test_standby1", "pool_test_standby2", "pool_test_standby3"} { + exists, err = con.CheckPoolExistByName(pool) + Expect(err).To(BeNil()) + if exists { + Expect(con.DeletePool(pool)).To(BeNil()) + } + } + }) +}) diff --git a/pkg/resource/obtenant_task.go b/pkg/resource/obtenant_task.go index 535149d15..91325ef89 100644 --- a/pkg/resource/obtenant_task.go +++ b/pkg/resource/obtenant_task.go @@ -14,16 +14,17 @@ package resource import ( "fmt" - "github.com/oceanbase/ob-operator/api/v1alpha1" - "github.com/oceanbase/ob-operator/pkg/oceanbase/const/config" - "github.com/oceanbase/ob-operator/pkg/oceanbase/const/status/tenant" - "github.com/oceanbase/ob-operator/pkg/oceanbase/model" - "github.com/pkg/errors" "reflect" "sort" "strconv" "strings" "time" + + "github.com/oceanbase/ob-operator/api/v1alpha1" + "github.com/oceanbase/ob-operator/pkg/oceanbase/const/config" + "github.com/oceanbase/ob-operator/pkg/oceanbase/const/status/tenant" + "github.com/oceanbase/ob-operator/pkg/oceanbase/model" + "github.com/pkg/errors" ) // ---------- task entry point ---------- From 6c330c8bdd49e368871a39ddc79aaf4d762876df Mon Sep 17 00:00:00 2001 From: yuyi Date: Wed, 20 Sep 2023 21:11:59 +0800 Subject: [PATCH 02/19] fix: add type for const var list --- api/constants/tenant.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/api/constants/tenant.go b/api/constants/tenant.go index d7b816435..e21bb5cd2 100644 --- a/api/constants/tenant.go +++ b/api/constants/tenant.go @@ -23,15 +23,15 @@ type TenantOperationType string const ( TenantOpSwitchover TenantOperationType = "SWITCHOVER" - TenantOpFailover = "FAILOVER" - TenantOpChangePwd = "CHANGE_PASSWORD" + TenantOpFailover TenantOperationType = "FAILOVER" + TenantOpChangePwd TenantOperationType = "CHANGE_PASSWORD" ) type TenantOperationStatus string const ( TenantOpStarting TenantOperationStatus = "STARTING" - TenantOpRunning = "RUNNING" - TenantOpSuccessful = "SUCCESSFUL" - TenantOpFailed = "FAILED" + TenantOpRunning TenantOperationStatus = "RUNNING" + TenantOpSuccessful TenantOperationStatus = "SUCCESSFUL" + TenantOpFailed TenantOperationStatus = "FAILED" ) From 9608fa8ac9d8b38e0c226b7eb16a2287bedcd3c0 Mon Sep 17 00:00:00 2001 From: yuyi Date: Thu, 21 Sep 2023 13:31:34 +0800 Subject: [PATCH 03/19] chore: update fields of Tenant Source --- api/v1alpha1/obtenantrestore_types.go | 7 ++-- api/v1alpha1/zz_generated.deepcopy.go | 36 ++++++++++++++--- cmd/main.go | 4 +- ...anbase.oceanbase.com_obtenantrestores.yaml | 21 +++++++++- .../oceanbase.oceanbase.com_obtenants.yaml | 18 ++++++++- config/rbac/role.yaml | 40 +++++++++++++++++++ .../status/tenantstatus/obtenant_status.go | 3 ++ pkg/controller/obtenant_controller.go | 3 ++ .../obtenantoperation_controller.go | 7 ++-- pkg/controller/obtenantrestore_controller.go | 8 ++-- pkg/resource/obtenant_manager.go | 13 +++--- 11 files changed, 131 insertions(+), 29 deletions(-) diff --git a/api/v1alpha1/obtenantrestore_types.go b/api/v1alpha1/obtenantrestore_types.go index 9d4384d90..df3db43c6 100644 --- a/api/v1alpha1/obtenantrestore_types.go +++ b/api/v1alpha1/obtenantrestore_types.go @@ -26,9 +26,10 @@ type OBTenantRestoreSpec struct { // INSERT ADDITIONAL SPEC FIELDS - desired state of cluster // Important: Run "make" to regenerate code after modifying this file - TargetTenant string `json:"targetTenant"` - RestoreRole constants.TenantRole `json:"restoreRole"` - Source TenantSourceSpec `json:"source"` + TargetTenant string `json:"targetTenant"` + TargetCluster string `json:"targetCluster"` + RestoreRole constants.TenantRole `json:"restoreRole"` + Source TenantSourceSpec `json:"source"` } // +kubebuilder:object:generate=false diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 80cf58f39..835ece71b 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -1638,8 +1638,9 @@ func (in *ResourceSpec) DeepCopy() *ResourceSpec { } // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *RestoreSource) DeepCopyInto(out *RestoreSource) { +func (in *RestoreSourceSpec) DeepCopyInto(out *RestoreSourceSpec) { *out = *in + in.Until.DeepCopyInto(&out.Until) if in.Description != nil { in, out := &in.Description, &out.Description *out = new(string) @@ -1647,17 +1648,42 @@ func (in *RestoreSource) DeepCopyInto(out *RestoreSource) { } if in.ReplayLogUntil != nil { in, out := &in.ReplayLogUntil, &out.ReplayLogUntil + *out = new(RestoreUntilConfig) + (*in).DeepCopyInto(*out) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreSourceSpec. +func (in *RestoreSourceSpec) DeepCopy() *RestoreSourceSpec { + if in == nil { + return nil + } + out := new(RestoreSourceSpec) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *RestoreUntilConfig) DeepCopyInto(out *RestoreUntilConfig) { + *out = *in + if in.Timestamp != nil { + in, out := &in.Timestamp, &out.Timestamp + *out = new(string) + **out = **in + } + if in.Scn != nil { + in, out := &in.Scn, &out.Scn *out = new(string) **out = **in } } -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreSource. -func (in *RestoreSource) DeepCopy() *RestoreSource { +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RestoreUntilConfig. +func (in *RestoreUntilConfig) DeepCopy() *RestoreUntilConfig { if in == nil { return nil } - out := new(RestoreSource) + out := new(RestoreUntilConfig) in.DeepCopyInto(out) return out } @@ -1703,7 +1729,7 @@ func (in *TenantSourceSpec) DeepCopyInto(out *TenantSourceSpec) { } if in.Restore != nil { in, out := &in.Restore, &out.Restore - *out = new(RestoreSource) + *out = new(RestoreSourceSpec) (*in).DeepCopyInto(*out) } } diff --git a/cmd/main.go b/cmd/main.go index bd5b92e27..644693c50 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -32,7 +32,6 @@ import ( "sigs.k8s.io/controller-runtime/pkg/healthz" "sigs.k8s.io/controller-runtime/pkg/log/zap" - oceanbasev1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" "github.com/oceanbase/ob-operator/pkg/controller" "github.com/oceanbase/ob-operator/pkg/controller/config" @@ -48,7 +47,6 @@ func init() { utilruntime.Must(clientgoscheme.AddToScheme(scheme)) utilruntime.Must(v1alpha1.AddToScheme(scheme)) - utilruntime.Must(oceanbasev1alpha1.AddToScheme(scheme)) //+kubebuilder:scaffold:scheme } @@ -211,7 +209,7 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "OBTenantBackupPolicy") os.Exit(1) } - if err = (&oceanbasev1alpha1.OBTenantBackupPolicy{}).SetupWebhookWithManager(mgr); err != nil { + if err = (&v1alpha1.OBTenantBackupPolicy{}).SetupWebhookWithManager(mgr); err != nil { setupLog.Error(err, "unable to create webhook", "webhook", "OBTenantBackupPolicy") os.Exit(1) } diff --git a/config/crd/bases/oceanbase.oceanbase.com_obtenantrestores.yaml b/config/crd/bases/oceanbase.oceanbase.com_obtenantrestores.yaml index 7f2a4815e..7819a18ed 100644 --- a/config/crd/bases/oceanbase.oceanbase.com_obtenantrestores.yaml +++ b/config/crd/bases/oceanbase.oceanbase.com_obtenantrestores.yaml @@ -45,11 +45,25 @@ spec: description: type: string replayLogUntil: - type: string + properties: + scn: + type: string + timestamp: + type: string + unlimited: + type: boolean + type: object sourceUri: type: string until: - type: string + properties: + scn: + type: string + timestamp: + type: string + unlimited: + type: boolean + type: object required: - sourceUri - until @@ -57,11 +71,14 @@ spec: tenant: type: string type: object + targetCluster: + type: string targetTenant: type: string required: - restoreRole - source + - targetCluster - targetTenant type: object status: diff --git a/config/crd/bases/oceanbase.oceanbase.com_obtenants.yaml b/config/crd/bases/oceanbase.oceanbase.com_obtenants.yaml index d607cb2d9..9087bc50e 100644 --- a/config/crd/bases/oceanbase.oceanbase.com_obtenants.yaml +++ b/config/crd/bases/oceanbase.oceanbase.com_obtenants.yaml @@ -165,11 +165,25 @@ spec: description: type: string replayLogUntil: - type: string + properties: + scn: + type: string + timestamp: + type: string + unlimited: + type: boolean + type: object sourceUri: type: string until: - type: string + properties: + scn: + type: string + timestamp: + type: string + unlimited: + type: boolean + type: object required: - sourceUri - until diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index 2f9df2198..182b1a83e 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -285,6 +285,26 @@ rules: - get - patch - update +- apiGroups: + - oceanbase.oceanbase.com + resources: + - obtenant + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - oceanbase.oceanbase.com + resources: + - obtenant/status + verbs: + - get + - patch + - update - apiGroups: - oceanbase.oceanbase.com resources: @@ -383,6 +403,26 @@ rules: - get - patch - update +- apiGroups: + - oceanbase.oceanbase.com + resources: + - obtenantrestore + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - oceanbase.oceanbase.com + resources: + - obtenantrestore/status + verbs: + - get + - patch + - update - apiGroups: - oceanbase.oceanbase.com resources: diff --git a/pkg/const/status/tenantstatus/obtenant_status.go b/pkg/const/status/tenantstatus/obtenant_status.go index c6d4dffef..f32211b64 100644 --- a/pkg/const/status/tenantstatus/obtenant_status.go +++ b/pkg/const/status/tenantstatus/obtenant_status.go @@ -26,4 +26,7 @@ const ( DeletingTenant = "deleting" FinalizerFinished = "finalizer finished" PausingReconcile = "pausing reconcile" + + Restoring = "restoring" + SwitchingRole = "switching role" ) diff --git a/pkg/controller/obtenant_controller.go b/pkg/controller/obtenant_controller.go index 5074ae473..a80bee2d3 100644 --- a/pkg/controller/obtenant_controller.go +++ b/pkg/controller/obtenant_controller.go @@ -43,6 +43,9 @@ type OBTenantReconciler struct { // +kubebuilder:rbac:groups=oceanbase.oceanbase.com,resources=obtenants/finalizers,verbs=update // +kubebuilder:rbac:groups="",resources=events,verbs=create;patch +//+kubebuilder:rbac:groups=oceanbase.oceanbase.com,resources=obtenantrestore,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=oceanbase.oceanbase.com,resources=obtenantrestore/status,verbs=get;update;patch + // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. // TODO(user): Modify the Reconcile function to compare the state specified by diff --git a/pkg/controller/obtenantoperation_controller.go b/pkg/controller/obtenantoperation_controller.go index aaad06062..7c782abbd 100644 --- a/pkg/controller/obtenantoperation_controller.go +++ b/pkg/controller/obtenantoperation_controller.go @@ -37,12 +37,11 @@ type OBTenantOperationReconciler struct { //+kubebuilder:rbac:groups=oceanbase.oceanbase.com,resources=obtenantoperations/status,verbs=get;update;patch //+kubebuilder:rbac:groups=oceanbase.oceanbase.com,resources=obtenantoperations/finalizers,verbs=update +//+kubebuilder:rbac:groups=oceanbase.oceanbase.com,resources=obtenant,verbs=get;list;watch;create;update;patch;delete +//+kubebuilder:rbac:groups=oceanbase.oceanbase.com,resources=obtenant/status,verbs=get;update;patch + // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the OBTenantOperation object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. // // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.15.0/pkg/reconcile diff --git a/pkg/controller/obtenantrestore_controller.go b/pkg/controller/obtenantrestore_controller.go index 208248634..b240a8d3f 100644 --- a/pkg/controller/obtenantrestore_controller.go +++ b/pkg/controller/obtenantrestore_controller.go @@ -39,13 +39,11 @@ type OBTenantRestoreReconciler struct { //+kubebuilder:rbac:groups=oceanbase.oceanbase.com,resources=obtenantrestores/status,verbs=get;update;patch //+kubebuilder:rbac:groups=oceanbase.oceanbase.com,resources=obtenantrestores/finalizers,verbs=update +//+kubebuilder:rbac:groups=oceanbase.oceanbase.com,resources=obtenant,verbs=get;list;watch +//+kubebuilder:rbac:groups=oceanbase.oceanbase.com,resources=obtenant/status,verbs=get + // Reconcile is part of the main kubernetes reconciliation loop which aims to // move the current state of the cluster closer to the desired state. -// TODO(user): Modify the Reconcile function to compare the state specified by -// the OBTenantRestore object against the actual cluster state, and then -// perform operations to make the cluster state reflect the state specified by -// the user. -// // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile func (r *OBTenantRestoreReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { diff --git a/pkg/resource/obtenant_manager.go b/pkg/resource/obtenant_manager.go index a75a32a76..41bb6cfc7 100644 --- a/pkg/resource/obtenant_manager.go +++ b/pkg/resource/obtenant_manager.go @@ -20,6 +20,7 @@ import ( "strings" "github.com/go-logr/logr" + "github.com/oceanbase/ob-operator/api/constants" "github.com/oceanbase/ob-operator/api/v1alpha1" "github.com/oceanbase/ob-operator/pkg/const/status/tenantstatus" "github.com/oceanbase/ob-operator/pkg/oceanbase/const/status/tenant" @@ -70,12 +71,14 @@ func (m *OBTenantManager) IsDeleting() bool { } func (m *OBTenantManager) InitStatus() { - m.Logger.Info("newly created obtenant, init status") - status := v1alpha1.OBTenantStatus{ - Status: tenantstatus.CreatingTenant, - Pools: make([]v1alpha1.ResourcePoolStatus, 0, len(m.OBTenant.Spec.Pools)), + m.OBTenant.Status = v1alpha1.OBTenantStatus{ + Pools: make([]v1alpha1.ResourcePoolStatus, 0, len(m.OBTenant.Spec.Pools)), + } + if m.OBTenant.Spec.TenantRole == constants.TenantRoleStandby { + m.OBTenant.Status.Status = tenantstatus.Restoring + } else { + m.OBTenant.Status.Status = tenantstatus.CreatingTenant } - m.OBTenant.Status = status } func (m *OBTenantManager) SetOperationContext(ctx *v1alpha1.OperationContext) { From d95afe04d07a62584bbf7afc233f1f19be15a43c Mon Sep 17 00:00:00 2001 From: yuyi Date: Thu, 21 Sep 2023 13:39:23 +0800 Subject: [PATCH 04/19] chore: adjust definition of restoreSource.until fields --- api/v1alpha1/obtenant_types.go | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/api/v1alpha1/obtenant_types.go b/api/v1alpha1/obtenant_types.go index 7fde60890..717625af1 100644 --- a/api/v1alpha1/obtenant_types.go +++ b/api/v1alpha1/obtenant_types.go @@ -54,15 +54,21 @@ type OBTenantSpec struct { // Source for restoring or creating standby type TenantSourceSpec struct { - Tenant *string `json:"tenant,omitempty"` - Restore *RestoreSource `json:"restore,omitempty"` + Tenant *string `json:"tenant,omitempty"` + Restore *RestoreSourceSpec `json:"restore,omitempty"` } -type RestoreSource struct { - SourceUri string `json:"sourceUri"` - Until string `json:"until"` - Description *string `json:"description,omitempty"` - ReplayLogUntil *string `json:"replayLogUntil,omitempty"` +type RestoreSourceSpec struct { + SourceUri string `json:"sourceUri"` + Until RestoreUntilConfig `json:"until"` + Description *string `json:"description,omitempty"` + ReplayLogUntil *RestoreUntilConfig `json:"replayLogUntil,omitempty"` +} + +type RestoreUntilConfig struct { + Timestamp *string `json:"timestamp,omitempty"` + Scn *string `json:"scn,omitempty"` + Unlimited bool `json:"unlimited,omitempty"` } type ResourcePoolSpec struct { From 1da79f52cd30365c329c9cf979663032ca4608a4 Mon Sep 17 00:00:00 2001 From: yuyi Date: Thu, 21 Sep 2023 17:41:01 +0800 Subject: [PATCH 05/19] fix: goimports -ed lint error --- api/v1alpha1/obtenant_types.go | 11 +++++------ api/v1alpha1/obtenantoperation_types.go | 3 ++- pkg/oceanbase/test/restore_test.go | 5 +++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/api/v1alpha1/obtenant_types.go b/api/v1alpha1/obtenant_types.go index 717625af1..6584d0865 100644 --- a/api/v1alpha1/obtenant_types.go +++ b/api/v1alpha1/obtenant_types.go @@ -17,11 +17,11 @@ limitations under the License. package v1alpha1 import ( - "github.com/oceanbase/ob-operator/api/constants" - "github.com/oceanbase/ob-operator/pkg/oceanbase/model" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/oceanbase/ob-operator/api/constants" ) // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! @@ -112,10 +112,9 @@ type OBTenantStatus struct { Source *TenantSourceStatus `json:"source,omitempty"` } -// +kubebuilder:object:generate=false type TenantSourceStatus struct { - Tenant *string `json:"tenant,omitempty"` - Restore *model.RestoreHistory `json:"restore,omitempty"` + Tenant *string `json:"tenant,omitempty"` + Restore *OBTenantRestoreStatus `json:"restore,omitempty"` } func (in *OBTenantStatus) DeepCopyInto(out *OBTenantStatus) { @@ -149,7 +148,7 @@ func (in *TenantSourceStatus) DeepCopyInto(out *TenantSourceStatus) { } if in.Restore != nil { in, out := &in.Restore, &out.Restore - *out = new(model.RestoreHistory) + *out = new(OBTenantRestoreStatus) **out = **in } } diff --git a/api/v1alpha1/obtenantoperation_types.go b/api/v1alpha1/obtenantoperation_types.go index 3b9c68027..02d7c4444 100644 --- a/api/v1alpha1/obtenantoperation_types.go +++ b/api/v1alpha1/obtenantoperation_types.go @@ -17,9 +17,10 @@ limitations under the License. package v1alpha1 import ( - "github.com/oceanbase/ob-operator/api/constants" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/oceanbase/ob-operator/api/constants" ) // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! diff --git a/pkg/oceanbase/test/restore_test.go b/pkg/oceanbase/test/restore_test.go index b3ba1d153..2da23302c 100644 --- a/pkg/oceanbase/test/restore_test.go +++ b/pkg/oceanbase/test/restore_test.go @@ -18,12 +18,13 @@ import ( "time" "github.com/go-logr/logr" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/oceanbase/ob-operator/api/constants" "github.com/oceanbase/ob-operator/pkg/oceanbase/connector" "github.com/oceanbase/ob-operator/pkg/oceanbase/model" "github.com/oceanbase/ob-operator/pkg/oceanbase/operation" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" ) var _ = Describe("Test Restore Operation", Serial, Label("restore"), func() { From 9a535d6b5071fb93d1f319591b232c391b6ee6a2 Mon Sep 17 00:00:00 2001 From: yuyi Date: Thu, 21 Sep 2023 18:01:30 +0800 Subject: [PATCH 06/19] fix: golangci lint var-name, modify Id to ID --- pkg/oceanbase/model/backup.go | 40 +++++++++++++++++----------------- pkg/oceanbase/model/restore.go | 8 +++---- 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/pkg/oceanbase/model/backup.go b/pkg/oceanbase/model/backup.go index 26df8659d..f3d8c1b68 100644 --- a/pkg/oceanbase/model/backup.go +++ b/pkg/oceanbase/model/backup.go @@ -14,7 +14,7 @@ package model // OBArchiveDest matches view DBA_OB_ARCHIVE_DEST type OBArchiveDest struct { - TenantId int64 `json:"tenant_id" db:"tenant_id"` + TenantID int64 `json:"tenant_id" db:"tenant_id"` DestNo int64 `json:"dest_no" db:"dest_no"` Name string `json:"name" db:"name"` Value string `json:"value" db:"value"` @@ -27,9 +27,9 @@ type OBBackupParameter struct { // OBArchiveLogSummary matches view DBA_OB_ARCHIVELOG_SUMMARY type OBArchiveLogSummary struct { - TenantId int64 `json:"tenant_id" db:"tenant_id"` - DestId int64 `json:"dest_id" db:"dest_id"` - RoundId int64 `json:"round_id" db:"round_id"` + TenantID int64 `json:"tenant_id" db:"tenant_id"` + DestID int64 `json:"dest_id" db:"dest_id"` + RoundID int64 `json:"round_id" db:"round_id"` DestNo int64 `json:"dest_no" db:"dest_no"` Status string `json:"status" db:"status"` StartScn int64 `json:"start_scn" db:"start_scn"` @@ -37,8 +37,8 @@ type OBArchiveLogSummary struct { CheckpointScn int64 `json:"checkpoint_scn" db:"checkpoint_scn"` CheckpointScnDisplay string `json:"checkpoint_scn_display" db:"checkpoint_scn_display"` Compatible string `json:"compatible" db:"compatible"` - BasePieceId int64 `json:"base_piece_id" db:"base_piece_id"` - UsedPieceId int64 `json:"used_piece_id" db:"used_piece_id"` + BasePieceID int64 `json:"base_piece_id" db:"base_piece_id"` + UsedPieceID int64 `json:"used_piece_id" db:"used_piece_id"` PieceSwitchInterval string `json:"piece_switch_interval" db:"piece_switch_interval"` InputBytes int64 `json:"input_bytes" db:"input_bytes"` InputBytesDisplay string `json:"input_bytes_display" db:"input_bytes_display"` @@ -57,9 +57,9 @@ type OBArchiveLogSummary struct { type OBArchiveLogJob OBArchiveLogSummary type JobCommon struct { - TenantId int64 `json:"tenant_id" db:"tenant_id"` - JobId int64 `json:"job_id" db:"job_id"` - ExecutorTenantId int64 `json:"executor_tenant_id" db:"executor_tenant_id"` + TenantID int64 `json:"tenant_id" db:"tenant_id"` + JobID int64 `json:"job_id" db:"job_id"` + ExecutorTenantID int64 `json:"executor_tenant_id" db:"executor_tenant_id"` JobLevel string `json:"job_level" db:"job_level"` StartTimestamp string `json:"start_timestamp" db:"start_timestamp"` EndTimestamp *string `json:"end_timestamp,omitempty" db:"end_timestamp"` @@ -72,7 +72,7 @@ type JobCommon struct { type OBBackupJob struct { JobCommon `json:",inline" db:",inline"` - BackupSetId int64 `json:"backup_set_id" db:"backup_set_id"` + BackupSetID int64 `json:"backup_set_id" db:"backup_set_id"` PlusArchiveLog string `json:"plus_archivelog" db:"plus_archivelog"` BackupType string `json:"backup_type" db:"backup_type"` EncryptionMode string `json:"encryption_mode" db:"encryption_mode"` @@ -83,7 +83,7 @@ type OBBackupJobHistory OBBackupJob // OBBackupCleanPolicy matches view DBA_OB_BACKUP_DELETE_POLICY type OBBackupCleanPolicy struct { - TenantId int64 `json:"tenant_id" db:"tenant_id"` + TenantID int64 `json:"tenant_id" db:"tenant_id"` PolicyName string `json:"policy_name" db:"policy_name"` RecoveryWindow string `json:"recovery_window" db:"recovery_window"` } @@ -100,16 +100,16 @@ type OBBackupCleanJob struct { // OBBackupTask belonging to OBBackupJob, matches DBA_OB_BACKUP_TASKS type OBBackupTask struct { - TenantId int64 `json:"tenant_id" db:"tenant_id"` - JobId int64 `json:"job_id" db:"job_id"` - BackupSetId int64 `json:"backup_set_id" db:"backup_set_id"` + TenantID int64 `json:"tenant_id" db:"tenant_id"` + JobID int64 `json:"job_id" db:"job_id"` + BackupSetID int64 `json:"backup_set_id" db:"backup_set_id"` StartTimestamp string `json:"start_timestamp" db:"start_timestamp"` EndTimestamp *string `json:"end_timestamp,omitempty" db:"end_timestamp"` Status string `json:"status" db:"status"` Result string `json:"result" db:"result"` Comment string `json:"comment" db:"comment"` - TaskId int64 `json:"task_id" db:"task_id"` + TaskID int64 `json:"task_id" db:"task_id"` Incarnation int64 `json:"incarnation" db:"incarnation"` StartScn int64 `json:"start_scn" db:"start_scn"` EndScn int64 `json:"end_scn" db:"end_scn"` @@ -125,16 +125,16 @@ type OBBackupTask struct { MacroBlockCount int64 `json:"macro_block_count" db:"macro_block_count"` FinishMacroBlockCount int64 `json:"finish_macro_block_count" db:"finish_macro_block_count"` FileCount int64 `json:"file_count" db:"file_count"` - MetaTurnId int64 `json:"meta_turn_id" db:"meta_turn_id"` - DataTurnId int64 `json:"data_turn_id" db:"data_turn_id"` + MetaTurnID int64 `json:"meta_turn_id" db:"meta_turn_id"` + DataTurnID int64 `json:"data_turn_id" db:"data_turn_id"` Path string `json:"path" db:"path"` } // OBArchiveLogPieceFile matches DBA_OB_ARCHIVELOG_PIECE_FILES type OBArchiveLogPieceFile struct { - DestId int64 `json:"dest_id" db:"dest_id"` - RoundId int64 `json:"round_id" db:"round_id"` - PieceId int64 `json:"piece_id" db:"piece_id"` + DestID int64 `json:"dest_id" db:"dest_id"` + RoundID int64 `json:"round_id" db:"round_id"` + PieceID int64 `json:"piece_id" db:"piece_id"` Incarnation int64 `json:"incarnation" db:"incarnation"` DestNo int64 `json:"dest_no" db:"dest_no"` Status string `json:"status" db:"status"` diff --git a/pkg/oceanbase/model/restore.go b/pkg/oceanbase/model/restore.go index a9c9b3155..754198303 100644 --- a/pkg/oceanbase/model/restore.go +++ b/pkg/oceanbase/model/restore.go @@ -14,12 +14,12 @@ package model // RestoreProgress is the progress of restore job, matches view CDB_OB_RESTORE_PROGRESS type RestoreProgress struct { - TenantId int64 `json:"tenant_id" db:"tenant_id"` - JobId int64 `json:"job_id" db:"job_id"` + TenantID int64 `json:"tenant_id" db:"tenant_id"` + JobID int64 `json:"job_id" db:"job_id"` RestoreTenantName string `json:"restore_tenant_name" db:"restore_tenant_name"` - RestoreTenantId int64 `json:"restore_tenant_id" db:"restore_tenant_id"` + RestoreTenantID int64 `json:"restore_tenant_id" db:"restore_tenant_id"` BackupTenantName string `json:"backup_tenant_name" db:"backup_tenant_name"` - BackupTenantId int64 `json:"backup_tenant_id" db:"backup_tenant_id"` + BackupTenantID int64 `json:"backup_tenant_id" db:"backup_tenant_id"` BackupClusterName string `json:"backup_cluster_name" db:"backup_cluster_name"` BackupDest string `json:"backup_dest" db:"backup_dest"` RestoreOption string `json:"restore_option" db:"restore_option"` From 10644dcf8052d6fc384c1a388dd6a4e907cdccf5 Mon Sep 17 00:00:00 2001 From: yuyi Date: Thu, 21 Sep 2023 19:36:08 +0800 Subject: [PATCH 07/19] chore: fix lint error, finish partial implementation of restore --- .golangci.yaml | 14 +- PROJECT | 4 + api/constants/restore.go | 24 ++ api/v1alpha1/obtenant_types.go | 12 +- api/v1alpha1/obtenant_webhook.go | 91 +++++++ api/v1alpha1/obtenantbackup_types.go | 3 +- api/v1alpha1/obtenantbackuppolicy_types.go | 3 +- api/v1alpha1/obtenantbackuppolicy_webhook.go | 6 +- api/v1alpha1/obtenantoperation_types.go | 5 +- api/v1alpha1/obtenantrestore_types.go | 25 +- api/v1alpha1/webhook_suite_test.go | 3 + api/v1alpha1/zz_generated.deepcopy.go | 32 ++- cmd/main.go | 4 + ...base.oceanbase.com_obtenantoperations.yaml | 14 +- .../oceanbase.oceanbase.com_obtenants.yaml | 236 ++++++++++-------- config/webhook/manifests.yaml | 40 +++ pkg/controller/obcluster_controller_test.go | 5 +- pkg/controller/obcluster_test_helper.go | 4 +- pkg/controller/obclusterbackup_controller.go | 1 + pkg/controller/obclusterrestore_controller.go | 1 + pkg/controller/obtenant_controller.go | 5 +- pkg/controller/obtenantbackup_controller.go | 10 +- .../obtenantoperation_controller.go | 1 + pkg/controller/obtenantrestore_controller.go | 1 + pkg/controller/obunit_controller.go | 1 + pkg/controller/suite_test.go | 3 +- pkg/oceanbase/connector/datasource.go | 24 +- pkg/oceanbase/operation/backup.go | 5 +- pkg/oceanbase/operation/cluster.go | 6 +- pkg/oceanbase/operation/manager.go | 3 +- pkg/oceanbase/operation/parameter.go | 2 +- pkg/oceanbase/operation/restore.go | 5 +- pkg/oceanbase/operation/server.go | 3 +- pkg/oceanbase/operation/tenant.go | 48 ++-- pkg/oceanbase/operation/user.go | 3 +- pkg/oceanbase/operation/zone.go | 3 +- pkg/oceanbase/test/backup_test.go | 11 +- pkg/oceanbase/test/misc_test.go | 3 +- pkg/oceanbase/test/operation_suite_test.go | 4 +- pkg/oceanbase/test/system_test.go | 5 +- pkg/resource/const/condition/condition.go | 6 +- pkg/resource/coordinator.go | 16 +- pkg/resource/obcluster_task.go | 18 +- pkg/resource/obparameter_manager.go | 7 +- pkg/resource/observer_manager.go | 3 +- pkg/resource/observer_task.go | 22 +- pkg/resource/obtenant_manager.go | 27 +- pkg/resource/obtenant_task.go | 16 +- pkg/resource/obtenantbackuppolicy_manager.go | 25 +- pkg/resource/obtenantbackuppolicy_task.go | 36 +-- pkg/resource/obtenantrestore_manager.go | 139 +++++++++++ pkg/resource/obtenantrestore_task.go | 148 +++++++++++ pkg/resource/obzone_manager.go | 9 +- pkg/resource/obzone_task.go | 13 +- pkg/resource/template_manager.go | 84 +++++++ pkg/resource/util.go | 17 +- pkg/task/const/flow/name/task_flow_names.go | 11 + pkg/task/const/task/name/backup.go | 7 - pkg/task/const/task/name/restore.go | 21 ++ pkg/task/const/task/name/task_names.go | 6 +- pkg/task/obtenant_flow.go | 19 ++ pkg/task/task_flow.go | 2 +- pkg/task/task_manager.go | 13 +- pkg/util/codec/json.go | 6 +- 64 files changed, 1003 insertions(+), 341 deletions(-) create mode 100644 api/constants/restore.go create mode 100644 api/v1alpha1/obtenant_webhook.go create mode 100644 pkg/resource/obtenantrestore_manager.go create mode 100644 pkg/resource/obtenantrestore_task.go create mode 100644 pkg/resource/template_manager.go create mode 100644 pkg/task/const/task/name/restore.go diff --git a/.golangci.yaml b/.golangci.yaml index eaa07d81e..8b3099880 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -6,7 +6,7 @@ linters: - gocritic - gofmt - goimports - - gosimple + # - gosimple - govet - ineffassign - misspell @@ -16,7 +16,7 @@ linters: - sqlclosecheck - staticcheck - typecheck - - unused + # - unused issues: exclude-rules: @@ -35,6 +35,16 @@ linters-settings: revive: enable-all-rules: true rules: + - name: struct-tag + disabled: true + - name: var-naming + disabled: true + - name: comment-spacings + disabled: true + - name: exported + disabled: true + - name: unused-receiver + disabled: true - name: file-header disabled: true - name: line-length-limit diff --git a/PROJECT b/PROJECT index f6b0e1855..4a519005a 100644 --- a/PROJECT +++ b/PROJECT @@ -65,6 +65,10 @@ resources: kind: OBTenant path: github.com/oceanbase/ob-operator/api/v1alpha1 version: v1alpha1 + webhooks: + defaulting: true + validation: true + webhookVersion: v1 - api: crdVersion: v1 namespaced: true diff --git a/api/constants/restore.go b/api/constants/restore.go new file mode 100644 index 000000000..8aef68fa2 --- /dev/null +++ b/api/constants/restore.go @@ -0,0 +1,24 @@ +/* +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 constants + +type RestoreJobStatus string + +const ( + RestoreJobStarting RestoreJobStatus = "STARTING" + RestoreJobRunning RestoreJobStatus = "RUNNING" + RestoreJobFailed RestoreJobStatus = "FAILED" + RestoreJobCanceling RestoreJobStatus = "CANCELING" + RestoreJobSuccessful RestoreJobStatus = "SUCCESSFUL" + RestoreJobCanceled RestoreJobStatus = "CANCELED" +) diff --git a/api/v1alpha1/obtenant_types.go b/api/v1alpha1/obtenant_types.go index 6584d0865..7fd24d01e 100644 --- a/api/v1alpha1/obtenant_types.go +++ b/api/v1alpha1/obtenant_types.go @@ -17,7 +17,6 @@ limitations under the License. package v1alpha1 import ( - corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -47,9 +46,14 @@ type OBTenantSpec struct { Pools []ResourcePoolSpec `json:"pools"` //+kubebuilder:default=PRIMARY - TenantRole constants.TenantRole `json:"tenantRole,omitempty"` - Source *TenantSourceSpec `json:"source,omitempty"` - Credentials []corev1.SecretReference `json:"credentials,omitempty"` + TenantRole constants.TenantRole `json:"tenantRole,omitempty"` + Source *TenantSourceSpec `json:"source,omitempty"` + Credentials TenantCredentials `json:"credentials,omitempty"` +} + +type TenantCredentials struct { + Root string `json:"root,omitempty"` + StandbyRO string `json:"standbyRo,omitempty"` } // Source for restoring or creating standby diff --git a/api/v1alpha1/obtenant_webhook.go b/api/v1alpha1/obtenant_webhook.go new file mode 100644 index 000000000..54baf92ab --- /dev/null +++ b/api/v1alpha1/obtenant_webhook.go @@ -0,0 +1,91 @@ +/* +Copyright 2023. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +import ( + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + ctrl "sigs.k8s.io/controller-runtime" + logf "sigs.k8s.io/controller-runtime/pkg/log" + "sigs.k8s.io/controller-runtime/pkg/webhook" + "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/oceanbase/ob-operator/api/constants" +) + +// log is for logging in this package. +var _ = logf.Log.WithName("obtenant-resource") + +func (r *OBTenant) SetupWebhookWithManager(mgr ctrl.Manager) error { + return ctrl.NewWebhookManagedBy(mgr). + For(r). + Complete() +} + +//+kubebuilder:webhook:path=/mutate-oceanbase-oceanbase-com-v1alpha1-obtenant,mutating=true,failurePolicy=fail,sideEffects=None,groups=oceanbase.oceanbase.com,resources=obtenants,verbs=create;update,versions=v1alpha1,name=mobtenant.kb.io,admissionReviewVersions=v1 + +var _ webhook.Defaulter = &OBTenant{} + +// Default implements webhook.Defaulter so a webhook will be registered for the type +func (r *OBTenant) Default() { + if r.Spec.TenantRole == "" { + r.Spec.TenantRole = constants.TenantRolePrimary + } +} + +// TODO(user): change verbs to "verbs=create;update;delete" if you want to enable deletion validation. +//+kubebuilder:webhook:path=/validate-oceanbase-oceanbase-com-v1alpha1-obtenant,mutating=false,failurePolicy=fail,sideEffects=None,groups=oceanbase.oceanbase.com,resources=obtenants,verbs=create;update,versions=v1alpha1,name=vobtenant.kb.io,admissionReviewVersions=v1 + +var _ webhook.Validator = &OBTenant{} + +// ValidateCreate implements webhook.Validator so a webhook will be registered for the type +func (r *OBTenant) ValidateCreate() (admission.Warnings, error) { + // TODO(user): fill in your validation logic upon object creation. + return nil, r.validateMutation() +} + +// ValidateUpdate implements webhook.Validator so a webhook will be registered for the type +func (r *OBTenant) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { + _ = old + // TODO(user): fill in your validation logic upon object update. + return nil, r.validateMutation() +} + +func (r *OBTenant) validateMutation() error { + var allErrs field.ErrorList + + // 1. Standby tenant must have a source + if r.Spec.TenantRole == constants.TenantRoleStandby { + if r.Spec.Source == nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("source"), r.Spec.Source, "Standby tenant must have non-nil source field")) + } else if r.Spec.Source.Restore == nil && r.Spec.Source.Tenant == nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("tenantRole"), r.Spec.TenantRole, "Standby must have a source option, but both restore and tenantRef are nil now")) + } + } + + if len(allErrs) == 0 { + return nil + } + return apierrors.NewInvalid(GroupVersion.WithKind("OBTenant").GroupKind(), r.Name, allErrs) +} + +// ValidateDelete implements webhook.Validator so a webhook will be registered for the type +func (r *OBTenant) ValidateDelete() (admission.Warnings, error) { + // TODO(user): fill in your validation logic upon object deletion. + return nil, nil +} diff --git a/api/v1alpha1/obtenantbackup_types.go b/api/v1alpha1/obtenantbackup_types.go index e25c7d05a..18a4a0deb 100644 --- a/api/v1alpha1/obtenantbackup_types.go +++ b/api/v1alpha1/obtenantbackup_types.go @@ -15,9 +15,10 @@ See the Mulan PSL v2 for more details. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + constants "github.com/oceanbase/ob-operator/api/constants" "github.com/oceanbase/ob-operator/pkg/oceanbase/model" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! diff --git a/api/v1alpha1/obtenantbackuppolicy_types.go b/api/v1alpha1/obtenantbackuppolicy_types.go index 726e86d6e..97644e353 100644 --- a/api/v1alpha1/obtenantbackuppolicy_types.go +++ b/api/v1alpha1/obtenantbackuppolicy_types.go @@ -15,9 +15,10 @@ See the Mulan PSL v2 for more details. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + constants "github.com/oceanbase/ob-operator/api/constants" "github.com/oceanbase/ob-operator/pkg/oceanbase/model" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! diff --git a/api/v1alpha1/obtenantbackuppolicy_webhook.go b/api/v1alpha1/obtenantbackuppolicy_webhook.go index 106be7d47..f7eddcdea 100644 --- a/api/v1alpha1/obtenantbackuppolicy_webhook.go +++ b/api/v1alpha1/obtenantbackuppolicy_webhook.go @@ -20,7 +20,6 @@ import ( "errors" "regexp" - "github.com/oceanbase/ob-operator/api/constants" "github.com/robfig/cron/v3" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" @@ -29,10 +28,12 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/webhook" "sigs.k8s.io/controller-runtime/pkg/webhook/admission" + + "github.com/oceanbase/ob-operator/api/constants" ) // log is for logging in this package. -var obtenantbackuppolicylog = logf.Log.WithName("obtenantbackuppolicy-resource") +var _ = logf.Log.WithName("obtenantbackuppolicy-resource") func (r *OBTenantBackupPolicy) SetupWebhookWithManager(mgr ctrl.Manager) error { return ctrl.NewWebhookManagedBy(mgr). @@ -76,6 +77,7 @@ func (r *OBTenantBackupPolicy) ValidateCreate() (admission.Warnings, error) { // ValidateUpdate implements webhook.Validator so a webhook will be registered for the type func (r *OBTenantBackupPolicy) ValidateUpdate(old runtime.Object) (admission.Warnings, error) { + _ = old return nil, r.validateBackupPolicy() } diff --git a/api/v1alpha1/obtenantoperation_types.go b/api/v1alpha1/obtenantoperation_types.go index 02d7c4444..ad1d17fea 100644 --- a/api/v1alpha1/obtenantoperation_types.go +++ b/api/v1alpha1/obtenantoperation_types.go @@ -17,7 +17,6 @@ limitations under the License. package v1alpha1 import ( - corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/oceanbase/ob-operator/api/constants" @@ -47,8 +46,8 @@ type OBTenantOpFailoverSpec struct { } type OBTenantOpChangePwdSpec struct { - Tenant string `json:"tenant"` - SecretRef corev1.SecretReference `json:"secretRef"` + Tenant string `json:"tenant"` + SecretRef string `json:"secretRef"` } // OBTenantOperationStatus defines the observed state of OBTenantOperation diff --git a/api/v1alpha1/obtenantrestore_types.go b/api/v1alpha1/obtenantrestore_types.go index df3db43c6..fbdb1e771 100644 --- a/api/v1alpha1/obtenantrestore_types.go +++ b/api/v1alpha1/obtenantrestore_types.go @@ -13,9 +13,10 @@ See the Mulan PSL v2 for more details. package v1alpha1 import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "github.com/oceanbase/ob-operator/api/constants" "github.com/oceanbase/ob-operator/pkg/oceanbase/model" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) // EDIT THIS FILE! THIS IS SCAFFOLDING FOR YOU TO OWN! @@ -37,9 +38,9 @@ type OBTenantRestoreSpec struct { type OBTenantRestoreStatus struct { // INSERT ADDITIONAL STATUS FIELD - define observed state of cluster // Important: Run "make" to regenerate code after modifying this file - Status RestoreJobStatus `json:"status"` - RestoreProgress *model.RestoreHistory `json:"restoreProgress,omitempty"` - OperationContext *OperationContext `json:"operationContext,omitempty"` + Status constants.RestoreJobStatus `json:"status"` + RestoreProgress *model.RestoreHistory `json:"restoreProgress,omitempty"` + OperationContext *OperationContext `json:"operationContext,omitempty"` } func (in *OBTenantRestoreStatus) DeepCopyInto(out *OBTenantRestoreStatus) { @@ -81,19 +82,3 @@ type OBTenantRestoreList struct { func init() { SchemeBuilder.Register(&OBTenantRestore{}, &OBTenantRestoreList{}) } - -type RestoreJobType string - -const ( - RestoreJobRestore RestoreJobType = "RESTORE" - RestoreJobActivate RestoreJobType = "ACTIVATE" -) - -type RestoreJobStatus string - -const ( - RestoreJobRunning RestoreJobStatus = "RUNNING" - RestoreJobFailed RestoreJobStatus = "FAILED" - RestoreJobSuccessful RestoreJobStatus = "SUCCESSFUL" - RestoreJobCanceled RestoreJobStatus = "CANCELED" -) diff --git a/api/v1alpha1/webhook_suite_test.go b/api/v1alpha1/webhook_suite_test.go index 0d2c5c5fe..662a15a8a 100644 --- a/api/v1alpha1/webhook_suite_test.go +++ b/api/v1alpha1/webhook_suite_test.go @@ -102,6 +102,9 @@ var _ = BeforeSuite(func() { err = (&OBTenantBackupPolicy{}).SetupWebhookWithManager(mgr) Expect(err).NotTo(HaveOccurred()) + err = (&OBTenant{}).SetupWebhookWithManager(mgr) + Expect(err).NotTo(HaveOccurred()) + //+kubebuilder:scaffold:webhook go func() { diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index 835ece71b..d09d7886d 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -934,7 +934,6 @@ func (in *OBTenantList) DeepCopyObject() runtime.Object { // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *OBTenantOpChangePwdSpec) DeepCopyInto(out *OBTenantOpChangePwdSpec) { *out = *in - out.SecretRef = in.SecretRef } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OBTenantOpChangePwdSpec. @@ -1176,11 +1175,7 @@ func (in *OBTenantSpec) DeepCopyInto(out *OBTenantSpec) { *out = new(TenantSourceSpec) (*in).DeepCopyInto(*out) } - if in.Credentials != nil { - in, out := &in.Credentials, &out.Credentials - *out = make([]v1.SecretReference, len(*in)) - copy(*out, *in) - } + out.Credentials = in.Credentials } // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new OBTenantSpec. @@ -1704,6 +1699,21 @@ func (in *StorageSpec) DeepCopy() *StorageSpec { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TenantCredentials) DeepCopyInto(out *TenantCredentials) { + *out = *in +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantCredentials. +func (in *TenantCredentials) DeepCopy() *TenantCredentials { + if in == nil { + return nil + } + out := new(TenantCredentials) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TenantRecordInfo) DeepCopyInto(out *TenantRecordInfo) { *out = *in @@ -1744,6 +1754,16 @@ func (in *TenantSourceSpec) DeepCopy() *TenantSourceSpec { return out } +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TenantSourceStatus. +func (in *TenantSourceStatus) DeepCopy() *TenantSourceStatus { + if in == nil { + return nil + } + out := new(TenantSourceStatus) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *UnitConfig) DeepCopyInto(out *UnitConfig) { *out = *in diff --git a/cmd/main.go b/cmd/main.go index b72d40015..69bfebc48 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -206,6 +206,10 @@ func main() { setupLog.Error(err, "unable to create controller", "controller", "OBTenantOperation") os.Exit(1) } + if err = (&v1alpha1.OBTenant{}).SetupWebhookWithManager(mgr); err != nil { + setupLog.Error(err, "unable to create webhook", "webhook", "OBTenant") + os.Exit(1) + } //+kubebuilder:scaffold:builder if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil { diff --git a/config/crd/bases/oceanbase.oceanbase.com_obtenantoperations.yaml b/config/crd/bases/oceanbase.oceanbase.com_obtenantoperations.yaml index af6becb86..0caf377cf 100644 --- a/config/crd/bases/oceanbase.oceanbase.com_obtenantoperations.yaml +++ b/config/crd/bases/oceanbase.oceanbase.com_obtenantoperations.yaml @@ -37,19 +37,7 @@ spec: changePwd: properties: secretRef: - description: SecretReference represents a Secret Reference. It - has enough information to retrieve secret in any namespace - properties: - name: - description: name is unique within a namespace to reference - a secret resource. - type: string - namespace: - description: namespace defines the space within which the - secret name must be unique. - type: string - type: object - x-kubernetes-map-type: atomic + type: string tenant: type: string required: diff --git a/config/crd/bases/oceanbase.oceanbase.com_obtenants.yaml b/config/crd/bases/oceanbase.oceanbase.com_obtenants.yaml index 9087bc50e..d5e0446a4 100644 --- a/config/crd/bases/oceanbase.oceanbase.com_obtenants.yaml +++ b/config/crd/bases/oceanbase.oceanbase.com_obtenants.yaml @@ -69,21 +69,12 @@ spec: default: '%' type: string credentials: - items: - description: SecretReference represents a Secret Reference. It has - enough information to retrieve secret in any namespace - properties: - name: - description: name is unique within a namespace to reference - a secret resource. - type: string - namespace: - description: namespace defines the space within which the secret - name must be unique. - type: string - type: object - x-kubernetes-map-type: atomic - type: array + properties: + root: + type: string + standbyRo: + type: string + type: object forceDelete: default: false type: boolean @@ -348,94 +339,143 @@ spec: source: properties: restore: - description: RestoreHistory is the history of restore job, matches - view CDB_OB_RESTORE_HISTORY + description: OBTenantRestoreStatus defines the observed state + of OBTenantRestore properties: - backup_cluster_name: - type: string - backup_cluster_version: - type: string - backup_dest: - type: string - backup_piece_list: - type: string - backup_set_list: - type: string - backup_tenant_id: - format: int64 - type: integer - backup_tenant_name: - type: string - description: - type: string - finish_bytes: - format: int64 - type: integer - finish_bytes_display: - type: string - finish_ls_count: - format: int64 - type: integer - finish_tablet_count: - format: int64 - type: integer - finish_timestamp: - type: string - job_id: - format: int64 - type: integer - ls_count: - format: int64 - type: integer - restore_option: - type: string - restore_scn: - format: int64 - type: integer - restore_scn_display: - type: string - restore_tenant_id: - format: int64 - type: integer - restore_tenant_name: - type: string - start_timestamp: - type: string + operationContext: + properties: + failureRule: + properties: + failureStatus: + type: string + failureStrategy: + type: string + required: + - failureStatus + - failureStrategy + type: object + idx: + type: integer + name: + type: string + targetStatus: + type: string + task: + type: string + taskId: + type: string + taskStatus: + type: string + tasks: + items: + type: string + type: array + required: + - idx + - name + - targetStatus + - task + - taskId + - taskStatus + - tasks + type: object + restoreProgress: + description: RestoreHistory is the history of restore job, + matches view CDB_OB_RESTORE_HISTORY + properties: + backup_cluster_name: + type: string + backup_cluster_version: + type: string + backup_dest: + type: string + backup_piece_list: + type: string + backup_set_list: + type: string + backup_tenant_id: + format: int64 + type: integer + backup_tenant_name: + type: string + description: + type: string + finish_bytes: + format: int64 + type: integer + finish_bytes_display: + type: string + finish_ls_count: + format: int64 + type: integer + finish_tablet_count: + format: int64 + type: integer + finish_timestamp: + type: string + job_id: + format: int64 + type: integer + ls_count: + format: int64 + type: integer + restore_option: + type: string + restore_scn: + format: int64 + type: integer + restore_scn_display: + type: string + restore_tenant_id: + format: int64 + type: integer + restore_tenant_name: + type: string + start_timestamp: + type: string + status: + type: string + tablet_count: + format: int64 + type: integer + tenant_id: + format: int64 + type: integer + total_bytes: + format: int64 + type: integer + total_bytes_display: + type: string + required: + - backup_cluster_name + - backup_cluster_version + - backup_dest + - backup_piece_list + - backup_set_list + - backup_tenant_id + - backup_tenant_name + - finish_ls_count + - finish_tablet_count + - finish_timestamp + - job_id + - ls_count + - restore_option + - restore_scn + - restore_scn_display + - restore_tenant_id + - restore_tenant_name + - start_timestamp + - status + - tablet_count + - tenant_id + type: object status: - type: string - tablet_count: - format: int64 - type: integer - tenant_id: - format: int64 - type: integer - total_bytes: - format: int64 - type: integer - total_bytes_display: + description: 'INSERT ADDITIONAL STATUS FIELD - define observed + state of cluster Important: Run "make" to regenerate code + after modifying this file' type: string required: - - backup_cluster_name - - backup_cluster_version - - backup_dest - - backup_piece_list - - backup_set_list - - backup_tenant_id - - backup_tenant_name - - finish_ls_count - - finish_tablet_count - - finish_timestamp - - job_id - - ls_count - - restore_option - - restore_scn - - restore_scn_display - - restore_tenant_id - - restore_tenant_name - - start_timestamp - status - - tablet_count - - tenant_id type: object tenant: type: string diff --git a/config/webhook/manifests.yaml b/config/webhook/manifests.yaml index ab19cedc4..097614999 100644 --- a/config/webhook/manifests.yaml +++ b/config/webhook/manifests.yaml @@ -4,6 +4,26 @@ kind: MutatingWebhookConfiguration metadata: name: mutating-webhook-configuration webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /mutate-oceanbase-oceanbase-com-v1alpha1-obtenant + failurePolicy: Fail + name: mobtenant.kb.io + rules: + - apiGroups: + - oceanbase.oceanbase.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - obtenants + sideEffects: None - admissionReviewVersions: - v1 clientConfig: @@ -30,6 +50,26 @@ kind: ValidatingWebhookConfiguration metadata: name: validating-webhook-configuration webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: webhook-service + namespace: system + path: /validate-oceanbase-oceanbase-com-v1alpha1-obtenant + failurePolicy: Fail + name: vobtenant.kb.io + rules: + - apiGroups: + - oceanbase.oceanbase.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - obtenants + sideEffects: None - admissionReviewVersions: - v1 clientConfig: diff --git a/pkg/controller/obcluster_controller_test.go b/pkg/controller/obcluster_controller_test.go index 31f84816d..1b32f6944 100644 --- a/pkg/controller/obcluster_controller_test.go +++ b/pkg/controller/obcluster_controller_test.go @@ -17,13 +17,14 @@ import ( "fmt" "time" - "github.com/oceanbase/ob-operator/api/v1alpha1" - clusterstatus "github.com/oceanbase/ob-operator/pkg/const/status/obcluster" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" kubeerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" logf "sigs.k8s.io/controller-runtime/pkg/log" + + "github.com/oceanbase/ob-operator/api/v1alpha1" + clusterstatus "github.com/oceanbase/ob-operator/pkg/const/status/obcluster" ) const ( diff --git a/pkg/controller/obcluster_test_helper.go b/pkg/controller/obcluster_test_helper.go index cd14869b6..f7214ee1d 100644 --- a/pkg/controller/obcluster_test_helper.go +++ b/pkg/controller/obcluster_test_helper.go @@ -15,9 +15,10 @@ package controller import ( "fmt" - "github.com/oceanbase/ob-operator/api/v1alpha1" "k8s.io/apimachinery/pkg/api/resource" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + "github.com/oceanbase/ob-operator/api/v1alpha1" ) const ( @@ -64,7 +65,6 @@ func newOBCluster(name string, zoneNum int, serverNum int) *v1alpha1.OBCluster { Replica: serverNum, } topology[i] = zoneTopology - } userSecrets := &v1alpha1.OBUserSecrets{ diff --git a/pkg/controller/obclusterbackup_controller.go b/pkg/controller/obclusterbackup_controller.go index a3591a610..39a653d5e 100644 --- a/pkg/controller/obclusterbackup_controller.go +++ b/pkg/controller/obclusterbackup_controller.go @@ -52,6 +52,7 @@ type OBClusterBackupReconciler struct { // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile func (r *OBClusterBackupReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = req _ = log.FromContext(ctx) // TODO(user): your logic here diff --git a/pkg/controller/obclusterrestore_controller.go b/pkg/controller/obclusterrestore_controller.go index ca42d910e..432df30d5 100644 --- a/pkg/controller/obclusterrestore_controller.go +++ b/pkg/controller/obclusterrestore_controller.go @@ -49,6 +49,7 @@ type OBClusterRestoreReconciler struct { // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile func (r *OBClusterRestoreReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = req _ = log.FromContext(ctx) // TODO(user): your logic here diff --git a/pkg/controller/obtenant_controller.go b/pkg/controller/obtenant_controller.go index c761ca4f5..c0cd6d93d 100644 --- a/pkg/controller/obtenant_controller.go +++ b/pkg/controller/obtenant_controller.go @@ -19,8 +19,6 @@ package controller import ( "context" - "github.com/oceanbase/ob-operator/pkg/resource" - "github.com/oceanbase/ob-operator/pkg/util/codec" kubeerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/tools/record" @@ -28,6 +26,9 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" + "github.com/oceanbase/ob-operator/pkg/resource" + "github.com/oceanbase/ob-operator/pkg/util/codec" + v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" ) diff --git a/pkg/controller/obtenantbackup_controller.go b/pkg/controller/obtenantbackup_controller.go index 7e2391dc2..843fc659c 100644 --- a/pkg/controller/obtenantbackup_controller.go +++ b/pkg/controller/obtenantbackup_controller.go @@ -21,7 +21,6 @@ import ( "fmt" "time" - "github.com/oceanbase/ob-operator/pkg/resource" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" @@ -29,11 +28,14 @@ import ( "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" + "github.com/oceanbase/ob-operator/pkg/resource" + + "github.com/pkg/errors" + "github.com/oceanbase/ob-operator/api/constants" v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" "github.com/oceanbase/ob-operator/pkg/oceanbase/model" "github.com/oceanbase/ob-operator/pkg/oceanbase/operation" - "github.com/pkg/errors" ) // OBTenantBackupReconciler reconciles a OBTenantBackup object @@ -141,12 +143,12 @@ func (r *OBTenantBackupReconciler) maintainRunningBackupJob(ctx context.Context, } // archive log and data clean job should not be here } else { - modelJob, err := con.GetBackupJobWithId(job.Status.BackupJob.JobId) + modelJob, err := con.GetBackupJobWithId(job.Status.BackupJob.JobID) if err != nil { return err } if modelJob == nil { - return errors.New(fmt.Sprintf("backup job with id %d not found", job.Status.BackupJob.JobId)) + return fmt.Errorf("backup job with id %d not found", job.Status.BackupJob.JobID) } job.Status.BackupJob = modelJob targetJob = modelJob diff --git a/pkg/controller/obtenantoperation_controller.go b/pkg/controller/obtenantoperation_controller.go index 7c782abbd..e3f1b6660 100644 --- a/pkg/controller/obtenantoperation_controller.go +++ b/pkg/controller/obtenantoperation_controller.go @@ -46,6 +46,7 @@ type OBTenantOperationReconciler struct { // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.15.0/pkg/reconcile func (r *OBTenantOperationReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = req _ = log.FromContext(ctx) // TODO(user): your logic here diff --git a/pkg/controller/obtenantrestore_controller.go b/pkg/controller/obtenantrestore_controller.go index b240a8d3f..1077742a9 100644 --- a/pkg/controller/obtenantrestore_controller.go +++ b/pkg/controller/obtenantrestore_controller.go @@ -47,6 +47,7 @@ type OBTenantRestoreReconciler struct { // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile func (r *OBTenantRestoreReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = req _ = log.FromContext(ctx) // TODO(user): your logic here diff --git a/pkg/controller/obunit_controller.go b/pkg/controller/obunit_controller.go index 88524697f..200395a05 100644 --- a/pkg/controller/obunit_controller.go +++ b/pkg/controller/obunit_controller.go @@ -49,6 +49,7 @@ type OBUnitReconciler struct { // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile func (r *OBUnitReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { + _ = req _ = log.FromContext(ctx) // TODO(user): your logic here diff --git a/pkg/controller/suite_test.go b/pkg/controller/suite_test.go index eaa211ffd..4acff34dd 100644 --- a/pkg/controller/suite_test.go +++ b/pkg/controller/suite_test.go @@ -24,7 +24,6 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" - "github.com/oceanbase/ob-operator/pkg/controller/config" "k8s.io/client-go/kubernetes/scheme" "k8s.io/client-go/rest" ctrl "sigs.k8s.io/controller-runtime" @@ -33,6 +32,8 @@ import ( logf "sigs.k8s.io/controller-runtime/pkg/log" "sigs.k8s.io/controller-runtime/pkg/log/zap" + "github.com/oceanbase/ob-operator/pkg/controller/config" + v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" //+kubebuilder:scaffold:imports ) diff --git a/pkg/oceanbase/connector/datasource.go b/pkg/oceanbase/connector/datasource.go index d2dbb9245..42c5eac16 100644 --- a/pkg/oceanbase/connector/datasource.go +++ b/pkg/oceanbase/connector/datasource.go @@ -65,25 +65,25 @@ func (ds *OceanBaseDataSource) GetDatabase() string { return ds.Database } -func (s *OceanBaseDataSource) DataSourceName() string { +func (ds *OceanBaseDataSource) DataSourceName() string { passwordPart := "" tenantPart := "" - if s.Password != "" { - passwordPart = fmt.Sprintf(":%s", s.Password) + if ds.Password != "" { + passwordPart = fmt.Sprintf(":%s", ds.Password) } - if !(s.Tenant == "" || s.Tenant == oceanbaseconst.SysTenant) { + if !(ds.Tenant == "" || ds.Tenant == oceanbaseconst.SysTenant) { // fix: bootstrap stage will fail if concat this part after v4.2.0 - tenantPart = fmt.Sprintf("@%s", s.Tenant) + tenantPart = fmt.Sprintf("@%s", ds.Tenant) } - if s.Database != "" { - return fmt.Sprintf("%s%s%s@tcp(%s:%d)/%s?multiStatements=true&interpolateParams=true", s.User, tenantPart, passwordPart, s.Address, s.Port, s.Database) + if ds.Database != "" { + return fmt.Sprintf("%s%s%s@tcp(%s:%d)/%s?multiStatements=true&interpolateParams=true", ds.User, tenantPart, passwordPart, ds.Address, ds.Port, ds.Database) } - return fmt.Sprintf("%s%s%s@tcp(%s:%d)/", s.User, tenantPart, passwordPart, s.Address, s.Port) + return fmt.Sprintf("%s%s%s@tcp(%s:%d)/", ds.User, tenantPart, passwordPart, ds.Address, ds.Port) } -func (s *OceanBaseDataSource) ID() string { +func (ds *OceanBaseDataSource) ID() string { h := md5.New() - key := fmt.Sprintf("%s@%s@%s:%d/%s", s.User, s.Tenant, s.Address, s.Port, s.Database) + key := fmt.Sprintf("%s@%s@%s:%d/%s", ds.User, ds.Tenant, ds.Address, ds.Port, ds.Database) _, err := h.Write([]byte(key)) if err != nil { return key @@ -91,6 +91,6 @@ func (s *OceanBaseDataSource) ID() string { return hex.EncodeToString(h.Sum(nil)) } -func (s *OceanBaseDataSource) String() string { - return fmt.Sprintf("address: %s, port: %d, user: %s, tenant: %s, database: %s", s.Address, s.Port, s.User, s.Tenant, s.Database) +func (ds *OceanBaseDataSource) String() string { + return fmt.Sprintf("address: %s, port: %d, user: %s, tenant: %s, database: %s", ds.Address, ds.Port, ds.User, ds.Tenant, ds.Database) } diff --git a/pkg/oceanbase/operation/backup.go b/pkg/oceanbase/operation/backup.go index 4707dfa20..9b618936a 100644 --- a/pkg/oceanbase/operation/backup.go +++ b/pkg/oceanbase/operation/backup.go @@ -13,10 +13,11 @@ See the Mulan PSL v2 for more details. package operation import ( + "github.com/pkg/errors" + "github.com/oceanbase/ob-operator/api/constants" "github.com/oceanbase/ob-operator/pkg/oceanbase/const/sql" "github.com/oceanbase/ob-operator/pkg/oceanbase/model" - "github.com/pkg/errors" ) func (m *OceanbaseOperationManager) SetLogArchiveDestForTenant(uri string) error { @@ -204,7 +205,7 @@ func (m *OceanbaseOperationManager) GetLatestBackupJobOfTypeAndPath(jobType cons return m.getLatestBackupJob([]string{sql.QueryLatestBackupJobOfTypeAndPath, sql.QueryLatestBackupJobHistoryOfTypeAndPath}, jobType, path) } -func (m *OceanbaseOperationManager) getLatestBackupJob(statements []string, params ...interface{}) (*model.OBBackupJob, error) { +func (m *OceanbaseOperationManager) getLatestBackupJob(statements []string, params ...any) (*model.OBBackupJob, error) { if len(statements) != 2 { return nil, errors.New("unexpected # of statements, require exactly 2 statement") } diff --git a/pkg/oceanbase/operation/cluster.go b/pkg/oceanbase/operation/cluster.go index d7a792982..378cf11ce 100644 --- a/pkg/oceanbase/operation/cluster.go +++ b/pkg/oceanbase/operation/cluster.go @@ -16,10 +16,11 @@ import ( "fmt" "strings" + "github.com/pkg/errors" + "github.com/oceanbase/ob-operator/pkg/oceanbase/const/config" "github.com/oceanbase/ob-operator/pkg/oceanbase/const/sql" "github.com/oceanbase/ob-operator/pkg/oceanbase/model" - "github.com/pkg/errors" ) // TODO @@ -54,9 +55,8 @@ func (m *OceanbaseOperationManager) GetVersion() (*model.OBVersion, error) { } if version != nil && version.Compare(v) != 0 { return nil, errors.Errorf("Version %s of observer %s:%d is not consistent with other observer", observer.BuildVersion, observer.Ip, observer.Port) - } else { - version = v } + version = v } return version, nil } diff --git a/pkg/oceanbase/operation/manager.go b/pkg/oceanbase/operation/manager.go index 42ec5c871..a1bb78458 100644 --- a/pkg/oceanbase/operation/manager.go +++ b/pkg/oceanbase/operation/manager.go @@ -18,10 +18,11 @@ import ( "time" "github.com/go-logr/logr" + "github.com/pkg/errors" + "github.com/oceanbase/ob-operator/pkg/database" "github.com/oceanbase/ob-operator/pkg/oceanbase/connector" "github.com/oceanbase/ob-operator/pkg/oceanbase/const/config" - "github.com/pkg/errors" ) type OceanbaseOperationManager struct { diff --git a/pkg/oceanbase/operation/parameter.go b/pkg/oceanbase/operation/parameter.go index d0f6b5f36..272731575 100644 --- a/pkg/oceanbase/operation/parameter.go +++ b/pkg/oceanbase/operation/parameter.go @@ -32,7 +32,7 @@ func (m *OceanbaseOperationManager) GetParameter(name string, scope *param.Scope return parameters, err } -func (m *OceanbaseOperationManager) SetParameter(name string, value interface{}, scope *param.Scope) error { +func (m *OceanbaseOperationManager) SetParameter(name string, value any, scope *param.Scope) error { if scope == nil { setParameterSql := fmt.Sprintf(sql.SetParameter, name) return m.ExecWithDefaultTimeout(setParameterSql, value) diff --git a/pkg/oceanbase/operation/restore.go b/pkg/oceanbase/operation/restore.go index 323f5f44b..de5a63907 100644 --- a/pkg/oceanbase/operation/restore.go +++ b/pkg/oceanbase/operation/restore.go @@ -16,9 +16,10 @@ import ( "fmt" "time" + "github.com/pkg/errors" + "github.com/oceanbase/ob-operator/pkg/oceanbase/const/sql" "github.com/oceanbase/ob-operator/pkg/oceanbase/model" - "github.com/pkg/errors" ) func (m *OceanbaseOperationManager) SetRestorePassword(password string) error { @@ -30,7 +31,7 @@ func (m *OceanbaseOperationManager) SetRestorePassword(password string) error { return nil } -func (m *OceanbaseOperationManager) StartRestoreWithLimit(tenantName, uri, limitKey, restoreOption string, limitValue interface{}) error { +func (m *OceanbaseOperationManager) StartRestoreWithLimit(tenantName, uri, limitKey, restoreOption string, limitValue any) error { sqlStatement := fmt.Sprintf(sql.StartRestoreWithLimit, tenantName, limitKey) err := m.ExecWithDefaultTimeout(sqlStatement, uri, limitValue, restoreOption) if err != nil { diff --git a/pkg/oceanbase/operation/server.go b/pkg/oceanbase/operation/server.go index ac1ef73a5..cf0feb74b 100644 --- a/pkg/oceanbase/operation/server.go +++ b/pkg/oceanbase/operation/server.go @@ -15,9 +15,10 @@ package operation import ( "fmt" + "github.com/pkg/errors" + "github.com/oceanbase/ob-operator/pkg/oceanbase/const/sql" "github.com/oceanbase/ob-operator/pkg/oceanbase/model" - "github.com/pkg/errors" ) func (m *OceanbaseOperationManager) GetServer(s *model.ServerInfo) (*model.OBServer, error) { diff --git a/pkg/oceanbase/operation/tenant.go b/pkg/oceanbase/operation/tenant.go index 5d5f0bb66..a97359a38 100644 --- a/pkg/oceanbase/operation/tenant.go +++ b/pkg/oceanbase/operation/tenant.go @@ -17,10 +17,11 @@ import ( "strings" "time" + "github.com/pkg/errors" + "github.com/oceanbase/ob-operator/pkg/oceanbase/const/config" "github.com/oceanbase/ob-operator/pkg/oceanbase/const/sql" "github.com/oceanbase/ob-operator/pkg/oceanbase/model" - "github.com/pkg/errors" ) // Incompatible with model.Tenant struct which contains only essential fields for tenant management @@ -205,7 +206,6 @@ func (m *OceanbaseOperationManager) CheckRsJobExistByTenantID(tenantName int) (b // ------------ add ------------ func (m *OceanbaseOperationManager) AddTenant(tenantSQLParam model.TenantSQLParam) error { - preparedSQL, params := preparedSQLForAddTenant(tenantSQLParam) err := m.ExecWithTimeout(config.TenantSqlTimeout, preparedSQL, params...) if err != nil { @@ -296,9 +296,9 @@ func (m *OceanbaseOperationManager) SetTenant(tenantSQLParam model.TenantSQLPara // ---------- replacer sql and collect params ---------- -func preparedSQLForAddUnitConfigV4(unitConfigV4 *model.UnitConfigV4SQLParam) (string, []interface{}) { +func preparedSQLForAddUnitConfigV4(unitConfigV4 *model.UnitConfigV4SQLParam) (string, []any) { var optionSql string - params := make([]interface{}, 0) + params := make([]any, 0) params = append(params, unitConfigV4.MaxCPU, unitConfigV4.MemorySize) if unitConfigV4.MinCPU != 0 { optionSql = fmt.Sprint(optionSql, ", min_cpu ?") @@ -323,16 +323,16 @@ func preparedSQLForAddUnitConfigV4(unitConfigV4 *model.UnitConfigV4SQLParam) (st return fmt.Sprintf(sql.AddUnitConfigV4, unitConfigV4.UnitConfigName, optionSql), params } -func preparedSQLForAddPool(poolSQLParam model.PoolSQLParam) (string, []interface{}) { - params := make([]interface{}, 0) +func preparedSQLForAddPool(poolSQLParam model.PoolSQLParam) (string, []any) { + params := make([]any, 0) params = append(params, poolSQLParam.UnitName, poolSQLParam.UnitNum, poolSQLParam.ZoneList) return fmt.Sprintf(sql.AddPool, poolSQLParam.PoolName), params } -func preparedSQLForAddTenant(tenantSQLParam model.TenantSQLParam) (string, []interface{}) { +func preparedSQLForAddTenant(tenantSQLParam model.TenantSQLParam) (string, []any) { var option string var variableList string - params := make([]interface{}, 0) + params := make([]any, 0) params = append(params, tenantSQLParam.Charset, tenantSQLParam.PrimaryZone) symbols := make([]string, 0) @@ -352,9 +352,9 @@ func preparedSQLForAddTenant(tenantSQLParam model.TenantSQLParam) (string, []int return fmt.Sprintf(sql.AddTenant, tenantSQLParam.TenantName, strings.Join(symbols, ", "), option, variableList), params } -func preparedSQLForSetTenant(tenantSQLParam model.TenantSQLParam) (string, []interface{}) { +func preparedSQLForSetTenant(tenantSQLParam model.TenantSQLParam) (string, []any) { var alterItemStr string - params := make([]interface{}, 0) + params := make([]any, 0) alterItemList := make([]string, 0) if tenantSQLParam.PrimaryZone != "" { alterItemList = append(alterItemList, "PRIMARY_ZONE=?") @@ -380,9 +380,9 @@ func preparedSQLForSetTenant(tenantSQLParam model.TenantSQLParam) (string, []int return fmt.Sprintf(sql.SetTenant, tenantSQLParam.TenantName, alterItemStr), params } -func preparedSQLForSetUnitConfigV4(unitConfigV4 *model.UnitConfigV4SQLParam) (string, []interface{}) { +func preparedSQLForSetUnitConfigV4(unitConfigV4 *model.UnitConfigV4SQLParam) (string, []any) { var alterItemStr string - params := make([]interface{}, 0) + params := make([]any, 0) alterItemList := make([]string, 0) if unitConfigV4.MaxCPU != 0 { alterItemList = append(alterItemList, "max_cpu=?") @@ -442,33 +442,31 @@ func (m *OceanbaseOperationManager) AlterPool(poolParam *model.PoolParam) error return nil } -func (m *OceanbaseOperationManager) preparedSQLForSetTenantVariable(tenantName, variableList string) (string, []interface{}) { - params := make([]interface{}, 0) +func (m *OceanbaseOperationManager) preparedSQLForSetTenantVariable(tenantName, variableList string) (string, []any) { + params := make([]any, 0) return fmt.Sprintf(sql.SetTenantVariable, tenantName, variableList), params } -func (m *OceanbaseOperationManager) preparedSQLForSetTenantUnitNum(tenantNum string, unitNum int) (string, []interface{}) { - params := make([]interface{}, 0) +func (m *OceanbaseOperationManager) preparedSQLForSetTenantUnitNum(tenantNum string, unitNum int) (string, []any) { + params := make([]any, 0) params = append(params, unitNum) return fmt.Sprintf(sql.SetTenantUnitNum, tenantNum), params - } -func (m *OceanbaseOperationManager) preparedSQLForDeleteTenant(tenantName string, force bool) (string, []interface{}) { - params := make([]interface{}, 0) +func (m *OceanbaseOperationManager) preparedSQLForDeleteTenant(tenantName string, force bool) (string, []any) { + params := make([]any, 0) if force { return fmt.Sprintf(sql.DeleteTenant, tenantName, "force"), params - } else { - return fmt.Sprintf(sql.DeleteTenant, tenantName, ""), params } + return fmt.Sprintf(sql.DeleteTenant, tenantName, ""), params } -func (m *OceanbaseOperationManager) preparedSQLForDeletePool(poolName string) (string, []interface{}) { - params := make([]interface{}, 0) +func (m *OceanbaseOperationManager) preparedSQLForDeletePool(poolName string) (string, []any) { + params := make([]any, 0) return fmt.Sprintf(sql.DeletePool, poolName), params } -func (m *OceanbaseOperationManager) preparedSQLForDeleteUnitConfig(unitConfigName string) (string, []interface{}) { - params := make([]interface{}, 0) +func (m *OceanbaseOperationManager) preparedSQLForDeleteUnitConfig(unitConfigName string) (string, []any) { + params := make([]any, 0) return fmt.Sprintf(sql.DeleteUnitConfig, unitConfigName), params } diff --git a/pkg/oceanbase/operation/user.go b/pkg/oceanbase/operation/user.go index 08fb45062..24df6d786 100644 --- a/pkg/oceanbase/operation/user.go +++ b/pkg/oceanbase/operation/user.go @@ -15,8 +15,9 @@ package operation import ( "fmt" - "github.com/oceanbase/ob-operator/pkg/oceanbase/const/sql" "github.com/pkg/errors" + + "github.com/oceanbase/ob-operator/pkg/oceanbase/const/sql" ) func (m *OceanbaseOperationManager) CreateUser(userName string) error { diff --git a/pkg/oceanbase/operation/zone.go b/pkg/oceanbase/operation/zone.go index 260057ca9..fcaafedd9 100644 --- a/pkg/oceanbase/operation/zone.go +++ b/pkg/oceanbase/operation/zone.go @@ -13,10 +13,11 @@ See the Mulan PSL v2 for more details. package operation import ( + "github.com/pkg/errors" + "github.com/oceanbase/ob-operator/pkg/oceanbase/const/sql" zonestatus "github.com/oceanbase/ob-operator/pkg/oceanbase/const/status/zone" "github.com/oceanbase/ob-operator/pkg/oceanbase/model" - "github.com/pkg/errors" ) func (m *OceanbaseOperationManager) AddZone(zoneName string) error { diff --git a/pkg/oceanbase/test/backup_test.go b/pkg/oceanbase/test/backup_test.go index 7238fccf9..b416affc7 100644 --- a/pkg/oceanbase/test/backup_test.go +++ b/pkg/oceanbase/test/backup_test.go @@ -17,11 +17,12 @@ import ( "time" "github.com/go-logr/logr" + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + "github.com/oceanbase/ob-operator/api/constants" "github.com/oceanbase/ob-operator/pkg/oceanbase/connector" "github.com/oceanbase/ob-operator/pkg/oceanbase/operation" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" ) var _ = Describe("Test Backup Operation", Label("backup"), func() { @@ -135,10 +136,10 @@ var _ = Describe("Test Backup Operation", Label("backup"), func() { // Query tasks at once will get empty result time.Sleep(time.Second) - By(fmt.Sprintf("Query BackupJob with ID %d", job.JobId)) - tasks, err := con.ListBackupTaskWithJobId(job.JobId) + By(fmt.Sprintf("Query BackupJob with ID %d", job.JobID)) + tasks, err := con.ListBackupTaskWithJobId(job.JobID) Expect(err).To(BeNil()) - printSlice(tasks, fmt.Sprintf("BackupTasks of Job %d", job.JobId)) + printSlice(tasks, fmt.Sprintf("BackupTasks of Job %d", job.JobID)) }) It("Get Log Archive dest info", func() { diff --git a/pkg/oceanbase/test/misc_test.go b/pkg/oceanbase/test/misc_test.go index 8c0e44dec..31231a635 100644 --- a/pkg/oceanbase/test/misc_test.go +++ b/pkg/oceanbase/test/misc_test.go @@ -18,12 +18,13 @@ import ( "strings" "time" - "github.com/oceanbase/ob-operator/api/constants" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/robfig/cron/v3" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" + + "github.com/oceanbase/ob-operator/api/constants" ) var _ = Describe("Test Miscellaneous Operation", func() { diff --git a/pkg/oceanbase/test/operation_suite_test.go b/pkg/oceanbase/test/operation_suite_test.go index 313b88c0c..6a5da4ce9 100644 --- a/pkg/oceanbase/test/operation_suite_test.go +++ b/pkg/oceanbase/test/operation_suite_test.go @@ -48,7 +48,7 @@ func TestOperation(t *testing.T) { RunSpecs(t, "Operation Suite") } -func printSlice[T any](s []T, extraMsg ...interface{}) { +func printSlice[T any](s []T, extraMsg ...any) { for _, msg := range extraMsg { GinkgoWriter.Println("[TEST INFO]", msg) } @@ -57,7 +57,7 @@ func printSlice[T any](s []T, extraMsg ...interface{}) { } } -func printObject[T any](o T, extraMsg ...interface{}) { +func printObject[T any](o T, extraMsg ...any) { for _, msg := range extraMsg { GinkgoWriter.Println("[TEST INFO]", msg) } diff --git a/pkg/oceanbase/test/system_test.go b/pkg/oceanbase/test/system_test.go index 6eff8a81e..d4f0b976e 100644 --- a/pkg/oceanbase/test/system_test.go +++ b/pkg/oceanbase/test/system_test.go @@ -16,10 +16,11 @@ import ( "fmt" "github.com/go-logr/logr" - "github.com/oceanbase/ob-operator/pkg/oceanbase/connector" - "github.com/oceanbase/ob-operator/pkg/oceanbase/operation" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + + "github.com/oceanbase/ob-operator/pkg/oceanbase/connector" + "github.com/oceanbase/ob-operator/pkg/oceanbase/operation" ) var _ = Describe("Test System Operation", func() { diff --git a/pkg/resource/const/condition/condition.go b/pkg/resource/const/condition/condition.go index ca26be1de..eda6bcf04 100644 --- a/pkg/resource/const/condition/condition.go +++ b/pkg/resource/const/condition/condition.go @@ -16,7 +16,7 @@ type Condition string const ( New Condition = "new" - Modified = "modified" - Operating = "operating" - Satisfied = "satisfied" + Modified Condition = "modified" + Operating Condition = "operating" + Satisfied Condition = "satisfied" ) diff --git a/pkg/resource/coordinator.go b/pkg/resource/coordinator.go index b81a23887..964f88338 100644 --- a/pkg/resource/coordinator.go +++ b/pkg/resource/coordinator.go @@ -105,16 +105,14 @@ func (c *Coordinator) executeTaskFlow(f *task.TaskFlow) { c.Logger.Error(err, "Get task result got error", "task id", f.OperationContext.TaskId) c.Manager.PrintErrEvent(err) f.OperationContext.TaskStatus = taskstatus.Failed - } else { - if taskResult != nil { - c.Logger.Info("Task finished", "task id", f.OperationContext.TaskId, "task result", taskResult) - f.OperationContext.TaskStatus = taskResult.Status - if taskResult.Error != nil { - c.Manager.PrintErrEvent(taskResult.Error) - } - } else { - // Didn't get task result, task is still running" + } else if taskResult != nil { + c.Logger.Info("Task finished", "task id", f.OperationContext.TaskId, "task result", taskResult) + f.OperationContext.TaskStatus = taskResult.Status + if taskResult.Error != nil { + c.Manager.PrintErrEvent(taskResult.Error) } + + // Didn't get task result, task is still running" } case taskstatus.Successful: // clean operation context and set status to target status diff --git a/pkg/resource/obcluster_task.go b/pkg/resource/obcluster_task.go index 4402d4923..be09dab9b 100644 --- a/pkg/resource/obcluster_task.go +++ b/pkg/resource/obcluster_task.go @@ -19,9 +19,6 @@ import ( "time" "github.com/google/uuid" - obagentconst "github.com/oceanbase/ob-operator/pkg/const/obagent" - oceanbaseconst "github.com/oceanbase/ob-operator/pkg/const/oceanbase" - zonestatus "github.com/oceanbase/ob-operator/pkg/const/status/obzone" "github.com/pkg/errors" batchv1 "k8s.io/api/batch/v1" corev1 "k8s.io/api/core/v1" @@ -29,6 +26,10 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" + obagentconst "github.com/oceanbase/ob-operator/pkg/const/obagent" + oceanbaseconst "github.com/oceanbase/ob-operator/pkg/const/oceanbase" + zonestatus "github.com/oceanbase/ob-operator/pkg/const/status/obzone" + v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" "github.com/oceanbase/ob-operator/pkg/oceanbase/model" "github.com/oceanbase/ob-operator/pkg/oceanbase/operation" @@ -53,7 +54,7 @@ func (m *OBClusterManager) generateZoneName(zone string) string { } func (m *OBClusterManager) generateParameterName(name string) string { - return fmt.Sprintf("%s-%d-%s", m.OBCluster.Spec.ClusterName, m.OBCluster.Spec.ClusterId, strings.Replace(name, "_", "-", -1)) + return fmt.Sprintf("%s-%d-%s", m.OBCluster.Spec.ClusterName, m.OBCluster.Spec.ClusterId, strings.ReplaceAll(name, "_", "-")) } func (m *OBClusterManager) WaitOBZoneTopologyMatch() error { @@ -92,9 +93,8 @@ func (m *OBClusterManager) WaitOBZoneDeleted() error { } if waitSuccess { return nil - } else { - return errors.Errorf("OBCluster %s zone still not deleted when timeout", m.OBCluster.Name) } + return errors.Errorf("OBCluster %s zone still not deleted when timeout", m.OBCluster.Name) } func (m *OBClusterManager) generateWaitOBZoneStatusFunc(status string, timeoutSeconds int) func() error { @@ -248,7 +248,7 @@ func (m *OBClusterManager) Bootstrap() error { return errors.Wrap(err, "list obzones") } m.Logger.Info("successfully get obzone list", "obzone list", obzoneList) - if len(obzoneList.Items) <= 0 { + if len(obzoneList.Items) == 0 { return errors.Wrap(err, "no obzone belongs to this cluster") } var manager *operation.OceanbaseOperationManager @@ -466,7 +466,7 @@ func (m *OBClusterManager) ValidateUpgradeInfo() error { if err != nil { return errors.Wrapf(err, "Failed to get operation manager of obcluster %s", m.OBCluster.Name) } - //version, err := oceanbaseOperationManager.GetVersion() + // version, err := oceanbaseOperationManager.GetVersion() version, err := oceanbaseOperationManager.GetVersion() if err != nil { return errors.Wrapf(err, "Failed to get version of obcluster %s", m.OBCluster.Name) @@ -812,6 +812,6 @@ func (m *OBClusterManager) RestoreEssentialParameters() error { return errors.Wrapf(err, "Failed to set parameter %s to %s:%d", parameter.Name, parameter.SvrIp, parameter.SvrPort) } } - m.Client.Delete(m.Ctx, contextSecret) + _ = m.Client.Delete(m.Ctx, contextSecret) return nil } diff --git a/pkg/resource/obparameter_manager.go b/pkg/resource/obparameter_manager.go index 1edb14386..b61465221 100644 --- a/pkg/resource/obparameter_manager.go +++ b/pkg/resource/obparameter_manager.go @@ -17,9 +17,6 @@ import ( "fmt" "github.com/go-logr/logr" - "github.com/oceanbase/ob-operator/pkg/oceanbase/operation" - taskstatus "github.com/oceanbase/ob-operator/pkg/task/const/task/status" - "github.com/oceanbase/ob-operator/pkg/task/strategy" "github.com/pkg/errors" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/types" @@ -27,6 +24,10 @@ import ( "k8s.io/client-go/util/retry" "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/oceanbase/ob-operator/pkg/oceanbase/operation" + taskstatus "github.com/oceanbase/ob-operator/pkg/task/const/task/status" + "github.com/oceanbase/ob-operator/pkg/task/strategy" + v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" oceanbaseconst "github.com/oceanbase/ob-operator/pkg/const/oceanbase" clusterstatus "github.com/oceanbase/ob-operator/pkg/const/status/obcluster" diff --git a/pkg/resource/observer_manager.go b/pkg/resource/observer_manager.go index 7c385d7d4..e3c22dd49 100644 --- a/pkg/resource/observer_manager.go +++ b/pkg/resource/observer_manager.go @@ -19,7 +19,6 @@ import ( taskstatus "github.com/oceanbase/ob-operator/pkg/task/const/task/status" "github.com/oceanbase/ob-operator/pkg/task/strategy" - oceanbaseconst "github.com/oceanbase/ob-operator/pkg/const/oceanbase" corev1 "k8s.io/api/core/v1" kubeerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" @@ -28,6 +27,8 @@ import ( apipod "k8s.io/kubernetes/pkg/api/v1/pod" "sigs.k8s.io/controller-runtime/pkg/client" + oceanbaseconst "github.com/oceanbase/ob-operator/pkg/const/oceanbase" + "github.com/go-logr/logr" "github.com/pkg/errors" diff --git a/pkg/resource/observer_task.go b/pkg/resource/observer_task.go index d09b78590..222cf3c15 100644 --- a/pkg/resource/observer_task.go +++ b/pkg/resource/observer_task.go @@ -16,6 +16,11 @@ import ( "fmt" "time" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/intstr" + obagentconst "github.com/oceanbase/ob-operator/pkg/const/obagent" oceanbaseconst "github.com/oceanbase/ob-operator/pkg/const/oceanbase" podconst "github.com/oceanbase/ob-operator/pkg/const/pod" @@ -24,10 +29,6 @@ import ( observerstatus "github.com/oceanbase/ob-operator/pkg/oceanbase/const/status/server" "github.com/oceanbase/ob-operator/pkg/oceanbase/model" "github.com/oceanbase/ob-operator/pkg/oceanbase/operation" - "github.com/pkg/errors" - corev1 "k8s.io/api/core/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/util/intstr" "github.com/oceanbase/ob-operator/api/v1alpha1" ) @@ -141,6 +142,7 @@ func (m *OBServerManager) CreateOBPod() error { } func (m *OBServerManager) generatePVCSpec(name string, storageSpec *v1alpha1.StorageSpec) corev1.PersistentVolumeClaimSpec { + _ = name pvcSpec := &corev1.PersistentVolumeClaimSpec{} requestsResources := corev1.ResourceList{} requestsResources["storage"] = storageSpec.Size @@ -511,6 +513,9 @@ func (m *OBServerManager) DeleteOBServerInCluster() error { Port: oceanbaseconst.RpcPort, } observer, err := operationManager.GetServer(observerInfo) + if err != nil { + return err + } if observer != nil && observer.Status != "deleting" { if observer.Status == "deleting" { m.Logger.Info("observer is deleting", "observer", observerInfo.Ip) @@ -532,8 +537,7 @@ func (m *OBServerManager) AnnotateOBServerPod() error { if err != nil { return errors.Wrapf(err, "Failed to get pod of observer %s", m.OBServer.Name) } - switch m.OBServer.Status.CNI { - case oceanbaseconst.CNICalico: + if m.OBServer.Status.CNI == oceanbaseconst.CNICalico { m.Logger.Info("Update pod annotation, cni is calico") observerPod.Annotations[oceanbaseconst.AnnotationCalicoIpAddrs] = fmt.Sprintf("[\"%s\"]", m.OBServer.Status.PodIp) } @@ -616,9 +620,8 @@ func (m *OBServerManager) WaitOBServerActiveInCluster() error { if !active { m.Logger.Info("Wait observer active timeout") return errors.Errorf("Wait observer %s active timeout", observerInfo.Ip) - } else { - m.Logger.Info("observer active", "observer", observerInfo) } + m.Logger.Info("observer active", "observer", observerInfo) return nil } @@ -647,8 +650,7 @@ func (m *OBServerManager) WaitOBServerDeletedInCluster() error { if !deleted { m.Logger.Info("Wait observer deleted timeout") return errors.Errorf("Wait observer %s deleted timeout", observerInfo.Ip) - } else { - m.Logger.Info("observer deleted", "observer", observerInfo) } + m.Logger.Info("observer deleted", "observer", observerInfo) return nil } diff --git a/pkg/resource/obtenant_manager.go b/pkg/resource/obtenant_manager.go index b85d50218..c7af0a8c7 100644 --- a/pkg/resource/obtenant_manager.go +++ b/pkg/resource/obtenant_manager.go @@ -20,6 +20,15 @@ import ( "strings" "github.com/go-logr/logr" + "github.com/pkg/errors" + corev1 "k8s.io/api/core/v1" + kubeerrors "k8s.io/apimachinery/pkg/api/errors" + kuberesource "k8s.io/apimachinery/pkg/api/resource" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/tools/record" + "k8s.io/client-go/util/retry" + "sigs.k8s.io/controller-runtime/pkg/client" + "github.com/oceanbase/ob-operator/api/constants" "github.com/oceanbase/ob-operator/api/v1alpha1" "github.com/oceanbase/ob-operator/pkg/const/status/tenantstatus" @@ -31,14 +40,6 @@ import ( taskname "github.com/oceanbase/ob-operator/pkg/task/const/task/name" taskstatus "github.com/oceanbase/ob-operator/pkg/task/const/task/status" "github.com/oceanbase/ob-operator/pkg/task/strategy" - "github.com/pkg/errors" - corev1 "k8s.io/api/core/v1" - kubeerrors "k8s.io/apimachinery/pkg/api/errors" - kuberesource "k8s.io/apimachinery/pkg/api/resource" - "k8s.io/apimachinery/pkg/types" - "k8s.io/client-go/tools/record" - "k8s.io/client-go/util/retry" - "sigs.k8s.io/controller-runtime/pkg/client" ) type OBTenantManager struct { @@ -124,7 +125,7 @@ func (m *OBTenantManager) retryUpdateStatus() error { return client.IgnoreNotFound(err) } return retry.RetryOnConflict(retry.DefaultRetry, func() error { - obtenant.Status = *m.OBTenant.Status.DeepCopy() + m.OBTenant.Status.DeepCopyInto(&obtenant.Status) return m.Client.Status().Update(m.Ctx, obtenant) }) } @@ -398,8 +399,7 @@ func (m *OBTenantManager) hasModifiedUnitConfig() (bool, error) { m.Logger.Error(err, "maintain tenant failed, check and apply unitConfigV4", "tenantName", tenantName) return false, err } - switch string(version[0]) { - case tenant.Version4: + if string(version[0]) == tenant.Version4 { return m.hasModifiedUnitConfigV4(), nil } return false, errors.New("no match version for check and set unit config") @@ -492,7 +492,7 @@ func (m *OBTenantManager) buildTenantStatus() (*v1alpha1.OBTenantStatus, error) return nil, err } if !tenantExist { - return nil, errors.New(fmt.Sprintf("Tenant not exist, Tenant name: %s", tenantName)) + return nil, fmt.Errorf("Tenant not exist, Tenant name: %s", tenantName) } obtenant, err := m.getTenantByName(tenantName) if err != nil { @@ -543,7 +543,6 @@ func (m *OBTenantManager) buildTenantStatus() (*v1alpha1.OBTenantStatus, error) } func (m *OBTenantManager) buildPoolStatusList(obTenant *model.Tenant) ([]v1alpha1.ResourcePoolStatus, error) { - var poolStatusList []v1alpha1.ResourcePoolStatus var locality string var primaryZone string @@ -618,7 +617,7 @@ func (m *OBTenantManager) generateStatusZone(tenantID int64) ([]string, error) { zoneMap[unit.Zone] = unit.Zone } } - for k, _ := range zoneMap { + for k := range zoneMap { zoneList = append(zoneList, k) } return zoneList, nil diff --git a/pkg/resource/obtenant_task.go b/pkg/resource/obtenant_task.go index 91325ef89..f3bbd5d11 100644 --- a/pkg/resource/obtenant_task.go +++ b/pkg/resource/obtenant_task.go @@ -20,11 +20,12 @@ import ( "strings" "time" + "github.com/pkg/errors" + "github.com/oceanbase/ob-operator/api/v1alpha1" "github.com/oceanbase/ob-operator/pkg/oceanbase/const/config" "github.com/oceanbase/ob-operator/pkg/oceanbase/const/status/tenant" "github.com/oceanbase/ob-operator/pkg/oceanbase/model" - "github.com/pkg/errors" ) // ---------- task entry point ---------- @@ -162,8 +163,7 @@ func (m *OBTenantManager) MaintainUnitConfigTask() error { m.Logger.Error(err, "maintain tenant failed, check and apply unitConfigV4", "tenantName", tenantName) return err } - switch string(version[0]) { - case tenant.Version4: + if string(version[0]) == tenant.Version4 { return m.CheckAndApplyUnitConfigV4() } return errors.New("no match version for check and set unit config") @@ -464,8 +464,8 @@ func (m *OBTenantManager) tenantAddPool(poolAdd v1alpha1.ResourcePoolSpec) error Type: poolAdd.Type, UnitNumber: m.OBTenant.Spec.UnitNumber, } - - resourcePoolStatusList := append(m.OBTenant.Status.Pools, poolStatusAdd) + resourcePoolStatusList := m.OBTenant.Status.Pools + resourcePoolStatusList = append(resourcePoolStatusList, poolStatusAdd) statusLocalityMap := m.generateStatusLocalityMap(resourcePoolStatusList) localityList := m.generateLocalityList(statusLocalityMap) poolList := m.generateStatusPoolList(resourcePoolStatusList) @@ -501,7 +501,6 @@ func (m *OBTenantManager) tenantAddPool(poolAdd v1alpha1.ResourcePoolSpec) error } func (m *OBTenantManager) TenantDeletePool(poolDelete v1alpha1.ResourcePoolStatus) error { - tenantName := m.OBTenant.Spec.TenantName poolName := m.generatePoolName(poolDelete.ZoneList) unitName := m.generateUnitName(poolDelete.ZoneList) @@ -815,9 +814,8 @@ func (m *OBTenantManager) generateLocality(zones []v1alpha1.ResourcePoolSpec) st func (m *OBTenantManager) generateWhiteListInVariableForm(whiteList string) string { if whiteList == "" { return fmt.Sprintf("%s = '%s'", tenant.OBTcpInvitedNodes, tenant.DefaultOBTcpInvitedNodes) - } else { - return fmt.Sprintf("%s = '%s'", tenant.OBTcpInvitedNodes, whiteList) } + return fmt.Sprintf("%s = '%s'", tenant.OBTcpInvitedNodes, whiteList) } func (m *OBTenantManager) generateStatusTypeMapFromLocalityStr(locality string) map[string]v1alpha1.LocalityType { @@ -846,7 +844,7 @@ func (m *OBTenantManager) generateStatusPriorityMap(primaryZone string) map[stri for _, zone := range zoneList { priorityMap[zone] = priority } - priority -= 1 + priority-- } return priorityMap } diff --git a/pkg/resource/obtenantbackuppolicy_manager.go b/pkg/resource/obtenantbackuppolicy_manager.go index b608d3394..33312f06f 100644 --- a/pkg/resource/obtenantbackuppolicy_manager.go +++ b/pkg/resource/obtenantbackuppolicy_manager.go @@ -17,14 +17,6 @@ import ( "time" "github.com/go-logr/logr" - "github.com/oceanbase/ob-operator/api/constants" - v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" - "github.com/oceanbase/ob-operator/pkg/oceanbase/operation" - "github.com/oceanbase/ob-operator/pkg/task" - flow "github.com/oceanbase/ob-operator/pkg/task/const/flow/name" - taskname "github.com/oceanbase/ob-operator/pkg/task/const/task/name" - taskstatus "github.com/oceanbase/ob-operator/pkg/task/const/task/status" - "github.com/oceanbase/ob-operator/pkg/task/strategy" "github.com/pkg/errors" "github.com/robfig/cron/v3" corev1 "k8s.io/api/core/v1" @@ -33,6 +25,15 @@ import ( "k8s.io/client-go/util/retry" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" + + "github.com/oceanbase/ob-operator/api/constants" + v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" + "github.com/oceanbase/ob-operator/pkg/oceanbase/operation" + "github.com/oceanbase/ob-operator/pkg/task" + flow "github.com/oceanbase/ob-operator/pkg/task/const/flow/name" + taskname "github.com/oceanbase/ob-operator/pkg/task/const/task/name" + taskstatus "github.com/oceanbase/ob-operator/pkg/task/const/task/status" + "github.com/oceanbase/ob-operator/pkg/task/strategy" ) type ObTenantBackupPolicyManager struct { @@ -99,7 +100,7 @@ func (m *ObTenantBackupPolicyManager) UpdateStatus() error { m.BackupPolicy.Status.Status = constants.BackupPolicyStatusResuming } else if m.BackupPolicy.Status.Status == constants.BackupPolicyStatusRunning { if m.BackupPolicy.Status.TenantInfo == nil { - tenant, err := m.getTenantInfo() + tenant, err := m.getTenantRecord() if err != nil { return err } @@ -131,6 +132,9 @@ func (m *ObTenantBackupPolicyManager) UpdateStatus() error { var lastFullBackupFinishedAt time.Time if latestFull.EndTimestamp != nil { lastFullBackupFinishedAt, err = time.ParseInLocation(time.DateTime, *latestFull.EndTimestamp, time.Local) + if err != nil { + return err + } } nextFull := fullCron.Next(lastFullBackupFinishedAt) m.BackupPolicy.Status.NextFull = nextFull.Format(time.DateTime) @@ -145,6 +149,9 @@ func (m *ObTenantBackupPolicyManager) UpdateStatus() error { var lastIncrBackupFinishedAt time.Time if latestIncr.EndTimestamp != nil { lastIncrBackupFinishedAt, err = time.ParseInLocation(time.DateTime, *latestIncr.EndTimestamp, time.Local) + if err != nil { + return err + } } m.BackupPolicy.Status.NextIncremental = incrCron.Next(lastIncrBackupFinishedAt).Format(time.DateTime) } diff --git a/pkg/resource/obtenantbackuppolicy_task.go b/pkg/resource/obtenantbackuppolicy_task.go index 44d857505..eaf68119c 100644 --- a/pkg/resource/obtenantbackuppolicy_task.go +++ b/pkg/resource/obtenantbackuppolicy_task.go @@ -19,11 +19,6 @@ import ( "strings" "time" - constants "github.com/oceanbase/ob-operator/api/constants" - "github.com/oceanbase/ob-operator/api/v1alpha1" - oceanbaseconst "github.com/oceanbase/ob-operator/pkg/const/oceanbase" - "github.com/oceanbase/ob-operator/pkg/oceanbase/model" - "github.com/oceanbase/ob-operator/pkg/oceanbase/operation" "github.com/pkg/errors" cron "github.com/robfig/cron/v3" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -31,6 +26,12 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/types" "sigs.k8s.io/controller-runtime/pkg/client" + + constants "github.com/oceanbase/ob-operator/api/constants" + "github.com/oceanbase/ob-operator/api/v1alpha1" + oceanbaseconst "github.com/oceanbase/ob-operator/pkg/const/oceanbase" + "github.com/oceanbase/ob-operator/pkg/oceanbase/model" + "github.com/oceanbase/ob-operator/pkg/oceanbase/operation" ) const backupVolumePath = oceanbaseconst.BackupPath @@ -41,7 +42,7 @@ func (m *ObTenantBackupPolicyManager) ConfigureServerForBackup() error { if err != nil { return err } - tenantInfo, err := m.getTenantInfo() + tenantInfo, err := m.getTenantRecord() if err != nil { return err } @@ -147,7 +148,7 @@ func (m *ObTenantBackupPolicyManager) ConfigureServerForBackup() error { func (m *ObTenantBackupPolicyManager) GetTenantInfo() error { // Admission Control - _, err := m.getTenantInfo() + _, err := m.getTenantRecord() if err != nil { return err } @@ -160,7 +161,7 @@ func (m *ObTenantBackupPolicyManager) StartBackup() error { if err != nil { return err } - tenantInfo, err := m.getTenantInfo() + tenantInfo, err := m.getTenantRecord() if err != nil { return err } @@ -287,6 +288,7 @@ func (m *ObTenantBackupPolicyManager) CheckAndSpawnJobs() error { } } else if latestIncr.Status == "INIT" || latestIncr.Status == "DOING" { // do nothing + _ = latestIncr } else { m.Logger.Info("Incremental BackupJob are in status " + latestIncr.Status) } @@ -301,6 +303,7 @@ func (m *ObTenantBackupPolicyManager) CheckAndSpawnJobs() error { } } else if latestFull.Status == "INIT" || latestFull.Status == "DOING" { // do nothing + _ = latestFull } else { m.Logger.Info("BackupJob are in status " + latestFull.Status) } @@ -330,6 +333,9 @@ func (m *ObTenantBackupPolicyManager) CleanOldBackupJobs() error { Selector: fieldSelector, }, client.InNamespace(m.BackupPolicy.Namespace)) + if err != nil { + return err + } if len(jobs.Items) == 0 { return nil } @@ -475,10 +481,8 @@ func (m *ObTenantBackupPolicyManager) getArchiveDestPath() string { dest = "file://" + path.Join(backupVolumePath, m.BackupPolicy.Spec.TenantName, targetDest.Path) } return dest - - } else { - return targetDest.Path } + return targetDest.Path } func (m *ObTenantBackupPolicyManager) getArchiveDestSettingValue() string { @@ -498,12 +502,10 @@ func (m *ObTenantBackupPolicyManager) getBackupDestPath() string { if targetDest.Type == constants.BackupDestTypeNFS || isZero(targetDest.Type) { if targetDest.Path == "" { return "file://" + path.Join(backupVolumePath, m.BackupPolicy.Spec.TenantName, "data_backup") - } else { - return "file://" + path.Join(backupVolumePath, m.BackupPolicy.Spec.TenantName, targetDest.Path) } - } else { - return targetDest.Path + return "file://" + path.Join(backupVolumePath, m.BackupPolicy.Spec.TenantName, targetDest.Path) } + return targetDest.Path } func (m *ObTenantBackupPolicyManager) createBackupJob(jobType constants.BackupJobType) error { @@ -589,8 +591,8 @@ func (m *ObTenantBackupPolicyManager) noRunningJobs(jobType constants.BackupJobT return true, nil } -// getTenantInfo return tenant info from status if exists, otherwise query from database view -func (m *ObTenantBackupPolicyManager) getTenantInfo() (*model.OBTenant, error) { +// getTenantRecord return tenant info from status if exists, otherwise query from database view +func (m *ObTenantBackupPolicyManager) getTenantRecord() (*model.OBTenant, error) { if m.BackupPolicy.Status.TenantInfo != nil { return m.BackupPolicy.Status.TenantInfo, nil } diff --git a/pkg/resource/obtenantrestore_manager.go b/pkg/resource/obtenantrestore_manager.go new file mode 100644 index 000000000..fa66e8202 --- /dev/null +++ b/pkg/resource/obtenantrestore_manager.go @@ -0,0 +1,139 @@ +/* +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 resource + +import ( + "context" + + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/oceanbase/ob-operator/api/constants" + v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" + "github.com/oceanbase/ob-operator/pkg/oceanbase/operation" + "github.com/oceanbase/ob-operator/pkg/task" + flow "github.com/oceanbase/ob-operator/pkg/task/const/flow/name" + taskname "github.com/oceanbase/ob-operator/pkg/task/const/task/name" + "github.com/oceanbase/ob-operator/pkg/task/strategy" +) + +type ObTenantRestoreManager struct { + ResourceManager + + Ctx context.Context + Resource *v1alpha1.OBTenantRestore + Client client.Client + Recorder record.EventRecorder + Logger *logr.Logger + + con *operation.OceanbaseOperationManager +} + +func (m ObTenantRestoreManager) IsNewResource() bool { + return m.Resource.Status.Status == "" +} + +func (m ObTenantRestoreManager) IsDeleting() bool { + return m.Resource.GetDeletionTimestamp() != nil +} + +func (m ObTenantRestoreManager) CheckAndUpdateFinalizers() error { + return nil +} + +func (m ObTenantRestoreManager) InitStatus() { + m.Resource.Status.Status = constants.RestoreJobRunning +} + +func (m ObTenantRestoreManager) SetOperationContext(c *v1alpha1.OperationContext) { + m.Resource.Status.OperationContext = c +} + +func (m ObTenantRestoreManager) ClearTaskInfo() { + m.Resource.Status.Status = constants.RestoreJobRunning + m.Resource.Status.OperationContext = nil +} + +func (m ObTenantRestoreManager) FinishTask() { + m.Resource.Status.Status = constants.RestoreJobStatus(m.Resource.Status.OperationContext.TargetStatus) + m.Resource.Status.OperationContext = nil +} + +func (m ObTenantRestoreManager) HandleFailure() { + +} + +func (m ObTenantRestoreManager) UpdateStatus() error { + return m.Client.Status().Update(m.Ctx, m.Resource) +} + +func (m ObTenantRestoreManager) GetTaskFunc(name string) (func() error, error) { + switch name { + case taskname.StartRestoreJob: + return m.StartRestoreJobInOB, nil + case taskname.StartLogReplay: + return m.StartLogReplay, nil + case taskname.CancelRestoreJob: + return m.CancelRestoreJob, nil + case taskname.ActivateStandby: + return m.ActivateStandby, nil + case taskname.CheckRestoreProgress: + return m.CheckRestoreProgress, nil + } + return nil, nil +} + +func (m ObTenantRestoreManager) GetTaskFlow() (*task.TaskFlow, error) { + if m.Resource.Status.OperationContext != nil { + return task.NewTaskFlow(m.Resource.Status.OperationContext), nil + } + var taskFlow *task.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(flow.PrepareBackupPolicy) + case constants.RestoreJobRunning: + taskFlow, err = task.GetRegistry().Get(flow.PrepareBackupPolicy) + case constants.RestoreJobCanceling: + taskFlow, err = task.GetRegistry().Get(flow.PrepareBackupPolicy) + case constants.RestoreJobCanceled: + fallthrough + case constants.RestoreJobSuccessful: + fallthrough + case constants.RestoreJobFailed: + fallthrough + default: + 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 = string(status) + } + } + + return taskFlow, nil +} + +func (m ObTenantRestoreManager) PrintErrEvent(err error) { + m.Recorder.Event(m.Resource, corev1.EventTypeWarning, "task exec failed", err.Error()) +} diff --git a/pkg/resource/obtenantrestore_task.go b/pkg/resource/obtenantrestore_task.go new file mode 100644 index 000000000..99e556673 --- /dev/null +++ b/pkg/resource/obtenantrestore_task.go @@ -0,0 +1,148 @@ +/* +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 resource + +import ( + "time" + + "github.com/pkg/errors" + + 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" + "github.com/oceanbase/ob-operator/api/v1alpha1" + oceanbaseconst "github.com/oceanbase/ob-operator/pkg/const/oceanbase" + "github.com/oceanbase/ob-operator/pkg/oceanbase/operation" +) + +// Restore progress: +// 1. create unit (in tenant manager) +// 2. create resource pool (in tenant manager) +// 3. trigger restore job +// 4. wait for finishing +// 5. activate or replay log + +// OBTenantManager tasks completion + +func (m *OBTenantManager) CreateTenantRestoreJob() error { + var existingJobs v1alpha1.OBTenantRestoreList + var err error + + err = m.Client.List(m.Ctx, &existingJobs, + client.MatchingLabels{ + oceanbaseconst.LabelRefOBCluster: m.OBTenant.Spec.ClusterName, + oceanbaseconst.LabelTenantName: m.OBTenant.Spec.TenantName, + }, + client.InNamespace(m.OBTenant.Namespace)) + if err != nil { + return err + } + + if len(existingJobs.Items) != 0 { + return errors.New("There is already at least one restore job for this tenant") + } + + restoreJob := &v1alpha1.OBTenantRestore{ + ObjectMeta: metav1.ObjectMeta{ + Name: m.OBTenant.Spec.TenantName + "-restore", + Namespace: m.OBTenant.GetNamespace(), + OwnerReferences: []metav1.OwnerReference{{ + APIVersion: m.OBTenant.APIVersion, + Kind: m.OBTenant.Kind, + Name: m.OBTenant.Name, + UID: m.OBTenant.GetUID(), + BlockOwnerDeletion: getRef(true)}}, + Labels: map[string]string{ + oceanbaseconst.LabelRefOBCluster: m.OBTenant.Spec.ClusterName, + oceanbaseconst.LabelTenantName: m.OBTenant.Spec.TenantName, + oceanbaseconst.LabelRefUID: string(m.OBTenant.GetUID()), + }}, + Spec: v1alpha1.OBTenantRestoreSpec{ + TargetTenant: m.OBTenant.Spec.TenantName, + TargetCluster: m.OBTenant.Spec.ClusterName, + RestoreRole: m.OBTenant.Spec.TenantRole, + Source: *m.OBTenant.Spec.Source, + }, + } + err = m.Client.Create(m.Ctx, restoreJob) + if err != nil { + return err + } + return nil +} + +func (m *OBTenantManager) WatchRestoreJobToFinish() error { + var err error + for { + runningRestore := &v1alpha1.OBTenantRestore{} + err = m.Client.Get(m.Ctx, types.NamespacedName{ + Namespace: m.OBTenant.GetNamespace(), + Name: m.OBTenant.Spec.TenantName + "-restore", + }, runningRestore) + if err != nil { + return err + } + if runningRestore.Status.Status == constants.RestoreJobSuccessful { + break + } else if runningRestore.Status.Status == constants.RestoreJobFailed { + return errors.New("Restore job failed") + } + time.Sleep(5 * time.Second) + } + return nil +} + +// OBTenantRestore tasks + +func (m *ObTenantRestoreManager) StartRestoreJobInOB() error { + return nil +} + +func (m *ObTenantRestoreManager) CheckRestoreProgress() error { + return nil +} + +func (m *ObTenantRestoreManager) StartLogReplay() error { + return nil +} + +func (m *ObTenantRestoreManager) ActivateStandby() error { + return nil +} + +func (m *ObTenantRestoreManager) CancelRestoreJob() error { + return nil +} + +// get operation manager to exec sql +func (m *ObTenantRestoreManager) getOperationManager() (*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 := GetOceanbaseOperationManagerFromOBCluster(m.Client, m.Logger, obcluster) + if err != nil { + return nil, errors.Wrap(err, "get oceanbase operation manager") + } + m.con = con + return con, nil +} diff --git a/pkg/resource/obzone_manager.go b/pkg/resource/obzone_manager.go index cb315947f..e13cede0e 100644 --- a/pkg/resource/obzone_manager.go +++ b/pkg/resource/obzone_manager.go @@ -108,9 +108,8 @@ func (m *OBZoneManager) GetTaskFlow() (*task.TaskFlow, error) { } if len(obcluster.Status.OBZoneStatus) >= 3 { return task.GetRegistry().Get(flowname.UpgradeOBZone) - } else { - return task.GetRegistry().Get(flowname.ForceUpgradeOBZone) } + return task.GetRegistry().Get(flowname.ForceUpgradeOBZone) // TODO upgrade default: m.Logger.Info("no need to run anything for obzone") @@ -190,7 +189,7 @@ func (m *OBZoneManager) UpdateStatus() error { Status: observer.Status.Status, }) if observer.Status.Status != serverstatus.Unrecoverable { - availableReplica = availableReplica + 1 + availableReplica++ } if observer.Status.Image != m.OBZone.Spec.OBServerTemplate.Image { m.Logger.Info("Found observer image not match") @@ -215,9 +214,9 @@ func (m *OBZoneManager) UpdateStatus() error { } else if m.OBZone.Spec.Topology.Replica < len(m.OBZone.Status.OBServerStatus) { m.Logger.Info("Compare topology need delete observer") m.OBZone.Status.Status = zonestatus.DeleteOBServer - } else { - // do nothing when observer match topology replica } + // do nothing when observer match topology replica + // TODO resource change require pod restart, and since oceanbase is a distributed system, resource can be scaled by add more servers if m.OBZone.Status.Status == zonestatus.Running { if m.OBZone.Status.Image != m.OBZone.Spec.OBServerTemplate.Image { diff --git a/pkg/resource/obzone_task.go b/pkg/resource/obzone_task.go index 980ae06f0..087b65e0e 100644 --- a/pkg/resource/obzone_task.go +++ b/pkg/resource/obzone_task.go @@ -18,11 +18,12 @@ import ( "time" "github.com/google/uuid" + "github.com/pkg/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + oceanbaseconst "github.com/oceanbase/ob-operator/pkg/const/oceanbase" serverstatus "github.com/oceanbase/ob-operator/pkg/const/status/observer" "github.com/oceanbase/ob-operator/pkg/oceanbase/operation" - "github.com/pkg/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" ) @@ -99,7 +100,7 @@ func (m *OBZoneManager) CreateOBServer() error { currentReplica := 0 for _, observerStatus := range m.OBZone.Status.OBServerStatus { if observerStatus.Status != serverstatus.Unrecoverable { - currentReplica = currentReplica + 1 + currentReplica++ } } for i := currentReplica; i < m.OBZone.Spec.Topology.Replica; i++ { @@ -156,7 +157,7 @@ func (m *OBZoneManager) DeleteOBServer() error { } continue } - observerCount += 1 + observerCount++ } return nil } @@ -237,7 +238,7 @@ func (m *OBZoneManager) OBClusterHealthCheck() error { if err != nil { return errors.Wrap(err, "Get obcluster from K8s") } - ExecuteUpgradeScript(m.Client, m.Logger, obcluster, oceanbaseconst.UpgradeHealthCheckerScriptPath, "") + _ = ExecuteUpgradeScript(m.Client, m.Logger, obcluster, oceanbaseconst.UpgradeHealthCheckerScriptPath, "") return nil } @@ -247,7 +248,7 @@ func (m *OBZoneManager) OBZoneHealthCheck() error { return errors.Wrap(err, "Get obcluster from K8s") } zoneOpt := fmt.Sprintf("-z '%s'", m.OBZone.Spec.Topology.Zone) - ExecuteUpgradeScript(m.Client, m.Logger, obcluster, oceanbaseconst.UpgradeHealthCheckerScriptPath, zoneOpt) + _ = ExecuteUpgradeScript(m.Client, m.Logger, obcluster, oceanbaseconst.UpgradeHealthCheckerScriptPath, zoneOpt) return nil } diff --git a/pkg/resource/template_manager.go b/pkg/resource/template_manager.go new file mode 100644 index 000000000..bf58be220 --- /dev/null +++ b/pkg/resource/template_manager.go @@ -0,0 +1,84 @@ +/* +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 resource + +import ( + "context" + + "github.com/go-logr/logr" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" + "sigs.k8s.io/controller-runtime/pkg/client" + + v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" + "github.com/oceanbase/ob-operator/pkg/oceanbase/operation" + "github.com/oceanbase/ob-operator/pkg/task" +) + +type Resource interface { + client.Object + runtime.Object +} + +type ObResourceManager[T Resource] struct { + ResourceManager + + Ctx context.Context + Resource T + Client client.Client + Recorder record.EventRecorder + Logger *logr.Logger + + con *operation.OceanbaseOperationManager +} + +func (m *ObResourceManager[T]) IsNewResource() bool { + return false +} + +func (m *ObResourceManager[T]) IsDeleting() bool { + return false +} + +func (m *ObResourceManager[T]) CheckAndUpdateFinalizers() error { + return nil +} + +func (m *ObResourceManager[T]) InitStatus() {} + +func (m *ObResourceManager[T]) SetOperationContext(*v1alpha1.OperationContext) { + +} + +func (m *ObResourceManager[T]) ClearTaskInfo() {} + +func (m *ObResourceManager[T]) HandleFailure() {} + +func (m *ObResourceManager[T]) FinishTask() {} + +func (m *ObResourceManager[T]) UpdateStatus() error { + return m.Client.Status().Update(m.Ctx, m.Resource) +} + +func (m *ObResourceManager[T]) GetTaskFunc(string) (func() error, error) { + return nil, nil +} + +func (m *ObResourceManager[T]) GetTaskFlow() (*task.TaskFlow, error) { + return nil, nil +} + +func (m *ObResourceManager[T]) PrintErrEvent(err error) { + m.Recorder.Event(m.Resource, corev1.EventTypeWarning, "task exec failed", err.Error()) +} diff --git a/pkg/resource/util.go b/pkg/resource/util.go index 22a8fd9ef..fc4b90736 100644 --- a/pkg/resource/util.go +++ b/pkg/resource/util.go @@ -20,6 +20,13 @@ import ( "github.com/go-logr/logr" "github.com/google/uuid" + "github.com/pkg/errors" + batchv1 "k8s.io/api/batch/v1" + corev1 "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/v1alpha1" oceanbaseconst "github.com/oceanbase/ob-operator/pkg/const/oceanbase" secretconst "github.com/oceanbase/ob-operator/pkg/const/secret" @@ -27,12 +34,6 @@ import ( "github.com/oceanbase/ob-operator/pkg/oceanbase/connector" "github.com/oceanbase/ob-operator/pkg/oceanbase/model" "github.com/oceanbase/ob-operator/pkg/oceanbase/operation" - "github.com/pkg/errors" - batchv1 "k8s.io/api/batch/v1" - corev1 "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" ) func ReadPassword(c client.Client, namespace, secretName string) (string, error) { @@ -63,7 +64,7 @@ func getOperationClient(c client.Client, logger *logr.Logger, obcluster *v1alpha if err != nil { return nil, errors.Wrap(err, "Get observer list") } - if len(observerList.Items) <= 0 { + if len(observerList.Items) == 0 { return nil, errors.Errorf("No observer belongs to cluster %s", obcluster.Name) } @@ -127,7 +128,7 @@ func ExecuteUpgradeScript(c client.Client, logger *logr.Logger, obcluster *v1alp parts := strings.Split(uuid.New().String(), "-") suffix := parts[len(parts)-1] jobName := fmt.Sprintf("%s-%s", "script-runner", suffix) - var backoffLimit int32 = 0 + var backoffLimit int32 var ttl int32 = 300 container := corev1.Container{ Name: "script-runner", diff --git a/pkg/task/const/flow/name/task_flow_names.go b/pkg/task/const/flow/name/task_flow_names.go index 0f8baea23..408b8fea4 100644 --- a/pkg/task/const/flow/name/task_flow_names.go +++ b/pkg/task/const/flow/name/task_flow_names.go @@ -81,4 +81,15 @@ const ( MaintainUnitConfig = "maintain unit config" DeleteTenant = "delete tenant" + + // tenant restore + RestoreTenant = "Restore tenant" +) + +// tenant-level restore +const ( + StartRestoreFlow = "start restore" + RestoreAsStandbyFlow = "restore as standby" + RestoreAsPrimaryFlow = "restore as primary" + CancelRestoreFlow = "cancel restore" ) diff --git a/pkg/task/const/task/name/backup.go b/pkg/task/const/task/name/backup.go index 510053c54..77237d819 100644 --- a/pkg/task/const/task/name/backup.go +++ b/pkg/task/const/task/name/backup.go @@ -22,10 +22,3 @@ const ( PauseBackup = "pause backup" ResumeBackup = "resume backup" ) - -const ( - StartRestoreJob = "start restore job" - StartLogReplay = "start log replay" - CancelRestoreJob = "cancel restore job" - ActivateStandby = "activate standby" -) diff --git a/pkg/task/const/task/name/restore.go b/pkg/task/const/task/name/restore.go new file mode 100644 index 000000000..7c9ef3507 --- /dev/null +++ b/pkg/task/const/task/name/restore.go @@ -0,0 +1,21 @@ +/* +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 name + +const ( + StartRestoreJob = "start restore job" + StartLogReplay = "start log replay" + CancelRestoreJob = "cancel restore job" + ActivateStandby = "activate standby" + CheckRestoreProgress = "check restore progress" +) diff --git a/pkg/task/const/task/name/task_names.go b/pkg/task/const/task/name/task_names.go index 1aa6a3755..3e0ded213 100644 --- a/pkg/task/const/task/name/task_names.go +++ b/pkg/task/const/task/name/task_names.go @@ -83,7 +83,7 @@ const ( CheckPoolAndUnitConfig = "create pool and unit config check" CreateTenant = "create tenant" CreateResourcePoolAndUnitConfig = "create resource pool and unit config" - //AddFinalizer = "add finalizer" + // AddFinalizer = "add finalizer" // maintain tenant MaintainWhiteList = "maintain white list" @@ -100,4 +100,8 @@ const ( MaintainUnitConfig = "maintain unit config" DeleteTenant = "delete tenant" + + // tenant restore + CreateRestoreJob = "create restore job" + WatchRestoreJobToFinish = "watch restore job to finish" ) diff --git a/pkg/task/obtenant_flow.go b/pkg/task/obtenant_flow.go index fdc791029..f03d1f7f9 100644 --- a/pkg/task/obtenant_flow.go +++ b/pkg/task/obtenant_flow.go @@ -126,3 +126,22 @@ func DeleteTenant() *TaskFlow { }, } } + +func RestoreTenant() *TaskFlow { + return &TaskFlow{ + OperationContext: &v1alpha1.OperationContext{ + Name: flowname.RestoreTenant, + Tasks: []string{ + taskname.CheckTenant, + taskname.CheckPoolAndUnitConfig, + taskname.CreateResourcePoolAndUnitConfig, + taskname.CreateRestoreJob, + taskname.WatchRestoreJobToFinish, + }, + TargetStatus: tenantstatus.Running, + OnFailure: strategy.FailureRule{ + NextTryStatus: tenantstatus.Restoring, + }, + }, + } +} diff --git a/pkg/task/task_flow.go b/pkg/task/task_flow.go index 54b3cc6ae..19a1f8468 100644 --- a/pkg/task/task_flow.go +++ b/pkg/task/task_flow.go @@ -33,7 +33,7 @@ func (f *TaskFlow) NextTask() string { } else { f.OperationContext.TaskStatus = taskstatus.Pending f.OperationContext.Task = f.OperationContext.Tasks[f.OperationContext.Idx] - f.OperationContext.Idx = f.OperationContext.Idx + 1 + f.OperationContext.Idx++ f.OperationContext.TaskId = "" } return f.OperationContext.Task diff --git a/pkg/task/task_manager.go b/pkg/task/task_manager.go index e4350b159..cc690fe18 100644 --- a/pkg/task/task_manager.go +++ b/pkg/task/task_manager.go @@ -19,9 +19,10 @@ import ( "github.com/go-logr/logr" "github.com/google/uuid" - taskstatus "github.com/oceanbase/ob-operator/pkg/task/const/task/status" "github.com/pkg/errors" "sigs.k8s.io/controller-runtime/pkg/log" + + taskstatus "github.com/oceanbase/ob-operator/pkg/task/const/task/status" ) var taskManager *TaskManager @@ -52,10 +53,10 @@ type TaskManager struct { func (m *TaskManager) Submit(f func() error) string { retCh := make(chan *TaskResult, 1) - TaskId := uuid.New().String() + taskId := uuid.New().String() // TODO add lock to keep ResultMap safe - m.ResultMap[TaskId] = retCh - m.TaskResultCache[TaskId] = nil + m.ResultMap[taskId] = retCh + m.TaskResultCache[taskId] = nil go func() { defer func() { if r := recover(); r != nil { @@ -67,7 +68,7 @@ func (m *TaskManager) Submit(f func() error) string { }() err := f() if err != nil { - m.Logger.Error(err, "Run task got error", "taskId", TaskId) + m.Logger.Error(err, "Run task got error", "taskId", taskId) retCh <- &TaskResult{ Status: taskstatus.Failed, Error: err, @@ -78,7 +79,7 @@ func (m *TaskManager) Submit(f func() error) string { Error: nil, } }() - return TaskId + return taskId } func (m *TaskManager) GetTaskResult(taskId string) (*TaskResult, error) { diff --git a/pkg/util/codec/json.go b/pkg/util/codec/json.go index c6b64dbde..f05393653 100644 --- a/pkg/util/codec/json.go +++ b/pkg/util/codec/json.go @@ -16,13 +16,13 @@ import ( "encoding/json" ) -func EncodeToJSON(element interface{}) string { +func EncodeToJSON(element any) string { tempJSON, _ := json.Marshal(element) return string(tempJSON) } -func ParseFromJSON(content string) (map[string]interface{}, error) { - ret := make(map[string]interface{}) +func ParseFromJSON(content string) (map[string]any, error) { + ret := make(map[string]any) err := json.Unmarshal([]byte(content), &ret) return ret, err } From 9a4793dae70a1bc6ce33db283e6e9ab70cd29908 Mon Sep 17 00:00:00 2001 From: yuyi Date: Thu, 21 Sep 2023 20:27:06 +0800 Subject: [PATCH 08/19] chore: integrate golangci-lint into makefile --- Makefile | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Makefile b/Makefile index 656df2ba3..68f5dcf6d 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,8 @@ ENVTEST_K8S_VERSION = 1.26.1 YQ_DOWNLOAD_URL = https://github.com/mikefarah/yq/releases/download/v4.35.1/yq_linux_amd64 SEMVER_DOWNLOAD_URL = https://raw.githubusercontent.com/fsaintjacques/semver-tool/master/src/semver +GOLANG_CI_VERSION ?= v1.54.2 + # Get the currently used golang install path (in GOPATH/bin, unless GOBIN is set) ifeq (,$(shell go env GOBIN)) GOBIN=$(shell go env GOPATH)/bin @@ -188,3 +190,19 @@ $(ENVTEST): $(LOCALBIN) .PHONY: tools tools: $(YQ) $(SEMVER) + +.PHONY: GOLANGCI_LINT +GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint +$(GOLANG_LINT): + GOBIN=$(LOCALBIN) go install github.com/golangci/golangci-lint/cmd/golangci-lint@${GOLANG_CI_VERSION} + +.PHONY: lint +lint: $(GOLANGCI_LINT) ## Run linting. + $(GOLANGCI_LINT) run -v --timeout=10m + +.PHONY: commit-hook +commit-hook: $(GOLANGCI_LINT) ## Install commit hook. + touch .git/hooks/pre-commit + chmod +x .git/hooks/pre-commit + echo "#!/bin/sh" > .git/hooks/pre-commit + echo "make lint" >> .git/hooks/pre-commit \ No newline at end of file From 8ccf0d0c67e197c8d6651979b644bcc8acb567de Mon Sep 17 00:00:00 2001 From: yuyi Date: Fri, 22 Sep 2023 16:22:45 +0800 Subject: [PATCH 09/19] fix: obtenantbackup job refresh interval --- pkg/controller/obtenantbackup_controller.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/pkg/controller/obtenantbackup_controller.go b/pkg/controller/obtenantbackup_controller.go index 843fc659c..3841f21e2 100644 --- a/pkg/controller/obtenantbackup_controller.go +++ b/pkg/controller/obtenantbackup_controller.go @@ -70,18 +70,26 @@ func (r *OBTenantBackupReconciler) Reconcile(ctx context.Context, req ctrl.Reque fallthrough case constants.BackupJobStatusInitializing: crJob.Status.Status = constants.BackupJobStatusRunning - return ctrl.Result{}, r.createBackupJobInOB(ctx, crJob) + return ctrl.Result{ + RequeueAfter: time.Second * 5, + }, r.createBackupJobInOB(ctx, crJob) case constants.BackupJobStatusRunning: - return ctrl.Result{}, r.maintainRunningBackupJob(ctx, crJob) + return ctrl.Result{ + RequeueAfter: time.Second * 5, + }, r.maintainRunningBackupJob(ctx, crJob) default: // Completed, Failed, Canceled, do nothing return ctrl.Result{}, nil } case constants.BackupJobTypeArchive: - return ctrl.Result{}, r.maintainRunningArchiveLogJob(ctx, crJob) + return ctrl.Result{ + RequeueAfter: time.Second * 5, + }, r.maintainRunningArchiveLogJob(ctx, crJob) case constants.BackupJobTypeClean: - return ctrl.Result{}, r.maintainRunningBackupCleanJob(ctx, crJob) + return ctrl.Result{ + RequeueAfter: time.Second * 5, + }, r.maintainRunningBackupCleanJob(ctx, crJob) } return ctrl.Result{ From 19882554f41475081be412823c3160ddada820f0 Mon Sep 17 00:00:00 2001 From: yuyi Date: Fri, 22 Sep 2023 16:40:07 +0800 Subject: [PATCH 10/19] chore: finish coding, ready to test --- api/constants/restore.go | 4 +- api/v1alpha1/obtenant_types.go | 4 +- api/v1alpha1/obtenant_webhook.go | 7 ++ api/v1alpha1/obtenantbackuppolicy_types.go | 4 + api/v1alpha1/obtenantrestore_types.go | 3 +- ...anbase.oceanbase.com_obtenantrestores.yaml | 51 +++++----- .../oceanbase.oceanbase.com_obtenants.yaml | 7 +- config/manager/kustomization.yaml | 2 +- deploy/backup_policy.yaml | 4 +- deploy/tenant.yaml | 1 - .../status/tenantstatus/obtenant_status.go | 6 +- pkg/oceanbase/operation/manager.go | 3 +- pkg/oceanbase/operation/restore.go | 4 +- pkg/resource/generics.go | 21 ++++ pkg/resource/obtenant_manager.go | 18 +++- pkg/resource/obtenantbackuppolicy_manager.go | 16 +-- pkg/resource/obtenantrestore_manager.go | 97 ++++++++++++++++-- pkg/resource/obtenantrestore_task.go | 99 ++++++++++++++++--- pkg/resource/template_manager.go | 8 +- pkg/resource/util.go | 8 -- pkg/task/all.go | 6 ++ pkg/task/const/flow/name/task_flow_names.go | 4 +- pkg/task/const/task/name/restore.go | 8 +- pkg/task/const/task/name/task_names.go | 3 +- pkg/task/obtenant_flow.go | 14 ++- pkg/task/restore_flow.go | 60 +++++++++++ 26 files changed, 367 insertions(+), 95 deletions(-) create mode 100644 pkg/resource/generics.go create mode 100644 pkg/task/restore_flow.go diff --git a/api/constants/restore.go b/api/constants/restore.go index 8aef68fa2..fe58e66ea 100644 --- a/api/constants/restore.go +++ b/api/constants/restore.go @@ -18,7 +18,9 @@ const ( RestoreJobStarting RestoreJobStatus = "STARTING" RestoreJobRunning RestoreJobStatus = "RUNNING" RestoreJobFailed RestoreJobStatus = "FAILED" - RestoreJobCanceling RestoreJobStatus = "CANCELING" RestoreJobSuccessful RestoreJobStatus = "SUCCESSFUL" RestoreJobCanceled RestoreJobStatus = "CANCELED" + + RestoreJobStatusActivating RestoreJobStatus = "ACTIVATING" + RestoreJobStatusReplaying RestoreJobStatus = "REPLAYING" ) diff --git a/api/v1alpha1/obtenant_types.go b/api/v1alpha1/obtenant_types.go index 7fd24d01e..c15eaa32a 100644 --- a/api/v1alpha1/obtenant_types.go +++ b/api/v1alpha1/obtenant_types.go @@ -67,6 +67,7 @@ type RestoreSourceSpec struct { Until RestoreUntilConfig `json:"until"` Description *string `json:"description,omitempty"` ReplayLogUntil *RestoreUntilConfig `json:"replayLogUntil,omitempty"` + Cancel bool `json:"cancel,omitempty"` } type RestoreUntilConfig struct { @@ -194,7 +195,8 @@ type TenantRecordInfo struct { //+kubebuilder:object:root=true //+kubebuilder:subresource:status //+kubebuilder:printcolumn:name="status",type="string",JSONPath=".status.status" -//+kubebuilder:printcolumn:name="clusterName",type="string",JSONPath=".spec.clusterName" +//+kubebuilder:printcolumn:name="tenantName",type="string",JSONPath=".spec.tenantName" +//+kubebuilder:printcolumn:name="clusterName",type="string",JSONPath=".spec.obcluster" //+kubebuilder:printcolumn:name="Age",type="date",JSONPath=".metadata.creationTimestamp" //+kubebuilder:printcolumn:name="locality",type="string",JSONPath=".status.tenantRecordInfo.locality",priority=1 //+kubebuilder:printcolumn:name="primaryZone",type="string",JSONPath=".status.tenantRecordInfo.primaryZone",priority=1 diff --git a/api/v1alpha1/obtenant_webhook.go b/api/v1alpha1/obtenant_webhook.go index 54baf92ab..d3c01d19e 100644 --- a/api/v1alpha1/obtenant_webhook.go +++ b/api/v1alpha1/obtenant_webhook.go @@ -77,6 +77,13 @@ func (r *OBTenant) validateMutation() error { allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("tenantRole"), r.Spec.TenantRole, "Standby must have a source option, but both restore and tenantRef are nil now")) } } + // 2. Restore until with some limit must have a limit key + if r.Spec.Source != nil && r.Spec.Source.Restore != nil { + untilSpec := r.Spec.Source.Restore.Until + if !untilSpec.Unlimited && untilSpec.Scn == nil && untilSpec.Timestamp == nil { + allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("source").Child("restore").Child("until"), untilSpec, "Restore until must have a limit key, scn and timestamp are both nil now")) + } + } if len(allErrs) == 0 { return nil diff --git a/api/v1alpha1/obtenantbackuppolicy_types.go b/api/v1alpha1/obtenantbackuppolicy_types.go index 97644e353..ac8616234 100644 --- a/api/v1alpha1/obtenantbackuppolicy_types.go +++ b/api/v1alpha1/obtenantbackuppolicy_types.go @@ -184,3 +184,7 @@ func (in *JobOverview) DeepCopy() *JobOverview { in.DeepCopyInto(out) return out } + +func (in *OBTenantBackupPolicy) CopyStatus(out *OBTenantBackupPolicy) { + in.Status = out.Status +} diff --git a/api/v1alpha1/obtenantrestore_types.go b/api/v1alpha1/obtenantrestore_types.go index fbdb1e771..069a11776 100644 --- a/api/v1alpha1/obtenantrestore_types.go +++ b/api/v1alpha1/obtenantrestore_types.go @@ -30,7 +30,8 @@ type OBTenantRestoreSpec struct { TargetTenant string `json:"targetTenant"` TargetCluster string `json:"targetCluster"` RestoreRole constants.TenantRole `json:"restoreRole"` - Source TenantSourceSpec `json:"source"` + Source RestoreSourceSpec `json:"source"` + Option string `json:"restoreOption"` } // +kubebuilder:object:generate=false diff --git a/config/crd/bases/oceanbase.oceanbase.com_obtenantrestores.yaml b/config/crd/bases/oceanbase.oceanbase.com_obtenantrestores.yaml index 7819a18ed..80b49e04f 100644 --- a/config/crd/bases/oceanbase.oceanbase.com_obtenantrestores.yaml +++ b/config/crd/bases/oceanbase.oceanbase.com_obtenantrestores.yaml @@ -35,47 +35,46 @@ spec: spec: description: OBTenantRestoreSpec defines the desired state of OBTenantRestore properties: + restoreOption: + type: string restoreRole: type: string source: - description: Source for restoring or creating standby properties: - restore: + cancel: + type: boolean + description: + type: string + replayLogUntil: properties: - description: + scn: type: string - replayLogUntil: - properties: - scn: - type: string - timestamp: - type: string - unlimited: - type: boolean - type: object - sourceUri: + timestamp: type: string - until: - properties: - scn: - type: string - timestamp: - type: string - unlimited: - type: boolean - type: object - required: - - sourceUri - - until + unlimited: + type: boolean type: object - tenant: + sourceUri: type: string + until: + properties: + scn: + type: string + timestamp: + type: string + unlimited: + type: boolean + type: object + required: + - sourceUri + - until type: object targetCluster: type: string targetTenant: type: string required: + - restoreOption - restoreRole - source - targetCluster diff --git a/config/crd/bases/oceanbase.oceanbase.com_obtenants.yaml b/config/crd/bases/oceanbase.oceanbase.com_obtenants.yaml index d5e0446a4..c871df26d 100644 --- a/config/crd/bases/oceanbase.oceanbase.com_obtenants.yaml +++ b/config/crd/bases/oceanbase.oceanbase.com_obtenants.yaml @@ -18,7 +18,10 @@ spec: - jsonPath: .status.status name: status type: string - - jsonPath: .spec.clusterName + - jsonPath: .spec.tenantName + name: tenantName + type: string + - jsonPath: .spec.obcluster name: clusterName type: string - jsonPath: .metadata.creationTimestamp @@ -153,6 +156,8 @@ spec: properties: restore: properties: + cancel: + type: boolean description: type: string replayLogUntil: diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 7d3d6f7d7..8be95a95d 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -5,4 +5,4 @@ kind: Kustomization images: - name: controller newName: oceanbasedev/ob-operator - newTag: 2.0.0 + newTag: 2.0.0-alpha.5 diff --git a/deploy/backup_policy.yaml b/deploy/backup_policy.yaml index fd4115980..86d324cd5 100644 --- a/deploy/backup_policy.yaml +++ b/deploy/backup_policy.yaml @@ -12,8 +12,8 @@ metadata: namespace: oceanbase spec: obClusterName: "test" - tenantName: "obtenant_test" - tenantSecret: "obtenant_test-credential" + tenantName: "t1" + tenantSecret: "t1-credential" jobKeepWindow: "1d" dataClean: recoveryWindow: "8d" diff --git a/deploy/tenant.yaml b/deploy/tenant.yaml index 2f9481f86..935c51396 100644 --- a/deploy/tenant.yaml +++ b/deploy/tenant.yaml @@ -22,7 +22,6 @@ spec: minCPU: 2 maxIops: 1024 minIops: 1024 - iopsWeight: logDiskSize: - zone: zone2 type: diff --git a/pkg/const/status/tenantstatus/obtenant_status.go b/pkg/const/status/tenantstatus/obtenant_status.go index f32211b64..d84943dc4 100644 --- a/pkg/const/status/tenantstatus/obtenant_status.go +++ b/pkg/const/status/tenantstatus/obtenant_status.go @@ -27,6 +27,8 @@ const ( FinalizerFinished = "finalizer finished" PausingReconcile = "pausing reconcile" - Restoring = "restoring" - SwitchingRole = "switching role" + Restoring = "restoring" + SwitchingRole = "switching role" + RestoreCanceled = "restore canceled" + CancelingRestore = "canceling restore" ) diff --git a/pkg/oceanbase/operation/manager.go b/pkg/oceanbase/operation/manager.go index a1bb78458..f2d074318 100644 --- a/pkg/oceanbase/operation/manager.go +++ b/pkg/oceanbase/operation/manager.go @@ -69,8 +69,9 @@ func (m *OceanbaseOperationManager) QueryRowWithTimeout(timeout time.Duration, r } return err } + func (m *OceanbaseOperationManager) QueryRow(ret any, sql string, params ...any) error { - return m.QueryRowWithTimeout(config.DefaultSqlTimeout, ret, sql, params) + return m.QueryRowWithTimeout(config.DefaultSqlTimeout, ret, sql, params...) } func (m *OceanbaseOperationManager) QueryListWithTimeout(timeout time.Duration, ret any, sql string, params ...any) error { diff --git a/pkg/oceanbase/operation/restore.go b/pkg/oceanbase/operation/restore.go index de5a63907..35408bfba 100644 --- a/pkg/oceanbase/operation/restore.go +++ b/pkg/oceanbase/operation/restore.go @@ -31,9 +31,9 @@ func (m *OceanbaseOperationManager) SetRestorePassword(password string) error { return nil } -func (m *OceanbaseOperationManager) StartRestoreWithLimit(tenantName, uri, limitKey, restoreOption string, limitValue any) error { +func (m *OceanbaseOperationManager) StartRestoreWithLimit(tenantName, uri, restoreOption string, limitKey, limitValue any) error { sqlStatement := fmt.Sprintf(sql.StartRestoreWithLimit, tenantName, limitKey) - err := m.ExecWithDefaultTimeout(sqlStatement, uri, limitValue, restoreOption) + err := m.ExecWithTimeout(600*time.Second, sqlStatement, uri, limitValue, restoreOption) if err != nil { m.Logger.Error(err, "Got exception when start restore with limit") return errors.Wrap(err, "Start restore with limit") diff --git a/pkg/resource/generics.go b/pkg/resource/generics.go new file mode 100644 index 000000000..de3af3c72 --- /dev/null +++ b/pkg/resource/generics.go @@ -0,0 +1,21 @@ +/* +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 resource + +func getRef[T any](val T) *T { + return &val +} + +func isZero[T comparable](val T) bool { + return val == *(new(T)) +} diff --git a/pkg/resource/obtenant_manager.go b/pkg/resource/obtenant_manager.go index c7af0a8c7..894ed8bc7 100644 --- a/pkg/resource/obtenant_manager.go +++ b/pkg/resource/obtenant_manager.go @@ -29,7 +29,6 @@ import ( "k8s.io/client-go/util/retry" "sigs.k8s.io/controller-runtime/pkg/client" - "github.com/oceanbase/ob-operator/api/constants" "github.com/oceanbase/ob-operator/api/v1alpha1" "github.com/oceanbase/ob-operator/pkg/const/status/tenantstatus" "github.com/oceanbase/ob-operator/pkg/oceanbase/const/status/tenant" @@ -76,7 +75,7 @@ func (m *OBTenantManager) InitStatus() { m.OBTenant.Status = v1alpha1.OBTenantStatus{ Pools: make([]v1alpha1.ResourcePoolStatus, 0, len(m.OBTenant.Spec.Pools)), } - if m.OBTenant.Spec.TenantRole == constants.TenantRoleStandby { + if m.OBTenant.Spec.Source != nil && m.OBTenant.Spec.Source.Restore != nil { m.OBTenant.Status.Status = tenantstatus.Restoring } else { m.OBTenant.Status.Status = tenantstatus.CreatingTenant @@ -138,6 +137,11 @@ func (m *OBTenantManager) UpdateStatus() error { return nil } else if m.IsDeleting() { m.OBTenant.Status.Status = tenantstatus.DeletingTenant + } else if m.OBTenant.Status.Status == tenantstatus.Restoring && + m.OBTenant.Spec.Source != nil && + m.OBTenant.Spec.Source.Restore != nil && + m.OBTenant.Spec.Source.Restore.Cancel { + m.OBTenant.Status.Status = tenantstatus.CancelingRestore } else if m.OBTenant.Status.Status != tenantstatus.Running { m.Logger.Info(fmt.Sprintf("OBTenant status is %s (not running), skip compare", m.OBTenant.Status.Status)) } else { @@ -221,6 +225,12 @@ func (m *OBTenantManager) GetTaskFunc(taskName string) (func() error, error) { return m.MaintainUnitConfigTask, nil case taskname.DeleteTenant: return m.DeleteTenantTask, nil + case taskname.CreateRestoreJobCR: + return m.CreateTenantRestoreJobCR, nil + case taskname.WatchRestoreJobToFinish: + return m.WatchRestoreJobToFinish, nil + case taskname.CancelRestoreJob: + return m.CancelTenantRestoreJob, nil default: return nil, errors.Errorf("Can not find an function for task %s", taskName) } @@ -272,6 +282,10 @@ func (m *OBTenantManager) GetTaskFlow() (*task.TaskFlow, error) { 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(flowname.RestoreTenant) + case tenantstatus.CancelingRestore: + taskFlow, err = task.GetRegistry().Get(flowname.CancelRestoreFlow) default: m.Logger.Info("no need to run anything for obtenant") return nil, nil diff --git a/pkg/resource/obtenantbackuppolicy_manager.go b/pkg/resource/obtenantbackuppolicy_manager.go index 33312f06f..73d0eb77e 100644 --- a/pkg/resource/obtenantbackuppolicy_manager.go +++ b/pkg/resource/obtenantbackuppolicy_manager.go @@ -223,15 +223,15 @@ func (m *ObTenantBackupPolicyManager) GetTaskFlow() (*task.TaskFlow, error) { } func (m *ObTenantBackupPolicyManager) retryUpdateStatus() error { - policy := &v1alpha1.OBTenantBackupPolicy{} - err := m.Client.Get(m.Ctx, types.NamespacedName{ - Namespace: m.BackupPolicy.GetNamespace(), - Name: m.BackupPolicy.GetName(), - }, policy) - if err != nil { - return client.IgnoreNotFound(err) - } return retry.RetryOnConflict(retry.DefaultRetry, func() error { + policy := &v1alpha1.OBTenantBackupPolicy{} + err := m.Client.Get(m.Ctx, types.NamespacedName{ + Namespace: m.BackupPolicy.GetNamespace(), + Name: m.BackupPolicy.GetName(), + }, policy) + if err != nil { + return client.IgnoreNotFound(err) + } policy.Status = m.BackupPolicy.Status return m.Client.Status().Update(m.Ctx, policy) }) diff --git a/pkg/resource/obtenantrestore_manager.go b/pkg/resource/obtenantrestore_manager.go index fa66e8202..7c5fb92e6 100644 --- a/pkg/resource/obtenantrestore_manager.go +++ b/pkg/resource/obtenantrestore_manager.go @@ -17,15 +17,19 @@ import ( "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" + "k8s.io/client-go/util/retry" "sigs.k8s.io/controller-runtime/pkg/client" "github.com/oceanbase/ob-operator/api/constants" v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" + "github.com/oceanbase/ob-operator/pkg/oceanbase/model" "github.com/oceanbase/ob-operator/pkg/oceanbase/operation" "github.com/oceanbase/ob-operator/pkg/task" flow "github.com/oceanbase/ob-operator/pkg/task/const/flow/name" taskname "github.com/oceanbase/ob-operator/pkg/task/const/task/name" + taskstatus "github.com/oceanbase/ob-operator/pkg/task/const/task/status" "github.com/oceanbase/ob-operator/pkg/task/strategy" ) @@ -54,7 +58,7 @@ func (m ObTenantRestoreManager) CheckAndUpdateFinalizers() error { } func (m ObTenantRestoreManager) InitStatus() { - m.Resource.Status.Status = constants.RestoreJobRunning + m.Resource.Status.Status = constants.RestoreJobStarting } func (m ObTenantRestoreManager) SetOperationContext(c *v1alpha1.OperationContext) { @@ -72,11 +76,71 @@ func (m ObTenantRestoreManager) FinishTask() { } func (m ObTenantRestoreManager) HandleFailure() { + if m.IsDeleting() { + m.Resource.Status.OperationContext = nil + } else { + operationContext := m.Resource.Status.OperationContext + failureRule := operationContext.OnFailure + switch failureRule.Strategy { + case "": + fallthrough + case strategy.StartOver: + m.Resource.Status.Status = constants.RestoreJobStatus(failureRule.NextTryStatus) + m.Resource.Status.OperationContext = nil + case strategy.RetryFromCurrent: + operationContext.TaskStatus = taskstatus.Pending + case strategy.Pause: + } + } +} +func (m *ObTenantRestoreManager) checkRestoreProgress() error { + con, err := m.getOperationManager() + if err != nil { + return err + } + restoreJob, err := con.GetLatestRestoreProgressOfTenant(m.Resource.Spec.TargetTenant) + if err != nil { + return err + } + if restoreJob != nil { + m.Resource.Status.RestoreProgress = &model.RestoreHistory{RestoreProgress: *restoreJob} + if restoreJob.Status == "SUCCESS" { + m.Recorder.Event(m.Resource, corev1.EventTypeNormal, "Restore job finished", "Restore job finished") + if m.Resource.Spec.RestoreRole == constants.TenantRoleStandby { + m.Resource.Status.Status = constants.RestoreJobStatusReplaying + } else { + m.Resource.Status.Status = constants.RestoreJobStatusActivating + } + } + } + if restoreJob == nil { + restoreHistory, err := con.GetLatestRestoreHistoryOfTenant(m.Resource.Spec.TargetTenant) + if err != nil { + return err + } + m.Resource.Status.RestoreProgress = restoreHistory + if restoreHistory != nil && restoreHistory.Status == "SUCCESS" { + m.Recorder.Event(m.Resource, corev1.EventTypeNormal, "Restore job finished", "Restore job finished") + if m.Resource.Spec.RestoreRole == constants.TenantRoleStandby { + m.Resource.Status.Status = constants.RestoreJobStatusReplaying + } else { + m.Resource.Status.Status = constants.RestoreJobStatusActivating + } + } + } + return nil } func (m ObTenantRestoreManager) UpdateStatus() error { - return m.Client.Status().Update(m.Ctx, m.Resource) + var err error + if m.Resource.Status.Status == constants.RestoreJobRunning { + err = m.checkRestoreProgress() + if err != nil { + return err + } + } + return m.retryUpdateStatus() } func (m ObTenantRestoreManager) GetTaskFunc(name string) (func() error, error) { @@ -85,12 +149,8 @@ func (m ObTenantRestoreManager) GetTaskFunc(name string) (func() error, error) { return m.StartRestoreJobInOB, nil case taskname.StartLogReplay: return m.StartLogReplay, nil - case taskname.CancelRestoreJob: - return m.CancelRestoreJob, nil case taskname.ActivateStandby: return m.ActivateStandby, nil - case taskname.CheckRestoreProgress: - return m.CheckRestoreProgress, nil } return nil, nil } @@ -105,11 +165,13 @@ func (m ObTenantRestoreManager) GetTaskFlow() (*task.TaskFlow, error) { // get task flow depending on BackupPolicy status switch status { case constants.RestoreJobStarting: - taskFlow, err = task.GetRegistry().Get(flow.PrepareBackupPolicy) + taskFlow, err = task.GetRegistry().Get(flow.StartRestoreFlow) + case constants.RestoreJobStatusActivating: + taskFlow, err = task.GetRegistry().Get(flow.RestoreAsPrimaryFlow) + case constants.RestoreJobStatusReplaying: + taskFlow, err = task.GetRegistry().Get(flow.RestoreAsStandbyFlow) case constants.RestoreJobRunning: - taskFlow, err = task.GetRegistry().Get(flow.PrepareBackupPolicy) - case constants.RestoreJobCanceling: - taskFlow, err = task.GetRegistry().Get(flow.PrepareBackupPolicy) + fallthrough case constants.RestoreJobCanceled: fallthrough case constants.RestoreJobSuccessful: @@ -137,3 +199,18 @@ func (m ObTenantRestoreManager) GetTaskFlow() (*task.TaskFlow, error) { func (m ObTenantRestoreManager) PrintErrEvent(err error) { m.Recorder.Event(m.Resource, corev1.EventTypeWarning, "task exec failed", err.Error()) } + +func (m *ObTenantRestoreManager) retryUpdateStatus() error { + resource := &v1alpha1.OBTenantRestore{} + err := m.Client.Get(m.Ctx, types.NamespacedName{ + Namespace: m.Resource.GetNamespace(), + Name: m.Resource.GetName(), + }, resource) + if err != nil { + return client.IgnoreNotFound(err) + } + return retry.RetryOnConflict(retry.DefaultRetry, func() error { + resource.Status = m.Resource.Status + return m.Client.Status().Update(m.Ctx, resource) + }) +} diff --git a/pkg/resource/obtenantrestore_task.go b/pkg/resource/obtenantrestore_task.go index 99e556673..9701b9544 100644 --- a/pkg/resource/obtenantrestore_task.go +++ b/pkg/resource/obtenantrestore_task.go @@ -13,6 +13,8 @@ See the Mulan PSL v2 for more details. package resource import ( + "fmt" + "strings" "time" "github.com/pkg/errors" @@ -36,7 +38,14 @@ import ( // OBTenantManager tasks completion -func (m *OBTenantManager) CreateTenantRestoreJob() error { +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() error { var existingJobs v1alpha1.OBTenantRestoreList var err error @@ -73,7 +82,8 @@ func (m *OBTenantManager) CreateTenantRestoreJob() error { TargetTenant: m.OBTenant.Spec.TenantName, TargetCluster: m.OBTenant.Spec.ClusterName, RestoreRole: m.OBTenant.Spec.TenantRole, - Source: *m.OBTenant.Spec.Source, + Source: *m.OBTenant.Spec.Source.Restore, + Option: m.generateRestoreOption(), }, } err = m.Client.Create(m.Ctx, restoreJob) @@ -104,26 +114,91 @@ func (m *OBTenantManager) WatchRestoreJobToFinish() error { return nil } -// OBTenantRestore tasks - -func (m *ObTenantRestoreManager) StartRestoreJobInOB() error { +func (m *OBTenantManager) CancelTenantRestoreJob() error { + con, err := m.getOceanbaseOperationManager() + if err != nil { + return err + } + err = con.CancelRestoreOfTenant(m.OBTenant.Spec.TenantName) + if err != nil { + return err + } + err = m.deletePool() + if err != nil { + return err + } + err = m.deleteUnitConfig() + if err != nil { + return err + } + err = m.Client.Delete(m.Ctx, &v1alpha1.OBTenantRestore{ + ObjectMeta: metav1.ObjectMeta{ + Name: m.OBTenant.Spec.TenantName + "-restore", + Namespace: m.OBTenant.GetNamespace(), + }, + }) + if err != nil { + m.Logger.Error(err, "delete restore job CR") + return err + } return nil } -func (m *ObTenantRestoreManager) CheckRestoreProgress() error { +// OBTenantRestore tasks + +func (m *ObTenantRestoreManager) StartRestoreJobInOB() error { + con, err := m.getOperationManager() + if err != nil { + return err + } + restoreSpec := m.Resource.Spec.Source + if restoreSpec.Until.Unlimited { + err = con.StartRestoreUnlimited(m.Resource.Spec.TargetTenant, restoreSpec.SourceUri, m.Resource.Spec.Option) + if err != nil { + return err + } + } else { + if restoreSpec.Until.Timestamp != nil { + err = con.StartRestoreWithLimit(m.Resource.Spec.TargetTenant, restoreSpec.SourceUri, m.Resource.Spec.Option, "timestamp", *restoreSpec.Until.Timestamp) + if err != nil { + return err + } + } else if restoreSpec.Until.Scn != nil { + err = con.StartRestoreWithLimit(m.Resource.Spec.TargetTenant, restoreSpec.SourceUri, m.Resource.Spec.Option, "scn", *restoreSpec.Until.Scn) + if err != nil { + return err + } + } else { + return errors.New("Restore until must have a limit key, scn and timestamp are both nil now") + } + } return nil } func (m *ObTenantRestoreManager) StartLogReplay() error { - return nil + con, err := m.getOperationManager() + if err != nil { + return err + } + replayUntil := m.Resource.Spec.Source.ReplayLogUntil + if replayUntil == nil || replayUntil.Unlimited { + err = con.ReplayStandbyLog(m.Resource.Spec.TargetTenant, "UNLIMITED") + } else if replayUntil.Timestamp != nil { + err = con.ReplayStandbyLog(m.Resource.Spec.TargetTenant, fmt.Sprintf("TIME='%s'", *replayUntil.Timestamp)) + } else if replayUntil.Scn != nil { + err = con.ReplayStandbyLog(m.Resource.Spec.TargetTenant, fmt.Sprintf("SCN=%s", *replayUntil.Scn)) + } else { + return errors.New("Replay until with limit must have a limit key, scn and timestamp are both nil now") + } + return err } func (m *ObTenantRestoreManager) ActivateStandby() error { - return nil -} - -func (m *ObTenantRestoreManager) CancelRestoreJob() error { - return nil + con, err := m.getOperationManager() + if err != nil { + return err + } + return con.ActivateStandby(m.Resource.Spec.TargetTenant) } // get operation manager to exec sql diff --git a/pkg/resource/template_manager.go b/pkg/resource/template_manager.go index bf58be220..3f6bdf6ab 100644 --- a/pkg/resource/template_manager.go +++ b/pkg/resource/template_manager.go @@ -17,7 +17,6 @@ import ( "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/tools/record" "sigs.k8s.io/controller-runtime/pkg/client" @@ -26,12 +25,7 @@ import ( "github.com/oceanbase/ob-operator/pkg/task" ) -type Resource interface { - client.Object - runtime.Object -} - -type ObResourceManager[T Resource] struct { +type ObResourceManager[T client.Object] struct { ResourceManager Ctx context.Context diff --git a/pkg/resource/util.go b/pkg/resource/util.go index fc4b90736..28339c465 100644 --- a/pkg/resource/util.go +++ b/pkg/resource/util.go @@ -198,11 +198,3 @@ func NeedAnnotation(pod *corev1.Pod, cni string) bool { return false } } - -func getRef[T any](val T) *T { - return &val -} - -func isZero[T comparable](val T) bool { - return val == *(new(T)) -} diff --git a/pkg/task/all.go b/pkg/task/all.go index 1a7882ba0..41e377ee6 100644 --- a/pkg/task/all.go +++ b/pkg/task/all.go @@ -69,4 +69,10 @@ func init() { // obparameter GetRegistry().Register(flowname.SetOBParameter, SetOBParameter) + + // tenant-level restore + GetRegistry().Register(flowname.StartRestoreFlow, StartRestoreJob) + GetRegistry().Register(flowname.CancelRestoreFlow, CancelRestoreJob) // handle by tenant-manager + GetRegistry().Register(flowname.RestoreAsPrimaryFlow, RestoreAsPrimary) + GetRegistry().Register(flowname.RestoreAsStandbyFlow, RestoreAsStandby) } diff --git a/pkg/task/const/flow/name/task_flow_names.go b/pkg/task/const/flow/name/task_flow_names.go index 408b8fea4..f3a2a0549 100644 --- a/pkg/task/const/flow/name/task_flow_names.go +++ b/pkg/task/const/flow/name/task_flow_names.go @@ -83,7 +83,8 @@ const ( DeleteTenant = "delete tenant" // tenant restore - RestoreTenant = "Restore tenant" + RestoreTenant = "Restore tenant" + CancelRestoreFlow = "cancel restore" ) // tenant-level restore @@ -91,5 +92,4 @@ const ( StartRestoreFlow = "start restore" RestoreAsStandbyFlow = "restore as standby" RestoreAsPrimaryFlow = "restore as primary" - CancelRestoreFlow = "cancel restore" ) diff --git a/pkg/task/const/task/name/restore.go b/pkg/task/const/task/name/restore.go index 7c9ef3507..fb04a3676 100644 --- a/pkg/task/const/task/name/restore.go +++ b/pkg/task/const/task/name/restore.go @@ -13,9 +13,7 @@ See the Mulan PSL v2 for more details. package name const ( - StartRestoreJob = "start restore job" - StartLogReplay = "start log replay" - CancelRestoreJob = "cancel restore job" - ActivateStandby = "activate standby" - CheckRestoreProgress = "check restore progress" + StartRestoreJob = "start restore job" + StartLogReplay = "start log replay" + ActivateStandby = "activate standby" ) diff --git a/pkg/task/const/task/name/task_names.go b/pkg/task/const/task/name/task_names.go index 3e0ded213..fca575cff 100644 --- a/pkg/task/const/task/name/task_names.go +++ b/pkg/task/const/task/name/task_names.go @@ -102,6 +102,7 @@ const ( DeleteTenant = "delete tenant" // tenant restore - CreateRestoreJob = "create restore job" + CreateRestoreJobCR = "create restore job CR" WatchRestoreJobToFinish = "watch restore job to finish" + CancelRestoreJob = "cancel restore job" ) diff --git a/pkg/task/obtenant_flow.go b/pkg/task/obtenant_flow.go index f03d1f7f9..f191854d6 100644 --- a/pkg/task/obtenant_flow.go +++ b/pkg/task/obtenant_flow.go @@ -135,7 +135,7 @@ func RestoreTenant() *TaskFlow { taskname.CheckTenant, taskname.CheckPoolAndUnitConfig, taskname.CreateResourcePoolAndUnitConfig, - taskname.CreateRestoreJob, + taskname.CreateRestoreJobCR, taskname.WatchRestoreJobToFinish, }, TargetStatus: tenantstatus.Running, @@ -145,3 +145,15 @@ func RestoreTenant() *TaskFlow { }, } } + +func CancelRestoreJob() *TaskFlow { + return &TaskFlow{ + OperationContext: &v1alpha1.OperationContext{ + Name: flowname.CancelRestoreFlow, + Tasks: []string{ + taskname.CancelRestoreJob, + }, + TargetStatus: tenantstatus.RestoreCanceled, + }, + } +} diff --git a/pkg/task/restore_flow.go b/pkg/task/restore_flow.go new file mode 100644 index 000000000..9fef7b9e0 --- /dev/null +++ b/pkg/task/restore_flow.go @@ -0,0 +1,60 @@ +/* +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 task + +import ( + "github.com/oceanbase/ob-operator/api/constants" + v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" + flowname "github.com/oceanbase/ob-operator/pkg/task/const/flow/name" + taskname "github.com/oceanbase/ob-operator/pkg/task/const/task/name" + "github.com/oceanbase/ob-operator/pkg/task/strategy" +) + +func StartRestoreJob() *TaskFlow { + return &TaskFlow{ + OperationContext: &v1alpha1.OperationContext{ + Name: flowname.StartRestoreFlow, + Tasks: []string{taskname.StartBackupJob}, + TargetStatus: string(constants.RestoreJobRunning), + OnFailure: strategy.FailureRule{ + NextTryStatus: string(constants.RestoreJobStarting), + }, + }, + } +} + +func RestoreAsPrimary() *TaskFlow { + return &TaskFlow{ + OperationContext: &v1alpha1.OperationContext{ + Name: flowname.RestoreAsPrimaryFlow, + Tasks: []string{taskname.ActivateStandby}, + TargetStatus: string(constants.RestoreJobSuccessful), + OnFailure: strategy.FailureRule{ + NextTryStatus: string(constants.RestoreJobFailed), + }, + }, + } +} + +func RestoreAsStandby() *TaskFlow { + return &TaskFlow{ + OperationContext: &v1alpha1.OperationContext{ + Name: flowname.RestoreAsStandbyFlow, + Tasks: []string{taskname.StartLogReplay}, + TargetStatus: string(constants.RestoreJobSuccessful), + OnFailure: strategy.FailureRule{ + NextTryStatus: string(constants.RestoreJobFailed), + }, + }, + } +} From 48868eaea9d3ffee9f182a06c88f03f7ffe4b73d Mon Sep 17 00:00:00 2001 From: chris-sun-star Date: Fri, 22 Sep 2023 08:53:40 +0000 Subject: [PATCH 11/19] fix tenant add replica, optimize resource update with retry --- distribution/oceanbase/Dockerfile | 8 +- distribution/oceanbase/build.sh | 2 +- pkg/oceanbase/const/sql/tenant.go | 4 +- pkg/resource/obcluster_manager.go | 8 +- pkg/resource/obcluster_task.go | 93 +++++++++++--------- pkg/resource/obparameter_manager.go | 16 ++-- pkg/resource/observer_manager.go | 8 +- pkg/resource/obtenant_manager.go | 16 ++-- pkg/resource/obtenantbackuppolicy_manager.go | 17 ++-- pkg/resource/obzone_manager.go | 8 +- pkg/resource/obzone_task.go | 27 +++--- pkg/task/obtenant_flow.go | 3 + 12 files changed, 111 insertions(+), 99 deletions(-) diff --git a/distribution/oceanbase/Dockerfile b/distribution/oceanbase/Dockerfile index 6c314c965..593523ceb 100644 --- a/distribution/oceanbase/Dockerfile +++ b/distribution/oceanbase/Dockerfile @@ -5,15 +5,13 @@ COPY ./oceanbase-helper . RUN GO11MODULE=ON CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o oceanbase-helper main.go FROM openanolis/anolisos:8.4-x86_64 +ARG VERSION WORKDIR /home/admin/oceanbase RUN mkdir -p /home/admin/oceanbase/bin COPY --from=builder /workspace/oceanbase-helper /home/admin/oceanbase/bin RUN yum -y install python27 RUN pip2 install mysql-connector -i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com - +RUN yum install -y http://mirrors.aliyun.com/oceanbase/community/stable/el/8/x86_64/oceanbase-ce-libs-${VERSION}.el8.x86_64.rpm +RUN yum install -y http://mirrors.aliyun.com/oceanbase/community/stable/el/8/x86_64/oceanbase-ce-${VERSION}.el8.x86_64.rpm RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime - -RUN yum install -y http://mirrors.aliyun.com/oceanbase/community/stable/el/8/x86_64/oceanbase-ce-libs-4.1.0.1-102000042023061314.el8.x86_64.rpm -RUN yum install -y http://mirrors.aliyun.com/oceanbase/community/stable/el/8/x86_64/oceanbase-ce-4.1.0.1-102000042023061314.el8.x86_64.rpm - ENV LD_LIBRARY_PATH /home/admin/oceanbase/lib diff --git a/distribution/oceanbase/build.sh b/distribution/oceanbase/build.sh index 50a63e114..9ff6e687c 100755 --- a/distribution/oceanbase/build.sh +++ b/distribution/oceanbase/build.sh @@ -1,2 +1,2 @@ #!/bin/bash - docker build -t $1 --build-arg GOPROXY=${GOPROXY} . + docker build -t $1 --build-arg GOPROXY=${GOPROXY} --build-arg VERSION=$2 . diff --git a/pkg/oceanbase/const/sql/tenant.go b/pkg/oceanbase/const/sql/tenant.go index cf5b4edca..ceb93d83b 100644 --- a/pkg/oceanbase/const/sql/tenant.go +++ b/pkg/oceanbase/const/sql/tenant.go @@ -31,8 +31,8 @@ const ( GetRsJob = "select job_id, job_type, job_status, tenant_id from DBA_OB_TENANT_JOBS where tenant_name=? and job_status ='INPROGRESS' and job_type='ALTER_TENANT_LOCALITY'" GetObVersion = "SELECT ob_version() as version;" - AddUnitConfigV4 = "CREATE RESOURCE UNIT %s max_cpu ?, memory_size ? %s;" - AddPool = "CREATE RESOURCE POOL %s UNIT=?, UNIT_NUM=?, ZONE_LIST=(?);" + AddUnitConfigV4 = "CREATE RESOURCE UNIT IF NOT EXISTS %s max_cpu ?, memory_size ? %s;" + AddPool = "CREATE RESOURCE POOL IF NOT EXISTS %s UNIT=?, UNIT_NUM=?, ZONE_LIST=(?);" AlterPool = "ALTER RESOURCE POOL %s UNIT=?, UNIT_NUM=?, ZONE_LIST=(?);" AddTenant = "CREATE TENANT IF NOT EXISTS %s CHARSET=?, PRIMARY_ZONE=?, RESOURCE_POOL_LIST=(%s) %s %s;" diff --git a/pkg/resource/obcluster_manager.go b/pkg/resource/obcluster_manager.go index 0e2ff8ad5..863d556fc 100644 --- a/pkg/resource/obcluster_manager.go +++ b/pkg/resource/obcluster_manager.go @@ -122,11 +122,11 @@ func (m *OBClusterManager) CheckAndUpdateFinalizers() error { } func (m *OBClusterManager) retryUpdateStatus() error { - obcluster, err := m.getOBCluster() - if err != nil { - return client.IgnoreNotFound(err) - } return retry.RetryOnConflict(retry.DefaultRetry, func() error { + obcluster, err := m.getOBCluster() + if err != nil { + return client.IgnoreNotFound(err) + } obcluster.Status = *m.OBCluster.Status.DeepCopy() return m.Client.Status().Update(m.Ctx, obcluster) }) diff --git a/pkg/resource/obcluster_task.go b/pkg/resource/obcluster_task.go index 4402d4923..bdc6a707d 100644 --- a/pkg/resource/obcluster_task.go +++ b/pkg/resource/obcluster_task.go @@ -28,6 +28,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/intstr" + "k8s.io/client-go/util/retry" v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" "github.com/oceanbase/ob-operator/pkg/oceanbase/model" @@ -123,24 +124,26 @@ func (m *OBClusterManager) generateWaitOBZoneStatusFunc(status string, timeoutSe } func (m *OBClusterManager) ModifyOBZoneReplica() error { - obzoneList, err := m.listOBZones() - if err != nil { - m.Logger.Error(err, "List obzone failed") - return errors.Wrapf(err, "List obzone of obcluster %s failed", m.OBCluster.Name) - } - for _, zone := range m.OBCluster.Spec.Topology { - for _, obzone := range obzoneList.Items { - if zone.Zone == obzone.Spec.Topology.Zone && zone.Replica != obzone.Spec.Topology.Replica { - m.Logger.Info("Modify obzone replica", "obzone", zone.Zone) - obzone.Spec.Topology.Replica = zone.Replica - err = m.Client.Update(m.Ctx, &obzone) - if err != nil { - return errors.Wrapf(err, "Modify obzone %s replica failed", zone.Zone) + return retry.RetryOnConflict(retry.DefaultRetry, func() error { + obzoneList, err := m.listOBZones() + if err != nil { + m.Logger.Error(err, "List obzone failed") + return errors.Wrapf(err, "List obzone of obcluster %s failed", m.OBCluster.Name) + } + for _, zone := range m.OBCluster.Spec.Topology { + for _, obzone := range obzoneList.Items { + if zone.Zone == obzone.Spec.Topology.Zone && zone.Replica != obzone.Spec.Topology.Replica { + m.Logger.Info("Modify obzone replica", "obzone", zone.Zone) + obzone.Spec.Topology.Replica = zone.Replica + err = m.Client.Update(m.Ctx, &obzone) + if err != nil { + return errors.Wrapf(err, "Modify obzone %s replica failed", zone.Zone) + } } } } - } - return nil + return nil + }) } func (m *OBClusterManager) getZonesToDelete() ([]v1alpha1.OBZone, error) { @@ -427,20 +430,22 @@ func (m *OBClusterManager) CreateOBParameter(parameter *v1alpha1.Parameter) erro } func (m *OBClusterManager) UpdateOBParameter(parameter *v1alpha1.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.Update(m.Ctx, obparameter) - if err != nil { - return errors.Wrap(err, "Update obparameter") - } - return nil + 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 *v1alpha1.Parameter) error { @@ -600,23 +605,25 @@ func (m *OBClusterManager) WaitOBZoneUpgradeFinished(zoneName string) error { // TODO: add timeout func (m *OBClusterManager) RollingUpgradeByZone() error { - zones, err := m.listOBZones() - if err != nil { - return errors.Wrap(err, "Failed to get obzone list") - } - for _, zone := range zones.Items { - // update image and tag - zone.Spec.OBServerTemplate.Image = m.OBCluster.Spec.OBServerTemplate.Image - err = m.Client.Update(m.Ctx, &zone) + return retry.RetryOnConflict(retry.DefaultRetry, func() error { + zones, err := m.listOBZones() if err != nil { - return errors.Wrap(err, "Failed to update obzone image") + return errors.Wrap(err, "Failed to get obzone list") } - err = m.WaitOBZoneUpgradeFinished(zone.Name) - if err != nil { - return errors.Wrapf(err, "Wait obzone %s upgrade finish failed", zone.Name) + for _, zone := range zones.Items { + // update image and tag + zone.Spec.OBServerTemplate.Image = m.OBCluster.Spec.OBServerTemplate.Image + err = m.Client.Update(m.Ctx, &zone) + if err != nil { + return errors.Wrap(err, "Failed to update obzone image") + } + err = m.WaitOBZoneUpgradeFinished(zone.Name) + if err != nil { + return errors.Wrapf(err, "Wait obzone %s upgrade finish failed", zone.Name) + } } - } - return nil + return nil + }) } func (m *OBClusterManager) FinishUpgrade() error { diff --git a/pkg/resource/obparameter_manager.go b/pkg/resource/obparameter_manager.go index 1edb14386..e45688448 100644 --- a/pkg/resource/obparameter_manager.go +++ b/pkg/resource/obparameter_manager.go @@ -105,15 +105,15 @@ func (m *OBParameterManager) CheckAndUpdateFinalizers() error { } func (m *OBParameterManager) retryUpdateStatus() 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) - } 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/pkg/resource/observer_manager.go b/pkg/resource/observer_manager.go index 7c385d7d4..14412b1b4 100644 --- a/pkg/resource/observer_manager.go +++ b/pkg/resource/observer_manager.go @@ -121,11 +121,11 @@ func (m *OBServerManager) getCurrentOBServerFromOB() (*model.OBServer, error) { } func (m *OBServerManager) retryUpdateStatus() error { - observer, err := m.getOBServer() - if err != nil { - return client.IgnoreNotFound(err) - } return retry.RetryOnConflict(retry.DefaultRetry, func() error { + observer, err := m.getOBServer() + if err != nil { + return client.IgnoreNotFound(err) + } observer.Status = *m.OBServer.Status.DeepCopy() return m.Client.Status().Update(m.Ctx, observer) }) diff --git a/pkg/resource/obtenant_manager.go b/pkg/resource/obtenant_manager.go index cd106ac6a..ca9336973 100644 --- a/pkg/resource/obtenant_manager.go +++ b/pkg/resource/obtenant_manager.go @@ -112,15 +112,15 @@ func (m *OBTenantManager) FinishTask() { } func (m *OBTenantManager) retryUpdateStatus() error { - obtenant := &v1alpha1.OBTenant{} - err := m.Client.Get(m.Ctx, types.NamespacedName{ - Namespace: m.OBTenant.GetNamespace(), - Name: m.OBTenant.GetName(), - }, obtenant) - if err != nil { - return client.IgnoreNotFound(err) - } return retry.RetryOnConflict(retry.DefaultRetry, func() error { + obtenant := &v1alpha1.OBTenant{} + err := m.Client.Get(m.Ctx, types.NamespacedName{ + Namespace: m.OBTenant.GetNamespace(), + Name: m.OBTenant.GetName(), + }, obtenant) + if err != nil { + return client.IgnoreNotFound(err) + } obtenant.Status = *m.OBTenant.Status.DeepCopy() return m.Client.Status().Update(m.Ctx, obtenant) }) diff --git a/pkg/resource/obtenantbackuppolicy_manager.go b/pkg/resource/obtenantbackuppolicy_manager.go index b608d3394..54f109baf 100644 --- a/pkg/resource/obtenantbackuppolicy_manager.go +++ b/pkg/resource/obtenantbackuppolicy_manager.go @@ -216,15 +216,15 @@ func (m *ObTenantBackupPolicyManager) GetTaskFlow() (*task.TaskFlow, error) { } func (m *ObTenantBackupPolicyManager) retryUpdateStatus() error { - policy := &v1alpha1.OBTenantBackupPolicy{} - err := m.Client.Get(m.Ctx, types.NamespacedName{ - Namespace: m.BackupPolicy.GetNamespace(), - Name: m.BackupPolicy.GetName(), - }, policy) - if err != nil { - return client.IgnoreNotFound(err) - } return retry.RetryOnConflict(retry.DefaultRetry, func() error { + policy := &v1alpha1.OBTenantBackupPolicy{} + err := m.Client.Get(m.Ctx, types.NamespacedName{ + Namespace: m.BackupPolicy.GetNamespace(), + Name: m.BackupPolicy.GetName(), + }, policy) + if err != nil { + return client.IgnoreNotFound(err) + } policy.Status = m.BackupPolicy.Status return m.Client.Status().Update(m.Ctx, policy) }) @@ -250,5 +250,6 @@ func (m *ObTenantBackupPolicyManager) HandleFailure() { } func (m *ObTenantBackupPolicyManager) PrintErrEvent(err error) { + m.Logger.Error(err, "Print event") m.Recorder.Event(m.BackupPolicy, corev1.EventTypeWarning, "task exec failed", err.Error()) } diff --git a/pkg/resource/obzone_manager.go b/pkg/resource/obzone_manager.go index cb315947f..e648f399c 100644 --- a/pkg/resource/obzone_manager.go +++ b/pkg/resource/obzone_manager.go @@ -164,11 +164,11 @@ func (m *OBZoneManager) CheckAndUpdateFinalizers() error { } func (m *OBZoneManager) retryUpdateStatus() error { - obzone, err := m.getOBZone() - if err != nil { - return client.IgnoreNotFound(err) - } return retry.RetryOnConflict(retry.DefaultRetry, func() error { + obzone, err := m.getOBZone() + if err != nil { + return client.IgnoreNotFound(err) + } obzone.Status = *m.OBZone.Status.DeepCopy() return m.Client.Status().Update(m.Ctx, obzone) }) diff --git a/pkg/resource/obzone_task.go b/pkg/resource/obzone_task.go index 980ae06f0..1ba8f3f27 100644 --- a/pkg/resource/obzone_task.go +++ b/pkg/resource/obzone_task.go @@ -23,6 +23,7 @@ import ( "github.com/oceanbase/ob-operator/pkg/oceanbase/operation" "github.com/pkg/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/client-go/util/retry" v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" ) @@ -252,20 +253,22 @@ func (m *OBZoneManager) OBZoneHealthCheck() error { } func (m *OBZoneManager) UpgradeOBServer() error { - observerList, err := m.listOBServers() - if err != nil { - m.Logger.Error(err, "List observers failed") - return errors.Wrapf(err, "List observrers of obzone %s", m.OBZone.Name) - } - for _, observer := range observerList.Items { - m.Logger.Info("upgrade observer", "observer", observer.Name) - observer.Spec.OBServerTemplate.Image = m.OBZone.Spec.OBServerTemplate.Image - err = m.Client.Update(m.Ctx, &observer) + return retry.RetryOnConflict(retry.DefaultRetry, func() error { + observerList, err := m.listOBServers() if err != nil { - return errors.Wrapf(err, "Upgrade observer %s failed", observer.Name) + m.Logger.Error(err, "List observers failed") + return errors.Wrapf(err, "List observrers of obzone %s", m.OBZone.Name) } - } - return nil + for _, observer := range observerList.Items { + m.Logger.Info("upgrade observer", "observer", observer.Name) + observer.Spec.OBServerTemplate.Image = m.OBZone.Spec.OBServerTemplate.Image + err = m.Client.Update(m.Ctx, &observer) + if err != nil { + return errors.Wrapf(err, "Upgrade observer %s failed", observer.Name) + } + } + return nil + }) } func (m *OBZoneManager) WaitOBServerUpgraded() error { diff --git a/pkg/task/obtenant_flow.go b/pkg/task/obtenant_flow.go index fdc791029..67088271b 100644 --- a/pkg/task/obtenant_flow.go +++ b/pkg/task/obtenant_flow.go @@ -90,6 +90,9 @@ func AddPool() *TaskFlow { Name: flowname.AddPool, Tasks: []string{taskname.CheckPoolAndUnitConfig, taskname.AddResourcePool}, TargetStatus: tenantstatus.Running, + OnFailure: strategy.FailureRule{ + Strategy: strategy.RetryFromCurrent, + }, }, } } From 591d3f0aec9caea84c1309f4660260a615fc5617 Mon Sep 17 00:00:00 2001 From: Powerfool Date: Fri, 22 Sep 2023 16:56:41 +0800 Subject: [PATCH 12/19] fix: obteanntbackup job refresh (#71) --- pkg/controller/obtenantbackup_controller.go | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/pkg/controller/obtenantbackup_controller.go b/pkg/controller/obtenantbackup_controller.go index 175881683..798945b6c 100644 --- a/pkg/controller/obtenantbackup_controller.go +++ b/pkg/controller/obtenantbackup_controller.go @@ -68,18 +68,26 @@ func (r *OBTenantBackupReconciler) Reconcile(ctx context.Context, req ctrl.Reque fallthrough case constants.BackupJobStatusInitializing: crJob.Status.Status = constants.BackupJobStatusRunning - return ctrl.Result{}, r.createBackupJobInOB(ctx, crJob) + return ctrl.Result{ + RequeueAfter: time.Second * 5, + }, r.createBackupJobInOB(ctx, crJob) case constants.BackupJobStatusRunning: - return ctrl.Result{}, r.maintainRunningBackupJob(ctx, crJob) + return ctrl.Result{ + RequeueAfter: time.Second * 5, + }, r.maintainRunningBackupJob(ctx, crJob) default: // Completed, Failed, Canceled, do nothing return ctrl.Result{}, nil } case constants.BackupJobTypeArchive: - return ctrl.Result{}, r.maintainRunningArchiveLogJob(ctx, crJob) + return ctrl.Result{ + RequeueAfter: time.Second * 5, + }, r.maintainRunningArchiveLogJob(ctx, crJob) case constants.BackupJobTypeClean: - return ctrl.Result{}, r.maintainRunningBackupCleanJob(ctx, crJob) + return ctrl.Result{ + RequeueAfter: time.Second * 5, + }, r.maintainRunningBackupCleanJob(ctx, crJob) } return ctrl.Result{ From f68a0e4803e23615b3213f0180670e982dd066bc Mon Sep 17 00:00:00 2001 From: yuyi Date: Fri, 22 Sep 2023 18:30:34 +0800 Subject: [PATCH 13/19] fix: task not registered --- deploy/tenant_restore.yaml | 60 +++++++++++++++++++++++++++++++++++++ pkg/resource/coordinator.go | 5 ++-- pkg/task/all.go | 4 ++- 3 files changed, 66 insertions(+), 3 deletions(-) create mode 100644 deploy/tenant_restore.yaml diff --git a/deploy/tenant_restore.yaml b/deploy/tenant_restore.yaml new file mode 100644 index 000000000..33be89f8b --- /dev/null +++ b/deploy/tenant_restore.yaml @@ -0,0 +1,60 @@ +apiVersion: oceanbase.oceanbase.com/v1alpha1 +kind: OBTenant +metadata: + name: test-standby-restore + namespace: oceanbase +spec: + obcluster: test + tenantName: t1s + unitNum: 1 + charset: utf8mb4 + connectWhiteList: '%' + forceDelete: true + tenantRole: PRIMARY + source: + restore: + sourceUri: "file:///ob-backup/t1/backup,file://ob-backup/t1/archive" + until: + unlimited: true + replayLogUntil: + unlimited: true + pools: + - zone: zone1 + type: + name: FUll + replica: 1 + isActive: true + resource: + maxCPU: 2500m + memorySize: 5Gi + minCPU: 2 + maxIops: 1024 + minIops: 1024 + logDiskSize: + - zone: zone2 + type: + name: Full + replica: 1 + isActive: true + resource: + maxCPU: 2500m + memorySize: 5Gi + minCPU: 2 + maxIops: 1024 + minIops: 1024 + iopsWeight: 2 + logDiskSize: 4Gi + - zone: zone3 + type: + name: Full + replica: 1 + isActive: true + priority: 3 + resource: + maxCPU: 2500m + memorySize: 5Gi + minCPU: 2 + maxIops: 1024 + minIops: 1024 + iopsWeight: 2 + logDiskSize: 4Gi diff --git a/pkg/resource/coordinator.go b/pkg/resource/coordinator.go index 964f88338..d01ab3d6b 100644 --- a/pkg/resource/coordinator.go +++ b/pkg/resource/coordinator.go @@ -103,13 +103,14 @@ func (c *Coordinator) executeTaskFlow(f *task.TaskFlow) { if err != nil { c.Logger.Error(err, "Get task result got error", "task id", f.OperationContext.TaskId) - c.Manager.PrintErrEvent(err) + // c.Manager.PrintErrEvent(err) f.OperationContext.TaskStatus = taskstatus.Failed } else if taskResult != nil { c.Logger.Info("Task finished", "task id", f.OperationContext.TaskId, "task result", taskResult) f.OperationContext.TaskStatus = taskResult.Status if taskResult.Error != nil { - c.Manager.PrintErrEvent(taskResult.Error) + // c.Manager.PrintErrEvent(taskResult.Error) + c.Logger.Error(err, "Task result error", "task id", f.OperationContext.TaskId, "task name", f.OperationContext.Name) } // Didn't get task result, task is still running" diff --git a/pkg/task/all.go b/pkg/task/all.go index 41e377ee6..7a3279056 100644 --- a/pkg/task/all.go +++ b/pkg/task/all.go @@ -59,6 +59,9 @@ func init() { GetRegistry().Register(flowname.MaintainUnitConfig, MaintainUnitConfig) GetRegistry().Register(flowname.DeleteTenant, DeleteTenant) + GetRegistry().Register(flowname.RestoreTenant, RestoreTenant) + GetRegistry().Register(flowname.CancelRestoreFlow, CancelRestoreJob) + // tenant-level backup GetRegistry().Register(flowname.PrepareBackupPolicy, PrepareBackupPolicy) GetRegistry().Register(flowname.StartBackupJob, StartBackupJob) @@ -72,7 +75,6 @@ func init() { // tenant-level restore GetRegistry().Register(flowname.StartRestoreFlow, StartRestoreJob) - GetRegistry().Register(flowname.CancelRestoreFlow, CancelRestoreJob) // handle by tenant-manager GetRegistry().Register(flowname.RestoreAsPrimaryFlow, RestoreAsPrimary) GetRegistry().Register(flowname.RestoreAsStandbyFlow, RestoreAsStandby) } From 5534d68b40f6053d8e11376dd3f5ca6f576a869b Mon Sep 17 00:00:00 2001 From: yuyi Date: Mon, 25 Sep 2023 10:28:38 +0800 Subject: [PATCH 14/19] fix: event recorder unintialized --- cmd/main.go | 10 +++++---- config/manager/kustomization.yaml | 2 +- pkg/controller/config/const.go | 22 ++++++++++--------- .../obtenantoperation_controller.go | 4 +++- pkg/resource/coordinator.go | 5 ++--- 5 files changed, 24 insertions(+), 19 deletions(-) diff --git a/cmd/main.go b/cmd/main.go index 69bfebc48..e006b80ce 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -189,8 +189,9 @@ func main() { // os.Exit(1) // } if err = (&controller.OBTenantBackupPolicyReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor(config.OBTenantBackupPolicyControllerName), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "OBTenantBackupPolicy") os.Exit(1) @@ -200,8 +201,9 @@ func main() { os.Exit(1) } if err = (&controller.OBTenantOperationReconciler{ - Client: mgr.GetClient(), - Scheme: mgr.GetScheme(), + Client: mgr.GetClient(), + Scheme: mgr.GetScheme(), + Recorder: mgr.GetEventRecorderFor(config.OBTenantOperationControllerName), }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "OBTenantOperation") os.Exit(1) diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 8be95a95d..0b5addd19 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -5,4 +5,4 @@ kind: Kustomization images: - name: controller newName: oceanbasedev/ob-operator - newTag: 2.0.0-alpha.5 + newTag: 2.0.0-alpha.8 diff --git a/pkg/controller/config/const.go b/pkg/controller/config/const.go index 4ceef3049..70d5ea134 100644 --- a/pkg/controller/config/const.go +++ b/pkg/controller/config/const.go @@ -13,14 +13,16 @@ See the Mulan PSL v2 for more details. package config const ( - OBClusterControllerName = "obcluster-controller" - OBZoneControllerName = "obzone-controller" - OBServerControllerName = "observer-controller" - OBParameterControllerName = "obparameter-controller" - OBTenantControllerName = "obtenant-controller" - OBUnitControllerName = "obunit-controller" - OBClusterBackupControllerName = "obclusterbackup-controller" - OBTenantBackupControllerName = "obtenantbackup-controller" - OBClusterRestoreControllerName = "obclusterrestore-controller" - OBTenantRestoreControllerName = "obtenantrestore-controller" + OBClusterControllerName = "obcluster-controller" + OBZoneControllerName = "obzone-controller" + OBServerControllerName = "observer-controller" + OBParameterControllerName = "obparameter-controller" + OBTenantControllerName = "obtenant-controller" + OBUnitControllerName = "obunit-controller" + OBClusterBackupControllerName = "obclusterbackup-controller" + OBTenantBackupControllerName = "obtenantbackup-controller" + OBClusterRestoreControllerName = "obclusterrestore-controller" + OBTenantRestoreControllerName = "obtenantrestore-controller" + OBTenantBackupPolicyControllerName = "obtenantbackuppolicy-controller" + OBTenantOperationControllerName = "obtenantoperation-controller" ) diff --git a/pkg/controller/obtenantoperation_controller.go b/pkg/controller/obtenantoperation_controller.go index e3f1b6660..b149164f7 100644 --- a/pkg/controller/obtenantoperation_controller.go +++ b/pkg/controller/obtenantoperation_controller.go @@ -20,6 +20,7 @@ import ( "context" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/log" @@ -30,7 +31,8 @@ import ( // OBTenantOperationReconciler reconciles a OBTenantOperation object type OBTenantOperationReconciler struct { client.Client - Scheme *runtime.Scheme + Scheme *runtime.Scheme + Recorder record.EventRecorder } //+kubebuilder:rbac:groups=oceanbase.oceanbase.com,resources=obtenantoperations,verbs=get;list;watch;create;update;patch;delete diff --git a/pkg/resource/coordinator.go b/pkg/resource/coordinator.go index d01ab3d6b..964f88338 100644 --- a/pkg/resource/coordinator.go +++ b/pkg/resource/coordinator.go @@ -103,14 +103,13 @@ func (c *Coordinator) executeTaskFlow(f *task.TaskFlow) { if err != nil { c.Logger.Error(err, "Get task result got error", "task id", f.OperationContext.TaskId) - // c.Manager.PrintErrEvent(err) + c.Manager.PrintErrEvent(err) f.OperationContext.TaskStatus = taskstatus.Failed } else if taskResult != nil { c.Logger.Info("Task finished", "task id", f.OperationContext.TaskId, "task result", taskResult) f.OperationContext.TaskStatus = taskResult.Status if taskResult.Error != nil { - // c.Manager.PrintErrEvent(taskResult.Error) - c.Logger.Error(err, "Task result error", "task id", f.OperationContext.TaskId, "task name", f.OperationContext.Name) + c.Manager.PrintErrEvent(taskResult.Error) } // Didn't get task result, task is still running" From 19d46483adaefa9419113474aa1afb6bf170f18e Mon Sep 17 00:00:00 2001 From: yuyi Date: Mon, 25 Sep 2023 20:30:48 +0800 Subject: [PATCH 15/19] feat: restore tenant from backup, activate or replay --- Makefile | 18 +- api/v1alpha1/obtenantrestore_types.go | 5 + ...anbase.oceanbase.com_obtenantrestores.yaml | 18 +- config/default/manager_auth_proxy_patch.yaml | 1 + config/manager/kustomization.yaml | 2 +- deploy/operator.yaml | 692 ++++++++++++++---- deploy/tenant_restore.yaml | 4 +- distribution/oceanbase/build.sh | 2 +- go.mod | 14 +- go.sum | 28 +- .../status/tenantstatus/obtenant_status.go | 1 + pkg/controller/obtenantrestore_controller.go | 35 +- pkg/oceanbase/const/sql/restore.go | 2 +- pkg/oceanbase/operation/restore.go | 2 +- pkg/oceanbase/test/restore_test.go | 104 +++ pkg/resource/obtenant_manager.go | 1 + pkg/resource/obtenantrestore_manager.go | 26 +- pkg/resource/obtenantrestore_task.go | 4 + pkg/task/obtenant_flow.go | 2 +- pkg/task/restore_flow.go | 4 +- 20 files changed, 783 insertions(+), 182 deletions(-) diff --git a/Makefile b/Makefile index 68f5dcf6d..c60ec3b95 100644 --- a/Makefile +++ b/Makefile @@ -205,4 +205,20 @@ commit-hook: $(GOLANGCI_LINT) ## Install commit hook. touch .git/hooks/pre-commit chmod +x .git/hooks/pre-commit echo "#!/bin/sh" > .git/hooks/pre-commit - echo "make lint" >> .git/hooks/pre-commit \ No newline at end of file + echo "make lint" >> .git/hooks/pre-commit + +.PHONY: connect +connect: +ifdef TENANT + mysq -h$(shell kubectl get pods -o jsonpath='{.items[0].status.podIP}') -P2881 -A -uroot@${TENANT} +else + mysql -h$(shell kubectl get pods -o jsonpath='{.items[0].status.podIP}') -P2881 -A -uroot -p +endif + +.PHONY: connectob +connectob: +ifdef TENANT + mysq -h$(shell kubectl get pods -o jsonpath='{.items[0].status.podIP}') -P2881 -A -uroot@${TENANT} -Doceanbase +else + mysql -h$(shell kubectl get pods -o jsonpath='{.items[0].status.podIP}') -P2881 -A -uroot -p -Doceanbase +endif diff --git a/api/v1alpha1/obtenantrestore_types.go b/api/v1alpha1/obtenantrestore_types.go index 069a11776..2b35bb9a5 100644 --- a/api/v1alpha1/obtenantrestore_types.go +++ b/api/v1alpha1/obtenantrestore_types.go @@ -60,6 +60,11 @@ func (in *OBTenantRestoreStatus) DeepCopyInto(out *OBTenantRestoreStatus) { //+kubebuilder:object:root=true //+kubebuilder:subresource:status +//+kubebuilder:printcolumn:name="Status",type=string,JSONPath=`.status.status` +//+kubebuilder:printcolumn:name="TargetTenant",type=string,JSONPath=`.spec.targetTenant` +//+kubebuilder:printcolumn:name="TargetCluster",type=string,JSONPath=`.spec.targetCluster` +//+kubebuilder:printcolumn:name="RestoreRole",type=string,JSONPath=`.spec.restoreRole` +//+kubebuilder:printcolumn:name="StatusInDB",type=string,JSONPath=`.status.restoreProgress.status` // OBTenantRestore is the Schema for the obtenantrestores API // An instance of OBTenantRestore stands for a tenant restore job diff --git a/config/crd/bases/oceanbase.oceanbase.com_obtenantrestores.yaml b/config/crd/bases/oceanbase.oceanbase.com_obtenantrestores.yaml index 80b49e04f..14c4e31f3 100644 --- a/config/crd/bases/oceanbase.oceanbase.com_obtenantrestores.yaml +++ b/config/crd/bases/oceanbase.oceanbase.com_obtenantrestores.yaml @@ -14,7 +14,23 @@ spec: singular: obtenantrestore scope: Namespaced versions: - - name: v1alpha1 + - additionalPrinterColumns: + - jsonPath: .status.status + name: Status + type: string + - jsonPath: .spec.targetTenant + name: TargetTenant + type: string + - jsonPath: .spec.targetCluster + name: TargetCluster + type: string + - jsonPath: .spec.restoreRole + name: RestoreRole + type: string + - jsonPath: .status.restoreProgress.status + name: StatusInDB + type: string + name: v1alpha1 schema: openAPIV3Schema: description: OBTenantRestore is the Schema for the obtenantrestores API An diff --git a/config/default/manager_auth_proxy_patch.yaml b/config/default/manager_auth_proxy_patch.yaml index b75126616..f06445fee 100644 --- a/config/default/manager_auth_proxy_patch.yaml +++ b/config/default/manager_auth_proxy_patch.yaml @@ -53,3 +53,4 @@ spec: - "--health-probe-bind-address=:8081" - "--metrics-bind-address=127.0.0.1:8080" - "--leader-elect" + - "--manager-namespace=oceanbase-system" diff --git a/config/manager/kustomization.yaml b/config/manager/kustomization.yaml index 0b5addd19..7d3d6f7d7 100644 --- a/config/manager/kustomization.yaml +++ b/config/manager/kustomization.yaml @@ -5,4 +5,4 @@ kind: Kustomization images: - name: controller newName: oceanbasedev/ob-operator - newTag: 2.0.0-alpha.8 + newTag: 2.0.0 diff --git a/deploy/operator.yaml b/deploy/operator.yaml index 10d49e26e..369051976 100644 --- a/deploy/operator.yaml +++ b/deploy/operator.yaml @@ -4188,7 +4188,6 @@ spec: - piece_switch_interval - round_id - start_scn - - start_scn_display - status - tenant_id - used_piece_id @@ -4626,7 +4625,6 @@ spec: - piece_switch_interval - round_id - start_scn - - start_scn_display - status - tenant_id - used_piece_id @@ -4789,6 +4787,128 @@ spec: --- apiVersion: apiextensions.k8s.io/v1 kind: CustomResourceDefinition +metadata: + annotations: + cert-manager.io/inject-ca-from: oceanbase-system/oceanbase-serving-cert + controller-gen.kubebuilder.io/version: v0.13.0 + name: obtenantoperations.oceanbase.oceanbase.com +spec: + group: oceanbase.oceanbase.com + names: + kind: OBTenantOperation + listKind: OBTenantOperationList + plural: obtenantoperations + singular: obtenantoperation + scope: Namespaced + versions: + - name: v1alpha1 + schema: + openAPIV3Schema: + description: OBTenantOperation is the Schema for the obtenantoperations API + properties: + apiVersion: + description: 'APIVersion defines the versioned schema of this representation + of an object. Servers should convert recognized schemas to the latest + internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources' + type: string + kind: + description: 'Kind is a string value representing the REST resource this + object represents. Servers may infer this from the endpoint the client + submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds' + type: string + metadata: + type: object + spec: + description: OBTenantOperationSpec defines the desired state of OBTenantOperation + properties: + changePwd: + properties: + secretRef: + type: string + tenant: + type: string + required: + - secretRef + - tenant + type: object + failover: + properties: + standbyTenant: + type: string + required: + - standbyTenant + type: object + switchover: + properties: + primaryTenant: + type: string + standbyTenant: + type: string + required: + - primaryTenant + - standbyTenant + type: object + type: + type: string + required: + - type + type: object + status: + description: OBTenantOperationStatus defines the observed state of OBTenantOperation + properties: + operationContext: + properties: + failureRule: + properties: + failureStatus: + type: string + failureStrategy: + type: string + required: + - failureStatus + - failureStrategy + type: object + idx: + type: integer + name: + type: string + targetStatus: + type: string + task: + type: string + taskId: + type: string + taskStatus: + type: string + tasks: + items: + type: string + type: array + required: + - idx + - name + - targetStatus + - task + - taskId + - taskStatus + - tasks + type: object + status: + description: 'INSERT ADDITIONAL STATUS FIELD - define observed state + of cluster Important: Run "make" to regenerate code after modifying + this file' + type: string + required: + - status + type: object + type: object + served: true + storage: true + subresources: + status: {} +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition metadata: annotations: cert-manager.io/inject-ca-from: oceanbase-system/oceanbase-serving-cert @@ -4824,146 +4944,54 @@ spec: spec: description: OBTenantRestoreSpec defines the desired state of OBTenantRestore properties: - obClusterName: - type: string - restoreTenantName: + restoreOption: type: string - sourceUri: + restoreRole: type: string - type: + source: + properties: + cancel: + type: boolean + description: + type: string + replayLogUntil: + properties: + scn: + type: string + timestamp: + type: string + unlimited: + type: boolean + type: object + sourceUri: + type: string + until: + properties: + scn: + type: string + timestamp: + type: string + unlimited: + type: boolean + type: object + required: + - sourceUri + - until + type: object + targetCluster: type: string - until: + targetTenant: type: string required: - - obClusterName - - restoreTenantName - - sourceUri - - type + - restoreOption + - restoreRole + - source + - targetCluster + - targetTenant type: object status: description: OBTenantRestoreStatus defines the observed state of OBTenantRestore properties: - jobStatus: - description: JobStatus represents the current state of a Job. - properties: - active: - description: The number of pending and running pods. - format: int32 - type: integer - completedIndexes: - description: completedIndexes holds the completed indexes when - .spec.completionMode = "Indexed" in a text format. The indexes - are represented as decimal integers separated by commas. The - numbers are listed in increasing order. Three or more consecutive - numbers are compressed and represented by the first and last - element of the series, separated by a hyphen. For example, if - the completed indexes are 1, 3, 4, 5 and 7, they are represented - as "1,3-5,7". - type: string - completionTime: - description: Represents time when the job was completed. It is - not guaranteed to be set in happens-before order across separate - operations. It is represented in RFC3339 form and is in UTC. - The completion time is only set when the job finishes successfully. - format: date-time - type: string - conditions: - description: 'The latest available observations of an object''s - current state. When a Job fails, one of the conditions will - have type "Failed" and status true. When a Job is suspended, - one of the conditions will have type "Suspended" and status - true; when the Job is resumed, the status of this condition - will become false. When a Job is completed, one of the conditions - will have type "Complete" and status true. More info: https://kubernetes.io/docs/concepts/workloads/controllers/jobs-run-to-completion/' - items: - description: JobCondition describes current state of a job. - properties: - lastProbeTime: - description: Last time the condition was checked. - format: date-time - type: string - lastTransitionTime: - description: Last time the condition transit from one status - to another. - format: date-time - type: string - message: - description: Human readable message indicating details about - last transition. - type: string - reason: - description: (brief) reason for the condition's last transition. - type: string - status: - description: Status of the condition, one of True, False, - Unknown. - type: string - type: - description: Type of job condition, Complete or Failed. - type: string - required: - - status - - type - type: object - type: array - x-kubernetes-list-type: atomic - failed: - description: The number of pods which reached phase Failed. - format: int32 - type: integer - ready: - description: "The number of pods which have a Ready condition. - \n This field is beta-level. The job controller populates the - field when the feature gate JobReadyPods is enabled (enabled - by default)." - format: int32 - type: integer - startTime: - description: Represents time when the job controller started processing - a job. When a Job is created in the suspended state, this field - is not set until the first time it is resumed. This field is - reset every time a Job is resumed from suspension. It is represented - in RFC3339 form and is in UTC. - format: date-time - type: string - succeeded: - description: The number of pods which reached phase Succeeded. - format: int32 - type: integer - uncountedTerminatedPods: - description: "uncountedTerminatedPods holds the UIDs of Pods that - have terminated but the job controller hasn't yet accounted - for in the status counters. \n The job controller creates pods - with a finalizer. When a pod terminates (succeeded or failed), - the controller does three steps to account for it in the job - status: \n 1. Add the pod UID to the arrays in this field. 2. - Remove the pod finalizer. 3. Remove the pod UID from the arrays - while increasing the corresponding counter. \n Old jobs might - not be tracked using this field, in which case the field remains - null." - properties: - failed: - description: failed holds UIDs of failed Pods. - items: - description: UID is a type that holds unique ID values, - including UUIDs. Because we don't ONLY use UUIDs, this - is an alias to string. Being a type captures intent and - helps make sure that UIDs and names do not get conflated. - type: string - type: array - x-kubernetes-list-type: set - succeeded: - description: succeeded holds UIDs of succeeded Pods. - items: - description: UID is a type that holds unique ID values, - including UUIDs. Because we don't ONLY use UUIDs, this - is an alias to string. Being a type captures intent and - helps make sure that UIDs and names do not get conflated. - type: string - type: array - x-kubernetes-list-type: set - type: object - type: object operationContext: properties: failureRule: @@ -5001,16 +5029,102 @@ spec: - taskStatus - tasks type: object - progress: - type: string + restoreProgress: + description: RestoreHistory is the history of restore job, matches + view CDB_OB_RESTORE_HISTORY + properties: + backup_cluster_name: + type: string + backup_cluster_version: + type: string + backup_dest: + type: string + backup_piece_list: + type: string + backup_set_list: + type: string + backup_tenant_id: + format: int64 + type: integer + backup_tenant_name: + type: string + description: + type: string + finish_bytes: + format: int64 + type: integer + finish_bytes_display: + type: string + finish_ls_count: + format: int64 + type: integer + finish_tablet_count: + format: int64 + type: integer + finish_timestamp: + type: string + job_id: + format: int64 + type: integer + ls_count: + format: int64 + type: integer + restore_option: + type: string + restore_scn: + format: int64 + type: integer + restore_scn_display: + type: string + restore_tenant_id: + format: int64 + type: integer + restore_tenant_name: + type: string + start_timestamp: + type: string + status: + type: string + tablet_count: + format: int64 + type: integer + tenant_id: + format: int64 + type: integer + total_bytes: + format: int64 + type: integer + total_bytes_display: + type: string + required: + - backup_cluster_name + - backup_cluster_version + - backup_dest + - backup_piece_list + - backup_set_list + - backup_tenant_id + - backup_tenant_name + - finish_ls_count + - finish_tablet_count + - finish_timestamp + - job_id + - ls_count + - restore_option + - restore_scn + - restore_scn_display + - restore_tenant_id + - restore_tenant_name + - start_timestamp + - status + - tablet_count + - tenant_id + type: object status: description: 'INSERT ADDITIONAL STATUS FIELD - define observed state of cluster Important: Run "make" to regenerate code after modifying this file' type: string required: - - jobStatus - - progress - status type: object type: object @@ -5039,7 +5153,10 @@ spec: - jsonPath: .status.status name: status type: string - - jsonPath: .spec.clusterName + - jsonPath: .spec.tenantName + name: tenantName + type: string + - jsonPath: .spec.obcluster name: clusterName type: string - jsonPath: .metadata.creationTimestamp @@ -5089,6 +5206,13 @@ spec: connectWhiteList: default: '%' type: string + credentials: + properties: + root: + type: string + standbyRo: + type: string + type: object forceDelete: default: false type: boolean @@ -5162,8 +5286,47 @@ spec: - zone type: object type: array + source: + description: Source for restoring or creating standby + properties: + restore: + properties: + cancel: + type: boolean + description: + type: string + replayLogUntil: + properties: + scn: + type: string + timestamp: + type: string + unlimited: + type: boolean + type: object + sourceUri: + type: string + until: + properties: + scn: + type: string + timestamp: + type: string + unlimited: + type: boolean + type: object + required: + - sourceUri + - until + type: object + tenant: + type: string + type: object tenantName: type: string + tenantRole: + default: PRIMARY + type: string unitNum: type: integer required: @@ -5313,6 +5476,150 @@ spec: - zoneList type: object type: array + source: + properties: + restore: + description: OBTenantRestoreStatus defines the observed state + of OBTenantRestore + properties: + operationContext: + properties: + failureRule: + properties: + failureStatus: + type: string + failureStrategy: + type: string + required: + - failureStatus + - failureStrategy + type: object + idx: + type: integer + name: + type: string + targetStatus: + type: string + task: + type: string + taskId: + type: string + taskStatus: + type: string + tasks: + items: + type: string + type: array + required: + - idx + - name + - targetStatus + - task + - taskId + - taskStatus + - tasks + type: object + restoreProgress: + description: RestoreHistory is the history of restore job, + matches view CDB_OB_RESTORE_HISTORY + properties: + backup_cluster_name: + type: string + backup_cluster_version: + type: string + backup_dest: + type: string + backup_piece_list: + type: string + backup_set_list: + type: string + backup_tenant_id: + format: int64 + type: integer + backup_tenant_name: + type: string + description: + type: string + finish_bytes: + format: int64 + type: integer + finish_bytes_display: + type: string + finish_ls_count: + format: int64 + type: integer + finish_tablet_count: + format: int64 + type: integer + finish_timestamp: + type: string + job_id: + format: int64 + type: integer + ls_count: + format: int64 + type: integer + restore_option: + type: string + restore_scn: + format: int64 + type: integer + restore_scn_display: + type: string + restore_tenant_id: + format: int64 + type: integer + restore_tenant_name: + type: string + start_timestamp: + type: string + status: + type: string + tablet_count: + format: int64 + type: integer + tenant_id: + format: int64 + type: integer + total_bytes: + format: int64 + type: integer + total_bytes_display: + type: string + required: + - backup_cluster_name + - backup_cluster_version + - backup_dest + - backup_piece_list + - backup_set_list + - backup_tenant_id + - backup_tenant_name + - finish_ls_count + - finish_tablet_count + - finish_timestamp + - job_id + - ls_count + - restore_option + - restore_scn + - restore_scn_display + - restore_tenant_id + - restore_tenant_name + - start_timestamp + - status + - tablet_count + - tenant_id + type: object + status: + description: 'INSERT ADDITIONAL STATUS FIELD - define observed + state of cluster Important: Run "make" to regenerate code + after modifying this file' + type: string + required: + - status + type: object + tenant: + type: string + type: object status: description: 'INSERT ADDITIONAL STATUS FIELD - define observed state of cluster Important: Run "make" to regenerate code after modifying @@ -5344,6 +5651,8 @@ spec: - primaryZone - tenantID type: object + tenantRole: + type: string required: - resourcePool - status @@ -7589,6 +7898,26 @@ rules: - get - patch - update +- apiGroups: + - oceanbase.oceanbase.com + resources: + - obtenant + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - oceanbase.oceanbase.com + resources: + - obtenant/status + verbs: + - get + - patch + - update - apiGroups: - oceanbase.oceanbase.com resources: @@ -7661,6 +7990,52 @@ rules: - get - patch - update +- apiGroups: + - oceanbase.oceanbase.com + resources: + - obtenantoperations + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - oceanbase.oceanbase.com + resources: + - obtenantoperations/finalizers + verbs: + - update +- apiGroups: + - oceanbase.oceanbase.com + resources: + - obtenantoperations/status + verbs: + - get + - patch + - update +- apiGroups: + - oceanbase.oceanbase.com + resources: + - obtenantrestore + verbs: + - create + - delete + - get + - list + - patch + - update + - watch +- apiGroups: + - oceanbase.oceanbase.com + resources: + - obtenantrestore/status + verbs: + - get + - patch + - update - apiGroups: - oceanbase.oceanbase.com resources: @@ -7957,6 +8332,7 @@ spec: - --health-probe-bind-address=:8081 - --metrics-bind-address=127.0.0.1:8080 - --leader-elect + - --manager-namespace=oceanbase-system command: - /manager image: oceanbasedev/ob-operator:2.0.0 @@ -8076,6 +8452,26 @@ metadata: app.kubernetes.io/part-of: ob-operator-generate name: oceanbase-mutating-webhook-configuration webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: oceanbase-webhook-service + namespace: oceanbase-system + path: /mutate-oceanbase-oceanbase-com-v1alpha1-obtenant + failurePolicy: Fail + name: mobtenant.kb.io + rules: + - apiGroups: + - oceanbase.oceanbase.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - obtenants + sideEffects: None - admissionReviewVersions: - v1 clientConfig: @@ -8111,6 +8507,26 @@ metadata: app.kubernetes.io/part-of: ob-operator-generate name: oceanbase-validating-webhook-configuration webhooks: +- admissionReviewVersions: + - v1 + clientConfig: + service: + name: oceanbase-webhook-service + namespace: oceanbase-system + path: /validate-oceanbase-oceanbase-com-v1alpha1-obtenant + failurePolicy: Fail + name: vobtenant.kb.io + rules: + - apiGroups: + - oceanbase.oceanbase.com + apiVersions: + - v1alpha1 + operations: + - CREATE + - UPDATE + resources: + - obtenants + sideEffects: None - admissionReviewVersions: - v1 clientConfig: diff --git a/deploy/tenant_restore.yaml b/deploy/tenant_restore.yaml index 33be89f8b..4a1ea9873 100644 --- a/deploy/tenant_restore.yaml +++ b/deploy/tenant_restore.yaml @@ -13,7 +13,7 @@ spec: tenantRole: PRIMARY source: restore: - sourceUri: "file:///ob-backup/t1/backup,file://ob-backup/t1/archive" + sourceUri: "file:///ob-backup/t1/data_backup_custom1,file:///ob-backup/t1/log_archive_custom1" until: unlimited: true replayLogUntil: @@ -21,7 +21,7 @@ spec: pools: - zone: zone1 type: - name: FUll + name: Full replica: 1 isActive: true resource: diff --git a/distribution/oceanbase/build.sh b/distribution/oceanbase/build.sh index 9ff6e687c..2f9547be8 100755 --- a/distribution/oceanbase/build.sh +++ b/distribution/oceanbase/build.sh @@ -1,2 +1,2 @@ #!/bin/bash - docker build -t $1 --build-arg GOPROXY=${GOPROXY} --build-arg VERSION=$2 . +docker build -t $1 --build-arg GOPROXY=$(go env GOPROXY) --build-arg VERSION=$2 . diff --git a/go.mod b/go.mod index c967649f3..9a0a6abd3 100644 --- a/go.mod +++ b/go.mod @@ -7,8 +7,8 @@ require ( github.com/go-sql-driver/mysql v1.7.1 github.com/google/uuid v1.3.0 github.com/jmoiron/sqlx v1.3.5 - github.com/onsi/ginkgo/v2 v2.9.5 - github.com/onsi/gomega v1.27.7 + github.com/onsi/ginkgo/v2 v2.11.0 + github.com/onsi/gomega v1.27.10 github.com/pkg/errors v0.9.1 github.com/robfig/cron/v3 v3.0.1 github.com/stretchr/testify v1.8.1 @@ -55,13 +55,13 @@ require ( go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect go.uber.org/zap v1.24.0 // indirect - golang.org/x/net v0.10.0 // indirect + golang.org/x/net v0.12.0 // indirect golang.org/x/oauth2 v0.5.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/term v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/sys v0.10.0 // indirect + golang.org/x/term v0.10.0 // indirect + golang.org/x/text v0.11.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.9.1 // indirect + golang.org/x/tools v0.9.3 // indirect gomodules.xyz/jsonpatch/v2 v2.3.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/protobuf v1.30.0 // indirect diff --git a/go.sum b/go.sum index 32da7d867..db5198d41 100644 --- a/go.sum +++ b/go.sum @@ -111,10 +111,10 @@ github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9G github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/onsi/ginkgo/v2 v2.9.5 h1:+6Hr4uxzP4XIUyAkg61dWBw8lb/gc4/X5luuxN/EC+Q= -github.com/onsi/ginkgo/v2 v2.9.5/go.mod h1:tvAoo1QUJwNEU2ITftXTpR7R1RbCzoZUOs3RonqW57k= -github.com/onsi/gomega v1.27.7 h1:fVih9JD6ogIiHUN6ePK7HJidyEDpWGVB5mzM7cWNXoU= -github.com/onsi/gomega v1.27.7/go.mod h1:1p8OOlwo2iUUDsHnOrjE5UKYJ+e3W8eQ3qSlRahPmr4= +github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= +github.com/onsi/ginkgo/v2 v2.11.0/go.mod h1:ZhrRA5XmEE3x3rhlzamx/JJvujdZoJ2uvgI7kR0iZvM= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -179,8 +179,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.5.0 h1:HuArIo48skDwlrvM3sEdHXElYslAMsf3KwRkkW4MC4s= golang.org/x/oauth2 v0.5.0/go.mod h1:9/XBHVqLaWO3/BRHs5jbpYCnOZVjj5V0ndyaAM7KB4I= @@ -200,16 +200,16 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols= -golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -221,8 +221,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= -golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= +golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= +golang.org/x/tools v0.9.3/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/const/status/tenantstatus/obtenant_status.go b/pkg/const/status/tenantstatus/obtenant_status.go index d84943dc4..553665b0c 100644 --- a/pkg/const/status/tenantstatus/obtenant_status.go +++ b/pkg/const/status/tenantstatus/obtenant_status.go @@ -31,4 +31,5 @@ const ( SwitchingRole = "switching role" RestoreCanceled = "restore canceled" CancelingRestore = "canceling restore" + RestoreFailed = "restore failed" ) diff --git a/pkg/controller/obtenantrestore_controller.go b/pkg/controller/obtenantrestore_controller.go index 1077742a9..5acd83ded 100644 --- a/pkg/controller/obtenantrestore_controller.go +++ b/pkg/controller/obtenantrestore_controller.go @@ -18,6 +18,7 @@ package controller import ( "context" + "time" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/tools/record" @@ -26,6 +27,7 @@ import ( "sigs.k8s.io/controller-runtime/pkg/log" v1alpha1 "github.com/oceanbase/ob-operator/api/v1alpha1" + "github.com/oceanbase/ob-operator/pkg/resource" ) // OBTenantRestoreReconciler reconciles a OBTenantRestore object @@ -48,11 +50,38 @@ type OBTenantRestoreReconciler struct { // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.14.4/pkg/reconcile func (r *OBTenantRestoreReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { _ = req - _ = log.FromContext(ctx) - // TODO(user): your logic here + logger := log.FromContext(ctx) + restore := &v1alpha1.OBTenantRestore{} + err := r.Client.Get(ctx, req.NamespacedName, restore) + if err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } - return ctrl.Result{}, nil + // finalizerName := "obtenantrestore.finalizers.oceanbase.com" + // // examine DeletionTimestamp to determine if the policy is under deletion + // if restore.ObjectMeta.DeletionTimestamp.IsZero() { + // if !controllerutil.ContainsFinalizer(restore, finalizerName) { + // controllerutil.AddFinalizer(restore, finalizerName) + // if err := r.Update(ctx, restore); err != nil { + // return ctrl.Result{}, err + // } + // } + // } + + mgr := &resource.ObTenantRestoreManager{ + Ctx: ctx, + Resource: restore, + Client: r.Client, + Recorder: r.Recorder, + Logger: &logger, + } + + coordinator := resource.NewCoordinator(mgr, &logger) + _, err = coordinator.Coordinate() + return ctrl.Result{ + RequeueAfter: 10 * time.Second, + }, err } // SetupWithManager sets up the controller with the Manager. diff --git a/pkg/oceanbase/const/sql/restore.go b/pkg/oceanbase/const/sql/restore.go index f7c87b18a..d99c60fd6 100644 --- a/pkg/oceanbase/const/sql/restore.go +++ b/pkg/oceanbase/const/sql/restore.go @@ -23,7 +23,7 @@ const ( StartRestoreWithLimit = "ALTER SYSTEM RESTORE %s FROM ? UNTIL %s=? WITH ?" // tenant_name, uri, restore_option StartRestoreUnlimited = "ALTER SYSTEM RESTORE %s FROM ? WITH ?" - CancelRestore = "ALTER SYSTEM CANCEL RESTORE ?" + CancelRestore = "ALTER SYSTEM CANCEL RESTORE %s" ReplayStandbyLog = "ALTER SYSTEM RECOVER STANDBY TENANT ? UNTIL %s" ActivateStandby = "ALTER SYSTEM ACTIVATE STANDBY TENANT ?" QueryRestoreProgress = "SELECT " + restoreProgressFields + " FROM CDB_OB_RESTORE_PROGRESS" diff --git a/pkg/oceanbase/operation/restore.go b/pkg/oceanbase/operation/restore.go index 35408bfba..a809e502e 100644 --- a/pkg/oceanbase/operation/restore.go +++ b/pkg/oceanbase/operation/restore.go @@ -51,7 +51,7 @@ func (m *OceanbaseOperationManager) StartRestoreUnlimited(tenantName, uri, resto } func (m *OceanbaseOperationManager) CancelRestoreOfTenant(tenantName string) error { - err := m.ExecWithDefaultTimeout(sql.CancelRestore, tenantName) + err := m.ExecWithDefaultTimeout(fmt.Sprintf(sql.CancelRestore, tenantName)) if err != nil { m.Logger.Error(err, "Got exception when cancel restore of tenant") return errors.Wrap(err, "Cancel restore of tenant") diff --git a/pkg/oceanbase/test/restore_test.go b/pkg/oceanbase/test/restore_test.go index 2da23302c..700d285fd 100644 --- a/pkg/oceanbase/test/restore_test.go +++ b/pkg/oceanbase/test/restore_test.go @@ -333,3 +333,107 @@ var _ = Describe("Test Restore Operation", Serial, Label("restore"), func() { } }) }) + +var _ = Describe("Test canceling restore", Serial, Label("canceling"), func() { + var con *operation.OceanbaseOperationManager + var standbyName string + var _ = BeforeEach(func() { + var err error + logger := logr.Discard() + ds := connector.NewOceanBaseDataSource(host, port, sysUser, "sys", sysPassword, database) + con, err = operation.GetOceanbaseOperationManager(ds) + Expect(err).To(BeNil()) + con.Logger = &logger + standbyName = tenant + "_standby" + }) + It("Create units", func() { + By("Create unit") + unitList, err := con.GetUnitConfigV4List() + Expect(err).To(BeNil()) + exists := false + for _, unit := range unitList { + if unit.Name == "unit_test" { + exists = true + break + } + } + if !exists { + err = con.AddUnitConfigV4(&model.UnitConfigV4SQLParam{ + UnitConfigName: "unit_test", + MinCPU: 2, + MaxCPU: 2, + MemorySize: 2147483648, + MaxIops: 1024, + LogDiskSize: 2147483648, + MinIops: 1024, + }) + Expect(err).To(BeNil()) + } + + }) + It("Start and cancel the restore", func() { + By("Check target tenant's existence") + exists, err := con.CheckTenantExistByName(standbyName) + Expect(err).To(BeNil()) + if exists { + Skip("Target standby tenant exists") + } + + By("Create resource pool") + poolList, err := con.GetPoolList() + Expect(err).To(BeNil()) + exists = false + for _, pool := range poolList { + if pool.Name == "pool_test_standby1" { + exists = true + break + } + } + if !exists { + for _, v := range []int{1, 2, 3} { + err = con.AddPool(model.PoolSQLParam{ + UnitNum: 1, + PoolName: fmt.Sprintf("pool_test_standby%d", v), + ZoneList: fmt.Sprintf("zone%d", v), + UnitName: "unit_test", + }) + Expect(err).To(BeNil()) + } + } + + By("Trigger restoration of standby tenant") + backupDest := "file:///ob-backup/" + tenant + "/data_backup_custom1" + archiveDest := "file:///ob-backup/" + tenant + "/log_archive_custom1" + err = con.StartRestoreUnlimited(standbyName, strings.Join([]string{backupDest, archiveDest}, ","), "pool_list=pool_test_standby1,pool_test_standby2,pool_test_standby3") + Expect(err).To(BeNil()) + + By("Cancel restoration of tenant") + err = con.CancelRestoreOfTenant(standbyName) + Expect(err).To(BeNil()) + }) + + It("Delete Tenants", Label("delete_tenants"), func() { + By("Deleting primary tenant") + exists, err := con.CheckTenantExistByName(tenant) + Expect(err).To(BeNil()) + if exists { + Expect(con.DeleteTenant(tenant, true)).To(BeNil()) + } + + By("Deleting standby tenants") + exists, err = con.CheckTenantExistByName(standbyName) + Expect(err).To(BeNil()) + if exists { + Expect(con.DeleteTenant(standbyName, true)).To(BeNil()) + } + + By("Deleting resource pools") + for _, pool := range []string{"pool_test1", "pool_test2", "pool_test3", "pool_test_standby1", "pool_test_standby2", "pool_test_standby3"} { + exists, err = con.CheckPoolExistByName(pool) + Expect(err).To(BeNil()) + if exists { + Expect(con.DeletePool(pool)).To(BeNil()) + } + } + }) +}) diff --git a/pkg/resource/obtenant_manager.go b/pkg/resource/obtenant_manager.go index 9ddd4659e..9fe2bf9db 100644 --- a/pkg/resource/obtenant_manager.go +++ b/pkg/resource/obtenant_manager.go @@ -141,6 +141,7 @@ func (m *OBTenantManager) UpdateStatus() error { m.OBTenant.Spec.Source != nil && m.OBTenant.Spec.Source.Restore != nil && m.OBTenant.Spec.Source.Restore.Cancel { + m.OBTenant.Status.OperationContext = nil m.OBTenant.Status.Status = tenantstatus.CancelingRestore } else if m.OBTenant.Status.Status != tenantstatus.Running { m.Logger.Info(fmt.Sprintf("OBTenant status is %s (not running), skip compare", m.OBTenant.Status.Status)) diff --git a/pkg/resource/obtenantrestore_manager.go b/pkg/resource/obtenantrestore_manager.go index 7c5fb92e6..4c3e390e7 100644 --- a/pkg/resource/obtenantrestore_manager.go +++ b/pkg/resource/obtenantrestore_manager.go @@ -16,6 +16,7 @@ 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/tools/record" @@ -112,6 +113,9 @@ func (m *ObTenantRestoreManager) checkRestoreProgress() error { } else { m.Resource.Status.Status = constants.RestoreJobStatusActivating } + } else if restoreJob.Status == "FAIL" { + m.Recorder.Event(m.Resource, corev1.EventTypeWarning, "Restore job is failed", "Restore job is failed") + m.Resource.Status.Status = constants.RestoreJobFailed } } if restoreJob == nil { @@ -127,6 +131,9 @@ func (m *ObTenantRestoreManager) checkRestoreProgress() error { } else { m.Resource.Status.Status = constants.RestoreJobStatusActivating } + } else if restoreHistory != nil && restoreHistory.Status == "FAIL" { + m.Recorder.Event(m.Resource, corev1.EventTypeWarning, "Restore job is failed", "Restore job is failed") + m.Resource.Status.Status = constants.RestoreJobFailed } } return nil @@ -151,8 +158,9 @@ func (m ObTenantRestoreManager) GetTaskFunc(name string) (func() error, error) { return m.StartLogReplay, nil case taskname.ActivateStandby: return m.ActivateStandby, nil + default: + return nil, errors.New("Task name not registered") } - return nil, nil } func (m ObTenantRestoreManager) GetTaskFlow() (*task.TaskFlow, error) { @@ -201,15 +209,15 @@ func (m ObTenantRestoreManager) PrintErrEvent(err error) { } func (m *ObTenantRestoreManager) retryUpdateStatus() error { - resource := &v1alpha1.OBTenantRestore{} - err := m.Client.Get(m.Ctx, types.NamespacedName{ - Namespace: m.Resource.GetNamespace(), - Name: m.Resource.GetName(), - }, resource) - if err != nil { - return client.IgnoreNotFound(err) - } return retry.RetryOnConflict(retry.DefaultRetry, func() error { + resource := &v1alpha1.OBTenantRestore{} + err := m.Client.Get(m.Ctx, types.NamespacedName{ + Namespace: m.Resource.GetNamespace(), + Name: m.Resource.GetName(), + }, resource) + if err != nil { + return client.IgnoreNotFound(err) + } resource.Status = m.Resource.Status return m.Client.Status().Update(m.Ctx, resource) }) diff --git a/pkg/resource/obtenantrestore_task.go b/pkg/resource/obtenantrestore_task.go index 9701b9544..e466d9be2 100644 --- a/pkg/resource/obtenantrestore_task.go +++ b/pkg/resource/obtenantrestore_task.go @@ -141,6 +141,10 @@ func (m *OBTenantManager) CancelTenantRestoreJob() error { m.Logger.Error(err, "delete restore job CR") return err } + err = m.Client.Delete(m.Ctx, m.OBTenant) + if err != nil { + m.Logger.Error(err, "delete tenant CR") + } return nil } diff --git a/pkg/task/obtenant_flow.go b/pkg/task/obtenant_flow.go index 5d4a04e5d..93edee6d9 100644 --- a/pkg/task/obtenant_flow.go +++ b/pkg/task/obtenant_flow.go @@ -143,7 +143,7 @@ func RestoreTenant() *TaskFlow { }, TargetStatus: tenantstatus.Running, OnFailure: strategy.FailureRule{ - NextTryStatus: tenantstatus.Restoring, + NextTryStatus: tenantstatus.RestoreFailed, }, }, } diff --git a/pkg/task/restore_flow.go b/pkg/task/restore_flow.go index 9fef7b9e0..133a3b711 100644 --- a/pkg/task/restore_flow.go +++ b/pkg/task/restore_flow.go @@ -24,10 +24,10 @@ func StartRestoreJob() *TaskFlow { return &TaskFlow{ OperationContext: &v1alpha1.OperationContext{ Name: flowname.StartRestoreFlow, - Tasks: []string{taskname.StartBackupJob}, + Tasks: []string{taskname.StartRestoreJob}, TargetStatus: string(constants.RestoreJobRunning), OnFailure: strategy.FailureRule{ - NextTryStatus: string(constants.RestoreJobStarting), + NextTryStatus: string(constants.RestoreJobFailed), }, }, } From 4928eed3c99c42cf6602c06aa08a3db84ab734b4 Mon Sep 17 00:00:00 2001 From: yuyi Date: Mon, 25 Sep 2023 20:32:56 +0800 Subject: [PATCH 16/19] chore: add gap between triggering restore job and canceling restore --- pkg/oceanbase/test/restore_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/pkg/oceanbase/test/restore_test.go b/pkg/oceanbase/test/restore_test.go index 700d285fd..7422d0184 100644 --- a/pkg/oceanbase/test/restore_test.go +++ b/pkg/oceanbase/test/restore_test.go @@ -406,6 +406,7 @@ var _ = Describe("Test canceling restore", Serial, Label("canceling"), func() { archiveDest := "file:///ob-backup/" + tenant + "/log_archive_custom1" err = con.StartRestoreUnlimited(standbyName, strings.Join([]string{backupDest, archiveDest}, ","), "pool_list=pool_test_standby1,pool_test_standby2,pool_test_standby3") Expect(err).To(BeNil()) + time.Sleep(5 * time.Second) By("Cancel restoration of tenant") err = con.CancelRestoreOfTenant(standbyName) From 8be5c6c67165e8cce1b3f80890244922d208f12e Mon Sep 17 00:00:00 2001 From: yuyi Date: Tue, 26 Sep 2023 14:27:27 +0800 Subject: [PATCH 17/19] fix: Makefile typo & backup_policy delete logic --- Makefile | 4 +- pkg/resource/obtenantbackuppolicy_manager.go | 47 +++++++++++++++++--- 2 files changed, 43 insertions(+), 8 deletions(-) diff --git a/Makefile b/Makefile index c60ec3b95..15b9dcc1f 100644 --- a/Makefile +++ b/Makefile @@ -210,7 +210,7 @@ commit-hook: $(GOLANGCI_LINT) ## Install commit hook. .PHONY: connect connect: ifdef TENANT - mysq -h$(shell kubectl get pods -o jsonpath='{.items[0].status.podIP}') -P2881 -A -uroot@${TENANT} + mysql -h$(shell kubectl get pods -o jsonpath='{.items[0].status.podIP}') -P2881 -A -uroot@${TENANT} else mysql -h$(shell kubectl get pods -o jsonpath='{.items[0].status.podIP}') -P2881 -A -uroot -p endif @@ -218,7 +218,7 @@ endif .PHONY: connectob connectob: ifdef TENANT - mysq -h$(shell kubectl get pods -o jsonpath='{.items[0].status.podIP}') -P2881 -A -uroot@${TENANT} -Doceanbase + mysql -h$(shell kubectl get pods -o jsonpath='{.items[0].status.podIP}') -P2881 -A -uroot@${TENANT} -Doceanbase else mysql -h$(shell kubectl get pods -o jsonpath='{.items[0].status.podIP}') -P2881 -A -uroot -p -Doceanbase endif diff --git a/pkg/resource/obtenantbackuppolicy_manager.go b/pkg/resource/obtenantbackuppolicy_manager.go index 6581b6436..f53cdfa13 100644 --- a/pkg/resource/obtenantbackuppolicy_manager.go +++ b/pkg/resource/obtenantbackuppolicy_manager.go @@ -20,6 +20,7 @@ import ( "github.com/pkg/errors" "github.com/robfig/cron/v3" corev1 "k8s.io/api/core/v1" + kubeerrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/types" "k8s.io/client-go/tools/record" "k8s.io/client-go/util/retry" @@ -58,15 +59,35 @@ func (m *ObTenantBackupPolicyManager) IsDeleting() bool { func (m *ObTenantBackupPolicyManager) CheckAndUpdateFinalizers() error { policy := m.BackupPolicy finalizerName := "obtenantbackuppolicy.finalizers.oceanbase.com" + finalizerFinished := false if controllerutil.ContainsFinalizer(policy, finalizerName) { - err := m.StopBackup() + obcluster, err := m.getOBCluster() if err != nil { - return err + if kubeerrors.IsNotFound(err) { + m.Logger.Info("OBCluster is deleted, no need to wait finalizer") + finalizerFinished = true + } else { + m.Logger.Error(err, "query obcluster failed") + return errors.Wrap(err, "Get obcluster failed") + } + } else if !obcluster.ObjectMeta.DeletionTimestamp.IsZero() { + m.Logger.Info("OBCluster is deleting, no need to wait finalizer") + finalizerFinished = true } - // remove our finalizer from the list and update it. - controllerutil.RemoveFinalizer(policy, finalizerName) - if err := m.Client.Update(m.Ctx, policy); err != nil { - return err + + if !finalizerFinished { + err := m.StopBackup() + if err != nil { + return err + } + finalizerFinished = true + } + if finalizerFinished { + // remove our finalizer from the list and update it. + controllerutil.RemoveFinalizer(policy, finalizerName) + if err := m.Client.Update(m.Ctx, policy); err != nil { + return err + } } } return nil @@ -260,3 +281,17 @@ func (m *ObTenantBackupPolicyManager) PrintErrEvent(err error) { m.Logger.Error(err, "Print event") m.Recorder.Event(m.BackupPolicy, corev1.EventTypeWarning, "task exec failed", err.Error()) } + +func (m *ObTenantBackupPolicyManager) getOBCluster() (*v1alpha1.OBCluster, error) { + clusterName := m.BackupPolicy.Spec.ObClusterName + obcluster := &v1alpha1.OBCluster{} + err := m.Client.Get(m.Ctx, types.NamespacedName{ + Namespace: m.BackupPolicy.Namespace, + Name: clusterName, + }, obcluster) + if err != nil { + m.Logger.Error(err, "get obcluster failed", "clusterName", clusterName, "namespaced", m.BackupPolicy.Namespace) + return nil, errors.Wrap(err, "get obcluster failed") + } + return obcluster, nil +} From 22e184c66905dd20510725ab017702021fa8bddb Mon Sep 17 00:00:00 2001 From: yuyi Date: Wed, 27 Sep 2023 14:12:12 +0800 Subject: [PATCH 18/19] chore: address some problems in PR comments --- deploy/tenant.yaml | 1 + distribution/oceanbase/build.sh | 2 +- pkg/oceanbase/const/config/tenant.go | 5 +++-- pkg/oceanbase/operation/restore.go | 6 +++--- pkg/resource/obtenantrestore_manager.go | 3 +-- 5 files changed, 9 insertions(+), 8 deletions(-) diff --git a/deploy/tenant.yaml b/deploy/tenant.yaml index 935c51396..9597c0a9d 100644 --- a/deploy/tenant.yaml +++ b/deploy/tenant.yaml @@ -22,6 +22,7 @@ spec: minCPU: 2 maxIops: 1024 minIops: 1024 + iopsWeight: logDiskSize: - zone: zone2 type: diff --git a/distribution/oceanbase/build.sh b/distribution/oceanbase/build.sh index 2f9547be8..e5f41d612 100755 --- a/distribution/oceanbase/build.sh +++ b/distribution/oceanbase/build.sh @@ -1,2 +1,2 @@ #!/bin/bash -docker build -t $1 --build-arg GOPROXY=$(go env GOPROXY) --build-arg VERSION=$2 . +docker build -t $1 --build-arg GOPROXY=${GOPROXY} --build-arg VERSION=$2 . diff --git a/pkg/oceanbase/const/config/tenant.go b/pkg/oceanbase/const/config/tenant.go index 32f1e061f..724dda307 100644 --- a/pkg/oceanbase/const/config/tenant.go +++ b/pkg/oceanbase/const/config/tenant.go @@ -15,6 +15,7 @@ package config import "time" const ( - TenantSqlTimeout = 600 * time.Second - PollingJobSleepTime = 1 * time.Second + TenantSqlTimeout = 600 * time.Second + PollingJobSleepTime = 1 * time.Second + TenantRestoreTimeOut = 600 * time.Second ) diff --git a/pkg/oceanbase/operation/restore.go b/pkg/oceanbase/operation/restore.go index a809e502e..dce41c7ae 100644 --- a/pkg/oceanbase/operation/restore.go +++ b/pkg/oceanbase/operation/restore.go @@ -14,10 +14,10 @@ package operation import ( "fmt" - "time" "github.com/pkg/errors" + "github.com/oceanbase/ob-operator/pkg/oceanbase/const/config" "github.com/oceanbase/ob-operator/pkg/oceanbase/const/sql" "github.com/oceanbase/ob-operator/pkg/oceanbase/model" ) @@ -33,7 +33,7 @@ func (m *OceanbaseOperationManager) SetRestorePassword(password string) error { func (m *OceanbaseOperationManager) StartRestoreWithLimit(tenantName, uri, restoreOption string, limitKey, limitValue any) error { sqlStatement := fmt.Sprintf(sql.StartRestoreWithLimit, tenantName, limitKey) - err := m.ExecWithTimeout(600*time.Second, sqlStatement, uri, limitValue, restoreOption) + err := m.ExecWithTimeout(config.TenantRestoreTimeOut, sqlStatement, uri, limitValue, restoreOption) if err != nil { m.Logger.Error(err, "Got exception when start restore with limit") return errors.Wrap(err, "Start restore with limit") @@ -42,7 +42,7 @@ func (m *OceanbaseOperationManager) StartRestoreWithLimit(tenantName, uri, resto } func (m *OceanbaseOperationManager) StartRestoreUnlimited(tenantName, uri, restoreOption string) error { - err := m.ExecWithTimeout(600*time.Second, fmt.Sprintf(sql.StartRestoreUnlimited, tenantName), uri, restoreOption) + err := m.ExecWithTimeout(config.TenantRestoreTimeOut, fmt.Sprintf(sql.StartRestoreUnlimited, tenantName), uri, restoreOption) if err != nil { m.Logger.Error(err, "Got exception when start restore unlimited") return errors.Wrap(err, "Start restore unlimited") diff --git a/pkg/resource/obtenantrestore_manager.go b/pkg/resource/obtenantrestore_manager.go index 4c3e390e7..26ab4f1e9 100644 --- a/pkg/resource/obtenantrestore_manager.go +++ b/pkg/resource/obtenantrestore_manager.go @@ -117,8 +117,7 @@ func (m *ObTenantRestoreManager) checkRestoreProgress() error { m.Recorder.Event(m.Resource, corev1.EventTypeWarning, "Restore job is failed", "Restore job is failed") m.Resource.Status.Status = constants.RestoreJobFailed } - } - if restoreJob == nil { + } else { restoreHistory, err := con.GetLatestRestoreHistoryOfTenant(m.Resource.Spec.TargetTenant) if err != nil { return err From 6cc128c36df066e93340733ee33557056aa31dad Mon Sep 17 00:00:00 2001 From: yuyi Date: Wed, 27 Sep 2023 14:15:52 +0800 Subject: [PATCH 19/19] fix: typo in Makefile --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 15b9dcc1f..a3aab1c01 100644 --- a/Makefile +++ b/Makefile @@ -193,7 +193,7 @@ tools: $(YQ) $(SEMVER) .PHONY: GOLANGCI_LINT GOLANGCI_LINT ?= $(LOCALBIN)/golangci-lint -$(GOLANG_LINT): +$(GOLANGCI_LINT): GOBIN=$(LOCALBIN) go install github.com/golangci/golangci-lint/cmd/golangci-lint@${GOLANG_CI_VERSION} .PHONY: lint