Skip to content

Commit

Permalink
Merge pull request #627 from weaveworks/raw-client-testutils
Browse files Browse the repository at this point in the history
Raw client testutils
  • Loading branch information
errordeveloper authored Mar 13, 2019
2 parents 0d45e46 + 11f3c24 commit 58e08dc
Show file tree
Hide file tree
Showing 5 changed files with 201 additions and 27 deletions.
36 changes: 36 additions & 0 deletions pkg/addons/default/helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package defaultaddons_test

import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

. "github.com/weaveworks/eksctl/pkg/addons/default"

"github.com/weaveworks/eksctl/pkg/testutils"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// TODO: test UpdateAWSNode

var _ = Describe("default addons", func() {
Describe("can load a set of resources and create a fake client", func() {
It("can create the fake client and verify objects get loaded client", func() {
clientSet, _ := testutils.NewFakeClientSetWithSamples("testdata/sample-1.10.json")

nsl, err := clientSet.CoreV1().Namespaces().List(metav1.ListOptions{})
Expect(err).ToNot(HaveOccurred())
Expect(nsl.Items).To(HaveLen(0))

dl, err := clientSet.AppsV1().Deployments(metav1.NamespaceAll).List(metav1.ListOptions{})
Expect(err).ToNot(HaveOccurred())
Expect(dl.Items).To(HaveLen(1))
Expect(dl.Items[0].Spec.Template.Spec.Containers).To(HaveLen(3))

kubeProxy, err := clientSet.AppsV1().DaemonSets(metav1.NamespaceSystem).Get(KubeProxy, metav1.GetOptions{})
Expect(err).ToNot(HaveOccurred())
Expect(kubeProxy).ToNot(BeNil())
Expect(kubeProxy.Spec.Template.Spec.Containers).To(HaveLen(1))
})
})
})
2 changes: 1 addition & 1 deletion pkg/addons/default/kube_proxy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import (
"k8s.io/client-go/kubernetes/fake"
)

