From c0dd4c32098dc39b01267da3ea216ed695d624c8 Mon Sep 17 00:00:00 2001 From: Emile Vauge Date: Wed, 20 Apr 2016 13:26:51 +0200 Subject: [PATCH] Add unit test Signed-off-by: Emile Vauge --- examples/k8s.ingress.yaml | 48 +++++----- examples/k8s.rc.yaml | 7 -- provider/k8s/client.go | 18 ++-- provider/k8s/service.go | 13 +++ provider/kubernetes.go | 14 +-- provider/kubernetes_test.go | 183 ++++++++++++++++++++++++++++++++++++ templates/kubernetes.tmpl | 2 +- webui/src/index.html | 4 +- 8 files changed, 242 insertions(+), 47 deletions(-) diff --git a/examples/k8s.ingress.yaml b/examples/k8s.ingress.yaml index 7963bb2a6..0e460f48f 100644 --- a/examples/k8s.ingress.yaml +++ b/examples/k8s.ingress.yaml @@ -2,14 +2,14 @@ apiVersion: v1 kind: Service metadata: - name: whoami-x + name: service1 labels: app: whoami spec: type: NodePort ports: - port: 80 - nodePort: 30301 + nodePort: 30283 targetPort: 80 protocol: TCP name: http @@ -19,24 +19,7 @@ spec: apiVersion: v1 kind: Service metadata: - name: whoami-default - labels: - app: whoami -spec: - type: NodePort - ports: - - port: 80 - nodePort: 30302 - targetPort: 80 - protocol: TCP - name: http - selector: - app: whoami ---- -apiVersion: v1 -kind: Service -metadata: - name: whoami-y + name: service2 labels: app: whoami spec: @@ -50,6 +33,23 @@ spec: selector: app: whoami --- +apiVersion: v1 +kind: Service +metadata: + name: service3 + labels: + app: whoami +spec: + type: NodePort + ports: + - port: 80 + nodePort: 30285 + targetPort: 80 + protocol: TCP + name: http + selector: + app: whoami +--- # A single RC matching all Services apiVersion: v1 kind: ReplicationController @@ -72,7 +72,7 @@ spec: apiVersion: extensions/v1beta1 kind: Ingress metadata: - name: whoamimap + name: whoamiIngress spec: rules: - host: foo.localhost @@ -80,14 +80,14 @@ spec: paths: - path: /bar backend: - serviceName: whoami-x + serviceName: service1 servicePort: 80 - host: bar.localhost http: paths: - backend: - serviceName: whoami-y + serviceName: service2 servicePort: 80 - backend: - serviceName: whoami-x + serviceName: service3 servicePort: 80 diff --git a/examples/k8s.rc.yaml b/examples/k8s.rc.yaml index 11963dfb2..74eb8aa62 100644 --- a/examples/k8s.rc.yaml +++ b/examples/k8s.rc.yaml @@ -19,13 +19,6 @@ spec: - image: containous/traefik:k8s name: traefik-ingress-lb imagePullPolicy: Always - # livenessProbe: - # httpGet: - # path: /healthz - # port: 10249 - # scheme: HTTP - # initialDelaySeconds: 30 - # timeoutSeconds: 5 ports: - containerPort: 80 hostPort: 80 diff --git a/provider/k8s/client.go b/provider/k8s/client.go index f1cbc9ddb..20e501cbb 100644 --- a/provider/k8s/client.go +++ b/provider/k8s/client.go @@ -21,7 +21,13 @@ const ( ) // Client is a client for the Kubernetes master. -type Client struct { +type Client interface { + GetIngresses(predicate func(Ingress) bool) ([]Ingress, error) + WatchIngresses(predicate func(Ingress) bool, stopCh <-chan bool) (chan interface{}, chan error, error) + GetServices(predicate func(Service) bool) ([]Service, error) +} + +type clientImpl struct { endpointURL string tls *tls.Config token string @@ -32,12 +38,12 @@ type Client struct { // The provided host is an url (scheme://hostname[:port]) of a // Kubernetes master without any path. // The provided client is an authorized http.Client used to perform requests to the Kubernetes API master. -func NewClient(baseURL string, caCert []byte, token string) (*Client, error) { +func NewClient(baseURL string, caCert []byte, token string) (Client, error) { validURL, err := url.Parse(baseURL) if err != nil { return nil, fmt.Errorf("failed to parse URL %q: %v", baseURL, err) } - return &Client{ + return &clientImpl{ endpointURL: strings.TrimSuffix(validURL.String(), "/"), token: token, caCert: caCert, @@ -45,7 +51,7 @@ func NewClient(baseURL string, caCert []byte, token string) (*Client, error) { } // GetIngresses returns all services in the cluster -func (c *Client) GetIngresses(predicate func(Ingress) bool) ([]Ingress, error) { +func (c *clientImpl) GetIngresses(predicate func(Ingress) bool) ([]Ingress, error) { getURL := c.endpointURL + extentionsEndpoint + defaultIngress request := gorequest.New().Get(getURL) if len(c.token) > 0 { @@ -76,7 +82,7 @@ func (c *Client) GetIngresses(predicate func(Ingress) bool) ([]Ingress, error) { } // WatchIngresses returns all services in the cluster -func (c *Client) WatchIngresses(predicate func(Ingress) bool, stopCh <-chan bool) (chan interface{}, chan error, error) { +func (c *clientImpl) WatchIngresses(predicate func(Ingress) bool, stopCh <-chan bool) (chan interface{}, chan error, error) { watchCh := make(chan interface{}) errCh := make(chan error) @@ -130,7 +136,7 @@ func (c *Client) WatchIngresses(predicate func(Ingress) bool, stopCh <-chan bool } // GetServices returns all services in the cluster -func (c *Client) GetServices(predicate func(Service) bool) ([]Service, error) { +func (c *clientImpl) GetServices(predicate func(Service) bool) ([]Service, error) { getURL := c.endpointURL + APIEndpoint + defaultService // Make request to Kubernetes API diff --git a/provider/k8s/service.go b/provider/k8s/service.go index de5711ca3..e501718ce 100644 --- a/provider/k8s/service.go +++ b/provider/k8s/service.go @@ -249,6 +249,19 @@ type IntOrString struct { StrVal string } +// FromInt creates an IntOrString object with an int32 value. It is +// your responsibility not to call this method with a value greater +// than int32. +// TODO: convert to (val int32) +func FromInt(val int) IntOrString { + return IntOrString{Type: Int, IntVal: int32(val)} +} + +// FromString creates an IntOrString object with a string value. +func FromString(val string) IntOrString { + return IntOrString{Type: String, StrVal: val} +} + // String returns the string value, or the Itoa of the int value. func (intstr *IntOrString) String() string { if intstr.Type == String { diff --git a/provider/kubernetes.go b/provider/kubernetes.go index bef3cf4a7..0ea5f06bb 100644 --- a/provider/kubernetes.go +++ b/provider/kubernetes.go @@ -23,7 +23,7 @@ type Kubernetes struct { Endpoint string } -func (provider *Kubernetes) createClient() (*k8s.Client, error) { +func (provider *Kubernetes) createClient() (k8s.Client, error) { var token string tokenBytes, err := ioutil.ReadFile(serviceAccountToken) if err == nil { @@ -103,7 +103,7 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage return nil } -func (provider *Kubernetes) loadIngresses(k8sClient *k8s.Client) (*types.Configuration, error) { +func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configuration, error) { ingresses, err := k8sClient.GetIngresses(func(ingress k8s.Ingress) bool { return true }) @@ -136,7 +136,7 @@ func (provider *Kubernetes) loadIngresses(k8sClient *k8s.Client) (*types.Configu } if len(pa.Path) > 0 { templateObjects.Frontends[r.Host+pa.Path].Routes[pa.Path] = types.Route{ - Rule: pa.Path, + Rule: "PathStrip:" + pa.Path, } } services, err := k8sClient.GetServices(func(service k8s.Service) bool { @@ -151,13 +151,13 @@ func (provider *Kubernetes) loadIngresses(k8sClient *k8s.Client) (*types.Configu for _, port := range service.Spec.Ports { if port.Port == pa.Backend.ServicePort.IntValue() { protocol = port.Name + templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{ + URL: protocol + "://" + service.Spec.ClusterIP + ":" + pa.Backend.ServicePort.String(), + Weight: 1, + } break } } - templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{ - URL: protocol + "://" + service.Spec.ClusterIP + ":" + pa.Backend.ServicePort.String(), - Weight: 1, - } } } } diff --git a/provider/kubernetes_test.go b/provider/kubernetes_test.go index 4f504f668..c525344bf 100644 --- a/provider/kubernetes_test.go +++ b/provider/kubernetes_test.go @@ -1 +1,184 @@ package provider + +import ( + "github.com/containous/traefik/provider/k8s" + "github.com/containous/traefik/types" + "reflect" + "testing" +) + +func TestLoadIngresses(t *testing.T) { + ingresses := []k8s.Ingress{{ + Spec: k8s.IngressSpec{ + Rules: []k8s.IngressRule{ + { + Host: "foo", + IngressRuleValue: k8s.IngressRuleValue{ + HTTP: &k8s.HTTPIngressRuleValue{ + Paths: []k8s.HTTPIngressPath{ + { + Path: "/bar", + Backend: k8s.IngressBackend{ + ServiceName: "service1", + ServicePort: k8s.FromInt(801), + }, + }, + }, + }, + }, + }, + { + Host: "bar", + IngressRuleValue: k8s.IngressRuleValue{ + HTTP: &k8s.HTTPIngressRuleValue{ + Paths: []k8s.HTTPIngressPath{ + { + Backend: k8s.IngressBackend{ + ServiceName: "service3", + ServicePort: k8s.FromInt(803), + }, + }, + { + Backend: k8s.IngressBackend{ + ServiceName: "service2", + ServicePort: k8s.FromInt(802), + }, + }, + }, + }, + }, + }, + }, + }, + }} + services := []k8s.Service{ + { + ObjectMeta: k8s.ObjectMeta{ + Name: "service1", + UID: "1", + }, + Spec: k8s.ServiceSpec{ + ClusterIP: "10.0.0.1", + Ports: []k8s.ServicePort{ + { + Name: "http", + Port: 801, + }, + }, + }, + }, + { + ObjectMeta: k8s.ObjectMeta{ + Name: "service2", + UID: "2", + }, + Spec: k8s.ServiceSpec{ + ClusterIP: "10.0.0.2", + Ports: []k8s.ServicePort{ + { + Name: "http", + Port: 802, + }, + }, + }, + }, + { + ObjectMeta: k8s.ObjectMeta{ + Name: "service3", + UID: "3", + }, + Spec: k8s.ServiceSpec{ + ClusterIP: "10.0.0.3", + Ports: []k8s.ServicePort{ + { + Name: "http", + Port: 803, + }, + }, + }, + }, + } + watchChan := make(chan interface{}) + client := clientMock{ + ingresses: ingresses, + services: services, + watchChan: watchChan, + } + provider := Kubernetes{} + actual, err := provider.loadIngresses(client) + if err != nil { + t.Fatalf("error %+v", err) + } + + expected := &types.Configuration{ + Backends: map[string]*types.Backend{ + "foo/bar": { + Servers: map[string]types.Server{ + "1": { + URL: "http://10.0.0.1:801", + Weight: 1, + }, + }, + CircuitBreaker: nil, + LoadBalancer: nil, + }, + "bar": { + Servers: map[string]types.Server{ + "2": { + URL: "http://10.0.0.2:802", + Weight: 1, + }, + "3": { + URL: "http://10.0.0.3:803", + Weight: 1, + }, + }, + CircuitBreaker: nil, + LoadBalancer: nil, + }, + }, + Frontends: map[string]*types.Frontend{ + "foo/bar": { + Backend: "foo/bar", + Routes: map[string]types.Route{ + "/bar": { + Rule: "PathStrip:/bar", + }, + "foo": { + Rule: "Host:foo", + }, + }, + }, + "bar": { + Backend: "bar", + Routes: map[string]types.Route{ + "bar": { + Rule: "Host:bar", + }, + }, + }, + }, + } + if !reflect.DeepEqual(actual.Backends, expected.Backends) { + t.Fatalf("expected %+v, got %+v", expected.Backends, actual.Backends) + } + if !reflect.DeepEqual(actual.Frontends, expected.Frontends) { + t.Fatalf("expected %+v, got %+v", expected.Frontends, actual.Frontends) + } +} + +type clientMock struct { + ingresses []k8s.Ingress + services []k8s.Service + watchChan chan interface{} +} + +func (c clientMock) GetIngresses(predicate func(k8s.Ingress) bool) ([]k8s.Ingress, error) { + return c.ingresses, nil +} +func (c clientMock) WatchIngresses(predicate func(k8s.Ingress) bool, stopCh <-chan bool) (chan interface{}, chan error, error) { + return c.watchChan, make(chan error), nil +} +func (c clientMock) GetServices(predicate func(k8s.Service) bool) ([]k8s.Service, error) { + return c.services, nil +} diff --git a/templates/kubernetes.tmpl b/templates/kubernetes.tmpl index 01a21c73c..1f7dfaba1 100644 --- a/templates/kubernetes.tmpl +++ b/templates/kubernetes.tmpl @@ -11,6 +11,6 @@ backend = "{{$frontend.Backend}}" {{range $routeName, $route := $frontend.Routes}} [frontends."{{$frontendName}}".routes."{{$routeName}}"] - rule = "PathStrip:{{$route.Rule}}" + rule = "{{$route.Rule}}" {{end}} {{end}} diff --git a/webui/src/index.html b/webui/src/index.html index 20d356187..0edd91a06 100644 --- a/webui/src/index.html +++ b/webui/src/index.html @@ -40,10 +40,10 @@