github.com/felipejfc/helm@v2.1.2+incompatible/pkg/kube/client_test.go (about)

     1  /*
     2  Copyright 2016 The Kubernetes Authors All rights reserved.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package kube
    18  
    19  import (
    20  	"bytes"
    21  	"io"
    22  	"io/ioutil"
    23  	"net/http"
    24  	"strings"
    25  	"testing"
    26  
    27  	"k8s.io/kubernetes/pkg/api"
    28  	"k8s.io/kubernetes/pkg/api/testapi"
    29  	"k8s.io/kubernetes/pkg/api/unversioned"
    30  	"k8s.io/kubernetes/pkg/api/validation"
    31  	"k8s.io/kubernetes/pkg/client/restclient/fake"
    32  	cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing"
    33  	"k8s.io/kubernetes/pkg/kubectl/resource"
    34  	"k8s.io/kubernetes/pkg/runtime"
    35  )
    36  
    37  func objBody(codec runtime.Codec, obj runtime.Object) io.ReadCloser {
    38  	return ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(codec, obj))))
    39  }
    40  
    41  func newPod(name string) api.Pod {
    42  	return api.Pod{
    43  		ObjectMeta: api.ObjectMeta{Name: name},
    44  		Spec: api.PodSpec{
    45  			Containers: []api.Container{{
    46  				Name:  "app:v4",
    47  				Image: "abc/app:v4",
    48  				Ports: []api.ContainerPort{{Name: "http", ContainerPort: 80}},
    49  			}},
    50  		},
    51  	}
    52  }
    53  
    54  func newPodList(names ...string) api.PodList {
    55  	var list api.PodList
    56  	for _, name := range names {
    57  		list.Items = append(list.Items, newPod(name))
    58  	}
    59  	return list
    60  }
    61  
    62  func notFoundBody() *unversioned.Status {
    63  	return &unversioned.Status{
    64  		Code:    http.StatusNotFound,
    65  		Status:  unversioned.StatusFailure,
    66  		Reason:  unversioned.StatusReasonNotFound,
    67  		Message: " \"\" not found",
    68  		Details: &unversioned.StatusDetails{},
    69  	}
    70  }
    71  
    72  func newResponse(code int, obj runtime.Object) (*http.Response, error) {
    73  	header := http.Header{}
    74  	header.Set("Content-Type", runtime.ContentTypeJSON)
    75  	body := ioutil.NopCloser(bytes.NewReader([]byte(runtime.EncodeOrDie(testapi.Default.Codec(), obj))))
    76  	return &http.Response{StatusCode: code, Header: header, Body: body}, nil
    77  }
    78  
    79  func TestUpdate(t *testing.T) {
    80  	listA := newPodList("starfish", "otter", "squid")
    81  	listB := newPodList("starfish", "otter", "dolphin")
    82  	listB.Items[0].Spec.Containers[0].Ports = []api.ContainerPort{{Name: "https", ContainerPort: 443}}
    83  
    84  	actions := make(map[string]string)
    85  
    86  	f, tf, codec, ns := cmdtesting.NewAPIFactory()
    87  	tf.Client = &fake.RESTClient{
    88  		NegotiatedSerializer: ns,
    89  		Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
    90  			p, m := req.URL.Path, req.Method
    91  			actions[p] = m
    92  			switch {
    93  			case p == "/namespaces/test/pods/starfish" && m == "GET":
    94  				return newResponse(200, &listA.Items[0])
    95  			case p == "/namespaces/test/pods/otter" && m == "GET":
    96  				return newResponse(200, &listA.Items[1])
    97  			case p == "/namespaces/test/pods/dolphin" && m == "GET":
    98  				return newResponse(404, notFoundBody())
    99  			case p == "/namespaces/test/pods/starfish" && m == "PATCH":
   100  				data, err := ioutil.ReadAll(req.Body)
   101  				if err != nil {
   102  					t.Fatalf("could not dump request: %s", err)
   103  				}
   104  				req.Body.Close()
   105  				expected := `{"spec":{"containers":[{"name":"app:v4","ports":[{"containerPort":443,"name":"https","protocol":"TCP"},{"$patch":"delete","containerPort":80}]}]}}`
   106  				if string(data) != expected {
   107  					t.Errorf("expected patch %s, got %s", expected, string(data))
   108  				}
   109  				return newResponse(200, &listB.Items[0])
   110  			case p == "/namespaces/test/pods" && m == "POST":
   111  				return newResponse(200, &listB.Items[1])
   112  			case p == "/namespaces/test/pods/squid" && m == "DELETE":
   113  				return newResponse(200, &listB.Items[1])
   114  			default:
   115  				t.Fatalf("unexpected request: %#v\n%#v", req.URL, req)
   116  				return nil, nil
   117  			}
   118  		}),
   119  	}
   120  
   121  	c := &Client{Factory: f}
   122  	if err := c.Update("test", objBody(codec, &listA), objBody(codec, &listB)); err != nil {
   123  		t.Fatal(err)
   124  	}
   125  
   126  	expectedActions := map[string]string{
   127  		"/namespaces/test/pods/dolphin":  "GET",
   128  		"/namespaces/test/pods/otter":    "GET",
   129  		"/namespaces/test/pods/starfish": "PATCH",
   130  		"/namespaces/test/pods":          "POST",
   131  		"/namespaces/test/pods/squid":    "DELETE",
   132  	}
   133  
   134  	for k, v := range expectedActions {
   135  		if m, ok := actions[k]; !ok || m != v {
   136  			t.Errorf("expected a %s request to %s", k, v)
   137  		}
   138  	}
   139  }
   140  
   141  func TestPerform(t *testing.T) {
   142  	tests := []struct {
   143  		name        string
   144  		namespace   string
   145  		reader      io.Reader
   146  		count       int
   147  		swaggerFile string
   148  		err         bool
   149  		errMessage  string
   150  	}{
   151  		{
   152  			name:      "Valid input",
   153  			namespace: "test",
   154  			reader:    strings.NewReader(guestbookManifest),
   155  			count:     6,
   156  		}, {
   157  			name:       "Empty manifests",
   158  			namespace:  "test",
   159  			reader:     strings.NewReader(""),
   160  			err:        true,
   161  			errMessage: "no objects visited",
   162  		}, {
   163  			name:        "Invalid schema",
   164  			namespace:   "test",
   165  			reader:      strings.NewReader(testInvalidServiceManifest),
   166  			swaggerFile: "../../vendor/k8s.io/kubernetes/api/swagger-spec/" + testapi.Default.GroupVersion().Version + ".json",
   167  			err:         true,
   168  			errMessage:  `error validating "": error validating data: expected type int, for field spec.ports[0].port, got string`,
   169  		},
   170  	}
   171  
   172  	for _, tt := range tests {
   173  		results := []*resource.Info{}
   174  
   175  		fn := func(info *resource.Info) error {
   176  			results = append(results, info)
   177  
   178  			if info.Namespace != tt.namespace {
   179  				t.Errorf("%q. expected namespace to be '%s', got %s", tt.name, tt.namespace, info.Namespace)
   180  			}
   181  			return nil
   182  		}
   183  
   184  		f, tf, _, _ := cmdtesting.NewAPIFactory()
   185  		c := &Client{Factory: f}
   186  		if tt.swaggerFile != "" {
   187  			data, err := ioutil.ReadFile(tt.swaggerFile)
   188  			if err != nil {
   189  				t.Fatalf("could not read swagger spec: %s", err)
   190  			}
   191  			validator, err := validation.NewSwaggerSchemaFromBytes(data, nil)
   192  			if err != nil {
   193  				t.Fatalf("could not load swagger spec: %s", err)
   194  			}
   195  			tf.Validator = validator
   196  		}
   197  
   198  		err := perform(c, tt.namespace, tt.reader, fn)
   199  		if (err != nil) != tt.err {
   200  			t.Errorf("%q. expected error: %v, got %v", tt.name, tt.err, err)
   201  		}
   202  		if err != nil && err.Error() != tt.errMessage {
   203  			t.Errorf("%q. expected error message: %v, got %v", tt.name, tt.errMessage, err)
   204  		}
   205  
   206  		if len(results) != tt.count {
   207  			t.Errorf("%q. expected %d result objects, got %d", tt.name, tt.count, len(results))
   208  		}
   209  	}
   210  }
   211  
   212  func TestReal(t *testing.T) {
   213  	t.Skip("This is a live test, comment this line to run")
   214  	c := New(nil)
   215  	if err := c.Create("test", strings.NewReader(guestbookManifest)); err != nil {
   216  		t.Fatal(err)
   217  	}
   218  
   219  	testSvcEndpointManifest := testServiceManifest + "\n---\n" + testEndpointManifest
   220  	c = New(nil)
   221  	if err := c.Create("test-delete", strings.NewReader(testSvcEndpointManifest)); err != nil {
   222  		t.Fatal(err)
   223  	}
   224  
   225  	if err := c.Delete("test-delete", strings.NewReader(testEndpointManifest)); err != nil {
   226  		t.Fatal(err)
   227  	}
   228  
   229  	// ensures that delete does not fail if a resource is not found
   230  	if err := c.Delete("test-delete", strings.NewReader(testSvcEndpointManifest)); err != nil {
   231  		t.Fatal(err)
   232  	}
   233  }
   234  
   235  const testServiceManifest = `
   236  kind: Service
   237  apiVersion: v1
   238  metadata:
   239    name: my-service
   240  spec:
   241    selector:
   242      app: myapp
   243    ports:
   244      - port: 80
   245        protocol: TCP
   246        targetPort: 9376
   247  `
   248  
   249  const testInvalidServiceManifest = `
   250  kind: Service
   251  apiVersion: v1
   252  spec:
   253    ports:
   254      - port: "80"
   255  `
   256  
   257  const testEndpointManifest = `
   258  kind: Endpoints
   259  apiVersion: v1
   260  metadata:
   261    name: my-service
   262  subsets:
   263    - addresses:
   264        - ip: "1.2.3.4"
   265      ports:
   266        - port: 9376
   267  `
   268  
   269  const guestbookManifest = `
   270  apiVersion: v1
   271  kind: Service
   272  metadata:
   273    name: redis-master
   274    labels:
   275      app: redis
   276      tier: backend
   277      role: master
   278  spec:
   279    ports:
   280    - port: 6379
   281      targetPort: 6379
   282    selector:
   283      app: redis
   284      tier: backend
   285      role: master
   286  ---
   287  apiVersion: extensions/v1beta1
   288  kind: Deployment
   289  metadata:
   290    name: redis-master
   291  spec:
   292    replicas: 1
   293    template:
   294      metadata:
   295        labels:
   296          app: redis
   297          role: master
   298          tier: backend
   299      spec:
   300        containers:
   301        - name: master
   302          image: gcr.io/google_containers/redis:e2e  # or just image: redis
   303          resources:
   304            requests:
   305              cpu: 100m
   306              memory: 100Mi
   307          ports:
   308          - containerPort: 6379
   309  ---
   310  apiVersion: v1
   311  kind: Service
   312  metadata:
   313    name: redis-slave
   314    labels:
   315      app: redis
   316      tier: backend
   317      role: slave
   318  spec:
   319    ports:
   320      # the port that this service should serve on
   321    - port: 6379
   322    selector:
   323      app: redis
   324      tier: backend
   325      role: slave
   326  ---
   327  apiVersion: extensions/v1beta1
   328  kind: Deployment
   329  metadata:
   330    name: redis-slave
   331  spec:
   332    replicas: 2
   333    template:
   334      metadata:
   335        labels:
   336          app: redis
   337          role: slave
   338          tier: backend
   339      spec:
   340        containers:
   341        - name: slave
   342          image: gcr.io/google_samples/gb-redisslave:v1
   343          resources:
   344            requests:
   345              cpu: 100m
   346              memory: 100Mi
   347          env:
   348          - name: GET_HOSTS_FROM
   349            value: dns
   350          ports:
   351          - containerPort: 6379
   352  ---
   353  apiVersion: v1
   354  kind: Service
   355  metadata:
   356    name: frontend
   357    labels:
   358      app: guestbook
   359      tier: frontend
   360  spec:
   361    ports:
   362    - port: 80
   363    selector:
   364      app: guestbook
   365      tier: frontend
   366  ---
   367  apiVersion: extensions/v1beta1
   368  kind: Deployment
   369  metadata:
   370    name: frontend
   371  spec:
   372    replicas: 3
   373    template:
   374      metadata:
   375        labels:
   376          app: guestbook
   377          tier: frontend
   378      spec:
   379        containers:
   380        - name: php-redis
   381          image: gcr.io/google-samples/gb-frontend:v4
   382          resources:
   383            requests:
   384              cpu: 100m
   385              memory: 100Mi
   386          env:
   387          - name: GET_HOSTS_FROM
   388            value: dns
   389          ports:
   390          - containerPort: 80
   391  `