Skip to content

Commit

Permalink
Add the field spec.readOnly and the logic required for this
Browse files Browse the repository at this point in the history
  • Loading branch information
MerceaOtniel committed Aug 21, 2018
1 parent eb2732c commit 6e78f63
Show file tree
Hide file tree
Showing 9 changed files with 339 additions and 18 deletions.
8 changes: 3 additions & 5 deletions cmd/mysql-helper/apphelper/apphelper.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,9 @@ func waitForMysqlReady() error {

func configReadOnly() error {
var query string
if tb.NodeRole() == "master" {
query = "SET GLOBAL READ_ONLY = 0"
} else {
query = "SET GLOBAL SUPER_READ_ONLY = 1"
}

query = "SET GLOBAL SUPER_READ_ONLY = 1"

if err := tb.RunQuery(query); err != nil {
return fmt.Errorf("failed to set read_only config, err: %s", err)
}
Expand Down
2 changes: 2 additions & 0 deletions deploy/mysqlclusters.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ spec:
type: integer
required:
- maxQueryTime
readOnly:
type: boolean
replicas:
description: The number of pods. This updates replicas filed Defaults
to 0
Expand Down
2 changes: 1 addition & 1 deletion hack/charts/mysql-operator/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ orchestrator:
RecoverMasterClusterFilters: ['.*']
RecoverIntermediateMasterClusterFilters: ['.*']
# `reset slave all` and `set read_only=0` on promoted master
ApplyMySQLPromotionAfterMasterFailover: true
ApplyMySQLPromotionAfterMasterFailover: false
# set downtime on the failed master
MasterFailoverLostInstancesDowntimeMinutes: 10
# https://github.com/github/orchestrator/blob/master/docs/configuration-recovery.md#promotion-actions
Expand Down
4 changes: 4 additions & 0 deletions pkg/apis/mysql/v1alpha1/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ type ClusterSpec struct {
// QueryLimits represents limits for a query
// +optional
QueryLimits *QueryLimits `json:"queryLimits,omitempty"`

ReadOnly bool `json:"readOnly,omitempty"`
}

type MysqlConf map[string]string
Expand Down Expand Up @@ -140,6 +142,7 @@ const (
ClusterConditionReady ClusterConditionType = "Ready"
ClusterConditionConfig = "ConfigReady"
ClusterConditionFailoverAck = "PendingFailoverAck"
ClusterConditionReadOnly = "ReadOnly"
)

type NodeStatus struct {
Expand All @@ -159,6 +162,7 @@ const (
NodeConditionLagged NodeConditionType = "Lagged"
NodeConditionReplicating = "Replicating"
NodeConditionMaster = "Master"
NodeConditionReadOnly = "ReadOnly"
)

type PodSpec struct {
Expand Down
126 changes: 123 additions & 3 deletions pkg/mysqlcluster/orc_reconciliation.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ func (f *cFactory) SyncOrchestratorStatus(ctx context.Context) error {
}

if insts, err := f.orcClient.Cluster(f.getClusterAlias()); err == nil {

f.updateStatusFromOrc(insts)

} else {
glog.Errorf("Fail to get cluster from orchestrator: %s. Now tries to register nodes.", err)
return f.registerNodesInOrc()
Expand Down Expand Up @@ -72,8 +74,101 @@ func (f *cFactory) SyncOrchestratorStatus(ctx context.Context) error {
return nil
}

func getInstance(hostname string, insts []orc.Instance) (*orc.Instance, error) {

for _, node := range insts {
host := node.Key.Hostname

if host == hostname {
return &node, nil
}
}

return nil, fmt.Errorf("the element was not found")
}

func getMaster(node *orc.Instance, insts []orc.Instance) (string, error) {

if len(node.MasterKey.Hostname) != 0 && node.IsCoMaster == false {
next, err := getInstance(node.MasterKey.Hostname, insts)
if err == nil {
return getMaster(next, insts)
} else {
return "", err
}
}

if node.IsCoMaster == true {
next, err := getInstance(node.MasterKey.Hostname, insts)
if err == nil {
return next.Key.Hostname, nil
} else {
return "", err
}
}

return node.Key.Hostname, nil
}

func determineMasterFor(insts []orc.Instance) (string, error) {

var masterForNode []string

for _, node := range insts {
master, err := getMaster(&node, insts)
if err == nil {
masterForNode = append(masterForNode, master)
} else {
return "", fmt.Errorf("not able to retrieve the root of this node %s", node.Key.Hostname)
}
}

if len(masterForNode) != 0 {
masterHostName := masterForNode[0]
var check bool = true
for _, node := range masterForNode {
if node != masterHostName {
check = false
}

}
if check == true {
return masterHostName, nil
} else {
return "", fmt.Errorf("multiple masters")
}
} else {
return "", fmt.Errorf("0 elements in instance array")
}

}

func (f *cFactory) setMasterWritable(masterHostName string, insts []orc.Instance) error {
master, err := getInstance(masterHostName, insts)

if err != nil {
return err
}

if f.cluster.Spec.ReadOnly == false {
err := f.orcClient.SetHostWritable(master.Key.Hostname, master.Key.Port)
if err != nil {
return err
}
} else {
err = f.orcClient.SetHostReadOnly(master.Key.Hostname, master.Key.Port)
if err != nil {
return err
}
}

return nil
}

func (f *cFactory) updateStatusFromOrc(insts []orc.Instance) {
updatedNodes := []string{}

var isReadOnly bool = true
for _, node := range insts {
host := node.Key.Hostname
updatedNodes = append(updatedNodes, host)
Expand Down Expand Up @@ -106,11 +201,36 @@ func (f *cFactory) updateStatusFromOrc(insts []orc.Instance) {
f.updateNodeCondition(host, api.NodeConditionReplicating, core.ConditionFalse)
}

if !node.ReadOnly {
f.updateNodeCondition(host, api.NodeConditionMaster, core.ConditionTrue)
f.updateNodeCondition(host, api.NodeConditionMaster, core.ConditionFalse)

isReadOnly = isReadOnly && node.ReadOnly

if node.ReadOnly == true {
f.updateNodeCondition(host, api.NodeConditionReadOnly, core.ConditionTrue)
} else {
f.updateNodeCondition(host, api.NodeConditionMaster, core.ConditionFalse)
f.updateNodeCondition(host, api.NodeConditionReadOnly, core.ConditionFalse)
}

}

masterHostName, err := determineMasterFor(insts)
if err == nil {
f.updateNodeCondition(masterHostName, api.NodeConditionMaster, core.ConditionTrue)
} else {
f.updateNodeCondition(masterHostName, api.NodeConditionMaster, core.ConditionFalse)
}

err = f.setMasterWritable(masterHostName, insts)
if err != nil {
glog.Infof("Error setting Master readOnly/writable %s", err)
}

if isReadOnly == true {
f.cluster.UpdateStatusCondition(api.ClusterConditionReadOnly,
core.ConditionTrue, "initializedTrue", "settingReadOnlyTrue")
} else {
f.cluster.UpdateStatusCondition(api.ClusterConditionReadOnly,
core.ConditionFalse, "initializedFalse", "settingReadOnlyFalse")
}

f.removeNodeConditionNotIn(updatedNodes)
Expand Down
116 changes: 107 additions & 9 deletions pkg/mysqlcluster/orc_reconciliation_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,20 @@ package mysqlcluster

import (
"context"
"testing"
"time"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/tools/record"

api "github.com/presslabs/mysql-operator/pkg/apis/mysql/v1alpha1"
fakeMyClient "github.com/presslabs/mysql-operator/pkg/generated/clientset/versioned/fake"
"github.com/presslabs/mysql-operator/pkg/util/options"
fakeOrc "github.com/presslabs/mysql-operator/pkg/util/orchestrator/fake"
tutil "github.com/presslabs/mysql-operator/pkg/util/test"
core "k8s.io/api/core/v1"
meta "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/tools/record"
"testing"
"time"
)

func TestReconciliation(t *testing.T) {
Expand Down Expand Up @@ -99,6 +98,8 @@ var _ = Describe("Mysql cluster reconcilation", func() {
Ω(rec.Events).Should(Receive(&event))
Expect(event).To(ContainSubstring("ReplicationStopped"))
Ω(rec.Events).Should(Receive(&event))
Expect(event).To(ContainSubstring("DemoteMaster"))
Ω(rec.Events).Should(Receive(&event))
Expect(event).To(ContainSubstring("PromoteMaster"))
})

Expand Down Expand Up @@ -148,13 +149,13 @@ var _ = Describe("Mysql cluster reconcilation", func() {
Expect(event).To(ContainSubstring("RecoveryAcked"))
})

It("node not uptodate in orc", func() {
It("master is in orc", func() {
orcClient.AddInstance("asd.default", cluster.GetPodHostname(0),
true, -1, false, false)
Ω(factory.SyncOrchestratorStatus(ctx)).Should(Succeed())

Expect(cluster.Status.Nodes[0].GetCondition(api.NodeConditionMaster).Status).To(
Equal(core.ConditionUnknown))
Equal(core.ConditionTrue))
})

It("node not in orc", func() {
Expand All @@ -173,6 +174,103 @@ var _ = Describe("Mysql cluster reconcilation", func() {

})

It("existence of a single master", func() {

orcClient.AddInstanceInTopology("asd.default", "foo122-mysql-0",
3306, false, -1, false, true, "", 0, false)

orcClient.AddInstanceInTopology("asd.default", "foo122-mysql-1",
3307, false, -1, false, true, "foo122-mysql-0", 3306, false)

orcClient.AddInstanceInTopology("asd.default", "foo122-mysql-2",
3308, false, -1, false, true, "foo122-mysql-0", 3306, false)

orcClient.AddInstanceInTopology("asd.default", "foo122-mysql-3",
3309, false, -1, false, true, "foo122-mysql-2", 3308, false)

orcClient.AddInstanceInTopology("asd.default", "foo122-mysql-4",
3310, false, -1, false, true, "foo122-mysql-3", 3309, false)

insts, _ := orcClient.Cluster("asd.default")

_, err := determineMasterFor(insts)
Expect(err).To(BeNil())

})

It("existence of multiple masters", func() {

orcClient.AddInstanceInTopology("asd.default", "foo122-mysql-0",
3306, false, -1, false, true, "foo122-mysql-5", 0, true)

orcClient.AddInstanceInTopology("asd.default", "foo122-mysql-1",
3307, false, -1, false, true, "foo122-mysql-0", 3306, false)

orcClient.AddInstanceInTopology("asd.default", "foo122-mysql-2",
3308, false, -1, false, true, "foo122-mysql-0", 3306, false)

orcClient.AddInstanceInTopology("asd.default", "foo122-mysql-3",
3309, false, -1, false, true, "foo122-mysql-2", 3308, false)

orcClient.AddInstanceInTopology("asd.default", "foo122-mysql-4",
3310, false, -1, false, true, "foo122-mysql-3", 3309, false)

orcClient.AddInstanceInTopology("asd.default", "foo122-mysql-5",
3311, false, -1, false, true, "foo122-mysql-0", 3309, true)

insts, _ := orcClient.Cluster("asd.default")

_, err := determineMasterFor(insts)
Expect(err).ToNot(BeNil())

})

It("no instances", func() {

insts, _ := orcClient.Cluster("asd.default")

_, err := determineMasterFor(insts)
Expect(err).ToNot(BeNil())

})

It("set master readOnly/Writable", func() {

//Set ReadOnly to true in order to get master ReadOnly

orcClient.AddInstance("asd.default", cluster.GetPodHostname(0),
true, -1, false, true)

factory.cluster.Spec.ReadOnly = true

insts, _ := orcClient.Cluster("asd.default")

err := factory.setMasterWritable(cluster.GetPodHostname(0), insts)
Expect(err).To(BeNil())

for _, instance := range insts {
if instance.Key.Hostname == cluster.GetPodHostname(0) && instance.Key.Port == 3306 {
Expect(instance.ReadOnly).To(Equal(true))
}
}

//Set ReadOnly to false in order to get the master Writable

factory.cluster.Spec.ReadOnly = false

insts, _ = orcClient.Cluster("asd.default")

err = factory.setMasterWritable(cluster.GetPodHostname(0), insts)
Expect(err).To(BeNil())

for _, instance := range insts {
if instance.Key.Hostname == cluster.GetPodHostname(0) && instance.Key.Port == 3306 {
Expect(instance.ReadOnly).To(Equal(false))
}
}

})

})
})
})
Expand Down
6 changes: 6 additions & 0 deletions pkg/openapi/openapi_generated.go
Original file line number Diff line number Diff line change
Expand Up @@ -363,6 +363,12 @@ func schema_pkg_apis_mysql_v1alpha1_ClusterSpec(ref common.ReferenceCallback) co
Ref: ref("github.com/presslabs/mysql-operator/pkg/apis/mysql/v1alpha1.QueryLimits"),
},
},
"readOnly": {
SchemaProps: spec.SchemaProps{
Type: []string{"boolean"},
Format: "",
},
},
},
},
},
Expand Down
Loading

0 comments on commit 6e78f63

Please sign in to comment.