diff --git a/cmd/backup-manager/app/backup/manager.go b/cmd/backup-manager/app/backup/manager.go index 6b3fe8ecdb..356790f7cf 100644 --- a/cmd/backup-manager/app/backup/manager.go +++ b/cmd/backup-manager/app/backup/manager.go @@ -27,6 +27,7 @@ import ( bkconstants "github.com/pingcap/tidb-operator/pkg/backup/constants" listers "github.com/pingcap/tidb-operator/pkg/client/listers/pingcap/v1alpha1" "github.com/pingcap/tidb-operator/pkg/controller" + pkgutil "github.com/pingcap/tidb-operator/pkg/util" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" errorutils "k8s.io/apimachinery/pkg/util/errors" @@ -110,7 +111,7 @@ func (bm *Manager) ProcessBackup() error { klog.Errorf("can't get dsn of tidb cluster %s, err: %s", bm, err) return false, err } - db, err = util.OpenDB(ctx, dsn) + db, err = pkgutil.OpenDB(ctx, dsn) if err != nil { klog.Warningf("can't connect to tidb cluster %s, err: %s", bm, err) if ctx.Err() != nil { diff --git a/cmd/backup-manager/app/export/manager.go b/cmd/backup-manager/app/export/manager.go index a2cb76407a..86df964627 100644 --- a/cmd/backup-manager/app/export/manager.go +++ b/cmd/backup-manager/app/export/manager.go @@ -28,6 +28,7 @@ import ( backuputil "github.com/pingcap/tidb-operator/pkg/backup/util" listers "github.com/pingcap/tidb-operator/pkg/client/listers/pingcap/v1alpha1" "github.com/pingcap/tidb-operator/pkg/controller" + pkgutil "github.com/pingcap/tidb-operator/pkg/util" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" errorutils "k8s.io/apimachinery/pkg/util/errors" @@ -121,7 +122,7 @@ func (bm *BackupManager) ProcessBackup() error { return false, err } - db, err = util.OpenDB(ctx, dsn) + db, err = pkgutil.OpenDB(ctx, dsn) if err != nil { klog.Warningf("can't connect to tidb cluster %s, err: %s", bm, err) if ctx.Err() != nil { diff --git a/cmd/backup-manager/app/restore/manager.go b/cmd/backup-manager/app/restore/manager.go index d52ade6dbf..2f72607b79 100644 --- a/cmd/backup-manager/app/restore/manager.go +++ b/cmd/backup-manager/app/restore/manager.go @@ -26,6 +26,7 @@ import ( bkconstants "github.com/pingcap/tidb-operator/pkg/backup/constants" listers "github.com/pingcap/tidb-operator/pkg/client/listers/pingcap/v1alpha1" "github.com/pingcap/tidb-operator/pkg/controller" + pkgutil "github.com/pingcap/tidb-operator/pkg/util" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" errorutils "k8s.io/apimachinery/pkg/util/errors" @@ -107,7 +108,7 @@ func (rm *Manager) ProcessRestore() error { return false, err } - db, err = util.OpenDB(ctx, dsn) + db, err = pkgutil.OpenDB(ctx, dsn) if err != nil { klog.Warningf("can't connect to tidb cluster %s, err: %s", rm, err) if ctx.Err() != nil { diff --git a/cmd/backup-manager/app/util/util.go b/cmd/backup-manager/app/util/util.go index aa0fd8fe3d..9f132ec271 100644 --- a/cmd/backup-manager/app/util/util.go +++ b/cmd/backup-manager/app/util/util.go @@ -15,7 +15,6 @@ package util import ( "context" - "database/sql" "fmt" "io/ioutil" "os" @@ -113,19 +112,6 @@ func GetStoragePath(backup *v1alpha1.Backup) (string, error) { } } -// OpenDB opens db -func OpenDB(ctx context.Context, dsn string) (*sql.DB, error) { - db, err := sql.Open("mysql", dsn) - if err != nil { - return nil, fmt.Errorf("open datasource failed, err: %v", err) - } - if err := db.PingContext(ctx); err != nil { - db.Close() - return nil, fmt.Errorf("cannot connect to mysql, err: %v", err) - } - return db, nil -} - // IsFileExist return true if file exist and is a regular file, other cases return false func IsFileExist(file string) bool { fi, err := os.Stat(file) diff --git a/docs/api-references/docs.md b/docs/api-references/docs.md index 36516e0523..40d60924c4 100644 --- a/docs/api-references/docs.md +++ b/docs/api-references/docs.md @@ -14624,6 +14624,33 @@ Kubernetes meta/v1.Time +

TiDBInitializer

+

+(Appears on: +TiDBSpec) +

+

+

+ + + + + + + + + + + + + +
FieldDescription
+createPassword
+ +bool + +
+

TiDBMember

(Appears on: @@ -15140,6 +15167,20 @@ TiDBProbe the default behavior is like setting type as “tcp”

+ + +initializer
+ + +TiDBInitializer + + + + +(Optional) +

Initializer is the init configurations of TiDB

+ +

TiDBStatus

@@ -15226,6 +15267,16 @@ string + + +passwordInitialized
+ +bool + + + + +

TiDBTLSClient

diff --git a/examples/basic-random-password/README.md b/examples/basic-random-password/README.md new file mode 100644 index 0000000000..896260382a --- /dev/null +++ b/examples/basic-random-password/README.md @@ -0,0 +1,55 @@ +# A Basic TiDB cluster with random password initialized + +> **Note:** +> +> This setup is for test or demo purpose only and **IS NOT** applicable for critical environment. + +The following steps will create a TiDB cluster with random password initialized. + +## Install + +The following commands is assumed to be executed in this directory. + +Install the cluster: + +```bash +kubectl -n apply -f ./ +``` + +Wait for cluster Pods ready: + +```bash +watch kubectl -n get pod +``` + +## Explore + +Get the password from secret: + +```bash +kubectl get secret basic-init -o=jsonpath='{.data.root}' -n | base64 --decode +``` + +Explore the TiDB SQL interface: + +```bash +kubectl -n port-forward svc/basic-tidb 4000:4000 +``` + +Test connection successfully: + +```bash +mysql -h 127.0.0.1 -P 4000 -u root -p --comments +``` + +## Destroy + +```bash +kubectl -n delete -f ./ +``` + +The PVCs used by TiDB cluster will not be deleted in the above process, therefore, the PVs will be not be released neither. You can delete PVCs and release the PVs by the following command: + +```bash +kubectl -n delete pvc -l app.kubernetes.io/instance=basic,app.kubernetes.io/managed-by=tidb-operator +``` diff --git a/examples/basic-random-password/tidb-cluster.yaml b/examples/basic-random-password/tidb-cluster.yaml new file mode 100644 index 0000000000..dfd675e075 --- /dev/null +++ b/examples/basic-random-password/tidb-cluster.yaml @@ -0,0 +1,56 @@ +# IT IS NOT SUITABLE FOR PRODUCTION USE. +# This YAML describes a basic TiDB cluster with minimum resource requirements, +# which should be able to run in any Kubernetes cluster with storage support. +apiVersion: pingcap.com/v1alpha1 +kind: TidbCluster +metadata: + name: basic +spec: + version: v5.3.0 + timezone: UTC + pvReclaimPolicy: Retain + enableDynamicConfiguration: true + configUpdateStrategy: RollingUpdate + discovery: {} + helper: + image: busybox:1.34.1 + pd: + baseImage: pingcap/pd + maxFailoverCount: 0 + replicas: 1 + # if storageClassName is not set, the default Storage Class of the Kubernetes cluster will be used + # storageClassName: local-storage + requests: + storage: "1Gi" + config: {} + tikv: + baseImage: pingcap/tikv + maxFailoverCount: 0 + # If only 1 TiKV is deployed, the TiKV region leader + # cannot be transferred during upgrade, so we have + # to configure a short timeout + evictLeaderTimeout: 1m + replicas: 1 + # if storageClassName is not set, the default Storage Class of the Kubernetes cluster will be used + # storageClassName: local-storage + requests: + storage: "1Gi" + config: + storage: + # In basic examples, we set this to avoid using too much storage. + reserve-space: "0MB" + rocksdb: + # In basic examples, we set this to avoid the following error in some Kubernetes clusters: + # "the maximum number of open file descriptors is too small, got 1024, expect greater or equal to 82920" + max-open-files: 256 + raftdb: + max-open-files: 256 + tidb: + initializer: + createPassword: true + baseImage: pingcap/tidb + maxFailoverCount: 0 + replicas: 1 + service: + type: ClusterIP + config: {} diff --git a/go.mod b/go.mod index bac6624039..269be24860 100644 --- a/go.mod +++ b/go.mod @@ -57,6 +57,7 @@ require ( github.com/prometheus/prom2json v1.3.0 github.com/prometheus/prometheus v1.8.2 github.com/robfig/cron v1.1.0 + github.com/sethvargo/go-password v0.2.0 github.com/sirupsen/logrus v1.6.0 github.com/spf13/cobra v1.0.0 github.com/spf13/pflag v1.0.5 diff --git a/go.sum b/go.sum index 76d7410d85..c1e83ba68e 100644 --- a/go.sum +++ b/go.sum @@ -801,6 +801,8 @@ github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdh github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/serenize/snaker v0.0.0-20171204205717-a683aaf2d516/go.mod h1:Yow6lPLSAXx2ifx470yD/nUe22Dv5vBvxK/UK9UUTVs= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/sethvargo/go-password v0.2.0 h1:BTDl4CC/gjf/axHMaDQtw507ogrXLci6XRiLc7i/UHI= +github.com/sethvargo/go-password v0.2.0/go.mod h1:Ym4Mr9JXLBycr02MFuVQ/0JHidNetSgbzutTr3zsYXE= github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= diff --git a/manifests/crd.yaml b/manifests/crd.yaml index 9693f3eb03..f9778e0e37 100644 --- a/manifests/crd.yaml +++ b/manifests/crd.yaml @@ -23710,6 +23710,11 @@ spec: - name type: object type: array + initializer: + properties: + createPassword: + type: boolean + type: object labels: additionalProperties: type: string @@ -29163,6 +29168,8 @@ spec: - name type: object type: object + passwordInitialized: + type: boolean phase: type: string resignDDLOwnerRetryCount: diff --git a/manifests/crd/v1/pingcap.com_tidbclusters.yaml b/manifests/crd/v1/pingcap.com_tidbclusters.yaml index 885522a130..e90add30c7 100644 --- a/manifests/crd/v1/pingcap.com_tidbclusters.yaml +++ b/manifests/crd/v1/pingcap.com_tidbclusters.yaml @@ -11764,6 +11764,11 @@ spec: - name type: object type: array + initializer: + properties: + createPassword: + type: boolean + type: object labels: additionalProperties: type: string @@ -17217,6 +17222,8 @@ spec: - name type: object type: object + passwordInitialized: + type: boolean phase: type: string resignDDLOwnerRetryCount: diff --git a/manifests/crd/v1beta1/pingcap.com_tidbclusters.yaml b/manifests/crd/v1beta1/pingcap.com_tidbclusters.yaml index ad7d7dafc8..faba1387de 100644 --- a/manifests/crd/v1beta1/pingcap.com_tidbclusters.yaml +++ b/manifests/crd/v1beta1/pingcap.com_tidbclusters.yaml @@ -11748,6 +11748,11 @@ spec: - name type: object type: array + initializer: + properties: + createPassword: + type: boolean + type: object labels: additionalProperties: type: string @@ -17194,6 +17199,8 @@ spec: - name type: object type: object + passwordInitialized: + type: boolean phase: type: string resignDDLOwnerRetryCount: diff --git a/manifests/crd_v1beta1.yaml b/manifests/crd_v1beta1.yaml index 0120f9c589..affbcd3afe 100644 --- a/manifests/crd_v1beta1.yaml +++ b/manifests/crd_v1beta1.yaml @@ -23694,6 +23694,11 @@ spec: - name type: object type: array + initializer: + properties: + createPassword: + type: boolean + type: object labels: additionalProperties: type: string @@ -29140,6 +29145,8 @@ spec: - name type: object type: object + passwordInitialized: + type: boolean phase: type: string resignDDLOwnerRetryCount: diff --git a/pkg/apis/pingcap/v1alpha1/openapi_generated.go b/pkg/apis/pingcap/v1alpha1/openapi_generated.go index dc9ebbcf10..8ef28f7f39 100644 --- a/pkg/apis/pingcap/v1alpha1/openapi_generated.go +++ b/pkg/apis/pingcap/v1alpha1/openapi_generated.go @@ -8128,12 +8128,18 @@ func schema_pkg_apis_pingcap_v1alpha1_TiDBSpec(ref common.ReferenceCallback) com Ref: ref("github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiDBProbe"), }, }, + "initializer": { + SchemaProps: spec.SchemaProps{ + Description: "Initializer is the init configurations of TiDB", + Ref: ref("github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiDBInitializer"), + }, + }, }, Required: []string{"replicas"}, }, }, Dependencies: []string{ - "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.StorageVolume", "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiDBConfigWraper", "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiDBProbe", "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiDBServiceSpec", "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiDBSlowLogTailerSpec", "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiDBTLSClient", "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TopologySpreadConstraint", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.Lifecycle", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.Volume", "k8s.io/api/core/v1.VolumeMount", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, + "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.StorageVolume", "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiDBConfigWraper", "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiDBInitializer", "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiDBProbe", "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiDBServiceSpec", "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiDBSlowLogTailerSpec", "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TiDBTLSClient", "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1.TopologySpreadConstraint", "k8s.io/api/core/v1.Affinity", "k8s.io/api/core/v1.Container", "k8s.io/api/core/v1.EnvVar", "k8s.io/api/core/v1.Lifecycle", "k8s.io/api/core/v1.LocalObjectReference", "k8s.io/api/core/v1.PodSecurityContext", "k8s.io/api/core/v1.Toleration", "k8s.io/api/core/v1.Volume", "k8s.io/api/core/v1.VolumeMount", "k8s.io/apimachinery/pkg/api/resource.Quantity"}, } } diff --git a/pkg/apis/pingcap/v1alpha1/tidbcluster.go b/pkg/apis/pingcap/v1alpha1/tidbcluster.go index b4a48b2b17..6d05bd2e12 100644 --- a/pkg/apis/pingcap/v1alpha1/tidbcluster.go +++ b/pkg/apis/pingcap/v1alpha1/tidbcluster.go @@ -732,6 +732,10 @@ func (tc *TidbCluster) IsTLSClusterEnabled() bool { return tc.Spec.TLSCluster != nil && tc.Spec.TLSCluster.Enabled } +func (tc *TidbCluster) NeedToSyncTiDBInitializer() bool { + return tc.Spec.TiDB != nil && tc.Spec.TiDB.Initializer != nil && tc.Spec.TiDB.Initializer.CreatePassword && tc.Status.TiDB.PasswordInitialized == nil +} + func (tc *TidbCluster) Scheme() string { if tc.IsTLSClusterEnabled() { return "https" diff --git a/pkg/apis/pingcap/v1alpha1/types.go b/pkg/apis/pingcap/v1alpha1/types.go index f0d4f75135..5229a61ced 100644 --- a/pkg/apis/pingcap/v1alpha1/types.go +++ b/pkg/apis/pingcap/v1alpha1/types.go @@ -776,6 +776,15 @@ type TiDBSpec struct { // the default behavior is like setting type as "tcp" // +optional ReadinessProbe *TiDBProbe `json:"readinessProbe,omitempty"` + + // Initializer is the init configurations of TiDB + // + // +optional + Initializer *TiDBInitializer `json:"initializer,omitempty"` +} + +type TiDBInitializer struct { + CreatePassword bool `json:"createPassword,omitempty"` } const ( @@ -1133,6 +1142,7 @@ type TiDBStatus struct { FailureMembers map[string]TiDBFailureMember `json:"failureMembers,omitempty"` ResignDDLOwnerRetryCount int32 `json:"resignDDLOwnerRetryCount,omitempty"` Image string `json:"image,omitempty"` + PasswordInitialized *bool `json:"passwordInitialized,omitempty"` } // TiDBMember is TiDB member diff --git a/pkg/apis/pingcap/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/pingcap/v1alpha1/zz_generated.deepcopy.go index 2f13446537..9c475dfdad 100644 --- a/pkg/apis/pingcap/v1alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/pingcap/v1alpha1/zz_generated.deepcopy.go @@ -5398,6 +5398,22 @@ func (in *TiDBFailureMember) DeepCopy() *TiDBFailureMember { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *TiDBInitializer) DeepCopyInto(out *TiDBInitializer) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TiDBInitializer. +func (in *TiDBInitializer) DeepCopy() *TiDBInitializer { + if in == nil { + return nil + } + out := new(TiDBInitializer) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TiDBMember) DeepCopyInto(out *TiDBMember) { *out = *in @@ -5574,6 +5590,11 @@ func (in *TiDBSpec) DeepCopyInto(out *TiDBSpec) { *out = new(TiDBProbe) (*in).DeepCopyInto(*out) } + if in.Initializer != nil { + in, out := &in.Initializer, &out.Initializer + *out = new(TiDBInitializer) + **out = **in + } return } @@ -5609,6 +5630,11 @@ func (in *TiDBStatus) DeepCopyInto(out *TiDBStatus) { (*out)[key] = *val.DeepCopy() } } + if in.PasswordInitialized != nil { + in, out := &in.PasswordInitialized, &out.PasswordInitialized + *out = new(bool) + **out = **in + } return } diff --git a/pkg/backup/constants/constants.go b/pkg/backup/constants/constants.go index 0a36145c64..658c59c738 100644 --- a/pkg/backup/constants/constants.go +++ b/pkg/backup/constants/constants.go @@ -49,4 +49,7 @@ const ( // KMS secret env prefix KMSSecretPrefix = "KMS_ENCRYPTED" + + // RootKey represents the username in tidb secret + TidbRootKey = "root" ) diff --git a/pkg/controller/controller_utils.go b/pkg/controller/controller_utils.go index 604a6406b6..f91f9629ec 100644 --- a/pkg/controller/controller_utils.go +++ b/pkg/controller/controller_utils.go @@ -345,6 +345,11 @@ func DMWorkerPeerMemberName(clusterName string) string { return fmt.Sprintf("%s-dm-worker-peer", clusterName) } +// TiDBInitSecret returns tidb init secret name +func TiDBInitSecret(clusterName string) string { + return fmt.Sprintf("%s-init", clusterName) +} + // AnnProm adds annotations for prometheus scraping metrics func AnnProm(port int32) map[string]string { return map[string]string{ diff --git a/pkg/manager/member/tidb_member_manager.go b/pkg/manager/member/tidb_member_manager.go index 1ed62781ad..251739fbf4 100644 --- a/pkg/manager/member/tidb_member_manager.go +++ b/pkg/manager/member/tidb_member_manager.go @@ -14,20 +14,23 @@ package member import ( + "context" "crypto/tls" + "database/sql" "fmt" "path" "strconv" "strings" + "time" + "github.com/pingcap/advanced-statefulset/client/apis/apps/v1/helper" "github.com/pingcap/tidb-operator/pkg/apis/label" "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1" + "github.com/pingcap/tidb-operator/pkg/backup/constants" "github.com/pingcap/tidb-operator/pkg/controller" "github.com/pingcap/tidb-operator/pkg/manager" mngerutils "github.com/pingcap/tidb-operator/pkg/manager/utils" "github.com/pingcap/tidb-operator/pkg/util" - - "github.com/pingcap/advanced-statefulset/client/apis/apps/v1/helper" apps "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/equality" @@ -39,6 +42,9 @@ import ( "k8s.io/klog/v2" podutil "k8s.io/kubernetes/pkg/api/v1/pod" "k8s.io/utils/pointer" + + // for sql/driver + _ "github.com/go-sql-driver/mysql" ) const ( @@ -107,6 +113,10 @@ func (m *tidbMemberManager) Sync(tc *v1alpha1.TidbCluster) error { } } + if tc.NeedToSyncTiDBInitializer() { + m.syncInitializer(tc) + } + // Sync TiDB StatefulSet return m.syncTiDBStatefulSetForTidbCluster(tc) } @@ -245,6 +255,110 @@ func (m *tidbMemberManager) syncTiDBStatefulSetForTidbCluster(tc *v1alpha1.TidbC return mngerutils.UpdateStatefulSetWithPrecheck(m.deps, tc, "FailedUpdateTiDBSTS", newTiDBSet, oldTiDBSet) } +func (m *tidbMemberManager) syncInitializer(tc *v1alpha1.TidbCluster) { + // set random password + ns := tc.Namespace + tcName := tc.Name + //check endpoints ready + isTiDBReady := false + eps, epErr := m.deps.EndpointLister.Endpoints(ns).Get(controller.TiDBMemberName(tcName)) + if epErr != nil { + klog.Errorf("Failed to get endpoints %s for cluster %s/%s, err: %s", controller.TiDBMemberName(tcName), ns, tcName, epErr) + return + } + // TiDB service has endpoints + if eps != nil && len(eps.Subsets[0].Addresses) > 0 { + isTiDBReady = true + } + + if !isTiDBReady { + klog.Infof("Wait for TiDB ready for cluster %s/%s", ns, tcName) + return + } + // sync password secret + var password string + secretName := controller.TiDBInitSecret(tcName) + secret, err := m.deps.SecretLister.Secrets(ns).Get(secretName) + passwordSecretExist := true + if err != nil { + if errors.IsNotFound(err) { + passwordSecretExist = false + } else { + klog.Errorf("Failed to get secret %s for cluster %s/%s, err: %s", secretName, ns, tcName, epErr) + return + } + } + + if !passwordSecretExist { + klog.Infof("Create random password secret for cluster %s/%s", ns, tcName) + var secret *corev1.Secret + secret, password = m.buildRandomPasswordSecret(tc) + err := m.deps.TypedControl.Create(tc, secret) + if err != nil { + klog.Errorf("Failed to create secret %s for cluster %s:%s, err: %s", secretName, ns, tcName, err) + return + } + } else { + password = string(secret.Data[constants.TidbRootKey]) + } + // init password + var db *sql.DB + dsn := util.GetDSN(tc) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + db, err = util.OpenDB(ctx, dsn) + + if err != nil { + if strings.Contains(fmt.Sprint(err), "Access denied") { + klog.Errorf("Can't connect to the TiDB service of the TiDB cluster [%s:%s], error: %s", ns, tcName, err) + val := true + tc.Status.TiDB.PasswordInitialized = &val + return + } + if ctx.Err() != nil { + klog.Errorf("Can't connect to the TiDB service of the TiDB cluster [%s:%s], error: %s, context error: %s", ns, tcName, err, ctx.Err()) + } else { + klog.Errorf("Can't connect to the TiDB service of the TiDB cluster [%s:%s], error: %s", ns, tcName, err) + } + return + } else { + klog.Infof("Set random password for cluster %s/%s", ns, tcName) + defer func(db *sql.DB) { + err := db.Close() + if err != nil { + klog.Errorf("Closed db connection for TiDB cluster %s/%s, err: %v", ns, tcName, err) + } + }(db) + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + err = util.SetPassword(ctx, db, password) + if err != nil { + klog.Errorf("Fail to set TiDB password for TiDB cluster %s/%s, err: %s", ns, tcName, err) + return + } + val := true + tc.Status.TiDB.PasswordInitialized = &val + klog.Infof("Set password successfully for TiDB cluster %s/%s", ns, tcName) + } +} + +func (m *tidbMemberManager) buildRandomPasswordSecret(tc *v1alpha1.TidbCluster) (*corev1.Secret, string) { + + s := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: controller.TiDBInitSecret(tc.Name), + Namespace: tc.Namespace, + Labels: label.New().Instance(tc.Name).Labels(), + OwnerReferences: []metav1.OwnerReference{controller.GetOwnerRef(tc)}, + }, + } + password := util.FixedLengthRandomPasswordBytes() + s.Data = map[string][]byte{ + constants.TidbRootKey: password, + } + return s, string(password) +} + func (m *tidbMemberManager) shouldRecover(tc *v1alpha1.TidbCluster) bool { if tc.Status.TiDB.FailureMembers == nil { return false diff --git a/pkg/manager/member/tidb_member_manager_test.go b/pkg/manager/member/tidb_member_manager_test.go index 13913a9b3b..7da3488c52 100644 --- a/pkg/manager/member/tidb_member_manager_test.go +++ b/pkg/manager/member/tidb_member_manager_test.go @@ -21,14 +21,13 @@ import ( "testing" "time" + "github.com/google/go-cmp/cmp" + . "github.com/onsi/gomega" "github.com/pingcap/tidb-operator/pkg/apis/label" "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1" "github.com/pingcap/tidb-operator/pkg/apis/util/toml" "github.com/pingcap/tidb-operator/pkg/controller" mngerutils "github.com/pingcap/tidb-operator/pkg/manager/utils" - - "github.com/google/go-cmp/cmp" - . "github.com/onsi/gomega" apps "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" v1 "k8s.io/api/core/v1" diff --git a/pkg/util/util.go b/pkg/util/util.go index 802b4c1a09..6961f60f5e 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -14,6 +14,8 @@ package util import ( + "context" + "database/sql" "encoding/json" "fmt" "os" @@ -25,6 +27,7 @@ import ( "github.com/pingcap/tidb-operator/pkg/apis/label" "github.com/pingcap/tidb-operator/pkg/apis/pingcap/v1alpha1" "github.com/pingcap/tidb-operator/pkg/features" + "github.com/sethvargo/go-password/password" apps "k8s.io/api/apps/v1" appsv1 "k8s.io/api/apps/v1" corev1 "k8s.io/api/core/v1" @@ -426,3 +429,45 @@ func ResolvePVCFromPod(pod *corev1.Pod, pvcLister corelisterv1.PersistentVolumeC } return pvcs, nil } + +// FixedLengthRandomPasswordBytes generates a random password +func FixedLengthRandomPasswordBytes() []byte { + return RandomBytes(13) +} + +// RandomBytes generates some random bytes that can be used as a token or as a key +func RandomBytes(length int) []byte { + return []byte(password.MustGenerate( + length, + length/3, // number of digits to include in the result + length/4, // number of symbols to include in the result + false, // noUpper + false, // allowRepeat + )) +} + +// OpenDB opens db +func OpenDB(ctx context.Context, dsn string) (*sql.DB, error) { + db, err := sql.Open("mysql", dsn) + if err != nil { + return nil, fmt.Errorf("open datasource failed, err: %v", err) + } + if err := db.PingContext(ctx); err != nil { + db.Close() + return nil, fmt.Errorf("cannot connect to tidb cluster, err: %v", err) + } + return db, nil +} + +// SetPassword set tidb password +func SetPassword(ctx context.Context, db *sql.DB, password string) error { + + sql := fmt.Sprintf("SET PASSWORD FOR 'root'@'%%' = '%s'; FLUSH PRIVILEGES;", password) + _, err := db.ExecContext(ctx, sql) + return err +} + +// GetDSN get tidb dsn +func GetDSN(tc *v1alpha1.TidbCluster) string { + return fmt.Sprintf("root:@tcp(%s-tidb.%s:4000)/?charset=utf8mb4,utf8&multiStatements=true", tc.Name, tc.Namespace) +}