Repository: radondb/radondb-mysql-kubernetes
Pull request #596 (2022-07-12 03:46:52)

*: Add inCluster related API and xenon checker
### What type of PR is this?

/enhancement

### Which issue(s) this PR fixes?

Fixes #

### What this PR does?

Summary:

- Add the dockerfile of xenon.
- Add inCluster related API and xenon checker

> TODO: Moving other dockerfiles to the Build directory, this requires
updating related CIs first.

```
        new file:   build/xenon/Dockerfile
        new file:   cmd/staticcheck.conf
        new file:   cmd/xenon/main.go
        modified:   mysqlcluster/container/xenon.go
        modified:   sidecar/config.go
        new file:   utils/incluster.go
```

### Special notes for your reviewer?



Patch:
diff --git a/build/xenon/Dockerfile b/build/xenon/Dockerfile
new file mode 100644
index 0000000..c05a73d
--- /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 0000000..f32065c
--- /dev/null
+++ b/cmd/staticcheck.conf
@@ -0,0 +1,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 0000000..e8fb4e3
--- /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 a76759f..e5104f3 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/utils/incluster.go b/utils/incluster.go
new file mode 100644
index 0000000..2fc738e
--- /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
+}


Test patch:
diff --git a/mysqlcluster/container/xenon_test.go b/mysqlcluster/container/xenon_test.go
index 54441da..1b56f04 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) {