diff --git a/build/xenon/Dockerfile b/build/xenon/Dockerfile new file mode 100644 index 00000000..c05a73d8 --- /dev/null +++ b/build/xenon/Dockerfile @@ -0,0 +1,22 @@ +FROM golang:1.16 as builder + +WORKDIR / +# Copy the Go Modules manifests +COPY go.mod go.mod +COPY go.sum go.sum +# cache deps before building and copying source so that we don't need to re-download as much +# and so that source changes don't invalidate our downloaded layer +RUN go env -w GOPROXY=https://goproxy.cn,direct; +#     go mod download +RUN  go mod download + +# Copy the go source +COPY cmd/xenon/main.go cmd/xenon/main.go +COPY utils/ utils/ + +# Build +RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o bin/xenonchecker cmd/xenon/main.go + +FROM radondb/xenon:1.1.5-alpha + +COPY --from=builder /bin/xenonchecker /xenonchecker diff --git a/cmd/staticcheck.conf b/cmd/staticcheck.conf new file mode 100644 index 00000000..f32065c0 --- /dev/null +++ b/cmd/staticcheck.conf @@ -0,0 +1 @@ +checks=[ "all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022", "-ST1001"] diff --git a/cmd/xenon/main.go b/cmd/xenon/main.go new file mode 100644 index 00000000..e8fb4e3f --- /dev/null +++ b/cmd/xenon/main.go @@ -0,0 +1,88 @@ +package main + +import ( + "log" + "os" + + . "github.com/radondb/radondb-mysql-kubernetes/utils" +) + +var ( + ns string + podName string +) + +func init() { + ns = os.Getenv("NAMESPACE") + podName = os.Getenv("POD_NAME") +} + +func main() { + switch os.Args[1] { + case "leaderStart": + if err := leaderStart(); err != nil { + log.Fatalf("leaderStart failed: %v", err) + } + case "leaderStop": + if err := leaderStop(); err != nil { + log.Fatalf("leaderStop failed: %v", err) + } + case "liveness": + if err := liveness(); err != nil { + log.Fatalf("liveness failed: %v", err) + } + case "readiness": + if err := readiness(); err != nil { + log.Fatalf("readiness failed: %v", err) + } + case "postStart": + if err := postStart(); err != nil { + log.Fatalf("postStart failed: %v", err) + } + case "preStop": + if err := preStop(); err != nil { + log.Fatalf("postStop failed: %v", err) + } + default: + log.Fatalf("Usage: %s leaderStart|leaderStop|liveness|readiness|postStart|preStop", os.Args[0]) + } +} + +// TODO +func leaderStart() error { + return nil +} + +func leaderStop() error { + return PatchRoleLabelTo(myself(string(Follower))) +} + +func liveness() error { + return XenonPingMyself() +} + +func readiness() error { + role := GetRole() + if role != string(Leader) { + return PatchRoleLabelTo(myself(role)) + } + return nil +} + +// TODO +func postStart() error { + return nil +} + +// TODO +func preStop() error { + return nil +} + +func myself(role string) MySQLNode { + return MySQLNode{ + PodName: podName, + Namespace: ns, + Role: role, + } +} diff --git a/mysqlcluster/container/xenon.go b/mysqlcluster/container/xenon.go index a76759f0..e5104f33 100644 --- a/mysqlcluster/container/xenon.go +++ b/mysqlcluster/container/xenon.go @@ -48,7 +48,26 @@ func (c *xenon) getCommand() []string { // getEnvVars get the container env. func (c *xenon) getEnvVars() []corev1.EnvVar { - return nil + return []corev1.EnvVar{ + { + Name: "NameSpace", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.namespace", + }, + }, + }, + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.name", + }, + }, + }, + } } // getLifecycle get the container lifecycle. diff --git a/mysqlcluster/container/xenon_test.go b/mysqlcluster/container/xenon_test.go index 54441da3..1b56f049 100644 --- a/mysqlcluster/container/xenon_test.go +++ b/mysqlcluster/container/xenon_test.go @@ -68,7 +68,26 @@ func TestGetXenonCommand(t *testing.T) { } func TestGetXenonEnvVar(t *testing.T) { - assert.Nil(t, xenonCase.Env) + assert.Equal(t, []corev1.EnvVar{ + { + Name: "NameSpace", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.namespace", + }, + }, + }, + { + Name: "POD_NAME", + ValueFrom: &corev1.EnvVarSource{ + FieldRef: &corev1.ObjectFieldSelector{ + APIVersion: "v1", + FieldPath: "metadata.name", + }, + }, + }, + }, xenonCase.Env) } func TestGetXenonLifecycle(t *testing.T) { diff --git a/utils/incluster.go b/utils/incluster.go new file mode 100644 index 00000000..2fc738e4 --- /dev/null +++ b/utils/incluster.go @@ -0,0 +1,81 @@ +package utils + +import ( + "context" + "encoding/json" + "fmt" + "log" + "os/exec" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" +) + +type raftStatus struct { + Leader string `json:"leader"` + State string `json:"state"` + Nodes []string `json:"nodes"` +} + +type MySQLNode struct { + PodName string + Namespace string + Role string +} + +func GetClientSet() (*kubernetes.Clientset, error) { + // Creates the in-cluster config + config, err := rest.InClusterConfig() + if err != nil { + return nil, fmt.Errorf("failed to create in-cluster config: %v", err) + } + // Creates the clientset + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, fmt.Errorf("failed to create clientset: %v", err) + } + return clientset, nil +} + +func PatchRoleLabelTo(n MySQLNode) error { + // Creates the clientset + clientset, err := GetClientSet() + if err != nil { + return fmt.Errorf("failed to create clientset: %v", err) + } + patch := fmt.Sprintf(`{"metadata":{"labels":{"role":"%s"}}}`, n.Role) + _, err = clientset.CoreV1().Pods(n.Namespace).Patch(context.TODO(), n.PodName, types.MergePatchType, []byte(patch), metav1.PatchOptions{}) + if err != nil { + return fmt.Errorf("failed to patch pod role label: %v", err) + } + return nil +} + +func XenonPingMyself() error { + args := []string{"xenon", "ping"} + cmd := exec.Command("xenoncli", args...) + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to exec xenoncli xenon ping: %v", err) + } + return nil +} + +func GetRaftStatus() *raftStatus { + args := []string{"raft", "status"} + cmd := exec.Command("xenoncli", args...) + res, err := cmd.Output() + if err != nil { + log.Fatalf("failed to exec xenoncli raft status: %v", err) + } + raftStatus := raftStatus{} + if err := json.Unmarshal(res, &raftStatus); err != nil { + log.Fatalf("failed to unmarshal raft status: %v", err) + } + return &raftStatus +} + +func GetRole() string { + return GetRaftStatus().State +}