var _ = Describe("default addons - kube-proxy", func() {
var _ = Describe("default addons - 1.10/kube-proxy", func() {
Describe("can load a resources and create fake client", func() {
var (
clientSet *fake.Clientset
Expand Down
21 changes: 15 additions & 6 deletions pkg/kubernetes/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,13 @@ import (
type RawClient struct {
mapper meta.RESTMapper
config *restclient.Config
ClientSet kubeclient.Interface
clientSet kubeclient.Interface
}

// RawClientInterface defines high level abstraction for RawClient for testing
type RawClientInterface interface {
ClientSet() kubeclient.Interface
NewRawResource(runtime.RawExtension) (*RawResource, error)
}

// RawResource holds info about a resource along with a type-specific raw client instance
Expand All @@ -41,14 +47,14 @@ type RawResource struct {
func NewRawClient(clientSet kubeclient.Interface, config *restclient.Config) (*RawClient, error) {
c := &RawClient{
config: config,
ClientSet: clientSet,
clientSet: clientSet,
}

return c.new()
}

func (c *RawClient) new() (*RawClient, error) {
apiGroupResources, err := restmapper.GetAPIGroupResources(c.ClientSet.Discovery())
apiGroupResources, err := restmapper.GetAPIGroupResources(c.ClientSet().Discovery())
if err != nil {
return nil, errors.Wrap(err, "getting list of API resources for raw REST client")
}
Expand All @@ -68,6 +74,9 @@ func (c *RawClient) new() (*RawClient, error) {
return c, nil
}

// ClientSet returns the underlying ClientSet
func (c *RawClient) ClientSet() kubeclient.Interface { return c.clientSet }

// NewHelperFor construct a raw client helper instance for a give gvk
// (it's based on k8s.io/kubernetes/pkg/kubectl/cmd/util/factory_client_access.go)
func (c *RawClient) NewHelperFor(gvk schema.GroupVersionKind) (*resource.Helper, error) {
Expand All @@ -93,8 +102,8 @@ func (c *RawClient) NewHelperFor(gvk schema.GroupVersionKind) (*resource.Helper,
return resource.NewHelper(client, mapping), nil
}

// NewRawResource construcst a type-specific instance or rawClient for rawObj
func NewRawResource(rawClient *RawClient, rawObj runtime.RawExtension) (*RawResource, error) {
// NewRawResource constructs a type-specific instance or RawClient for rawObj
func (c *RawClient) NewRawResource(rawObj runtime.RawExtension) (*RawResource, error) {
gvk := rawObj.Object.GetObjectKind().GroupVersionKind()

obj, ok := rawObj.Object.(metav1.Object)
Expand All @@ -108,7 +117,7 @@ func NewRawResource(rawClient *RawClient, rawObj runtime.RawExtension) (*RawReso
Object: rawObj.Object,
}

helper, err := rawClient.NewHelperFor(gvk)
helper, err := c.NewHelperFor(gvk)
if err != nil {
return nil, err
}
Expand Down
51 changes: 49 additions & 2 deletions pkg/kubernetes/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,80 @@ import (
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"

"github.com/weaveworks/eksctl/pkg/testutils"
)

var _ = Describe("default addons", func() {
Describe("can create or replace missing objects", func() {
It("can update objects that already exist", func() {
sampleAddons := testutils.LoadSamples("../addons/default/testdata/sample-1.10.json")
ct := testutils.NewCollectionTracker()

for _, item := range sampleAddons {
rc, track := testutils.NewFakeRawResource(item, false)
rc, track := testutils.NewFakeRawResource(item, false, ct)
_, err := rc.CreateOrReplace()
Expect(err).ToNot(HaveOccurred())
Expect(track).ToNot(BeNil())
Expect(track.Methods()).To(Equal([]string{"GET", "GET", "PUT"}))
}

Expect(ct.Updated()).ToNot(BeEmpty())
Expect(ct.UpdatedItems()).To(HaveLen(6))
Expect(ct.Created()).To(BeEmpty())
Expect(ct.CreatedItems()).To(BeEmpty())
})

It("can create objects that don't exist yet", func() {
sampleAddons := testutils.LoadSamples("../addons/default/testdata/sample-1.10.json")
ct := testutils.NewCollectionTracker()

for _, item := range sampleAddons {
rc, track := testutils.NewFakeRawResource(item, true)
rc, track := testutils.NewFakeRawResource(item, true, ct)
_, err := rc.CreateOrReplace()
Expect(err).ToNot(HaveOccurred())
Expect(track).ToNot(BeNil())
Expect(track.Methods()).To(Equal([]string{"GET", "POST"}))
}

Expect(ct.Created()).ToNot(BeEmpty())
Expect(ct.CreatedItems()).To(HaveLen(6))
Expect(ct.Updated()).To(BeEmpty())
Expect(ct.UpdatedItems()).To(BeEmpty())
})

It("can create objests that don't exist, and convert into a clientset", func() {
sampleAddons := testutils.LoadSamples("../addons/default/testdata/sample-1.10.json")

rawClient := testutils.NewFakeRawClient()

rawClient.AssumeObjectsMissing = true

for _, item := range sampleAddons {
rc, err := rawClient.NewRawResource(runtime.RawExtension{Object: item})
Expect(err).ToNot(HaveOccurred())
_, err = rc.CreateOrReplace()
Expect(err).ToNot(HaveOccurred())
}

ct := rawClient.Collection

Expect(ct.Updated()).To(BeEmpty())
Expect(ct.Created()).ToNot(BeEmpty())
Expect(ct.CreatedItems()).To(HaveLen(6))

dsl, err := rawClient.ClientSet().AppsV1().DaemonSets(metav1.NamespaceSystem).List(metav1.ListOptions{})
Expect(err).ToNot(HaveOccurred())
Expect(dsl.Items).To(HaveLen(2))

awsNode, err := rawClient.ClientSet().AppsV1().DaemonSets(metav1.NamespaceSystem).Get("aws-node", metav1.GetOptions{})
Expect(err).ToNot(HaveOccurred())
Expect(awsNode.Spec.Template.Spec.Containers).To(HaveLen(1))
Expect(awsNode.Spec.Template.Spec.Containers[0].Image).To(
Equal("602401143452.dkr.ecr.eu-west-2.amazonaws.com/amazon-k8s-cni:v1.3.2"),
)
})
})
})
118 changes: 100 additions & 18 deletions pkg/testutils/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"io/ioutil"
"net/http"

"github.com/kris-nova/logger"
. "github.com/onsi/gomega"

"github.com/weaveworks/eksctl/pkg/kubernetes"
Expand All @@ -16,8 +15,10 @@ import (
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/cli-runtime/pkg/genericclioptions/resource"
kubeclient "k8s.io/client-go/kubernetes"
"k8s.io/client-go/kubernetes/fake"
"k8s.io/client-go/kubernetes/scheme"

restfake "k8s.io/client-go/rest/fake"
)

Expand Down Expand Up @@ -50,28 +51,72 @@ func NewFakeClientSetWithSamples(manifest string) (*fake.Clientset, []runtime.Ob

var mapper = testrestmapper.TestOnlyStaticRESTMapper(scheme.Scheme)

type CollectionTracker struct {
created map[string]runtime.Object
updated map[string]runtime.Object
}

func NewCollectionTracker() *CollectionTracker {
return &CollectionTracker{
created: make(map[string]runtime.Object),
updated: make(map[string]runtime.Object),
}
}

type requestTracker struct {
requests *[]*http.Request
missing *bool
requests *[]*http.Request
missing *bool
collection *CollectionTracker
}

func (t *requestTracker) Append(req *http.Request) {
*t.requests = append(*t.requests, req)
logger.Critical("requests = %v", t.Methods())
func objectKey(req *http.Request, item runtime.Object) string {
return fmt.Sprintf("%s [%s] (%s)",
req.Method, req.URL.Path, item.(metav1.Object).GetName())
}

func (t *requestTracker) Append(req *http.Request) { *t.requests = append(*t.requests, req) }

func (t *requestTracker) Methods() (m []string) {
for _, r := range *t.requests {
m = append(m, r.Method)
}
return
}

func (t *requestTracker) IsMissing() bool {
return *t.missing
func (t *requestTracker) IsMissing() bool { return *t.missing }

func (t *requestTracker) Create(req *http.Request, item runtime.Object) {
*t.missing = false
if t.collection != nil {
t.collection.created[objectKey(req, item)] = item
}
}

func (c *CollectionTracker) Created() map[string]runtime.Object { return c.created }

func (c *CollectionTracker) CreatedItems() (items []runtime.Object) {
for _, item := range c.created {
items = append(items, item)
}
return
}

func (t *requestTracker) Update(req *http.Request, item runtime.Object) {
if t.collection != nil {
t.collection.updated[objectKey(req, item)] = item
}
}

func NewFakeRawResource(item runtime.Object, missing bool) (*kubernetes.RawResource, requestTracker) {
func (c *CollectionTracker) Updated() map[string]runtime.Object { return c.updated }

func (c *CollectionTracker) UpdatedItems() (items []runtime.Object) {
for _, item := range c.updated {
items = append(items, item)
}
return
}

func NewFakeRawResource(item runtime.Object, missing bool, ct *CollectionTracker) (*kubernetes.RawResource, requestTracker) {
obj, ok := item.(metav1.Object)
Expect(ok).To(BeTrue())

Expand All @@ -87,8 +132,15 @@ func NewFakeRawResource(item runtime.Object, missing bool) (*kubernetes.RawResou
}

rt := requestTracker{
requests: &[]*http.Request{},
missing: &missing,
requests: &[]*http.Request{},
missing: &missing,
collection: ct,
}

notFound := http.Response{StatusCode: 404, Body: ioutil.NopCloser(bytes.NewReader([]byte{}))}

echo := func(req *http.Request) (*http.Response, error) {
return &http.Response{StatusCode: 200, Body: req.Body}, nil
}

client := &restfake.RESTClient{
Expand All @@ -98,19 +150,19 @@ func NewFakeRawResource(item runtime.Object, missing bool) (*kubernetes.RawResou
rt.Append(req)
switch req.Method {
case http.MethodGet:
logger.Critical("missing = %v", rt.IsMissing())
if rt.IsMissing() {
res := &http.Response{StatusCode: 404, Body: ioutil.NopCloser(bytes.NewReader([]byte{}))}
return res, nil
return &notFound, nil
}
data, err := runtime.Encode(unstructured.UnstructuredJSONScheme, item)
Expect(err).To(Not(HaveOccurred()))
res := &http.Response{StatusCode: 200, Body: ioutil.NopCloser(bytes.NewReader(data))}
return res, nil
case http.MethodPut, http.MethodPost:
*rt.missing = false
res := &http.Response{StatusCode: 200, Body: req.Body}
return res, nil
case http.MethodPost:
rt.Create(req, item)
return echo(req)
case http.MethodPut:
rt.Update(req, item)
return echo(req)
default:
return nil, fmt.Errorf("unexpected request: %s %s", req.Method, req.URL.Path)
}
Expand All @@ -127,3 +179,33 @@ func NewFakeRawResource(item runtime.Object, missing bool) (*kubernetes.RawResou

return rc, rt
}

type FakeRawClient struct {
Collection *CollectionTracker
AssumeObjectsMissing bool
ClientSetUseUpdatedObjects bool
}

func NewFakeRawClient() *FakeRawClient {
return &FakeRawClient{
Collection: NewCollectionTracker(),
}
}

func (c *FakeRawClient) ClientSet() kubeclient.Interface {
if c.ClientSetUseUpdatedObjects {
return fake.NewSimpleClientset(c.Collection.UpdatedItems()...)
}
return fake.NewSimpleClientset(c.Collection.CreatedItems()...)
}

func (c *FakeRawClient) NewRawResource(item runtime.RawExtension) (*kubernetes.RawResource, error) {
r, _ := NewFakeRawResource(item.Object, c.AssumeObjectsMissing, c.Collection)
return r, nil
}

func (c *FakeRawClient) ClearUpdated() {
for k := range c.Collection.updated {
delete(c.Collection.updated, k)
}
}

0 comments on commit 58e08dc

Please sign in to comment.