From 4e5debcafb8451c54032f3a57c8084b3f6c186c7 Mon Sep 17 00:00:00 2001
From: "scott.wang" <scottwangsxll@gmail.com>
Date: Fri, 13 Mar 2020 14:53:06 +0800
Subject: [PATCH] Add ut for remote/kubernetes

---
 remoting/kubernetes/client.go      |  65 ++-
 remoting/kubernetes/client_test.go | 670 +++++++++++++++++++++++++++++
 remoting/kubernetes/store.go       |  12 +-
 3 files changed, 742 insertions(+), 5 deletions(-)
 create mode 100644 remoting/kubernetes/client_test.go

diff --git a/remoting/kubernetes/client.go b/remoting/kubernetes/client.go
index e4e9f8eb8..794491ebc 100644
--- a/remoting/kubernetes/client.go
+++ b/remoting/kubernetes/client.go
@@ -22,14 +22,13 @@ import (
 	"encoding/base64"
 	"encoding/json"
 	"os"
-	"runtime/debug"
 	"sync"
 	"time"
 )
 
 import (
 	perrors "github.com/pkg/errors"
-	v1 "k8s.io/api/core/v1"
+	"k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/fields"
 	"k8s.io/apimachinery/pkg/types"
@@ -106,6 +105,53 @@ func getCurrentNameSpace() (string, error) {
 	return v, nil
 }
 
+//  new mock client
+//  new a client for  test
+func newMockClient(namespace string, mockClientGenerator func() (kubernetes.Interface, error)) (*Client, error) {
+
+	rawClient, err := mockClientGenerator()
+	if err != nil {
+		return nil, perrors.WithMessage(err, "call mock generator")
+	}
+
+	currentPodName, err := getCurrentPodName()
+	if err != nil {
+		return nil, perrors.WithMessage(err, "get pod name")
+	}
+
+	ctx, cancel := context.WithCancel(context.Background())
+
+	c := &Client{
+		currentPodName: currentPodName,
+		ns:             namespace,
+		rawClient:      rawClient,
+		ctx:            ctx,
+		store:          newStore(ctx),
+		cancel:         cancel,
+	}
+
+	currentPod, err := c.initCurrentPod()
+	if err != nil {
+		return nil, perrors.WithMessage(err, "init current pod")
+	}
+
+	// record current status
+	c.currentPod = currentPod
+
+	// init the store by current pods
+	if err := c.initStore(); err != nil {
+		return nil, perrors.WithMessage(err, "init store")
+	}
+
+	// start kubernetes watch loop
+	if err := c.maintenanceStatus(); err != nil {
+		return nil, perrors.WithMessage(err, "maintenance the kubernetes status")
+	}
+
+	logger.Info("init kubernetes registry success")
+	return c, nil
+}
+
 // newClient
 // new a client for registry
 func newClient(namespace string) (*Client, error) {
@@ -272,6 +318,7 @@ func (c *Client) maintenanceStatusLoop() {
 				// double check ctx
 				case <-c.ctx.Done():
 					logger.Info("the kubernetes client stopped")
+					goto onceWatch
 
 					// get one element from result-chan
 				case event, ok := <-wc.ResultChan():
@@ -550,12 +597,22 @@ func (c *Client) GetChildren(k string) ([]string, []string, error) {
 	return kList, vList, nil
 }
 
+// Get
+// get k's value from kubernetes-store
+func (c *Client) Get(k string) (string, error) {
+
+	objectList, err := c.store.Get(k, false)
+	if err != nil {
+		return "", perrors.WithMessagef(err, "get from store on (%s)", k)
+	}
+
+	return objectList[0].Value, nil
+}
+
 // Watch
 // watch on spec key
 func (c *Client) Watch(k string) (<-chan *Object, error) {
 
-	debug.PrintStack()
-
 	w, err := c.store.Watch(k, false)
 	if err != nil {
 		return nil, perrors.WithMessagef(err, "watch on (%s)", k)
diff --git a/remoting/kubernetes/client_test.go b/remoting/kubernetes/client_test.go
new file mode 100644
index 000000000..ffd354040
--- /dev/null
+++ b/remoting/kubernetes/client_test.go
@@ -0,0 +1,670 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package kubernetes
+
+import (
+	"encoding/json"
+	"os"
+	"strings"
+	"sync"
+	"testing"
+	"time"
+)
+
+import (
+	"github.com/pkg/errors"
+	"github.com/stretchr/testify/suite"
+	v1 "k8s.io/api/core/v1"
+	"k8s.io/client-go/kubernetes"
+	"k8s.io/client-go/kubernetes/fake"
+)
+
+// tests dataset
+var tests = []struct {
+	input struct {
+		k string
+		v string
+	}
+}{
+	{input: struct {
+		k string
+		v string
+	}{k: "name", v: "scott.wang"}},
+	{input: struct {
+		k string
+		v string
+	}{k: "namePrefix", v: "prefix.scott.wang"}},
+	{input: struct {
+		k string
+		v string
+	}{k: "namePrefix1", v: "prefix1.scott.wang"}},
+	{input: struct {
+		k string
+		v string
+	}{k: "age", v: "27"}},
+}
+
+// test dataset prefix
+const prefix = "name"
+
+var clientPodJsonData = `{
+    "apiVersion": "v1",
+    "kind": "Pod",
+    "metadata": {
+        "annotations": {
+            "dubbo.io/annotation": "W3siayI6Ii9kdWJibyIsInYiOiIifSx7ImsiOiIvZHViYm8vY29tLmlrdXJlbnRvLnVzZXIuVXNlclByb3ZpZGVyIiwidiI6IiJ9LHsiayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvY29uc3VtZXJzIiwidiI6IiJ9LHsiayI6Ii9kdWJibyIsInYiOiIifSx7ImsiOiIvZHViYm8vY29tLmlrdXJlbnRvLnVzZXIuVXNlclByb3ZpZGVyIiwidiI6IiJ9LHsiayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvcHJvdmlkZXJzIiwidiI6IiJ9LHsiayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvY29uc3VtZXJzL2NvbnN1bWVyJTNBJTJGJTJGMTcyLjE3LjAuOCUyRlVzZXJQcm92aWRlciUzRmNhdGVnb3J5JTNEY29uc3VtZXJzJTI2ZHViYm8lM0RkdWJib2dvLWNvbnN1bWVyLTIuNi4wJTI2cHJvdG9jb2wlM0RkdWJibyIsInYiOiIifV0="
+        },
+        "creationTimestamp": "2020-03-13T03:38:57Z",
+        "labels": {
+            "dubbo.io/label": "dubbo.io-value"
+        },
+        "name": "client",
+        "namespace": "default",
+        "resourceVersion": "2449700",
+        "selfLink": "/api/v1/namespaces/default/pods/client",
+        "uid": "3ec394f5-dcc6-49c3-8061-57b4b2b41344"
+    },
+    "spec": {
+        "containers": [
+            {
+                "env": [
+                    {
+                        "name": "NAMESPACE",
+                        "valueFrom": {
+                            "fieldRef": {
+                                "apiVersion": "v1",
+                                "fieldPath": "metadata.namespace"
+                            }
+                        }
+                    }
+                ],
+                "image": "registry.cn-hangzhou.aliyuncs.com/scottwang/dubbogo-client",
+                "imagePullPolicy": "Always",
+                "name": "client",
+                "resources": {},
+                "terminationMessagePath": "/dev/termination-log",
+                "terminationMessagePolicy": "File",
+                "volumeMounts": [
+                    {
+                        "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
+                        "name": "dubbo-sa-token-l2lzh",
+                        "readOnly": true
+                    }
+                ]
+            }
+        ],
+        "dnsPolicy": "ClusterFirst",
+        "enableServiceLinks": true,
+        "nodeName": "minikube",
+        "priority": 0,
+        "restartPolicy": "Never",
+        "schedulerName": "default-scheduler",
+        "securityContext": {},
+        "serviceAccount": "dubbo-sa",
+        "serviceAccountName": "dubbo-sa",
+        "terminationGracePeriodSeconds": 30,
+        "tolerations": [
+            {
+                "effect": "NoExecute",
+                "key": "node.kubernetes.io/not-ready",
+                "operator": "Exists",
+                "tolerationSeconds": 300
+            },
+            {
+                "effect": "NoExecute",
+                "key": "node.kubernetes.io/unreachable",
+                "operator": "Exists",
+                "tolerationSeconds": 300
+            }
+        ],
+        "volumes": [
+            {
+                "name": "dubbo-sa-token-l2lzh",
+                "secret": {
+                    "defaultMode": 420,
+                    "secretName": "dubbo-sa-token-l2lzh"
+                }
+            }
+        ]
+    },
+    "status": {
+        "conditions": [
+            {
+                "lastProbeTime": null,
+                "lastTransitionTime": "2020-03-13T03:38:57Z",
+                "status": "True",
+                "type": "Initialized"
+            },
+            {
+                "lastProbeTime": null,
+                "lastTransitionTime": "2020-03-13T03:40:18Z",
+                "status": "True",
+                "type": "Ready"
+            },
+            {
+                "lastProbeTime": null,
+                "lastTransitionTime": "2020-03-13T03:40:18Z",
+                "status": "True",
+                "type": "ContainersReady"
+            },
+            {
+                "lastProbeTime": null,
+                "lastTransitionTime": "2020-03-13T03:38:57Z",
+                "status": "True",
+                "type": "PodScheduled"
+            }
+        ],
+        "containerStatuses": [
+            {
+                "containerID": "docker://2870d6abc19ca7fe22ca635ebcfac5d48c6d5550a659bafd74fb48104f6dfe3c",
+                "image": "registry.cn-hangzhou.aliyuncs.com/scottwang/dubbogo-client:latest",
+                "imageID": "docker-pullable://registry.cn-hangzhou.aliyuncs.com/scottwang/dubbogo-client@sha256:1f075131f708a0d400339e81549d7c4d4ed917ab0b6bd38ef458dd06ad25a559",
+                "lastState": {},
+                "name": "client",
+                "ready": true,
+                "restartCount": 0,
+                "state": {
+                    "running": {
+                        "startedAt": "2020-03-13T03:40:17Z"
+                    }
+                }
+            }
+        ],
+        "hostIP": "10.0.2.15",
+        "phase": "Running",
+        "podIP": "172.17.0.8",
+        "qosClass": "BestEffort",
+        "startTime": "2020-03-13T03:38:57Z"
+    }
+}
+`
+
+var server1PodJsonData = `{
+    "apiVersion": "v1",
+    "kind": "Pod",
+    "metadata": {
+        "annotations": {
+            "dubbo.io/annotation": "W3siayI6Ii9kdWJibyIsInYiOiIifSx7ImsiOiIvZHViYm8vY29tLmlrdXJlbnRvLnVzZXIuVXNlclByb3ZpZGVyIiwidiI6IiJ9LHsiayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvcHJvdmlkZXJzIiwidiI6IiJ9LHsiayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvcHJvdmlkZXJzL2R1YmJvJTNBJTJGJTJGMTcyLjE3LjAuNyUzQTIwMDAwJTJGVXNlclByb3ZpZGVyJTNGYWNjZXNzbG9nJTNEJTI2YW55aG9zdCUzRHRydWUlMjZhcHAudmVyc2lvbiUzRDAuMC4xJTI2YXBwbGljYXRpb24lM0RCRFRTZXJ2aWNlJTI2YmVhbi5uYW1lJTNEVXNlclByb3ZpZGVyJTI2Y2F0ZWdvcnklM0Rwcm92aWRlcnMlMjZjbHVzdGVyJTNEZmFpbG92ZXIlMjZkdWJibyUzRGR1YmJvLXByb3ZpZGVyLWdvbGFuZy0yLjYuMCUyNmVudmlyb25tZW50JTNEZGV2JTI2ZXhlY3V0ZS5saW1pdCUzRCUyNmV4ZWN1dGUubGltaXQucmVqZWN0ZWQuaGFuZGxlciUzRCUyNmdyb3VwJTNEJTI2aW50ZXJmYWNlJTNEY29tLmlrdXJlbnRvLnVzZXIuVXNlclByb3ZpZGVyJTI2aXAlM0QxNzIuMTcuMC43JTI2bG9hZGJhbGFuY2UlM0RyYW5kb20lMjZtZXRob2RzLkdldFVzZXIubG9hZGJhbGFuY2UlM0RyYW5kb20lMjZtZXRob2RzLkdldFVzZXIucmV0cmllcyUzRDElMjZtZXRob2RzLkdldFVzZXIudHBzLmxpbWl0LmludGVydmFsJTNEJTI2bWV0aG9kcy5HZXRVc2VyLnRwcy5saW1pdC5yYXRlJTNEJTI2bWV0aG9kcy5HZXRVc2VyLnRwcy5saW1pdC5zdHJhdGVneSUzRCUyNm1ldGhvZHMuR2V0VXNlci53ZWlnaHQlM0QwJTI2bW9kdWxlJTNEZHViYm9nbyUyQnVzZXItaW5mbyUyQnNlcnZlciUyNm5hbWUlM0RCRFRTZXJ2aWNlJTI2b3JnYW5pemF0aW9uJTNEaWt1cmVudG8uY29tJTI2b3duZXIlM0RaWCUyNnBpZCUzRDEwJTI2cmVnaXN0cnkucm9sZSUzRDMlMjZyZXRyaWVzJTNEJTI2c2VydmljZS5maWx0ZXIlM0RlY2hvJTI1MkN0b2tlbiUyNTJDYWNjZXNzbG9nJTI1MkN0cHMlMjUyQ2V4ZWN1dGUlMjZzaWRlJTNEcHJvdmlkZXIlMjZ0aW1lc3RhbXAlM0QxNTg0MDcwODEwJTI2dHBzLmxpbWl0LmludGVydmFsJTNEJTI2dHBzLmxpbWl0LnJhdGUlM0QlMjZ0cHMubGltaXQucmVqZWN0ZWQuaGFuZGxlciUzRCUyNnRwcy5saW1pdC5zdHJhdGVneSUzRCUyNnRwcy5saW1pdGVyJTNEJTI2dmVyc2lvbiUzRCUyNndhcm11cCUzRDEwMCIsInYiOiIifV0="
+        },
+        "creationTimestamp": "2020-03-13T03:38:57Z",
+        "generateName": "server-5b8f9f85c6-",
+        "labels": {
+            "dubbo.io/label": "dubbo.io-value",
+            "pod-template-hash": "5b8f9f85c6",
+            "role": "server"
+        },
+        "name": "server-5b8f9f85c6-2w5rq",
+        "namespace": "default",
+        "ownerReferences": [
+            {
+                "apiVersion": "apps/v1",
+                "blockOwnerDeletion": true,
+                "controller": true,
+                "kind": "ReplicaSet",
+                "name": "server-5b8f9f85c6",
+                "uid": "65e9d2b0-f286-4b21-ac31-260f1412556b"
+            }
+        ],
+        "resourceVersion": "2449678",
+        "selfLink": "/api/v1/namespaces/default/pods/server-5b8f9f85c6-2w5rq",
+        "uid": "ae7497c7-396d-40c5-b53e-5720a696e4ee"
+    },
+    "spec": {
+        "containers": [
+            {
+                "env": [
+                    {
+                        "name": "NAMESPACE",
+                        "valueFrom": {
+                            "fieldRef": {
+                                "apiVersion": "v1",
+                                "fieldPath": "metadata.namespace"
+                            }
+                        }
+                    }
+                ],
+                "image": "registry.cn-hangzhou.aliyuncs.com/scottwang/dubbogo-server",
+                "imagePullPolicy": "Always",
+                "name": "server",
+                "resources": {},
+                "terminationMessagePath": "/dev/termination-log",
+                "terminationMessagePolicy": "File",
+                "volumeMounts": [
+                    {
+                        "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
+                        "name": "dubbo-sa-token-l2lzh",
+                        "readOnly": true
+                    }
+                ]
+            }
+        ],
+        "dnsPolicy": "ClusterFirst",
+        "enableServiceLinks": true,
+        "nodeName": "minikube",
+        "priority": 0,
+        "restartPolicy": "Always",
+        "schedulerName": "default-scheduler",
+        "securityContext": {},
+        "serviceAccount": "dubbo-sa",
+        "serviceAccountName": "dubbo-sa",
+        "terminationGracePeriodSeconds": 30,
+        "tolerations": [
+            {
+                "effect": "NoExecute",
+                "key": "node.kubernetes.io/not-ready",
+                "operator": "Exists",
+                "tolerationSeconds": 300
+            },
+            {
+                "effect": "NoExecute",
+                "key": "node.kubernetes.io/unreachable",
+                "operator": "Exists",
+                "tolerationSeconds": 300
+            }
+        ],
+        "volumes": [
+            {
+                "name": "dubbo-sa-token-l2lzh",
+                "secret": {
+                    "defaultMode": 420,
+                    "secretName": "dubbo-sa-token-l2lzh"
+                }
+            }
+        ]
+    },
+    "status": {
+        "conditions": [
+            {
+                "lastProbeTime": null,
+                "lastTransitionTime": "2020-03-13T03:38:57Z",
+                "status": "True",
+                "type": "Initialized"
+            },
+            {
+                "lastProbeTime": null,
+                "lastTransitionTime": "2020-03-13T03:40:10Z",
+                "status": "True",
+                "type": "Ready"
+            },
+            {
+                "lastProbeTime": null,
+                "lastTransitionTime": "2020-03-13T03:40:10Z",
+                "status": "True",
+                "type": "ContainersReady"
+            },
+            {
+                "lastProbeTime": null,
+                "lastTransitionTime": "2020-03-13T03:38:57Z",
+                "status": "True",
+                "type": "PodScheduled"
+            }
+        ],
+        "containerStatuses": [
+            {
+                "containerID": "docker://88144bc6eabf783c0954c2e078a7270c8a0246d6f8af081dfcc0956e8c3cd2de",
+                "image": "registry.cn-hangzhou.aliyuncs.com/scottwang/dubbogo-server:latest",
+                "imageID": "docker-pullable://registry.cn-hangzhou.aliyuncs.com/scottwang/dubbogo-server@sha256:60654ddba3a16ca3de52c8e30650a4c1d8b2ed8f8542af489b7a5a459e46fe6b",
+                "lastState": {},
+                "name": "server",
+                "ready": true,
+                "restartCount": 0,
+                "state": {
+                    "running": {
+                        "startedAt": "2020-03-13T03:40:10Z"
+                    }
+                }
+            }
+        ],
+        "hostIP": "10.0.2.15",
+        "phase": "Running",
+        "podIP": "172.17.0.7",
+        "qosClass": "BestEffort",
+        "startTime": "2020-03-13T03:38:57Z"
+    }
+}
+`
+
+var server2PodJsonData = `{
+    "apiVersion": "v1",
+    "kind": "Pod",
+    "metadata": {
+        "annotations": {
+            "dubbo.io/annotation": "W3siayI6Ii9kdWJibyIsInYiOiIifSx7ImsiOiIvZHViYm8vY29tLmlrdXJlbnRvLnVzZXIuVXNlclByb3ZpZGVyIiwidiI6IiJ9LHsiayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvcHJvdmlkZXJzIiwidiI6IiJ9LHsiayI6Ii9kdWJiby9jb20uaWt1cmVudG8udXNlci5Vc2VyUHJvdmlkZXIvcHJvdmlkZXJzL2R1YmJvJTNBJTJGJTJGMTcyLjE3LjAuNiUzQTIwMDAwJTJGVXNlclByb3ZpZGVyJTNGYWNjZXNzbG9nJTNEJTI2YW55aG9zdCUzRHRydWUlMjZhcHAudmVyc2lvbiUzRDAuMC4xJTI2YXBwbGljYXRpb24lM0RCRFRTZXJ2aWNlJTI2YmVhbi5uYW1lJTNEVXNlclByb3ZpZGVyJTI2Y2F0ZWdvcnklM0Rwcm92aWRlcnMlMjZjbHVzdGVyJTNEZmFpbG92ZXIlMjZkdWJibyUzRGR1YmJvLXByb3ZpZGVyLWdvbGFuZy0yLjYuMCUyNmVudmlyb25tZW50JTNEZGV2JTI2ZXhlY3V0ZS5saW1pdCUzRCUyNmV4ZWN1dGUubGltaXQucmVqZWN0ZWQuaGFuZGxlciUzRCUyNmdyb3VwJTNEJTI2aW50ZXJmYWNlJTNEY29tLmlrdXJlbnRvLnVzZXIuVXNlclByb3ZpZGVyJTI2aXAlM0QxNzIuMTcuMC42JTI2bG9hZGJhbGFuY2UlM0RyYW5kb20lMjZtZXRob2RzLkdldFVzZXIubG9hZGJhbGFuY2UlM0RyYW5kb20lMjZtZXRob2RzLkdldFVzZXIucmV0cmllcyUzRDElMjZtZXRob2RzLkdldFVzZXIudHBzLmxpbWl0LmludGVydmFsJTNEJTI2bWV0aG9kcy5HZXRVc2VyLnRwcy5saW1pdC5yYXRlJTNEJTI2bWV0aG9kcy5HZXRVc2VyLnRwcy5saW1pdC5zdHJhdGVneSUzRCUyNm1ldGhvZHMuR2V0VXNlci53ZWlnaHQlM0QwJTI2bW9kdWxlJTNEZHViYm9nbyUyQnVzZXItaW5mbyUyQnNlcnZlciUyNm5hbWUlM0RCRFRTZXJ2aWNlJTI2b3JnYW5pemF0aW9uJTNEaWt1cmVudG8uY29tJTI2b3duZXIlM0RaWCUyNnBpZCUzRDEwJTI2cmVnaXN0cnkucm9sZSUzRDMlMjZyZXRyaWVzJTNEJTI2c2VydmljZS5maWx0ZXIlM0RlY2hvJTI1MkN0b2tlbiUyNTJDYWNjZXNzbG9nJTI1MkN0cHMlMjUyQ2V4ZWN1dGUlMjZzaWRlJTNEcHJvdmlkZXIlMjZ0aW1lc3RhbXAlM0QxNTg0MDcwODA5JTI2dHBzLmxpbWl0LmludGVydmFsJTNEJTI2dHBzLmxpbWl0LnJhdGUlM0QlMjZ0cHMubGltaXQucmVqZWN0ZWQuaGFuZGxlciUzRCUyNnRwcy5saW1pdC5zdHJhdGVneSUzRCUyNnRwcy5saW1pdGVyJTNEJTI2dmVyc2lvbiUzRCUyNndhcm11cCUzRDEwMCIsInYiOiIifV0="
+        },
+        "creationTimestamp": "2020-03-13T03:38:57Z",
+        "generateName": "server-5b8f9f85c6-",
+        "labels": {
+            "dubbo.io/label": "dubbo.io-value",
+            "pod-template-hash": "5b8f9f85c6",
+            "role": "server"
+        },
+        "name": "server-5b8f9f85c6-xk5md",
+        "namespace": "default",
+        "ownerReferences": [
+            {
+                "apiVersion": "apps/v1",
+                "blockOwnerDeletion": true,
+                "controller": true,
+                "kind": "ReplicaSet",
+                "name": "server-5b8f9f85c6",
+                "uid": "65e9d2b0-f286-4b21-ac31-260f1412556b"
+            }
+        ],
+        "resourceVersion": "2449667",
+        "selfLink": "/api/v1/namespaces/default/pods/server-5b8f9f85c6-xk5md",
+        "uid": "9e59e164-6620-473b-a983-472ebc1120e9"
+    },
+    "spec": {
+        "containers": [
+            {
+                "env": [
+                    {
+                        "name": "NAMESPACE",
+                        "valueFrom": {
+                            "fieldRef": {
+                                "apiVersion": "v1",
+                                "fieldPath": "metadata.namespace"
+                            }
+                        }
+                    }
+                ],
+                "image": "registry.cn-hangzhou.aliyuncs.com/scottwang/dubbogo-server",
+                "imagePullPolicy": "Always",
+                "name": "server",
+                "resources": {},
+                "terminationMessagePath": "/dev/termination-log",
+                "terminationMessagePolicy": "File",
+                "volumeMounts": [
+                    {
+                        "mountPath": "/var/run/secrets/kubernetes.io/serviceaccount",
+                        "name": "dubbo-sa-token-l2lzh",
+                        "readOnly": true
+                    }
+                ]
+            }
+        ],
+        "dnsPolicy": "ClusterFirst",
+        "enableServiceLinks": true,
+        "nodeName": "minikube",
+        "priority": 0,
+        "restartPolicy": "Always",
+        "schedulerName": "default-scheduler",
+        "securityContext": {},
+        "serviceAccount": "dubbo-sa",
+        "serviceAccountName": "dubbo-sa",
+        "terminationGracePeriodSeconds": 30,
+        "tolerations": [
+            {
+                "effect": "NoExecute",
+                "key": "node.kubernetes.io/not-ready",
+                "operator": "Exists",
+                "tolerationSeconds": 300
+            },
+            {
+                "effect": "NoExecute",
+                "key": "node.kubernetes.io/unreachable",
+                "operator": "Exists",
+                "tolerationSeconds": 300
+            }
+        ],
+        "volumes": [
+            {
+                "name": "dubbo-sa-token-l2lzh",
+                "secret": {
+                    "defaultMode": 420,
+                    "secretName": "dubbo-sa-token-l2lzh"
+                }
+            }
+        ]
+    },
+    "status": {
+        "conditions": [
+            {
+                "lastProbeTime": null,
+                "lastTransitionTime": "2020-03-13T03:38:57Z",
+                "status": "True",
+                "type": "Initialized"
+            },
+            {
+                "lastProbeTime": null,
+                "lastTransitionTime": "2020-03-13T03:40:09Z",
+                "status": "True",
+                "type": "Ready"
+            },
+            {
+                "lastProbeTime": null,
+                "lastTransitionTime": "2020-03-13T03:40:09Z",
+                "status": "True",
+                "type": "ContainersReady"
+            },
+            {
+                "lastProbeTime": null,
+                "lastTransitionTime": "2020-03-13T03:38:57Z",
+                "status": "True",
+                "type": "PodScheduled"
+            }
+        ],
+        "containerStatuses": [
+            {
+                "containerID": "docker://442dc055392cc720b6a6eb0bc4105f13ea86e63cbdced5c83f15463bc61add76",
+                "image": "registry.cn-hangzhou.aliyuncs.com/scottwang/dubbogo-server:latest",
+                "imageID": "docker-pullable://registry.cn-hangzhou.aliyuncs.com/scottwang/dubbogo-server@sha256:60654ddba3a16ca3de52c8e30650a4c1d8b2ed8f8542af489b7a5a459e46fe6b",
+                "lastState": {},
+                "name": "server",
+                "ready": true,
+                "restartCount": 0,
+                "state": {
+                    "running": {
+                        "startedAt": "2020-03-13T03:40:09Z"
+                    }
+                }
+            }
+        ],
+        "hostIP": "10.0.2.15",
+        "phase": "Running",
+        "podIP": "172.17.0.6",
+        "qosClass": "BestEffort",
+        "startTime": "2020-03-13T03:38:57Z"
+    }
+}`
+
+type KubernetesClientTestSuite struct {
+	suite.Suite
+
+	client *Client
+
+	currentPod     v1.Pod
+	fakeServerPod1 v1.Pod
+	fakeServerPod2 v1.Pod
+}
+
+func (s *KubernetesClientTestSuite) SetupSuite() {
+
+	t := s.T()
+
+	// 1. install test data
+	if err := json.Unmarshal([]byte(clientPodJsonData), &s.currentPod); err != nil {
+		t.Fatal(err)
+	}
+	if err := json.Unmarshal([]byte(server1PodJsonData), &s.fakeServerPod1); err != nil {
+		t.Fatal(err)
+	}
+	if err := json.Unmarshal([]byte(server2PodJsonData), &s.fakeServerPod2); err != nil {
+		t.Fatal(err)
+	}
+
+	// 2. set downward-api inject env
+	if err := os.Setenv(podNameKey, s.currentPod.GetName()); err != nil {
+		t.Fatal(err)
+	}
+	if err := os.Setenv(nameSpaceKey, s.currentPod.GetNamespace()); err != nil {
+		t.Fatal(err)
+	}
+}
+
+func (s *KubernetesClientTestSuite) TearDownSuite() {
+	s.client.Close()
+	os.Unsetenv(podNameKey)
+	os.Unsetenv(nameSpaceKey)
+}
+
+func (s *KubernetesClientTestSuite) SetupTest() {
+
+	t := s.T()
+	var err error
+	s.client, err = newMockClient(s.currentPod.GetNamespace(), func() (kubernetes.Interface, error) {
+
+		out := fake.NewSimpleClientset()
+
+		// mock current pod
+		if _, err := out.CoreV1().Pods(s.currentPod.GetNamespace()).Create(&s.currentPod); err != nil {
+			return nil, errors.WithMessage(err, "mock current pod ")
+		}
+		return out, nil
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+}
+
+func (s *KubernetesClientTestSuite) TestClientValid() {
+
+	t := s.T()
+
+	if s.client.Valid() != true {
+		t.Fatal("client is not valid")
+	}
+	s.client.Close()
+
+	if s.client.Valid() != false {
+		t.Fatal("client is valid")
+	}
+}
+
+func (s *KubernetesClientTestSuite) TestClientDone() {
+
+	t := s.T()
+
+	go func() {
+		time.Sleep(time.Second)
+		s.client.Close()
+	}()
+
+	<-s.client.Done()
+
+	if s.client.Valid() == true {
+		t.Fatal("client should be invalid then")
+	}
+}
+
+func (s *KubernetesClientTestSuite) TestClientCreateKV() {
+
+	t := s.T()
+	defer s.client.Close()
+
+	for _, tc := range tests {
+
+		k := tc.input.k
+		v := tc.input.v
+
+		if err := s.client.Create(k, v); err != nil {
+			t.Fatal(err)
+		}
+
+	}
+}
+
+func (s *KubernetesClientTestSuite) TestClientGetChildrenKVList() {
+
+	t := s.T()
+	defer s.client.Close()
+
+	expect := make(map[string]string)
+	got := make(map[string]string)
+
+	for _, tc := range tests {
+
+		k := tc.input.k
+		v := tc.input.v
+
+		if strings.Contains(k, prefix) {
+			expect[k] = v
+		}
+
+		if err := s.client.Create(k, v); err != nil {
+			t.Fatal(err)
+		}
+	}
+
+	kList, vList, err := s.client.GetChildren(prefix)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	for i := 0; i < len(kList); i++ {
+		got[kList[i]] = vList[i]
+	}
+
+	for expectK, expectV := range expect {
+
+		if got[expectK] != expectV {
+			t.Fatalf("expect {%s: %s} but got {%s: %v}", expectK, expectV, expectK, got[expectK])
+		}
+	}
+
+}
+
+func (s *KubernetesClientTestSuite) TestClientWatch() {
+
+	t := s.T()
+
+	wg := sync.WaitGroup{}
+	wg.Add(1)
+
+	go func() {
+
+		defer wg.Done()
+
+		wc, err := s.client.WatchWithPrefix(prefix)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		for e := range wc {
+			t.Logf("got event %v k %s v %s", e.EventType, e.Key, e.Value)
+		}
+
+	}()
+
+	for _, tc := range tests {
+
+		k := tc.input.k
+		v := tc.input.v
+
+		if err := s.client.Create(k, v); err != nil {
+			t.Fatal(err)
+		}
+	}
+
+	s.client.Close()
+	wg.Wait()
+}
+
+func TestKubernetesClient(t *testing.T) {
+	suite.Run(t, new(KubernetesClientTestSuite))
+}
diff --git a/remoting/kubernetes/store.go b/remoting/kubernetes/store.go
index d7f35ced6..5feab9f0b 100644
--- a/remoting/kubernetes/store.go
+++ b/remoting/kubernetes/store.go
@@ -76,7 +76,7 @@ type Store interface {
 	// put the object to the store
 	Put(object *Object) error
 	// if prefix is false,
-	// the len([]*Object) == 0
+	// the len([]*Object) == 1
 	Get(key string, prefix bool) ([]*Object, error)
 	// watch the spec key or key prefix
 	Watch(key string, prefix bool) (Watcher, error)
@@ -166,6 +166,16 @@ func (s *storeImpl) Put(object *Object) error {
 	if object.EventType == Delete {
 		delete(s.cache, object.Key)
 	} else {
+
+		old, ok := s.cache[object.Key]
+		if ok {
+			if old.Value == object.Value {
+				// already have this k/v pair
+				return nil
+			}
+		}
+
+		// refresh the object
 		s.cache[object.Key] = object
 	}
 
-- 
GitLab