├── version └── version.go ├── deploy ├── service_account.yaml ├── role_binding.yaml ├── crds │ ├── vitess_v1alpha2_vitesscell_crd.yaml │ ├── vitess_v1alpha2_vitessshard_crd.yaml │ ├── vitess_v1alpha2_vitesstablet_crd.yaml │ ├── vitess_v1alpha2_vitesscluster_crd.yaml │ ├── vitess_v1alpha2_vitesskeyspace_crd.yaml │ ├── vitess_v1alpha2_vitesslockserver_crd.yaml │ └── vitess_v1alpha2_vitesstablet_crd.yaml.bak ├── role.yaml └── operator.yaml ├── .dockerignore ├── pkg ├── apis │ ├── vitess │ │ └── v1alpha2 │ │ │ ├── interfaces.go │ │ │ ├── doc.go │ │ │ ├── shared_helpers.go │ │ │ ├── vitesscell_helpers.go │ │ │ ├── register.go │ │ │ ├── vitesskeyspace_helpers.go │ │ │ ├── vitessshard_helpers.go │ │ │ ├── vitesskeyspace_types.go │ │ │ ├── vitesslockserver_types.go │ │ │ ├── vitessshard_types.go │ │ │ ├── shared_types.go │ │ │ ├── vitesscluster_helpers.go │ │ │ ├── vitesscell_types.go │ │ │ ├── vitesscluster_types.go │ │ │ ├── vitesstablet_types.go │ │ │ ├── vitesstablet_helpers.go │ │ │ └── samples.yaml │ ├── addtoscheme_vitess_v1alpha2.go │ └── apis.go ├── controller │ ├── vitesscluster │ │ ├── utils.go │ │ ├── reconcile_shard.go │ │ ├── reconcile_keyspace.go │ │ ├── reconcile_cell_test.go │ │ ├── reconcile_cluster.go │ │ ├── vitesscluster_controller.go │ │ ├── vitesscluster_controller_test.go │ │ ├── reconcile_cell.go │ │ └── reconcile_tablet.go │ ├── add_vitesscluster.go │ ├── add_vitesslockserver.go │ ├── controller.go │ └── vitesslockserver │ │ └── vitesslockserver_controller.go ├── util │ └── scripts │ │ ├── init-mysql-creds.go │ │ ├── vtcltd.go │ │ ├── vtgate.go │ │ ├── mysql.go │ │ ├── init_replica_master.go │ │ ├── main.go │ │ └── tablet.go └── normalizer │ ├── sanity.go │ ├── errors.go │ ├── validation.go │ ├── normalizer.go │ └── normalizer_test.go ├── examples ├── etcd-clusters-minimal.yaml ├── etcd-clusters.yaml ├── distributed.yaml └── all-in-one.yaml ├── .gitignore ├── Gopkg.toml ├── cmd └── manager │ └── main.go ├── my-vitess.yaml ├── README.md ├── LICENSE └── Gopkg.lock /version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | var ( 4 | Version = "0.0.2" 5 | ) 6 | -------------------------------------------------------------------------------- /deploy/service_account.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: ServiceAccount 3 | metadata: 4 | name: vitess-operator 5 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | # Ignore everything 2 | ** 3 | 4 | 5 | # Allow build artifact 6 | !build/_output/bin/vitess-operator 7 | -------------------------------------------------------------------------------- /pkg/apis/vitess/v1alpha2/interfaces.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | type ConfigProvider interface { 4 | GetTabletContainers() *TabletContainers 5 | } 6 | -------------------------------------------------------------------------------- /pkg/controller/vitesscluster/utils.go: -------------------------------------------------------------------------------- 1 | package vitesscluster 2 | 3 | func getInt32Ptr(id int32) *int32 { 4 | return &id 5 | } 6 | 7 | func getInt64Ptr(id int64) *int64 { 8 | return &id 9 | } 10 | -------------------------------------------------------------------------------- /pkg/apis/vitess/v1alpha2/doc.go: -------------------------------------------------------------------------------- 1 | // Package v1alpha2 contains API Schema definitions for the vitess v1alpha2 API group 2 | // +k8s:deepcopy-gen=package,register 3 | // +groupName=vitess.io 4 | package v1alpha2 5 | -------------------------------------------------------------------------------- /deploy/role_binding.yaml: -------------------------------------------------------------------------------- 1 | kind: RoleBinding 2 | apiVersion: rbac.authorization.k8s.io/v1 3 | metadata: 4 | name: vitess-operator 5 | subjects: 6 | - kind: ServiceAccount 7 | name: vitess-operator 8 | roleRef: 9 | kind: Role 10 | name: vitess-operator 11 | apiGroup: rbac.authorization.k8s.io 12 | -------------------------------------------------------------------------------- /pkg/apis/vitess/v1alpha2/shared_helpers.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | func (kr *KeyRange) String() string { 4 | if kr.From != "" || kr.To != "" { 5 | return kr.From + "-" + kr.To 6 | } 7 | 8 | // If no From or To is set, then default to the Vitess convention of 0 as they Keyrange string 9 | return "0" 10 | } 11 | -------------------------------------------------------------------------------- /pkg/controller/add_vitesscluster.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "vitess.io/vitess-operator/pkg/controller/vitesscluster" 5 | ) 6 | 7 | func init() { 8 | // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. 9 | AddToManagerFuncs = append(AddToManagerFuncs, vitesscluster.Add) 10 | } 11 | -------------------------------------------------------------------------------- /pkg/apis/addtoscheme_vitess_v1alpha2.go: -------------------------------------------------------------------------------- 1 | package apis 2 | 3 | import ( 4 | "vitess.io/vitess-operator/pkg/apis/vitess/v1alpha2" 5 | ) 6 | 7 | func init() { 8 | // Register the types with the Scheme so the components can map objects to GroupVersionKinds and back 9 | AddToSchemes = append(AddToSchemes, v1alpha2.SchemeBuilder.AddToScheme) 10 | } 11 | -------------------------------------------------------------------------------- /pkg/controller/add_vitesslockserver.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "vitess.io/vitess-operator/pkg/controller/vitesslockserver" 5 | ) 6 | 7 | func init() { 8 | // AddToManagerFuncs is a list of functions to create controllers and add them to a manager. 9 | AddToManagerFuncs = append(AddToManagerFuncs, vitesslockserver.Add) 10 | } 11 | -------------------------------------------------------------------------------- /pkg/apis/apis.go: -------------------------------------------------------------------------------- 1 | package apis 2 | 3 | import ( 4 | "k8s.io/apimachinery/pkg/runtime" 5 | ) 6 | 7 | // AddToSchemes may be used to add all resources defined in the project to a Scheme 8 | var AddToSchemes runtime.SchemeBuilder 9 | 10 | // AddToScheme adds all Resources to the Scheme 11 | func AddToScheme(s *runtime.Scheme) error { 12 | return AddToSchemes.AddToScheme(s) 13 | } 14 | -------------------------------------------------------------------------------- /deploy/crds/vitess_v1alpha2_vitesscell_crd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1beta1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: vitesscells.vitess.io 5 | spec: 6 | group: vitess.io 7 | names: 8 | kind: VitessCell 9 | listKind: VitessCellList 10 | plural: vitesscells 11 | singular: vitesscell 12 | scope: Namespaced 13 | version: v1alpha2 14 | subresources: 15 | status: {} 16 | -------------------------------------------------------------------------------- /deploy/crds/vitess_v1alpha2_vitessshard_crd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1beta1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: vitessshards.vitess.io 5 | spec: 6 | group: vitess.io 7 | names: 8 | kind: VitessShard 9 | listKind: VitessShardList 10 | plural: vitessshards 11 | singular: vitessshard 12 | scope: Namespaced 13 | version: v1alpha2 14 | subresources: 15 | status: {} 16 | -------------------------------------------------------------------------------- /deploy/crds/vitess_v1alpha2_vitesstablet_crd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1beta1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: vitesstablets.vitess.io 5 | spec: 6 | group: vitess.io 7 | names: 8 | kind: VitessTablet 9 | listKind: VitessTabletList 10 | plural: vitesstablets 11 | singular: vitesstablet 12 | scope: Namespaced 13 | version: v1alpha2 14 | subresources: 15 | status: {} 16 | -------------------------------------------------------------------------------- /deploy/crds/vitess_v1alpha2_vitesscluster_crd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1beta1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: vitessclusters.vitess.io 5 | spec: 6 | group: vitess.io 7 | names: 8 | kind: VitessCluster 9 | listKind: VitessClusterList 10 | plural: vitessclusters 11 | singular: vitesscluster 12 | scope: Namespaced 13 | version: v1alpha2 14 | subresources: 15 | status: {} 16 | -------------------------------------------------------------------------------- /deploy/crds/vitess_v1alpha2_vitesskeyspace_crd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1beta1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: vitesskeyspaces.vitess.io 5 | spec: 6 | group: vitess.io 7 | names: 8 | kind: VitessKeyspace 9 | listKind: VitessKeyspaceList 10 | plural: vitesskeyspaces 11 | singular: vitesskeyspace 12 | scope: Namespaced 13 | version: v1alpha2 14 | subresources: 15 | status: {} 16 | -------------------------------------------------------------------------------- /deploy/crds/vitess_v1alpha2_vitesslockserver_crd.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1beta1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: vitesslockservers.vitess.io 5 | spec: 6 | group: vitess.io 7 | names: 8 | kind: VitessLockserver 9 | listKind: VitessLockserverList 10 | plural: vitesslockservers 11 | singular: vitesslockserver 12 | scope: Namespaced 13 | version: v1alpha2 14 | subresources: 15 | status: {} 16 | -------------------------------------------------------------------------------- /pkg/util/scripts/init-mysql-creds.go: -------------------------------------------------------------------------------- 1 | package scripts 2 | 3 | var ( 4 | InitMySQLCreds = ` 5 | set -ex 6 | creds=$(cat < /mysqlcreds/creds.json 19 | ` 20 | ) 21 | -------------------------------------------------------------------------------- /pkg/normalizer/sanity.go: -------------------------------------------------------------------------------- 1 | package normalizer 2 | 3 | import ( 4 | "fmt" 5 | 6 | vitessv1alpha2 "vitess.io/vitess-operator/pkg/apis/vitess/v1alpha2" 7 | ) 8 | 9 | func (n *Normalizer) TestClusterSanity(cluster *vitessv1alpha2.VitessCluster) error { 10 | // Lockserver and LockserverRef are mutuallly exclusive 11 | if cluster.Spec.Lockserver != nil && cluster.Spec.LockserverRef != nil { 12 | return fmt.Errorf("Cannot specify both a lockserver and lockserverRef") 13 | } 14 | 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /pkg/controller/controller.go: -------------------------------------------------------------------------------- 1 | package controller 2 | 3 | import ( 4 | "sigs.k8s.io/controller-runtime/pkg/manager" 5 | ) 6 | 7 | // AddToManagerFuncs is a list of functions to add all Controllers to the Manager 8 | var AddToManagerFuncs []func(manager.Manager) error 9 | 10 | // AddToManager adds all Controllers to the Manager 11 | func AddToManager(m manager.Manager) error { 12 | for _, f := range AddToManagerFuncs { 13 | if err := f(m); err != nil { 14 | return err 15 | } 16 | } 17 | return nil 18 | } 19 | -------------------------------------------------------------------------------- /pkg/apis/vitess/v1alpha2/vitesscell_helpers.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | func (cell *VitessCell) SetParentCluster(cluster *VitessCluster) { 8 | cell.Spec.parent.Cluster = cluster 9 | } 10 | 11 | func (cell *VitessCell) Cluster() *VitessCluster { 12 | return cell.Spec.parent.Cluster 13 | } 14 | 15 | func (cell *VitessCell) Lockserver() *VitessLockserver { 16 | return cell.Spec.Lockserver 17 | } 18 | 19 | func (cell *VitessCell) GetScopedName(extra ...string) string { 20 | return strings.Join(append( 21 | []string{ 22 | cell.Cluster().GetName(), 23 | cell.GetName(), 24 | }, 25 | extra...), "-") 26 | } 27 | -------------------------------------------------------------------------------- /pkg/util/scripts/vtcltd.go: -------------------------------------------------------------------------------- 1 | package scripts 2 | 3 | const ( 4 | VtCtldStart = `eval exec /vt/bin/vtctld $(cat <= MaxTabletHostnameLength { 71 | return ValidationErrorTabletNameTooLong 72 | } 73 | 74 | return nil 75 | } 76 | 77 | // getMaxExpectedTabletHostLength returns the maximum possible hostname of 78 | // this tablet given the max oridinal length allowed 79 | func getMaxExpectedTabletHostLength(tablet *vitessv1alpha2.VitessTablet) int { 80 | return len(strings.Join([]string{ 81 | tablet.GetStatefulSetName(), 82 | "-", 83 | strings.Repeat("9", MaxTabletOrdinalLength), 84 | ".", 85 | tablet.Cluster().GetTabletServiceName(), 86 | }, "")) 87 | } 88 | -------------------------------------------------------------------------------- /cmd/manager/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "context" 5 | "flag" 6 | "fmt" 7 | "os" 8 | "runtime" 9 | 10 | "github.com/operator-framework/operator-sdk/pkg/k8sutil" 11 | "github.com/operator-framework/operator-sdk/pkg/leader" 12 | "github.com/operator-framework/operator-sdk/pkg/ready" 13 | sdkVersion "github.com/operator-framework/operator-sdk/version" 14 | "vitess.io/vitess-operator/pkg/apis" 15 | "vitess.io/vitess-operator/pkg/controller" 16 | _ "k8s.io/client-go/plugin/pkg/client/auth/gcp" 17 | "sigs.k8s.io/controller-runtime/pkg/client/config" 18 | "sigs.k8s.io/controller-runtime/pkg/manager" 19 | logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" 20 | "sigs.k8s.io/controller-runtime/pkg/runtime/signals" 21 | ) 22 | 23 | var log = logf.Log.WithName("cmd") 24 | 25 | func printVersion() { 26 | log.Info(fmt.Sprintf("Go Version: %s", runtime.Version())) 27 | log.Info(fmt.Sprintf("Go OS/Arch: %s/%s", runtime.GOOS, runtime.GOARCH)) 28 | log.Info(fmt.Sprintf("operator-sdk Version: %v", sdkVersion.Version)) 29 | } 30 | 31 | func main() { 32 | flag.Parse() 33 | 34 | // The logger instantiated here can be changed to any logger 35 | // implementing the logr.Logger interface. This logger will 36 | // be propagated through the whole operator, generating 37 | // uniform and structured logs. 38 | logf.SetLogger(logf.ZapLogger(false)) 39 | 40 | printVersion() 41 | 42 | namespace, err := k8sutil.GetWatchNamespace() 43 | if err != nil { 44 | log.Error(err, "failed to get watch namespace") 45 | os.Exit(1) 46 | } 47 | 48 | // Get a config to talk to the apiserver 49 | cfg, err := config.GetConfig() 50 | if err != nil { 51 | log.Error(err, "") 52 | os.Exit(1) 53 | } 54 | 55 | // Become the leader before proceeding 56 | leader.Become(context.TODO(), "vitess-operator-lock") 57 | 58 | r := ready.NewFileReady() 59 | err = r.Set() 60 | if err != nil { 61 | log.Error(err, "") 62 | os.Exit(1) 63 | } 64 | defer r.Unset() 65 | 66 | // Create a new Cmd to provide shared dependencies and start components 67 | mgr, err := manager.New(cfg, manager.Options{Namespace: namespace}) 68 | if err != nil { 69 | log.Error(err, "") 70 | os.Exit(1) 71 | } 72 | 73 | log.Info("Registering Components.") 74 | 75 | // Setup Scheme for all resources 76 | if err := apis.AddToScheme(mgr.GetScheme()); err != nil { 77 | log.Error(err, "") 78 | os.Exit(1) 79 | } 80 | 81 | // Setup all Controllers 82 | if err := controller.AddToManager(mgr); err != nil { 83 | log.Error(err, "") 84 | os.Exit(1) 85 | } 86 | 87 | log.Info("Starting the Cmd.") 88 | 89 | // Start the Cmd 90 | if err := mgr.Start(signals.SetupSignalHandler()); err != nil { 91 | log.Error(err, "manager exited non-zero") 92 | os.Exit(1) 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /pkg/util/scripts/init_replica_master.go: -------------------------------------------------------------------------------- 1 | package scripts 2 | 3 | var ( 4 | InitReplicaMaster = ` 5 | set -ex 6 | 7 | VTCTLD_SVC={{ .Cluster.Name }}-{{ .Cell.Name }}-vtctld.{{ .Cluster.Namespace }}:15999 8 | SECONDS=0 9 | TIMEOUT_SECONDS=600 10 | VTCTL_EXTRA_FLAGS=() 11 | 12 | # poll every 5 seconds to see if vtctld is ready 13 | until vtctlclient ${VTCTL_EXTRA_FLAGS[@]} -server $VTCTLD_SVC ListAllTablets {{ .Cell.Name }} > /dev/null 2>&1; do 14 | if (( $SECONDS > $TIMEOUT_SECONDS )); then 15 | echo "timed out waiting for vtctlclient to be ready" 16 | exit 1 17 | fi 18 | sleep 5 19 | done 20 | 21 | until [ $TABLETS_READY ]; do 22 | # get all the tablets in the current cell 23 | cellTablets="$(vtctlclient ${VTCTL_EXTRA_FLAGS[@]} -server $VTCTLD_SVC ListAllTablets {{ .Cell.Name }})" 24 | 25 | # filter to only the tablets in our current shard 26 | shardTablets=$( echo "$cellTablets" | grep -w '{{ .Cluster.Name }}-{{ .Cell.Name }}-{{ .Keyspace.Name }}-{{ .Shard.Name }}' || : ) 27 | 28 | # check for a master tablet from the ListAllTablets call 29 | masterTablet=$( echo "$shardTablets" | awk '$4 == "master" {print $1}') 30 | if [ $masterTablet ]; then 31 | echo "'$masterTablet' is already the master tablet, exiting without running InitShardMaster" 32 | exit 33 | fi 34 | 35 | # check for a master tablet from the GetShard call 36 | master_alias=$(vtctlclient ${VTLCTL_EXTRA_FLAGS[@]} -server $VTCTLD_SVC GetShard {{ .Keyspace.Name }}/{{ .Shard.Spec.KeyRange }} | jq '.master_alias.uid') 37 | if [ "$master_alias" != "null" -a "$master_alias" != "" ]; then 38 | echo "'$master_alias' is already the master tablet, exiting without running InitShardMaster" 39 | exit 40 | fi 41 | 42 | # count the number of newlines for the given shard to get the tablet count 43 | tabletCount=$( echo "$shardTablets" | wc | awk '{print $1}') 44 | 45 | # check to see if the tablet count equals the expected tablet count 46 | if [ $tabletCount == 2 ]; then 47 | TABLETS_READY=true 48 | else 49 | if (( $SECONDS > $TIMEOUT_SECONDS )); then 50 | echo "timed out waiting for tablets to be ready" 51 | exit 1 52 | fi 53 | 54 | # wait 5 seconds for vttablets to continue getting ready 55 | sleep 5 56 | fi 57 | 58 | done 59 | 60 | # find the tablet id for the "-replica-0" stateful set for a given cell, keyspace and shard 61 | tablet_id=$( echo "$shardTablets" | grep -w '{{ .ScopedName }}-replica-0' | awk '{print $1}') 62 | 63 | # initialize the shard master 64 | until vtctlclient ${VTCTL_EXTRA_FLAGS[@]} -server $VTCTLD_SVC InitShardMaster -force {{ .Keyspace.Name }}/{{ .Shard.Spec.KeyRange }} $tablet_id; do 65 | if (( $SECONDS > $TIMEOUT_SECONDS )); then 66 | echo "timed out waiting for InitShardMaster to succeed" 67 | exit 1 68 | fi 69 | sleep 5 70 | done 71 | ` 72 | ) 73 | -------------------------------------------------------------------------------- /deploy/crds/vitess_v1alpha2_vitesstablet_crd.yaml.bak: -------------------------------------------------------------------------------- 1 | apiVersion: apiextensions.k8s.io/v1beta1 2 | kind: CustomResourceDefinition 3 | metadata: 4 | name: vitesstablets.vitess.io 5 | spec: 6 | group: vitess.io 7 | names: 8 | kind: VitessTablet 9 | listKind: VitessTabletList 10 | plural: vitesstablets 11 | singular: vitesstablet 12 | scope: Namespaced 13 | versions: 14 | - name: v1alpha2 15 | served: true 16 | storage: true 17 | subresources: 18 | status: {} 19 | validation: 20 | openAPIV3Schema: 21 | properties: 22 | spec: 23 | properties: 24 | tabletID: 25 | type: integer 26 | format: int64 27 | replicas: 28 | type: integer 29 | format: int32 30 | CellID: 31 | type: string 32 | pattern: '^[a-z0-9]([-a-z0-9]*[a-z0-9])?$' 33 | type: 34 | type: string 35 | enum: 36 | - master 37 | - replica 38 | - readonly 39 | - backup 40 | - restore 41 | - drained 42 | datastore: 43 | type: string 44 | enum: 45 | - "" 46 | - local 47 | containers: 48 | type: object 49 | properties: 50 | dbflavor: 51 | type: string 52 | enum: 53 | - "" 54 | - mysql 55 | mysql: 56 | type: object 57 | properties: 58 | image: 59 | type: string 60 | resources: 61 | type: object 62 | properties: 63 | limits: 64 | type: object 65 | additionalProperties: 66 | type: string 67 | requests: 68 | type: object 69 | additionalProperties: 70 | type: string 71 | dbflavor: 72 | type: string 73 | vttablet: 74 | type: object 75 | properties: 76 | image: 77 | type: string 78 | resources: 79 | type: object 80 | properties: 81 | limits: 82 | type: object 83 | additionalProperties: 84 | type: string 85 | requests: 86 | type: object 87 | additionalProperties: 88 | type: string 89 | dbflavor: 90 | type: string 91 | enum: 92 | - "" 93 | - mysql 94 | -------------------------------------------------------------------------------- /pkg/apis/vitess/v1alpha2/vitesscell_types.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | ) 7 | 8 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 9 | // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file 10 | 11 | // VitessCellSpec defines the desired state of VitessCell 12 | type VitessCellSpec struct { 13 | Lockserver *VitessLockserver `json:"lockserver"` 14 | 15 | LockserverRef *corev1.LocalObjectReference `json:"lockserverRef,omitempty"` 16 | 17 | Defaults *VitessCellDefaults `json:"defaults"` 18 | 19 | MySQLProtocol *VitessCellMySQLProtocol `json:"mysqlProtocol"` 20 | 21 | VTGate []VTComponent `json:"vtgate"` 22 | 23 | VTWorker []VTComponent `json:"vtworker"` 24 | 25 | VTCtld []VTComponent `json:"vtctld"` 26 | 27 | Orchestrator []VTComponent `json:"orchestrator"` 28 | 29 | // parent is unexported on purpose. 30 | // It should only be used during processing and never stored 31 | parent VitessCellParents 32 | } 33 | 34 | type VitessCellParents struct { 35 | Cluster *VitessCluster 36 | } 37 | 38 | type VitessCellDefaults struct { 39 | Replicas *int32 `json:"replicas"` 40 | 41 | Image string `json:"image"` 42 | } 43 | 44 | type VitessCellMySQLProtocol struct { 45 | AuthType VitessMySQLAuthType `json:"authType,omitempty"` 46 | 47 | Username string `json:"image,omitempty"` 48 | 49 | // Password string `json:"password"` 50 | 51 | PasswordSecretRef *corev1.SecretKeySelector `json:"passwordSecretRef,omitempty"` 52 | } 53 | 54 | type VitessMySQLAuthType string 55 | 56 | const ( 57 | VitessMySQLAuthTypeNone VitessMySQLAuthType = "none" 58 | ) 59 | 60 | type VTGate struct { 61 | // Inline common component struct members 62 | VTComponent `json:",inline"` 63 | 64 | Credentials VTGateCredentials `json:"credentials,omitempty"` 65 | 66 | Cells []string `json:"cells:` 67 | 68 | CellSelector *CellSelector `json:"cellSelector,omitempty"` 69 | } 70 | 71 | type VTGateCredentials struct { 72 | // SecretRef points a Secret resource which contains the credentials 73 | // +optional 74 | SecretRef *corev1.SecretReference `json:"secretRef,omitempty" protobuf:"bytes,4,opt,name=secretRef"` 75 | } 76 | 77 | type CellSelector struct { 78 | MatchLabels map[string]string `json:"matchLabels,omitempty"` 79 | 80 | MatchExpressions []ResourceSelector `json:"matchExpressions,omitempty"` 81 | } 82 | 83 | type VTComponent struct { 84 | Replicas int64 `json:"replicas,omitempty"` 85 | 86 | ContainerSpec []*corev1.Container `json:"containerSpec,omitempty"` 87 | } 88 | 89 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 90 | 91 | // VitessCell is the Schema for the vitesscells API 92 | // +k8s:openapi-gen=true 93 | type VitessCell struct { 94 | metav1.TypeMeta `json:",inline"` 95 | metav1.ObjectMeta `json:"metadata,omitempty"` 96 | 97 | Spec VitessCellSpec `json:"spec,omitempty"` 98 | } 99 | 100 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 101 | 102 | // VitessCellList contains a list of VitessCell 103 | type VitessCellList struct { 104 | metav1.TypeMeta `json:",inline"` 105 | metav1.ListMeta `json:"metadata,omitempty"` 106 | Items []VitessCell `json:"items"` 107 | } 108 | 109 | func init() { 110 | SchemeBuilder.Register(&VitessCell{}, &VitessCellList{}) 111 | } 112 | -------------------------------------------------------------------------------- /pkg/apis/vitess/v1alpha2/vitesscluster_types.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | ) 7 | 8 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 9 | // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file 10 | 11 | // VitessClusterSpec defines the desired state of VitessCluster 12 | type VitessClusterSpec struct { 13 | Lockserver *VitessLockserver `json:"lockserver,omitempty"` 14 | 15 | LockserverRef *corev1.LocalObjectReference `json:"lockserverRef,omitempty"` 16 | 17 | Cells []*VitessCell `json:"cells,omitempty"` 18 | 19 | CellSelector []ResourceSelector `json:"cellSelector,omitempty"` 20 | 21 | Keyspaces []*VitessKeyspace `json:"keyspaces,omitempty"` 22 | 23 | KeyspaceSelector []ResourceSelector `json:"keyspaceSelector,omitempty"` 24 | } 25 | 26 | // VitessClusterStatus defines the observed state of VitessCluster 27 | type VitessClusterStatus struct { 28 | Phase ClusterPhase `json:"phase,omitempty"` 29 | 30 | Reason string `json:"reason,omitempty"` 31 | 32 | Message string `json:"reason,omitempty"` 33 | 34 | Conditions []VitessClusterCondition `json:"conditions,omitempty"` 35 | 36 | Lockserver *VitessLockserverStatus `json:"lockserver,omitempty"` 37 | } 38 | 39 | type ClusterPhase string 40 | 41 | const ( 42 | ClusterPhaseNone ClusterPhase = "" 43 | ClusterPhaseCreating ClusterPhase = "Creating" 44 | ClusterPhaseReady ClusterPhase = "Ready" 45 | ) 46 | 47 | type VitessClusterCondition struct { 48 | // Type of cluster condition. 49 | Type ClusterConditionType `json:"type"` 50 | 51 | // Status of the condition, one of True, False, Unknown. 52 | Status corev1.ConditionStatus `json:"status"` 53 | 54 | // The last time this condition was updated. 55 | LastUpdateTime string `json:"lastUpdateTime,omitempty"` 56 | 57 | // Last time the condition transitioned from one status to another. 58 | LastTransitionTime string `json:"lastTransitionTime,omitempty"` 59 | 60 | // The reason for the condition's last transition. 61 | Reason string `json:"reason,omitempty"` 62 | 63 | // A human readable message indicating details about the transition. 64 | Message string `json:"message,omitempty"` 65 | } 66 | 67 | type ClusterConditionType string 68 | 69 | const ( 70 | VitessClusterConditionAvailable ClusterConditionType = "Available" 71 | VitessClusterConditionRecovering ClusterConditionType = "Recovering" 72 | VitessClusterConditionScaling ClusterConditionType = "Scaling" 73 | VitessClusterConditionUpgrading ClusterConditionType = "Upgrading" 74 | ) 75 | 76 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 77 | 78 | // VitessCluster is the Schema for the vitessclusters API 79 | // +k8s:openapi-gen=true 80 | type VitessCluster struct { 81 | metav1.TypeMeta `json:",inline"` 82 | metav1.ObjectMeta `json:"metadata,omitempty"` 83 | 84 | Spec VitessClusterSpec `json:"spec,omitempty"` 85 | Status VitessClusterStatus `json:"status,omitempty"` 86 | } 87 | 88 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 89 | 90 | // VitessClusterList contains a list of VitessCluster 91 | type VitessClusterList struct { 92 | metav1.TypeMeta `json:",inline"` 93 | metav1.ListMeta `json:"metadata,omitempty"` 94 | Items []VitessCluster `json:"items"` 95 | } 96 | 97 | func init() { 98 | SchemeBuilder.Register(&VitessCluster{}, &VitessClusterList{}) 99 | } 100 | -------------------------------------------------------------------------------- /pkg/apis/vitess/v1alpha2/vitesstablet_types.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | corev1 "k8s.io/api/core/v1" 5 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 6 | ) 7 | 8 | // NOTE: json tags are required. Any new fields you add must have json tags for the fields to be serialized. 9 | // Important: Run "operator-sdk generate k8s" to regenerate code after modifying this file 10 | 11 | // VitessTabletSpec defines the desired state of VitessTablet 12 | type VitessTabletSpec struct { 13 | TabletID int64 `json:"tabletID"` 14 | 15 | Replicas *int32 `json:"replicas"` 16 | 17 | CellID string `json:"cellID"` 18 | 19 | Type TabletType `json:"type"` 20 | 21 | Datastore TabletDatastore `json:"datastore"` 22 | 23 | Containers *TabletContainers `json:"containers"` 24 | 25 | VolumeClaim *corev1.PersistentVolumeClaimVolumeSource `json:"volumeclaim, omitempty"` 26 | 27 | Credentials *TabletCredentials `json:"credentials,omitempty"` 28 | 29 | // parent is unexported on purpose. 30 | // It should only be used during processing and never stored 31 | parent VitessTabletParents 32 | } 33 | 34 | type VitessTabletParents struct { 35 | Cluster *VitessCluster 36 | Cell *VitessCell 37 | Keyspace *VitessKeyspace 38 | Shard *VitessShard 39 | } 40 | 41 | type TabletType string 42 | 43 | const ( 44 | TabletTypeMaster TabletType = "master" 45 | TabletTypeReplica TabletType = "replica" 46 | TabletTypeReadOnly TabletType = "readonly" 47 | TabletTypeBackup TabletType = "backup" 48 | TabletTypeRestore TabletType = "restore" 49 | TabletTypeDrained TabletType = "drained" 50 | ) 51 | 52 | const TabletTypeDefault TabletType = TabletTypeReplica 53 | 54 | type TabletDatastore struct { 55 | Type TabletDatastoreType `json:"type"` 56 | } 57 | 58 | type TabletDatastoreType string 59 | 60 | const ( 61 | TabletDatastoreTypeLocal TabletDatastoreType = "local" 62 | ) 63 | 64 | const TabletDatastoreTypeDefault TabletDatastoreType = TabletDatastoreTypeLocal 65 | 66 | type TabletCredentials struct { 67 | // SecretRef points a Secret resource which contains the credentials 68 | // +optional 69 | SecretRef *corev1.SecretReference `json:"secretRef,omitempty" protobuf:"bytes,4,opt,name=secretRef"` 70 | } 71 | 72 | // status is for internal use only. If it was exported then it would dirty-up the 73 | // tablet objects embedded in other resources and would result in mixed status and spec data 74 | // it is here for use by the VitessCluster object and its controller 75 | type VitessTabletStatus struct { 76 | Phase TabletPhase `json:"-"` 77 | } 78 | 79 | type TabletPhase string 80 | 81 | const ( 82 | TabletPhaseNone TabletPhase = "" 83 | TabletPhaseReady TabletPhase = "Ready" 84 | ) 85 | 86 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 87 | 88 | // VitessTablet is the Schema for the vitesstablets API 89 | // +k8s:openapi-gen=true 90 | type VitessTablet struct { 91 | metav1.TypeMeta `json:",inline"` 92 | metav1.ObjectMeta `json:"metadata,omitempty"` 93 | 94 | Spec VitessTabletSpec `json:"spec,omitempty"` 95 | 96 | // internal use only. See struct def for details 97 | status VitessTabletStatus `json:"-"` 98 | } 99 | 100 | // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object 101 | 102 | // VitessTabletList contains a list of VitessTablet 103 | type VitessTabletList struct { 104 | metav1.TypeMeta `json:",inline"` 105 | metav1.ListMeta `json:"metadata,omitempty"` 106 | Items []VitessTablet `json:"items"` 107 | } 108 | 109 | func init() { 110 | SchemeBuilder.Register(&VitessTablet{}, &VitessTabletList{}) 111 | } 112 | -------------------------------------------------------------------------------- /my-vitess.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | # To keep the example workload to a minimum, a single 3 | # topology server is used for the local and global scope 4 | # This is fine in small installations but may not be 5 | # ideal for large, multi-zone installations 6 | apiVersion: etcd.database.coreos.com/v1beta2 7 | kind: EtcdCluster 8 | metadata: 9 | name: etcd-zone1 10 | spec: 11 | pod: 12 | affinity: 13 | podAntiAffinity: 14 | preferredDuringSchedulingIgnoredDuringExecution: 15 | - podAffinityTerm: 16 | labelSelector: 17 | matchLabels: 18 | etcd_cluster: etcd-vitess 19 | topologyKey: kubernetes.io/hostname 20 | weight: 100 21 | resources: 22 | requests: 23 | cpu: 200m 24 | memory: 100Mi 25 | repository: quay.io/coreos/etcd 26 | size: 1 27 | version: 3.3.10 28 | --- 29 | # Sample VitessCluster with all resources embedded within 30 | # the VitessCluster object. It is also possible to split 31 | # all or some of the resources into their own objects for easier 32 | # management. See the examples directory for more information 33 | apiVersion: vitess.io/v1alpha2 34 | kind: VitessCluster 35 | metadata: 36 | name: vt 37 | labels: 38 | app: vitess 39 | spec: 40 | lockserver: 41 | metadata: 42 | name: global 43 | spec: 44 | type: etcd2 45 | etcd2: 46 | address: etcd-global-client:2379 47 | pathPrefix: /vitess/global 48 | cells: 49 | - metadata: 50 | name: zone1 51 | spec: 52 | lockserver: 53 | metadata: 54 | name: zone1 55 | spec: 56 | type: etcd2 57 | etcd2: 58 | address: etcd-zone1-client:2379 59 | pathPrefix: /vitess/zone1 60 | defaults: 61 | replicas: 1 62 | image: vitess/vttablet:helm-1.0.4 63 | keyspaces: 64 | - metadata: 65 | name: unsharded-dbname 66 | spec: 67 | shards: 68 | - metadata: 69 | name: "0" 70 | spec: 71 | defaults: 72 | replicas: 2 73 | containers: 74 | mysql: 75 | image: percona:5.7.23 76 | vttablet: 77 | image: vitess/vttablet:helm-1.0.4 78 | tablets: 79 | - metadata: 80 | name: zone1 81 | spec: 82 | cellID: zone1 83 | tabletID: 101 84 | type: replica 85 | - metadata: 86 | name: sharded-dbname 87 | spec: 88 | shards: 89 | - metadata: 90 | name: "x-80" 91 | spec: 92 | keyRange: { to: "80" } 93 | defaults: 94 | replicas: 2 95 | containers: 96 | mysql: 97 | image: percona:5.7.23 98 | vttablet: 99 | image: vitess/vttablet:helm-1.0.4 100 | tablets: 101 | - metadata: 102 | name: zone1 103 | spec: 104 | cellID: zone1 105 | tabletID: 102 106 | type: replica 107 | - metadata: 108 | name: "80-x" 109 | spec: 110 | keyRange: { from: "80" } 111 | defaults: 112 | replicas: 2 113 | containers: 114 | mysql: 115 | image: percona:5.7.23 116 | vttablet: 117 | image: vitess/vttablet:helm-1.0.4 118 | tablets: 119 | - metadata: 120 | name: zone1 121 | spec: 122 | cellID: zone1 123 | tabletID: 103 124 | type: replica 125 | -------------------------------------------------------------------------------- /pkg/controller/vitesscluster/reconcile_cell_test.go: -------------------------------------------------------------------------------- 1 | package vitesscluster 2 | 3 | import ( 4 | "strings" 5 | "testing" 6 | 7 | appsv1 "k8s.io/api/apps/v1" 8 | corev1 "k8s.io/api/core/v1" 9 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 10 | 11 | vitessv1alpha2 "vitess.io/vitess-operator/pkg/apis/vitess/v1alpha2" 12 | // "vitess.io/vitess-operator/pkg/normalizer" 13 | ) 14 | 15 | func TestGetCellVTGateResources(t *testing.T) { 16 | 17 | // Define a minimal cluster 18 | cluster := &vitessv1alpha2.VitessCluster{ 19 | ObjectMeta: metav1.ObjectMeta{ 20 | Name: "testcluster", 21 | Namespace: "vitess", 22 | }, 23 | Spec: vitessv1alpha2.VitessClusterSpec{ 24 | Lockserver: &vitessv1alpha2.VitessLockserver{ 25 | Spec: vitessv1alpha2.VitessLockserverSpec{ 26 | Type: vitessv1alpha2.LockserverTypeEtcd2, 27 | Etcd2: &vitessv1alpha2.Etcd2Lockserver{ 28 | Address: "global-lockserver:8080", 29 | Path: "/global", 30 | }, 31 | }, 32 | }, 33 | }, 34 | } 35 | 36 | // Define a basic cell 37 | cell := &vitessv1alpha2.VitessCell{ 38 | ObjectMeta: metav1.ObjectMeta{ 39 | Name: "zone0", 40 | Namespace: "vitess", 41 | }, 42 | Spec: vitessv1alpha2.VitessCellSpec{ 43 | Lockserver: &vitessv1alpha2.VitessLockserver{ 44 | ObjectMeta: metav1.ObjectMeta{ 45 | Name: "cell-lockserver", 46 | }, 47 | Spec: vitessv1alpha2.VitessLockserverSpec{}, 48 | }, 49 | }, 50 | } 51 | 52 | cell.SetParentCluster(cluster) 53 | 54 | // Get the resources 55 | deployment, service, err := GetCellVTGateResources(cell) 56 | 57 | if err != nil { 58 | t.Errorf("Got error generating vtgate resources for cell: %s", err) 59 | } 60 | 61 | // Validate basic returns 62 | if deployment == nil { 63 | t.Error("Got nil vtgate deployment for cell") 64 | } 65 | 66 | if service == nil { 67 | t.Error("Got nil vtgate service for cell") 68 | } 69 | 70 | // Test no mysql protocol 71 | 72 | if vtGateServiceHasMySQLPort(service) { 73 | t.Error("vtgate service had mysql port set and shouldn't have") 74 | } 75 | 76 | if vtGateDeploymentHasMySQLOpts(deployment, "-mysql_auth") { 77 | t.Error("vtgate deployment had mysql auth flags set and shouldn't have") 78 | } 79 | 80 | // Test mysql protocol with explict auth disable 81 | cell.Spec.MySQLProtocol = &vitessv1alpha2.VitessCellMySQLProtocol{ 82 | AuthType: vitessv1alpha2.VitessMySQLAuthTypeNone, 83 | } 84 | 85 | deployment, service, err = GetCellVTGateResources(cell) 86 | 87 | if err != nil { 88 | t.Errorf("Got error generating vtgate resources for cell with mysql and no auth: %s", err) 89 | } 90 | 91 | if !vtGateServiceHasMySQLPort(service) { 92 | t.Error("vtgate service did not have mysql port set") 93 | } 94 | 95 | if !vtGateDeploymentHasMySQLOpts(deployment, "-mysql_auth_server_impl=\"none\"") { 96 | t.Error("vtgate deployment did not have mysql no auth flag") 97 | } 98 | 99 | // Test mysql protocol with static auth 100 | cell.Spec.MySQLProtocol = &vitessv1alpha2.VitessCellMySQLProtocol{ 101 | Username: "test", 102 | PasswordSecretRef: &corev1.SecretKeySelector{}, 103 | } 104 | 105 | deployment, service, err = GetCellVTGateResources(cell) 106 | 107 | if err != nil { 108 | t.Errorf("Got error generating vtgate resources for cell with mysql and basic auth: %s", err) 109 | } 110 | 111 | if !vtGateServiceHasMySQLPort(service) { 112 | t.Error("vtgate service did not have mysql port set") 113 | } 114 | 115 | if !vtGateDeploymentHasMySQLOpts(deployment, "-mysql_auth_server_impl=\"static\"") { 116 | t.Error("vtgate deployment did not have mysql static auth flag") 117 | } 118 | } 119 | 120 | func vtGateServiceHasMySQLPort(service *corev1.Service) bool { 121 | for _, port := range service.Spec.Ports { 122 | if port.Name == "mysql" { 123 | return true 124 | } 125 | } 126 | return false 127 | } 128 | 129 | func vtGateDeploymentHasMySQLOpts(deployment *appsv1.Deployment, optstr string) bool { 130 | if strings.Contains(deployment.Spec.Template.Spec.Containers[0].Args[1], optstr) { 131 | return true 132 | } 133 | 134 | return false 135 | } 136 | -------------------------------------------------------------------------------- /pkg/apis/vitess/v1alpha2/vitesstablet_helpers.go: -------------------------------------------------------------------------------- 1 | package v1alpha2 2 | 3 | import ( 4 | // "fmt" 5 | "strconv" 6 | "strings" 7 | ) 8 | 9 | // GetTabletContainers satisfies ConfigProvider 10 | func (tablet *VitessTablet) GetTabletContainers() *TabletContainers { 11 | return tablet.Spec.Containers 12 | } 13 | 14 | func (tablet *VitessTablet) SetParentCluster(cluster *VitessCluster) { 15 | tablet.Spec.parent.Cluster = cluster 16 | } 17 | 18 | func (tablet *VitessTablet) SetParentCell(cell *VitessCell) { 19 | tablet.Spec.parent.Cell = cell 20 | } 21 | 22 | func (tablet *VitessTablet) SetParentKeyspace(keyspace *VitessKeyspace) { 23 | tablet.Spec.parent.Keyspace = keyspace 24 | } 25 | 26 | func (tablet *VitessTablet) SetParentShard(shard *VitessShard) { 27 | tablet.Spec.parent.Shard = shard 28 | } 29 | 30 | func (tablet *VitessTablet) Lockserver() *VitessLockserver { 31 | return tablet.Cell().Lockserver() 32 | } 33 | 34 | func (tablet *VitessTablet) Cluster() *VitessCluster { 35 | return tablet.Spec.parent.Cluster 36 | } 37 | 38 | func (tablet *VitessTablet) Cell() *VitessCell { 39 | return tablet.Spec.parent.Cell 40 | } 41 | 42 | func (tablet *VitessTablet) Keyspace() *VitessKeyspace { 43 | return tablet.Spec.parent.Keyspace 44 | } 45 | 46 | func (tablet *VitessTablet) Shard() *VitessShard { 47 | return tablet.Spec.parent.Shard 48 | } 49 | 50 | func (tablet *VitessTablet) GetStatefulSetName() string { 51 | return tablet.GetScopedName(string(tablet.Spec.Type)) 52 | } 53 | 54 | func (tablet *VitessTablet) GetScopedName(extra ...string) string { 55 | return strings.Join(append( 56 | []string{ 57 | tablet.Cluster().GetName(), 58 | tablet.Cell().GetName(), 59 | tablet.Keyspace().GetName(), 60 | tablet.Shard().GetName(), 61 | }, 62 | extra...), "-") 63 | } 64 | 65 | func (tablet *VitessTablet) GetReplicas() *int32 { 66 | if tablet.Spec.Replicas != nil { 67 | return tablet.Spec.Replicas 68 | } 69 | 70 | if tablet.Shard().Spec.Defaults != nil && tablet.Shard().Spec.Defaults.Replicas != nil { 71 | return tablet.Shard().Spec.Defaults.Replicas 72 | } 73 | 74 | var def int32 75 | return &def 76 | } 77 | 78 | func (tablet *VitessTablet) GetMySQLContainer() *MySQLContainer { 79 | // Inheritance order, with most specific first 80 | providers := []ConfigProvider{ 81 | tablet, 82 | tablet.Spec.parent.Shard, 83 | tablet.Spec.parent.Keyspace, 84 | } 85 | 86 | for _, p := range providers { 87 | if containers := p.GetTabletContainers(); containers != nil && containers.MySQL != nil { 88 | // TODO get defaults from full range of providers 89 | if containers.MySQL.DBFlavor == "" && containers.DBFlavor != "" { 90 | containers.MySQL.DBFlavor = containers.DBFlavor 91 | } 92 | if containers.MySQL.DBFlavor == "" { 93 | containers.MySQL.DBFlavor = "mysql56" 94 | } 95 | return containers.MySQL 96 | } 97 | } 98 | return nil 99 | } 100 | 101 | func (tablet *VitessTablet) GetVTTabletContainer() *VTTabletContainer { 102 | // Inheritance order, with most specific first 103 | providers := []ConfigProvider{ 104 | tablet, 105 | tablet.Shard(), 106 | tablet.Keyspace(), 107 | } 108 | 109 | for _, p := range providers { 110 | if containers := p.GetTabletContainers(); containers != nil && containers.VTTablet != nil { 111 | // TODO get defaults from full range of providers 112 | if containers.VTTablet.DBFlavor == "" && containers.DBFlavor != "" { 113 | containers.VTTablet.DBFlavor = containers.DBFlavor 114 | } 115 | if containers.VTTablet.DBFlavor == "" { 116 | containers.VTTablet.DBFlavor = "mysql56" 117 | } 118 | return containers.VTTablet 119 | } 120 | } 121 | return nil 122 | } 123 | 124 | func (tablet *VitessTablet) GetTabletID() string { 125 | return strconv.FormatInt(tablet.Spec.TabletID, 10) 126 | } 127 | 128 | func (tablet *VitessTablet) Phase() TabletPhase { 129 | return tablet.status.Phase 130 | } 131 | 132 | func (tablet *VitessTablet) SetPhase(p TabletPhase) { 133 | tablet.status.Phase = p 134 | } 135 | 136 | func (tablet *VitessTablet) InPhase(p TabletPhase) bool { 137 | return tablet.status.Phase == p 138 | } 139 | -------------------------------------------------------------------------------- /pkg/util/scripts/main.go: -------------------------------------------------------------------------------- 1 | package scripts 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "text/template" 7 | 8 | "k8s.io/apimachinery/pkg/runtime" 9 | 10 | vitessv1alpha2 "vitess.io/vitess-operator/pkg/apis/vitess/v1alpha2" 11 | ) 12 | 13 | type ContainerScriptGenerator struct { 14 | ContainerType string 15 | Object runtime.Object 16 | Init string 17 | Start string 18 | PreStop string 19 | } 20 | 21 | func NewContainerScriptGenerator(containerType string, obj runtime.Object) *ContainerScriptGenerator { 22 | return &ContainerScriptGenerator{ 23 | ContainerType: containerType, 24 | Object: obj, 25 | } 26 | } 27 | 28 | func (csg *ContainerScriptGenerator) Generate() error { 29 | var err error 30 | switch csg.ContainerType { 31 | case "vttablet": 32 | csg.Init, err = csg.getTemplatedScript("vttabletinit", VTTabletInitTemplate) 33 | if err != nil { 34 | return err 35 | } 36 | csg.Start, err = csg.getTemplatedScript("vttabletstart", VTTabletStartTemplate) 37 | if err != nil { 38 | return err 39 | } 40 | csg.PreStop, err = csg.getTemplatedScript("vttabletPreStop", VTTabletPreStopTemplate) 41 | if err != nil { 42 | return err 43 | } 44 | case "mysql": 45 | csg.Init, err = csg.getTemplatedScript("mysqlinit", MySQLInitTemplate) 46 | if err != nil { 47 | return err 48 | } 49 | csg.Start, err = csg.getTemplatedScript("mysqlstart", MySQLStartTemplate) 50 | if err != nil { 51 | return err 52 | } 53 | csg.PreStop, err = csg.getTemplatedScript("mysqlPreStop", MySQLPreStopTemplate) 54 | if err != nil { 55 | return err 56 | } 57 | case "init_replica_master": 58 | csg.Start, err = csg.getTemplatedScript("init_replica_master", InitReplicaMaster) 59 | if err != nil { 60 | return err 61 | } 62 | case "vtctld": 63 | csg.Start, err = csg.getTemplatedScript("vtctld", VtCtldStart) 64 | if err != nil { 65 | return err 66 | } 67 | case "vtgate": 68 | csg.Start, err = csg.getTemplatedScript("vtgate", VTGateStart) 69 | if err != nil { 70 | return err 71 | } 72 | case "init-mysql-creds": 73 | csg.Start, err = csg.getTemplatedScript("init-mysql-creds", InitMySQLCreds) 74 | if err != nil { 75 | return err 76 | } 77 | default: 78 | return fmt.Errorf("Unsupported container type: %s", csg.ContainerType) 79 | } 80 | 81 | return nil 82 | } 83 | 84 | func (csg *ContainerScriptGenerator) getTemplatedScript(name string, templateStr string) (string, error) { 85 | tmpl, err := template.New(name).Parse(templateStr) 86 | if err != nil { 87 | return "", err 88 | } 89 | 90 | // if tablet, ok := csg.Object.(*vitessv1alpha2.VitessTablet); ok { 91 | // return getTemplatedScriptForTablet(name, templateStr) 92 | // } 93 | 94 | // if cell, ok := csg.Object.(*vitessv1alpha2.VitessTablet); ok { 95 | // return getTemplatedScriptForTablet(name, templateStr) 96 | // } 97 | // } 98 | 99 | // func (csg *ContainerScriptGenerator) getTemplatedScriptForTablet(name string, templateStr string) (string, error) { 100 | // Params are different depending on the resource type 101 | 102 | // For simplicity, the tablet and all parent objects are passed to the template. 103 | // This is safe while the templates are hard-coded. But if templates are ever made 104 | // end-user configurable could would potentially expose too much data and would need to be sanitized 105 | var params map[string]interface{} 106 | 107 | // Configure tablet params 108 | if tablet, ok := csg.Object.(*vitessv1alpha2.VitessTablet); ok { 109 | params = map[string]interface{}{ 110 | "LocalLockserver": tablet.Lockserver(), 111 | "GlobalLockserver": tablet.Cluster().Lockserver(), 112 | "Cluster": tablet.Cluster(), 113 | "Cell": tablet.Cell(), 114 | "Keyspace": tablet.Keyspace(), 115 | "Shard": tablet.Shard(), 116 | "Tablet": tablet, 117 | "ScopedName": tablet.GetScopedName(), 118 | } 119 | } 120 | 121 | // Configure shard params 122 | if cell, ok := csg.Object.(*vitessv1alpha2.VitessCell); ok { 123 | params = map[string]interface{}{ 124 | "LocalLockserver": cell.Lockserver(), 125 | "GlobalLockserver": cell.Cluster().Lockserver(), 126 | "Cluster": cell.Cluster(), 127 | "Cell": cell, 128 | "ScopedName": cell.GetScopedName(), 129 | } 130 | } 131 | 132 | var out bytes.Buffer 133 | err = tmpl.Execute(&out, params) 134 | if err != nil { 135 | return "", err 136 | } 137 | 138 | return out.String(), nil 139 | } 140 | -------------------------------------------------------------------------------- /pkg/controller/vitesslockserver/vitesslockserver_controller.go: -------------------------------------------------------------------------------- 1 | package vitesslockserver 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/go-logr/logr" 7 | corev1 "k8s.io/api/core/v1" 8 | "k8s.io/apimachinery/pkg/api/errors" 9 | "k8s.io/apimachinery/pkg/runtime" 10 | "sigs.k8s.io/controller-runtime/pkg/client" 11 | "sigs.k8s.io/controller-runtime/pkg/controller" 12 | "sigs.k8s.io/controller-runtime/pkg/handler" 13 | "sigs.k8s.io/controller-runtime/pkg/manager" 14 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 15 | logf "sigs.k8s.io/controller-runtime/pkg/runtime/log" 16 | "sigs.k8s.io/controller-runtime/pkg/source" 17 | 18 | vitessv1alpha2 "vitess.io/vitess-operator/pkg/apis/vitess/v1alpha2" 19 | ) 20 | 21 | var log = logf.Log.WithName("controller_vitesslockserver") 22 | 23 | // Add creates a new VitessLockserver Controller and adds it to the Manager. The Manager will set fields on the Controller 24 | // and Start it when the Manager is Started. 25 | func Add(mgr manager.Manager) error { 26 | return add(mgr, newReconciler(mgr)) 27 | } 28 | 29 | // newReconciler returns a new reconcile.Reconciler 30 | func newReconciler(mgr manager.Manager) reconcile.Reconciler { 31 | return &ReconcileVitessLockserver{client: mgr.GetClient(), scheme: mgr.GetScheme()} 32 | } 33 | 34 | // add adds a new Controller to mgr with r as the reconcile.Reconciler 35 | func add(mgr manager.Manager, r reconcile.Reconciler) error { 36 | // Create a new controller 37 | c, err := controller.New("vitesslockserver-controller", mgr, controller.Options{Reconciler: r}) 38 | if err != nil { 39 | return err 40 | } 41 | 42 | // Watch for changes to primary resource VitessLockserver 43 | err = c.Watch(&source.Kind{Type: &vitessv1alpha2.VitessLockserver{}}, &handler.EnqueueRequestForObject{}) 44 | if err != nil { 45 | return err 46 | } 47 | 48 | // TODO(user): Modify this to be the types you create that are owned by the primary resource 49 | // Watch for changes to secondary resource Pods and requeue the owner VitessLockserver 50 | err = c.Watch(&source.Kind{Type: &corev1.Pod{}}, &handler.EnqueueRequestForOwner{ 51 | IsController: true, 52 | OwnerType: &vitessv1alpha2.VitessLockserver{}, 53 | }) 54 | if err != nil { 55 | return err 56 | } 57 | 58 | return nil 59 | } 60 | 61 | var _ reconcile.Reconciler = &ReconcileVitessLockserver{} 62 | 63 | // ReconcileVitessLockserver reconciles a VitessLockserver object 64 | type ReconcileVitessLockserver struct { 65 | // This client, initialized using mgr.Client() above, is a split client 66 | // that reads objects from the cache and writes to the apiserver 67 | client client.Client 68 | scheme *runtime.Scheme 69 | } 70 | 71 | // Reconcile reads that state of the cluster for a VitessLockserver object and makes changes based on the state read 72 | // and what is in the VitessLockserver.Spec 73 | // The Controller will requeue the Request to be processed again if the returned error is non-nil or 74 | // Result.Requeue is true, otherwise upon completion it will remove the work from the queue. 75 | func (r *ReconcileVitessLockserver) Reconcile(request reconcile.Request) (reconcile.Result, error) { 76 | reqLogger := log.WithValues("Request.Namespace", request.Namespace, "Request.Name", request.Name) 77 | // reqLogger.Info("Reconciling VitessLockserver") 78 | 79 | // Fetch the VitessLockserver instance 80 | instance := &vitessv1alpha2.VitessLockserver{} 81 | err := r.client.Get(context.TODO(), request.NamespacedName, instance) 82 | if err != nil { 83 | if errors.IsNotFound(err) { 84 | // Request object not found, could have been deleted after reconcile request. 85 | // Owned objects are automatically garbage collected. For additional cleanup logic use finalizers. 86 | // Return and don't requeue 87 | return reconcile.Result{}, nil 88 | } 89 | // Error reading the object - requeue the request. 90 | return reconcile.Result{}, err 91 | } 92 | 93 | rr, err := ReconcileObject(instance, reqLogger) 94 | 95 | return rr, err 96 | } 97 | 98 | // ReconcileObject does all the actual reconcile work 99 | func ReconcileObject(instance *vitessv1alpha2.VitessLockserver, upstreamLog logr.Logger) (reconcile.Result, error) { 100 | reqLogger := upstreamLog.WithValues() 101 | reqLogger.Info("Reconciling VitessLockserver") 102 | 103 | // TODO actual reconcile 104 | // if instance.Status.State != "Ready" { 105 | // instance.Status.State = "Ready" 106 | // return reconcile.Result{Requeue: true}, nil 107 | // } 108 | 109 | return reconcile.Result{}, nil 110 | } 111 | -------------------------------------------------------------------------------- /pkg/apis/vitess/v1alpha2/samples.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: vitess.io/v1alpha2 3 | kind: VitessCluster 4 | metadata: 5 | name: superawesomecluster 6 | labels: 7 | app: vitess 8 | spec: 9 | lockserver: 10 | type: etcd3 11 | address: etcd-cluster-client:2379 12 | path: /vitess/global 13 | cells: 14 | cellSelector: 15 | matchLabels: 16 | matchExpression: 17 | keyspaces: 18 | keyspaceSelector: 19 | matchLabels: 20 | matchExpression: 21 | --- 22 | apiVersion: vitess.io/v1alpha2 23 | kind: VitessCell 24 | metadata: 25 | name: uswest 26 | labels: 27 | app: vitess 28 | spec: 29 | lockserver: 30 | type: etcd3 31 | address: etcd-cluster-client:2379 32 | path: /vitess/uswest 33 | vtgate: 34 | - count: 35 | containers: 36 | vtgate: 37 | image: 38 | resources: 39 | ... 40 | affinity: 41 | ... 42 | credentials: 43 | secret: 44 | name: 45 | key: 46 | cells: 47 | - uswest 48 | - useast 49 | cellSelector: 50 | matchLabels: 51 | matchExpression: 52 | vtworker: 53 | - count: 54 | containers: 55 | vtworker: 56 | image: 57 | resources: 58 | ... 59 | affinity: 60 | ... 61 | vtctld: 62 | - count: 63 | containers: 64 | vtctld: 65 | image: 66 | resources: 67 | ... 68 | afinity: 69 | ... 70 | --- 71 | apiVersion: vitess.io/v1alpha2 72 | kind: VitessKeyspace 73 | metadata: 74 | name: messagedb 75 | labels: 76 | app: vitess 77 | cluster: superawesomecluster 78 | spec: 79 | defaults: 80 | shards: 81 | count: 82 | replicas: 83 | count: 84 | batch: 85 | count: 86 | containers: 87 | vttablet: 88 | mysql: 89 | ... 90 | cells: 91 | ... 92 | cellSelector: 93 | ... 94 | shards: 95 | ... 96 | shardSelector: 97 | ... 98 | --- 99 | apiVersion: vitess.io/v1alpha2 100 | kind: VitessShard 101 | metadata: 102 | name: "-80" 103 | labels: 104 | keyspace: messagedb 105 | cluster: superawesomecluster 106 | app: vitess 107 | spec: 108 | defaults: 109 | replicas: 110 | batch: 111 | containers: 112 | vttablet: 113 | mysql: 114 | ... 115 | volumeClaim: 116 | ... 117 | keyrange: 118 | from: 119 | to: 120 | tablets: 121 | tabletSelector: 122 | --- 123 | apiVersion: vitess.io/v1alpha2 124 | kind: VitessTablet 125 | metadata: 126 | name: "" 127 | labels: 128 | shard: "-80" 129 | keyspace: messagedb 130 | cluster: superawesomecluster 131 | cell: uswest 132 | spec: 133 | tabletId: 101 134 | cell: uswest 135 | keyrange: 136 | from: 137 | to: 138 | type: "replica|rdonly" 139 | datastore: 140 | type: local 141 | containers: 142 | vttablet: 143 | image: vitess/base 144 | resources: 145 | limit: {cpu: "100m", memory: "128mi"} 146 | mysql: 147 | image: 148 | resources: 149 | volumeClaim: 150 | ... 151 | credentials: 152 | secret: 153 | name: 154 | key: 155 | --- 156 | apiVersion: vitess.io/v1alpha2 157 | kind: VitessCluster 158 | metadata: 159 | name: superawesomecluster 160 | labels: 161 | app: vitess 162 | spec: 163 | lockserver: 164 | provision: true 165 | etcd3: 166 | address: etcd-cluster-client:2379 167 | path: /vitess/global 168 | # lockserverRef: 169 | # name: etcd 170 | cells: 171 | - metadata: 172 | name: uswest 173 | spec: 174 | lockserver: 175 | etcd3: 176 | address: etc-cluster-client:2379 177 | path: /vitess/uswest 178 | vtgate: 179 | - count: 2 180 | vtworker: 181 | - count: 2 182 | vtctld: 183 | - count: 1 184 | keyspaces: 185 | - metadata: 186 | name: messagedb 187 | spec: 188 | shards: 189 | - metadata: 190 | name: "-80" 191 | spec: 192 | keyrange: { to: "80" } 193 | tablets: 194 | - metadata: 195 | name: "uswest-101" 196 | spec: 197 | tabletId: 101 198 | cell: uswest 199 | type: "replica" 200 | keyrange: { to: "80" } 201 | - metadata: 202 | name: "80-" 203 | spec: 204 | keyrange: { from: "80" } 205 | tablets: 206 | - metadata: 207 | name: "uswest-201" 208 | spec: 209 | tabletId: 201 210 | cell: uswest 211 | type: "replica" 212 | keyrange: { from: "80" } 213 | -------------------------------------------------------------------------------- /pkg/controller/vitesscluster/reconcile_cluster.go: -------------------------------------------------------------------------------- 1 | package vitesscluster 2 | 3 | import ( 4 | "context" 5 | 6 | corev1 "k8s.io/api/core/v1" 7 | "k8s.io/apimachinery/pkg/api/errors" 8 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 9 | "k8s.io/apimachinery/pkg/types" 10 | "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" 11 | "sigs.k8s.io/controller-runtime/pkg/reconcile" 12 | 13 | vitessv1alpha2 "vitess.io/vitess-operator/pkg/apis/vitess/v1alpha2" 14 | lockserver_controller "vitess.io/vitess-operator/pkg/controller/vitesslockserver" 15 | ) 16 | 17 | // ReconcileClusterResources should only be called against a fully-populated and verified VitessCluster object 18 | func (r *ReconcileVitessCluster) ReconcileClusterResources(cluster *vitessv1alpha2.VitessCluster) (reconcile.Result, error) { 19 | if r, err := r.ReconcileClusterLockserver(cluster); err != nil || r.Requeue { 20 | return r, err 21 | } 22 | 23 | if r, err := r.ReconcileClusterTabletService(cluster); err != nil || r.Requeue { 24 | return r, err 25 | } 26 | 27 | for _, cell := range cluster.Cells() { 28 | if r, err := r.ReconcileCell(cell); err != nil || r.Requeue { 29 | return r, err 30 | } 31 | } 32 | 33 | for _, keyspace := range cluster.Keyspaces() { 34 | if r, err := r.ReconcileKeyspace(keyspace); err != nil || r.Requeue { 35 | return r, err 36 | } 37 | } 38 | 39 | return reconcile.Result{}, nil 40 | } 41 | 42 | func (r *ReconcileVitessCluster) ReconcileClusterLockserver(cluster *vitessv1alpha2.VitessCluster) (reconcile.Result, error) { 43 | log.Info("Reconciling Embedded Lockserver") 44 | 45 | // Build a complete VitessLockserver 46 | lockserver := cluster.Spec.Lockserver.DeepCopy() 47 | 48 | if cluster.Status.Lockserver != nil { 49 | // If status is not empty, deepcopy it into the tmp object 50 | cluster.Status.Lockserver.DeepCopyInto(&lockserver.Status) 51 | } 52 | 53 | // Run it through the controller's reconcile func 54 | recResult, recErr := lockserver_controller.ReconcileObject(lockserver, log) 55 | 56 | // Split and store the spec and status in the parent VitessCluster 57 | cluster.Spec.Lockserver = lockserver.DeepCopy() 58 | cluster.Status.Lockserver = lockserver.Status.DeepCopy() 59 | 60 | // Using the split client here breaks the cluster normalization 61 | // TODO Fix and re-enable 62 | 63 | // if err := r.client.Status().Update(context.TODO(), cluster); err != nil { 64 | // log.Error(err, "Failed to update VitessCluster status after lockserver change.") 65 | // return reconcile.Result{}, err 66 | // } 67 | 68 | return recResult, recErr 69 | } 70 | 71 | func (r *ReconcileVitessCluster) ReconcileClusterTabletService(cluster *vitessv1alpha2.VitessCluster) (reconcile.Result, error) { 72 | service, serviceErr := getServiceForClusterTablets(cluster) 73 | if serviceErr != nil { 74 | log.Error(serviceErr, "failed to generate service for VitessCluster tablets", "VitessCluster.Namespace", cluster.GetNamespace(), "VitessCluster.Name", cluster.GetNamespace()) 75 | return reconcile.Result{}, serviceErr 76 | } 77 | foundService := &corev1.Service{} 78 | err := r.client.Get(context.TODO(), types.NamespacedName{Name: service.GetName(), Namespace: service.GetNamespace()}, foundService) 79 | if err != nil && errors.IsNotFound(err) { 80 | controllerutil.SetControllerReference(cluster, service, r.scheme) 81 | err = r.client.Create(context.TODO(), service) 82 | if err != nil { 83 | return reconcile.Result{}, err 84 | } 85 | } else if err != nil { 86 | log.Error(err, "failed to get Service") 87 | return reconcile.Result{}, err 88 | } 89 | 90 | return reconcile.Result{}, nil 91 | } 92 | 93 | // getServiceForClusterTablets takes a vitess cluster and returns a headless service that will point to all of the cluster's tablets 94 | func getServiceForClusterTablets(cluster *vitessv1alpha2.VitessCluster) (*corev1.Service, error) { 95 | labels := map[string]string{ 96 | "app": "vitess", 97 | "cluster": cluster.GetName(), 98 | "component": "vttablet", 99 | } 100 | 101 | service := &corev1.Service{ 102 | ObjectMeta: metav1.ObjectMeta{ 103 | Name: cluster.GetTabletServiceName(), 104 | Namespace: cluster.GetNamespace(), 105 | Labels: labels, 106 | Annotations: map[string]string{ 107 | "service.alpha.kubernetes.io/tolerate-unready-endpoints": "true", 108 | }, 109 | }, 110 | Spec: corev1.ServiceSpec{ 111 | ClusterIP: corev1.ClusterIPNone, 112 | Selector: labels, 113 | Type: corev1.ServiceTypeClusterIP, 114 | PublishNotReadyAddresses: true, 115 | Ports: []corev1.ServicePort{ 116 | { 117 | Name: "web", 118 | Port: 15002, 119 | }, 120 | { 121 | Name: "grpc", 122 | Port: 16002, 123 | }, 124 | // TODO: Configure ports below only if if ppm is enabled 125 | { 126 | Name: "query-data", 127 | Port: 42001, 128 | }, 129 | { 130 | Name: "mysql-metrics", 131 | Port: 42002, 132 | }, 133 | }, 134 | }, 135 | } 136 | 137 | // The error return is always nil right now, but it still returns one just 138 | // in case there are error states in the future 139 | return service, nil 140 | } 141 | -------------------------------------------------------------------------------- /pkg/util/scripts/tablet.go: -------------------------------------------------------------------------------- 1 | package scripts 2 | 3 | const ( 4 | VTTabletInitTemplate = ` 5 | set -ex 6 | # Split pod name (via hostname) into prefix and ordinal index. 7 | hostname=$(hostname -s) 8 | [[ $hostname =~ ^(.+)-([0-9]+)$ ]] || exit 1 9 | pod_prefix=${BASH_REMATCH[1]} 10 | pod_index=${BASH_REMATCH[2]} 11 | 12 | # Prepend cell name since tablet UIDs must be globally unique. 13 | uid_name=zone1-$pod_prefix 14 | 15 | # Take MD5 hash of cellname-podprefix. 16 | uid_hash=$(echo -n $uid_name | md5sum | awk "{print \$1}") 17 | 18 | # Take first 24 bits of hash, convert to decimal. 19 | # Shift left 2 decimal digits, add in index. 20 | tablet_uid=$((16#${uid_hash:0:6} * 100 + $pod_index)) 21 | 22 | # Save UID for other containers to read. 23 | echo $tablet_uid > /vtdataroot/tabletdata/tablet-uid 24 | 25 | # Tell MySQL what hostname to report in SHOW SLAVE HOSTS. 26 | echo report-host=$hostname.{{ .Cluster.Name }}-tab > /vtdataroot/tabletdata/report-host.cnf 27 | 28 | # Orchestrator looks there, so it should match -tablet_hostname above. 29 | 30 | # make sure that etcd is initialized 31 | eval exec /vt/bin/vtctl $(cat <