From 15540764a0fd987ca136b57973a0c0ee3ee7ca71 Mon Sep 17 00:00:00 2001 From: Yves Peter Date: Fri, 11 Nov 2016 23:50:20 +0100 Subject: [PATCH] Switched Kubernetes provider to new client implementation: https://github.com/kubernetes/client-go --- glide.yaml | 2 + provider/k8s/client.go | 420 +++++++++++++++----------------- provider/k8s/endpoints.go | 84 ------- provider/k8s/ingress.go | 151 ------------ provider/k8s/namespace.go | 32 +++ provider/k8s/service.go | 326 ------------------------- provider/kubernetes.go | 152 +++--------- provider/kubernetes_test.go | 464 ++++++++++++++++++------------------ script/generate | 6 + traefik.go | 4 +- 10 files changed, 510 insertions(+), 1131 deletions(-) delete mode 100644 provider/k8s/endpoints.go delete mode 100644 provider/k8s/ingress.go create mode 100644 provider/k8s/namespace.go delete mode 100644 provider/k8s/service.go diff --git a/glide.yaml b/glide.yaml index 33ae718fc..0cc2a2f53 100644 --- a/glide.yaml +++ b/glide.yaml @@ -102,3 +102,5 @@ import: - package: github.com/ArthurHlt/go-eureka-client subpackages: - eureka +- package: k8s.io/client-go + version: ^v1.5.0 diff --git a/provider/k8s/client.go b/provider/k8s/client.go index ad9679cae..400677db0 100644 --- a/provider/k8s/client.go +++ b/provider/k8s/client.go @@ -1,167 +1,206 @@ package k8s import ( - "crypto/tls" - "crypto/x509" - "encoding/json" - "fmt" - "github.com/containous/traefik/log" - "github.com/parnurzeal/gorequest" - "net/http" - "net/url" - "strings" -) - -const ( - // APIEndpoint defines the base path for kubernetes API resources. - APIEndpoint = "/api/v1" - extentionsEndpoint = "/apis/extensions/v1beta1" - defaultIngress = "/ingresses" - namespaces = "/namespaces/" + "k8s.io/client-go/1.5/kubernetes" + "k8s.io/client-go/1.5/pkg/api" + "k8s.io/client-go/1.5/pkg/api/v1" + "k8s.io/client-go/1.5/pkg/apis/extensions/v1beta1" + "k8s.io/client-go/1.5/pkg/fields" + "k8s.io/client-go/1.5/pkg/labels" + "k8s.io/client-go/1.5/pkg/runtime" + "k8s.io/client-go/1.5/pkg/watch" + "k8s.io/client-go/1.5/rest" + "k8s.io/client-go/1.5/tools/cache" ) // Client is a client for the Kubernetes master. type Client interface { - GetIngresses(labelSelector string, predicate func(Ingress) bool) ([]Ingress, error) - GetService(name, namespace string) (Service, error) - GetEndpoints(name, namespace string) (Endpoints, error) - WatchAll(labelSelector string, stopCh <-chan bool) (chan interface{}, chan error, error) + GetIngresses(namespaces Namespaces) []*v1beta1.Ingress + GetService(namespace, name string) (*v1.Service, bool, error) + GetEndpoints(namespace, name string) (*v1.Endpoints, bool, error) + WatchAll(labelSelector string, stopCh <-chan bool) (chan interface{}, error) } type clientImpl struct { - endpointURL string - tls *tls.Config - token string - caCert []byte + ingController *cache.Controller + svcController *cache.Controller + epController *cache.Controller + + ingStore cache.Store + svcStore cache.Store + epStore cache.Store + + clientset *kubernetes.Clientset } -// NewClient returns a new Kubernetes client. -// 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) { - validURL, err := url.Parse(baseURL) +// NewInClusterClient returns a new Kubernetes client. +// WatchAll starts the watch of the Kubernetes ressources and updates the stores. +// The stores can be accessed via the Get* functions. +func NewInClusterClient() (Client, error) { + config, err := rest.InClusterConfig() if err != nil { - return nil, fmt.Errorf("failed to parse URL %q: %v", baseURL, err) + return nil, err } + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + return &clientImpl{ - endpointURL: strings.TrimSuffix(validURL.String(), "/"), - token: token, - caCert: caCert, + clientset: clientset, }, nil } -func makeQueryString(baseParams map[string]string, labelSelector string) (string, error) { - if labelSelector != "" { - baseParams["labelSelector"] = labelSelector - } - queryData, err := json.Marshal(baseParams) +// NewInClusterClientWithEndpoint is the same as NewInClusterClient but uses the provided endpoint URL +func NewInClusterClientWithEndpoint(endpoint string) (Client, error) { + config, err := rest.InClusterConfig() if err != nil { - return "", err + return nil, err } - return string(queryData), nil + + config.Host = endpoint + + clientset, err := kubernetes.NewForConfig(config) + if err != nil { + return nil, err + } + + return &clientImpl{ + clientset: clientset, + }, nil } // GetIngresses returns all ingresses in the cluster -func (c *clientImpl) GetIngresses(labelSelector string, predicate func(Ingress) bool) ([]Ingress, error) { - getURL := c.endpointURL + extentionsEndpoint + defaultIngress - queryParams := map[string]string{} - queryData, err := makeQueryString(queryParams, labelSelector) - if err != nil { - return nil, fmt.Errorf("Had problems constructing query string %s : %v", queryParams, err) - } - body, err := c.do(c.request(getURL, queryData)) - if err != nil { - return nil, fmt.Errorf("failed to create ingresses request: GET %q : %v", getURL, err) - } +func (c *clientImpl) GetIngresses(namespaces Namespaces) []*v1beta1.Ingress { + ingList := c.ingStore.List() + result := make([]*v1beta1.Ingress, 0, len(ingList)) - var ingressList IngressList - if err := json.Unmarshal(body, &ingressList); err != nil { - return nil, fmt.Errorf("failed to decode list of ingress resources: %v", err) - } - ingresses := ingressList.Items[:0] - for _, ingress := range ingressList.Items { - if predicate(ingress) { - ingresses = append(ingresses, ingress) + for _, obj := range ingList { + ingress := obj.(*v1beta1.Ingress) + if HasNamespace(ingress, namespaces) { + result = append(result, ingress) } } - return ingresses, nil + + return result } -// WatchIngresses returns all ingresses in the cluster -func (c *clientImpl) WatchIngresses(labelSelector string, stopCh <-chan bool) (chan interface{}, chan error, error) { - getURL := c.endpointURL + extentionsEndpoint + defaultIngress - return c.watch(getURL, labelSelector, stopCh) +// WatchIngresses starts the watch of Kubernetes Ingresses resources and updates the corresponding store +func (c *clientImpl) WatchIngresses(labelSelector labels.Selector, stopCh <-chan struct{}) chan interface{} { + watchCh := make(chan interface{}, 10) + + source := NewListWatchFromClient( + c.clientset.ExtensionsClient, + "ingresses", + api.NamespaceAll, + fields.Everything(), + labelSelector) + + c.ingStore, c.ingController = cache.NewInformer( + source, + &v1beta1.Ingress{}, + 0, + newResourceEventHandlerFuncs(watchCh)) + go c.ingController.Run(stopCh) + + return watchCh +} + +func newResourceEventHandlerFuncs(events chan interface{}) cache.ResourceEventHandlerFuncs { + + return cache.ResourceEventHandlerFuncs{ + AddFunc: func(obj interface{}) { events <- obj }, + UpdateFunc: func(old, new interface{}) { events <- new }, + DeleteFunc: func(obj interface{}) { events <- obj }, + } + } // GetService returns the named service from the named namespace -func (c *clientImpl) GetService(name, namespace string) (Service, error) { - getURL := c.endpointURL + APIEndpoint + namespaces + namespace + "/services/" + name - - body, err := c.do(c.request(getURL, "")) - if err != nil { - return Service{}, fmt.Errorf("failed to create services request: GET %q : %v", getURL, err) +func (c *clientImpl) GetService(namespace, name string) (*v1.Service, bool, error) { + var service *v1.Service + item, exists, err := c.svcStore.GetByKey(namespace + "/" + name) + if item != nil { + service = item.(*v1.Service) } - var service Service - if err := json.Unmarshal(body, &service); err != nil { - return Service{}, fmt.Errorf("failed to decode service resource: %v", err) - } - return service, nil + return service, exists, err } -// WatchServices returns all services in the cluster -func (c *clientImpl) WatchServices(stopCh <-chan bool) (chan interface{}, chan error, error) { - getURL := c.endpointURL + APIEndpoint + "/services" - return c.watch(getURL, "", stopCh) +// WatchServices starts the watch of Kubernetes Service resources and updates the corresponding store +func (c *clientImpl) WatchServices(stopCh <-chan struct{}) chan interface{} { + watchCh := make(chan interface{}, 10) + + source := cache.NewListWatchFromClient( + c.clientset.CoreClient, + "services", + api.NamespaceAll, + fields.Everything()) + + c.svcStore, c.svcController = cache.NewInformer( + source, + &v1.Service{}, + 0, + newResourceEventHandlerFuncs(watchCh)) + go c.svcController.Run(stopCh) + + return watchCh } // GetEndpoints returns the named Endpoints // Endpoints have the same name as the coresponding service -func (c *clientImpl) GetEndpoints(name, namespace string) (Endpoints, error) { - getURL := c.endpointURL + APIEndpoint + namespaces + namespace + "/endpoints/" + name +func (c *clientImpl) GetEndpoints(namespace, name string) (*v1.Endpoints, bool, error) { + var endpoint *v1.Endpoints + item, exists, err := c.epStore.GetByKey(namespace + "/" + name) - body, err := c.do(c.request(getURL, "")) - if err != nil { - return Endpoints{}, fmt.Errorf("failed to create endpoints request: GET %q : %v", getURL, err) + if item != nil { + endpoint = item.(*v1.Endpoints) } - var endpoints Endpoints - if err := json.Unmarshal(body, &endpoints); err != nil { - return Endpoints{}, fmt.Errorf("failed to decode endpoints resources: %v", err) - } - return endpoints, nil + return endpoint, exists, err } -// WatchEndpoints returns endpoints in the cluster -func (c *clientImpl) WatchEndpoints(stopCh <-chan bool) (chan interface{}, chan error, error) { - getURL := c.endpointURL + APIEndpoint + "/endpoints" - return c.watch(getURL, "", stopCh) -} - -// WatchAll returns events in the cluster -func (c *clientImpl) WatchAll(labelSelector string, stopCh <-chan bool) (chan interface{}, chan error, error) { +// WatchEndpoints starts the watch of Kubernetes Endpoints resources and updates the corresponding store +func (c *clientImpl) WatchEndpoints(stopCh <-chan struct{}) chan interface{} { watchCh := make(chan interface{}, 10) - errCh := make(chan error, 10) - stopIngresses := make(chan bool) - chanIngresses, chanIngressesErr, err := c.WatchIngresses(labelSelector, stopIngresses) + source := cache.NewListWatchFromClient( + c.clientset.CoreClient, + "endpoints", + api.NamespaceAll, + fields.Everything()) + + c.epStore, c.epController = cache.NewInformer( + source, + &v1.Endpoints{}, + 0, + newResourceEventHandlerFuncs(watchCh)) + go c.epController.Run(stopCh) + + return watchCh +} + +// WatchAll returns events in the cluster and updates the stores via informer +// Filters ingresses by labelSelector +func (c *clientImpl) WatchAll(labelSelector string, stopCh <-chan bool) (chan interface{}, error) { + watchCh := make(chan interface{}, 10) + + kubeLabelSelector, err := labels.Parse(labelSelector) if err != nil { - return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err) - } - stopServices := make(chan bool) - chanServices, chanServicesErr, err := c.WatchServices(stopServices) - if err != nil { - return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err) - } - stopEndpoints := make(chan bool) - chanEndpoints, chanEndpointsErr, err := c.WatchEndpoints(stopEndpoints) - if err != nil { - return watchCh, errCh, fmt.Errorf("failed to create watch: %v", err) + return nil, err } + + stopIngresses := make(chan struct{}) + chanIngresses := c.WatchIngresses(kubeLabelSelector, stopIngresses) + + stopServices := make(chan struct{}) + chanServices := c.WatchServices(stopServices) + + stopEndpoints := make(chan struct{}) + chanEndpoints := c.WatchEndpoints(stopEndpoints) + go func() { defer close(watchCh) - defer close(errCh) defer close(stopIngresses) defer close(stopServices) defer close(stopEndpoints) @@ -169,128 +208,63 @@ func (c *clientImpl) WatchAll(labelSelector string, stopCh <-chan bool) (chan in for { select { case <-stopCh: - stopIngresses <- true - stopServices <- true - stopEndpoints <- true return - case err := <-chanIngressesErr: - errCh <- err - case err := <-chanServicesErr: - errCh <- err - case err := <-chanEndpointsErr: - errCh <- err case event := <-chanIngresses: - watchCh <- event + c.fireEvent(event, watchCh) case event := <-chanServices: - watchCh <- event + c.fireEvent(event, watchCh) case event := <-chanEndpoints: - watchCh <- event + c.fireEvent(event, watchCh) } } }() - return watchCh, errCh, nil + return watchCh, nil } -func (c *clientImpl) do(request *gorequest.SuperAgent) ([]byte, error) { - res, body, errs := request.EndBytes() - if errs != nil { - return nil, fmt.Errorf("failed to create request: GET %q : %v", request.Url, errs) +// fireEvent checks if all controllers have synced before firing +// Used after startup or a reconnect +func (c *clientImpl) fireEvent(event interface{}, watchCh chan interface{}) { + if c.ingController.HasSynced() && c.svcController.HasSynced() && c.epController.HasSynced() { + watchCh <- event } - defer res.Body.Close() - if res.StatusCode != http.StatusOK { - return nil, fmt.Errorf("http error %d GET %q: %q", res.StatusCode, request.Url, string(body)) - } - return body, nil } -func (c *clientImpl) request(reqURL string, queryContent interface{}) *gorequest.SuperAgent { - // Make request to Kubernetes API - parsedURL, parseErr := url.Parse(reqURL) - if parseErr != nil { - log.Errorf("Had issues parsing url %s. Trying anyway.", reqURL) +// HasNamespace checks if the ingress is in one of the namespaces +func HasNamespace(ingress *v1beta1.Ingress, namespaces Namespaces) bool { + if len(namespaces) == 0 { + return true } - request := gorequest.New().Get(reqURL) - request.Transport.DisableKeepAlives = true - - if parsedURL.Scheme == "https" { - pool := x509.NewCertPool() - pool.AppendCertsFromPEM(c.caCert) - c.tls = &tls.Config{RootCAs: pool} - request.TLSClientConfig(c.tls) - } - if len(c.token) > 0 { - request.Header["Authorization"] = "Bearer " + c.token - } - request.Query(queryContent) - return request -} - -// GenericObject generic object -type GenericObject struct { - TypeMeta `json:",inline"` - ListMeta `json:"metadata,omitempty"` -} - -func (c *clientImpl) watch(url string, labelSelector string, stopCh <-chan bool) (chan interface{}, chan error, error) { - watchCh := make(chan interface{}, 10) - errCh := make(chan error, 10) - - // get version - body, err := c.do(c.request(url, "")) - if err != nil { - return watchCh, errCh, fmt.Errorf("failed to do version request: GET %q : %v", url, err) - } - - var generic GenericObject - if err := json.Unmarshal(body, &generic); err != nil { - return watchCh, errCh, fmt.Errorf("failed to decode version %v", err) - } - resourceVersion := generic.ResourceVersion - queryParams := map[string]string{"watch": "", "resourceVersion": resourceVersion} - queryData, err := makeQueryString(queryParams, labelSelector) - if err != nil { - return watchCh, errCh, fmt.Errorf("Unable to construct query args") - } - request := c.request(url, queryData) - req, err := request.MakeRequest() - if err != nil { - return watchCh, errCh, fmt.Errorf("failed to make watch request: GET %q : %v", url, err) - } - request.Client.Transport = request.Transport - - res, err := request.Client.Do(req) - if err != nil { - return watchCh, errCh, fmt.Errorf("failed to do watch request: GET %q: %v", url, err) - } - - go func() { - finishCh := make(chan bool) - defer close(finishCh) - defer close(watchCh) - defer close(errCh) - go func() { - defer res.Body.Close() - for { - var eventList interface{} - if err := json.NewDecoder(res.Body).Decode(&eventList); err != nil { - if !strings.Contains(err.Error(), "net/http: request canceled") { - errCh <- fmt.Errorf("failed to decode watch event: GET %q : %v", url, err) - } - finishCh <- true - return - } - watchCh <- eventList - } - }() - select { - case <-stopCh: - go func() { - request.Transport.CancelRequest(req) - }() - <-finishCh - return + for _, n := range namespaces { + if ingress.ObjectMeta.Namespace == n { + return true } - }() - return watchCh, errCh, nil + } + return false +} + +// NewListWatchFromClient creates a new ListWatch from the specified client, resource, namespace, field selector and label selector. +// Extends cache.NewListWatchFromClient to support labelSelector +func NewListWatchFromClient(c cache.Getter, resource string, namespace string, fieldSelector fields.Selector, labelSelector labels.Selector) *cache.ListWatch { + listFunc := func(options api.ListOptions) (runtime.Object, error) { + return c.Get(). + Namespace(namespace). + Resource(resource). + VersionedParams(&options, api.ParameterCodec). + FieldsSelectorParam(fieldSelector). + LabelsSelectorParam(labelSelector). + Do(). + Get() + } + watchFunc := func(options api.ListOptions) (watch.Interface, error) { + return c.Get(). + Prefix("watch"). + Namespace(namespace). + Resource(resource). + VersionedParams(&options, api.ParameterCodec). + FieldsSelectorParam(fieldSelector). + LabelsSelectorParam(labelSelector). + Watch() + } + return &cache.ListWatch{ListFunc: listFunc, WatchFunc: watchFunc} } diff --git a/provider/k8s/endpoints.go b/provider/k8s/endpoints.go deleted file mode 100644 index 123ffe36c..000000000 --- a/provider/k8s/endpoints.go +++ /dev/null @@ -1,84 +0,0 @@ -package k8s - -// Endpoints is a collection of endpoints that implement the actual service. Example: -// Name: "mysvc", -// Subsets: [ -// { -// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}], -// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}] -// }, -// { -// Addresses: [{"ip": "10.10.3.3"}], -// Ports: [{"name": "a", "port": 93}, {"name": "b", "port": 76}] -// }, -// ] -type Endpoints struct { - TypeMeta `json:",inline"` - ObjectMeta `json:"metadata,omitempty"` - - // The set of all endpoints is the union of all subsets. - Subsets []EndpointSubset -} - -// EndpointSubset is a group of addresses with a common set of ports. The -// expanded set of endpoints is the Cartesian product of Addresses x Ports. -// For example, given: -// { -// Addresses: [{"ip": "10.10.1.1"}, {"ip": "10.10.2.2"}], -// Ports: [{"name": "a", "port": 8675}, {"name": "b", "port": 309}] -// } -// The resulting set of endpoints can be viewed as: -// a: [ 10.10.1.1:8675, 10.10.2.2:8675 ], -// b: [ 10.10.1.1:309, 10.10.2.2:309 ] -type EndpointSubset struct { - Addresses []EndpointAddress - NotReadyAddresses []EndpointAddress - Ports []EndpointPort -} - -// EndpointAddress is a tuple that describes single IP address. -type EndpointAddress struct { - // The IP of this endpoint. - // IPv6 is also accepted but not fully supported on all platforms. Also, certain - // kubernetes components, like kube-proxy, are not IPv6 ready. - // TODO: This should allow hostname or IP, see #4447. - IP string - // Optional: Hostname of this endpoint - // Meant to be used by DNS servers etc. - Hostname string `json:"hostname,omitempty"` - // Optional: The kubernetes object related to the entry point. - TargetRef *ObjectReference -} - -// EndpointPort is a tuple that describes a single port. -type EndpointPort struct { - // The name of this port (corresponds to ServicePort.Name). Optional - // if only one port is defined. Must be a DNS_LABEL. - Name string - - // The port number. - Port int32 - - // The IP protocol for this port. - Protocol Protocol -} - -// ObjectReference contains enough information to let you inspect or modify the referred object. -type ObjectReference struct { - Kind string `json:"kind,omitempty"` - Namespace string `json:"namespace,omitempty"` - Name string `json:"name,omitempty"` - UID UID `json:"uid,omitempty"` - APIVersion string `json:"apiVersion,omitempty"` - ResourceVersion string `json:"resourceVersion,omitempty"` - - // Optional. If referring to a piece of an object instead of an entire object, this string - // should contain information to identify the sub-object. For example, if the object - // reference is to a container within a pod, this would take on a value like: - // "spec.containers{name}" (where "name" refers to the name of the container that triggered - // the event) or if no container name is specified "spec.containers[2]" (container with - // index 2 in this pod). This syntax is chosen only to have some well-defined way of - // referencing a part of an object. - // TODO: this design is not final and this field is subject to change in the future. - FieldPath string `json:"fieldPath,omitempty"` -} diff --git a/provider/k8s/ingress.go b/provider/k8s/ingress.go deleted file mode 100644 index f3b7c8dce..000000000 --- a/provider/k8s/ingress.go +++ /dev/null @@ -1,151 +0,0 @@ -package k8s - -// Ingress is a collection of rules that allow inbound connections to reach the -// endpoints defined by a backend. An Ingress can be configured to give services -// externally-reachable urls, load balance traffic, terminate SSL, offer name -// based virtual hosting etc. -type Ingress struct { - TypeMeta `json:",inline"` - // Standard object's metadata. - // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata - ObjectMeta `json:"metadata,omitempty"` - - // Spec is the desired state of the Ingress. - // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status - Spec IngressSpec `json:"spec,omitempty"` - - // Status is the current state of the Ingress. - // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#spec-and-status - Status IngressStatus `json:"status,omitempty"` -} - -// IngressList is a collection of Ingress. -type IngressList struct { - TypeMeta `json:",inline"` - // Standard object's metadata. - // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata - ListMeta `json:"metadata,omitempty"` - - // Items is the list of Ingress. - Items []Ingress `json:"items"` -} - -// IngressSpec describes the Ingress the user wishes to exist. -type IngressSpec struct { - // A default backend capable of servicing requests that don't match any - // rule. At least one of 'backend' or 'rules' must be specified. This field - // is optional to allow the loadbalancer controller or defaulting logic to - // specify a global default. - Backend *IngressBackend `json:"backend,omitempty"` - - // TLS configuration. Currently the Ingress only supports a single TLS - // port, 443. If multiple members of this list specify different hosts, they - // will be multiplexed on the same port according to the hostname specified - // through the SNI TLS extension, if the ingress controller fulfilling the - // ingress supports SNI. - TLS []IngressTLS `json:"tls,omitempty"` - - // A list of host rules used to configure the Ingress. If unspecified, or - // no rule matches, all traffic is sent to the default backend. - Rules []IngressRule `json:"rules,omitempty"` - // TODO: Add the ability to specify load-balancer IP through claims -} - -// IngressTLS describes the transport layer security associated with an Ingress. -type IngressTLS struct { - // Hosts are a list of hosts included in the TLS certificate. The values in - // this list must match the name/s used in the tlsSecret. Defaults to the - // wildcard host setting for the loadbalancer controller fulfilling this - // Ingress, if left unspecified. - Hosts []string `json:"hosts,omitempty"` - // SecretName is the name of the secret used to terminate SSL traffic on 443. - // Field is left optional to allow SSL routing based on SNI hostname alone. - // If the SNI host in a listener conflicts with the "Host" header field used - // by an IngressRule, the SNI host is used for termination and value of the - // Host header is used for routing. - SecretName string `json:"secretName,omitempty"` - // TODO: Consider specifying different modes of termination, protocols etc. -} - -// IngressStatus describe the current state of the Ingress. -type IngressStatus struct { - // LoadBalancer contains the current status of the load-balancer. - LoadBalancer LoadBalancerStatus `json:"loadBalancer,omitempty"` -} - -// IngressRule represents the rules mapping the paths under a specified host to -// the related backend services. Incoming requests are first evaluated for a host -// match, then routed to the backend associated with the matching IngressRuleValue. -type IngressRule struct { - // Host is the fully qualified domain name of a network host, as defined - // by RFC 3986. Note the following deviations from the "host" part of the - // URI as defined in the RFC: - // 1. IPs are not allowed. Currently an IngressRuleValue can only apply to the - // IP in the Spec of the parent Ingress. - // 2. The `:` delimiter is not respected because ports are not allowed. - // Currently the port of an Ingress is implicitly :80 for http and - // :443 for https. - // Both these may change in the future. - // Incoming requests are matched against the host before the IngressRuleValue. - // If the host is unspecified, the Ingress routes all traffic based on the - // specified IngressRuleValue. - Host string `json:"host,omitempty"` - // IngressRuleValue represents a rule to route requests for this IngressRule. - // If unspecified, the rule defaults to a http catch-all. Whether that sends - // just traffic matching the host to the default backend or all traffic to the - // default backend, is left to the controller fulfilling the Ingress. Http is - // currently the only supported IngressRuleValue. - IngressRuleValue `json:",inline,omitempty"` -} - -// IngressRuleValue represents a rule to apply against incoming requests. If the -// rule is satisfied, the request is routed to the specified backend. Currently -// mixing different types of rules in a single Ingress is disallowed, so exactly -// one of the following must be set. -type IngressRuleValue struct { - //TODO: - // 1. Consider renaming this resource and the associated rules so they - // aren't tied to Ingress. They can be used to route intra-cluster traffic. - // 2. Consider adding fields for ingress-type specific global options - // usable by a loadbalancer, like http keep-alive. - - HTTP *HTTPIngressRuleValue `json:"http,omitempty"` -} - -// HTTPIngressRuleValue is a list of http selectors pointing to backends. -// In the example: http:///? -> backend where -// where parts of the url correspond to RFC 3986, this resource will be used -// to match against everything after the last '/' and before the first '?' -// or '#'. -type HTTPIngressRuleValue struct { - // A collection of paths that map requests to backends. - Paths []HTTPIngressPath `json:"paths"` - // TODO: Consider adding fields for ingress-type specific global - // options usable by a loadbalancer, like http keep-alive. -} - -// HTTPIngressPath associates a path regex with a backend. Incoming urls matching -// the path are forwarded to the backend. -type HTTPIngressPath struct { - // Path is a extended POSIX regex as defined by IEEE Std 1003.1, - // (i.e this follows the egrep/unix syntax, not the perl syntax) - // matched against the path of an incoming request. Currently it can - // contain characters disallowed from the conventional "path" - // part of a URL as defined by RFC 3986. Paths must begin with - // a '/'. If unspecified, the path defaults to a catch all sending - // traffic to the backend. - Path string `json:"path,omitempty"` - - // Backend defines the referenced service endpoint to which the traffic - // will be forwarded to. - Backend IngressBackend `json:"backend"` -} - -// IngressBackend describes all endpoints for a given service and port. -type IngressBackend struct { - // Specifies the name of the referenced service. - ServiceName string `json:"serviceName"` - - // Specifies the port of the referenced service. - ServicePort IntOrString `json:"servicePort"` -} diff --git a/provider/k8s/namespace.go b/provider/k8s/namespace.go new file mode 100644 index 000000000..6f458a7d8 --- /dev/null +++ b/provider/k8s/namespace.go @@ -0,0 +1,32 @@ +package k8s + +import ( + "fmt" + "strings" +) + +// Namespaces holds kubernetes namespaces +type Namespaces []string + +//Set adds strings elem into the the parser +//it splits str on , and ; +func (ns *Namespaces) Set(str string) error { + fargs := func(c rune) bool { + return c == ',' || c == ';' + } + // get function + slice := strings.FieldsFunc(str, fargs) + *ns = append(*ns, slice...) + return nil +} + +//Get []string +func (ns *Namespaces) Get() interface{} { return Namespaces(*ns) } + +//String return slice in a string +func (ns *Namespaces) String() string { return fmt.Sprintf("%v", *ns) } + +//SetValue sets []string into the parser +func (ns *Namespaces) SetValue(val interface{}) { + *ns = Namespaces(val.(Namespaces)) +} diff --git a/provider/k8s/service.go b/provider/k8s/service.go deleted file mode 100644 index e501718ce..000000000 --- a/provider/k8s/service.go +++ /dev/null @@ -1,326 +0,0 @@ -package k8s - -import ( - "encoding/json" - "strconv" - "time" -) - -// TypeMeta describes an individual object in an API response or request -// with strings representing the type of the object and its API schema version. -// Structures that are versioned or persisted should inline TypeMeta. -type TypeMeta struct { - // Kind is a string value representing the REST resource this object represents. - // Servers may infer this from the endpoint the client submits requests to. - // Cannot be updated. - // In CamelCase. - // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#types-kinds - Kind string `json:"kind,omitempty"` - - // APIVersion defines the versioned schema of this representation of an object. - // Servers should convert recognized schemas to the latest internal value, and - // may reject unrecognized values. - // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#resources - APIVersion string `json:"apiVersion,omitempty"` -} - -// ObjectMeta is metadata that all persisted resources must have, which includes all objects -// users must create. -type ObjectMeta struct { - // Name is unique within a namespace. Name is required when creating resources, although - // some resources may allow a client to request the generation of an appropriate name - // automatically. Name is primarily intended for creation idempotence and configuration - // definition. - Name string `json:"name,omitempty"` - - // GenerateName indicates that the name should be made unique by the server prior to persisting - // it. A non-empty value for the field indicates the name will be made unique (and the name - // returned to the client will be different than the name passed). The value of this field will - // be combined with a unique suffix on the server if the Name field has not been provided. - // The provided value must be valid within the rules for Name, and may be truncated by the length - // of the suffix required to make the value unique on the server. - // - // If this field is specified, and Name is not present, the server will NOT return a 409 if the - // generated name exists - instead, it will either return 201 Created or 500 with Reason - // ServerTimeout indicating a unique name could not be found in the time allotted, and the client - // should retry (optionally after the time indicated in the Retry-After header). - GenerateName string `json:"generateName,omitempty"` - - // Namespace defines the space within which name must be unique. An empty namespace is - // equivalent to the "default" namespace, but "default" is the canonical representation. - // Not all objects are required to be scoped to a namespace - the value of this field for - // those objects will be empty. - Namespace string `json:"namespace,omitempty"` - - // SelfLink is a URL representing this object. - SelfLink string `json:"selfLink,omitempty"` - - // UID is the unique in time and space value for this object. It is typically generated by - // the server on successful creation of a resource and is not allowed to change on PUT - // operations. - UID UID `json:"uid,omitempty"` - - // An opaque value that represents the version of this resource. May be used for optimistic - // concurrency, change detection, and the watch operation on a resource or set of resources. - // Clients must treat these values as opaque and values may only be valid for a particular - // resource or set of resources. Only servers will generate resource versions. - ResourceVersion string `json:"resourceVersion,omitempty"` - - // A sequence number representing a specific generation of the desired state. - // Populated by the system. Read-only. - Generation int64 `json:"generation,omitempty"` - - // CreationTimestamp is a timestamp representing the server time when this object was - // created. It is not guaranteed to be set in happens-before order across separate operations. - // Clients may not set this value. It is represented in RFC3339 form and is in UTC. - CreationTimestamp Time `json:"creationTimestamp,omitempty"` - - // DeletionTimestamp is the time after which this resource will be deleted. This - // field is set by the server when a graceful deletion is requested by the user, and is not - // directly settable by a client. The resource will be deleted (no longer visible from - // resource lists, and not reachable by name) after the time in this field. Once set, this - // value may not be unset or be set further into the future, although it may be shortened - // or the resource may be deleted prior to this time. For example, a user may request that - // a pod is deleted in 30 seconds. The Kubelet will react by sending a graceful termination - // signal to the containers in the pod. Once the resource is deleted in the API, the Kubelet - // will send a hard termination signal to the container. - DeletionTimestamp *Time `json:"deletionTimestamp,omitempty"` - - // DeletionGracePeriodSeconds records the graceful deletion value set when graceful deletion - // was requested. Represents the most recent grace period, and may only be shortened once set. - DeletionGracePeriodSeconds *int64 `json:"deletionGracePeriodSeconds,omitempty"` - - // Labels are key value pairs that may be used to scope and select individual resources. - // Label keys are of the form: - // label-key ::= prefixed-name | name - // prefixed-name ::= prefix '/' name - // prefix ::= DNS_SUBDOMAIN - // name ::= DNS_LABEL - // The prefix is optional. If the prefix is not specified, the key is assumed to be private - // to the user. Other system components that wish to use labels must specify a prefix. The - // "kubernetes.io/" prefix is reserved for use by kubernetes components. - // TODO: replace map[string]string with labels.LabelSet type - Labels map[string]string `json:"labels,omitempty"` - - // Annotations are unstructured key value data stored with a resource that may be set by - // external tooling. They are not queryable and should be preserved when modifying - // objects. Annotation keys have the same formatting restrictions as Label keys. See the - // comments on Labels for details. - Annotations map[string]string `json:"annotations,omitempty"` -} - -// UID is a type that holds unique ID values, including UUIDs. Because we -// don't ONLY use UUIDs, this is an alias to string. Being a type captures -// intent and helps make sure that UIDs and names do not get conflated. -type UID string - -// Time is a wrapper around time.Time which supports correct -// marshaling to YAML and JSON. Wrappers are provided for many -// of the factory methods that the time package offers. -// -// +protobuf.options.marshal=false -// +protobuf.as=Timestamp -type Time struct { - time.Time `protobuf:"-"` -} - -// Service is a named abstraction of software service (for example, mysql) consisting of local port -// (for example 3306) that the proxy listens on, and the selector that determines which pods -// will answer requests sent through the proxy. -type Service struct { - TypeMeta `json:",inline"` - ObjectMeta `json:"metadata,omitempty"` - - // Spec defines the behavior of a service. - Spec ServiceSpec `json:"spec,omitempty"` - - // Status represents the current status of a service. - Status ServiceStatus `json:"status,omitempty"` -} - -// ServiceSpec describes the attributes that a user creates on a service -type ServiceSpec struct { - // Type determines how the service will be exposed. Valid options: ClusterIP, NodePort, LoadBalancer - Type ServiceType `json:"type,omitempty"` - - // Required: The list of ports that are exposed by this service. - Ports []ServicePort `json:"ports"` - - // This service will route traffic to pods having labels matching this selector. If empty or not present, - // the service is assumed to have endpoints set by an external process and Kubernetes will not modify - // those endpoints. - Selector map[string]string `json:"selector"` - - // ClusterIP is usually assigned by the master. If specified by the user - // we will try to respect it or else fail the request. This field can - // not be changed by updates. - // Valid values are None, empty string (""), or a valid IP address - // None can be specified for headless services when proxying is not required - ClusterIP string `json:"clusterIP,omitempty"` - - // ExternalIPs are used by external load balancers, or can be set by - // users to handle external traffic that arrives at a node. - ExternalIPs []string `json:"externalIPs,omitempty"` - - // Only applies to Service Type: LoadBalancer - // LoadBalancer will get created with the IP specified in this field. - // This feature depends on whether the underlying cloud-provider supports specifying - // the loadBalancerIP when a load balancer is created. - // This field will be ignored if the cloud-provider does not support the feature. - LoadBalancerIP string `json:"loadBalancerIP,omitempty"` - - // Required: Supports "ClientIP" and "None". Used to maintain session affinity. - SessionAffinity ServiceAffinity `json:"sessionAffinity,omitempty"` -} - -// ServicePort service port -type ServicePort struct { - // Optional if only one ServicePort is defined on this service: The - // name of this port within the service. This must be a DNS_LABEL. - // All ports within a ServiceSpec must have unique names. This maps to - // the 'Name' field in EndpointPort objects. - Name string `json:"name"` - - // The IP protocol for this port. Supports "TCP" and "UDP". - Protocol Protocol `json:"protocol"` - - // The port that will be exposed on the service. - Port int `json:"port"` - - // Optional: The target port on pods selected by this service. If this - // is a string, it will be looked up as a named port in the target - // Pod's container ports. If this is not specified, the value - // of the 'port' field is used (an identity map). - // This field is ignored for services with clusterIP=None, and should be - // omitted or set equal to the 'port' field. - TargetPort IntOrString `json:"targetPort"` - - // The port on each node on which this service is exposed. - // Default is to auto-allocate a port if the ServiceType of this Service requires one. - NodePort int `json:"nodePort"` -} - -// ServiceStatus represents the current status of a service -type ServiceStatus struct { - // LoadBalancer contains the current status of the load-balancer, - // if one is present. - LoadBalancer LoadBalancerStatus `json:"loadBalancer,omitempty"` -} - -// LoadBalancerStatus represents the status of a load-balancer -type LoadBalancerStatus struct { - // Ingress is a list containing ingress points for the load-balancer; - // traffic intended for the service should be sent to these ingress points. - Ingress []LoadBalancerIngress `json:"ingress,omitempty"` -} - -// LoadBalancerIngress represents the status of a load-balancer ingress point: -// traffic intended for the service should be sent to an ingress point. -type LoadBalancerIngress struct { - // IP is set for load-balancer ingress points that are IP based - // (typically GCE or OpenStack load-balancers) - IP string `json:"ip,omitempty"` - - // Hostname is set for load-balancer ingress points that are DNS based - // (typically AWS load-balancers) - Hostname string `json:"hostname,omitempty"` -} - -// ServiceAffinity Session Affinity Type string -type ServiceAffinity string - -// ServiceType Service Type string describes ingress methods for a service -type ServiceType string - -// Protocol defines network protocols supported for things like container ports. -type Protocol string - -// IntOrString is a type that can hold an int32 or a string. When used in -// JSON or YAML marshalling and unmarshalling, it produces or consumes the -// inner type. This allows you to have, for example, a JSON field that can -// accept a name or number. -// TODO: Rename to Int32OrString -// -// +protobuf=true -// +protobuf.options.(gogoproto.goproto_stringer)=false -type IntOrString struct { - Type Type - IntVal int32 - 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 { - return intstr.StrVal - } - return strconv.Itoa(intstr.IntValue()) -} - -// IntValue returns the IntVal if type Int, or if -// it is a String, will attempt a conversion to int. -func (intstr *IntOrString) IntValue() int { - if intstr.Type == String { - i, _ := strconv.Atoi(intstr.StrVal) - return i - } - return int(intstr.IntVal) -} - -// UnmarshalJSON implements the json.Unmarshaller interface. -func (intstr *IntOrString) UnmarshalJSON(value []byte) error { - if value[0] == '"' { - intstr.Type = String - return json.Unmarshal(value, &intstr.StrVal) - } - intstr.Type = Int - return json.Unmarshal(value, &intstr.IntVal) -} - -// Type represents the stored type of IntOrString. -type Type int - -const ( - // Int int - Int Type = iota // The IntOrString holds an int. - //String string - String // The IntOrString holds a string. -) - -// ServiceList holds a list of services. -type ServiceList struct { - TypeMeta `json:",inline"` - ListMeta `json:"metadata,omitempty"` - - Items []Service `json:"items"` -} - -// ListMeta describes metadata that synthetic resources must have, including lists and -// various status objects. A resource may have only one of {ObjectMeta, ListMeta}. -type ListMeta struct { - // SelfLink is a URL representing this object. - // Populated by the system. - // Read-only. - SelfLink string `json:"selfLink,omitempty"` - - // String that identifies the server's internal version of this object that - // can be used by clients to determine when objects have changed. - // Value must be treated as opaque by clients and passed unmodified back to the server. - // Populated by the system. - // Read-only. - // More info: http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#concurrency-control-and-consistency - ResourceVersion string `json:"resourceVersion,omitempty"` -} diff --git a/provider/kubernetes.go b/provider/kubernetes.go index 3ca6cd681..17715c1a2 100644 --- a/provider/kubernetes.go +++ b/provider/kubernetes.go @@ -1,101 +1,49 @@ package provider import ( - "fmt" - "github.com/containous/traefik/log" - "github.com/containous/traefik/provider/k8s" - "github.com/containous/traefik/safe" - "github.com/containous/traefik/types" - "io/ioutil" - "os" "reflect" "strconv" "strings" "text/template" "time" + "k8s.io/client-go/1.5/pkg/api/v1" + "k8s.io/client-go/1.5/pkg/util/intstr" + + "github.com/containous/traefik/log" + "github.com/containous/traefik/provider/k8s" + "github.com/containous/traefik/safe" + "github.com/containous/traefik/types" + "github.com/cenk/backoff" "github.com/containous/traefik/job" ) -const ( - serviceAccountToken = "/var/run/secrets/kubernetes.io/serviceaccount/token" - serviceAccountCACert = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt" - defaultKubeEndpoint = "http://127.0.0.1:8080" -) - -// Namespaces holds kubernetes namespaces -type Namespaces []string - -//Set adds strings elem into the the parser -//it splits str on , and ; -func (ns *Namespaces) Set(str string) error { - fargs := func(c rune) bool { - return c == ',' || c == ';' - } - // get function - slice := strings.FieldsFunc(str, fargs) - *ns = append(*ns, slice...) - return nil -} - -//Get []string -func (ns *Namespaces) Get() interface{} { return Namespaces(*ns) } - -//String return slice in a string -func (ns *Namespaces) String() string { return fmt.Sprintf("%v", *ns) } - -//SetValue sets []string into the parser -func (ns *Namespaces) SetValue(val interface{}) { - *ns = Namespaces(val.(Namespaces)) -} - var _ Provider = (*Kubernetes)(nil) // Kubernetes holds configurations of the Kubernetes provider. type Kubernetes struct { BaseProvider `mapstructure:",squash"` - Endpoint string `description:"Kubernetes server endpoint"` - DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers"` - Namespaces Namespaces `description:"Kubernetes namespaces"` - LabelSelector string `description:"Kubernetes api label selector to use"` + Endpoint string `description:"Kubernetes server endpoint"` + DisablePassHostHeaders bool `description:"Kubernetes disable PassHost Headers"` + Namespaces k8s.Namespaces `description:"Kubernetes namespaces"` + LabelSelector string `description:"Kubernetes api label selector to use"` lastConfiguration safe.Safe } -func (provider *Kubernetes) createClient() (k8s.Client, error) { - var token string - tokenBytes, err := ioutil.ReadFile(serviceAccountToken) - if err == nil { - token = string(tokenBytes) - log.Debugf("Kubernetes token: %s", token) - } else { - log.Errorf("Kubernetes load token error: %s", err) +func (provider *Kubernetes) newK8sClient() (k8s.Client, error) { + if provider.Endpoint != "" { + log.Infof("Creating in cluster Kubernetes client with endpoint %", provider.Endpoint) + return k8s.NewInClusterClientWithEndpoint(provider.Endpoint) } - caCert, err := ioutil.ReadFile(serviceAccountCACert) - if err == nil { - log.Debugf("Kubernetes CA cert: %s", serviceAccountCACert) - } else { - log.Errorf("Kubernetes load token error: %s", err) - } - kubernetesHost := os.Getenv("KUBERNETES_SERVICE_HOST") - kubernetesPort := os.Getenv("KUBERNETES_SERVICE_PORT_HTTPS") - // Prioritize user provided kubernetes endpoint since kube container runtime will almost always have it - if provider.Endpoint == "" && len(kubernetesPort) > 0 && len(kubernetesHost) > 0 { - log.Debugf("Using environment provided kubernetes endpoint") - provider.Endpoint = "https://" + kubernetesHost + ":" + kubernetesPort - } - if provider.Endpoint == "" { - log.Debugf("Using default kubernetes api endpoint") - provider.Endpoint = defaultKubeEndpoint - } - log.Debugf("Kubernetes endpoint: %s", provider.Endpoint) - return k8s.NewClient(provider.Endpoint, caCert, token) + log.Info("Creating in cluster Kubernetes client") + return k8s.NewInClusterClient() } // Provide allows the provider to provide configurations to traefik // using the given configuration channel. func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage, pool *safe.Pool, constraints []types.Constraint) error { - k8sClient, err := provider.createClient() + k8sClient, err := provider.newK8sClient() if err != nil { return err } @@ -107,7 +55,7 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage stopWatch := make(chan bool, 5) defer close(stopWatch) log.Debugf("Using label selector: '%s'", provider.LabelSelector) - eventsChan, errEventsChan, err := k8sClient.WatchAll(provider.LabelSelector, stopWatch) + eventsChan, err := k8sClient.WatchAll(provider.LabelSelector, stopWatch) if err != nil { log.Errorf("Error watching kubernetes events: %v", err) timer := time.NewTimer(1 * time.Second) @@ -123,9 +71,6 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage case <-stop: stopWatch <- true return nil - case err, _ := <-errEventsChan: - stopWatch <- true - return err case event := <-eventsChan: log.Debugf("Received event from kubernetes %+v", event) templateObjects, err := provider.loadIngresses(k8sClient) @@ -133,7 +78,7 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage return err } if reflect.DeepEqual(provider.lastConfiguration.Get(), templateObjects) { - log.Debugf("Skipping event from kubernetes %+v", event) + log.Debug("Skipping event") } else { provider.lastConfiguration.Set(templateObjects) configurationChan <- types.ConfigMessage{ @@ -155,39 +100,12 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage } }) - templateObjects, err := provider.loadIngresses(k8sClient) - if err != nil { - return err - } - if reflect.DeepEqual(provider.lastConfiguration.Get(), templateObjects) { - log.Debugf("Skipping configuration from kubernetes %+v", templateObjects) - } else { - provider.lastConfiguration.Set(templateObjects) - configurationChan <- types.ConfigMessage{ - ProviderName: "kubernetes", - Configuration: provider.loadConfig(*templateObjects), - } - } - return nil } func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configuration, error) { - ingresses, err := k8sClient.GetIngresses(provider.LabelSelector, func(ingress k8s.Ingress) bool { - if len(provider.Namespaces) == 0 { - return true - } - for _, n := range provider.Namespaces { - if ingress.ObjectMeta.Namespace == n { - return true - } - } - return false - }) - if err != nil { - log.Errorf("Error retrieving ingresses: %+v", err) - return nil, err - } + ingresses := k8sClient.GetIngresses(provider.Namespaces) + templateObjects := types.Configuration{ map[string]*types.Backend{}, map[string]*types.Frontend{}, @@ -239,28 +157,28 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur Rule: ruleType + ":" + pa.Path, } } - service, err := k8sClient.GetService(pa.Backend.ServiceName, i.ObjectMeta.Namespace) - if err != nil { - log.Warnf("Error retrieving services: %v", err) + service, exists, err := k8sClient.GetService(i.ObjectMeta.Namespace, pa.Backend.ServiceName) + if err != nil || !exists { + log.Warnf("Error retrieving service %s/%s: %v", i.ObjectMeta.Namespace, pa.Backend.ServiceName, err) delete(templateObjects.Frontends, r.Host+pa.Path) - log.Warnf("Error retrieving services %s", pa.Backend.ServiceName) continue } + protocol := "http" for _, port := range service.Spec.Ports { if equalPorts(port, pa.Backend.ServicePort) { if port.Port == 443 { protocol = "https" } - endpoints, err := k8sClient.GetEndpoints(service.ObjectMeta.Name, service.ObjectMeta.Namespace) - if err != nil { - log.Errorf("Error retrieving endpoints: %v", err) + endpoints, exists, err := k8sClient.GetEndpoints(service.ObjectMeta.Namespace, service.ObjectMeta.Name) + if err != nil || !exists { + log.Errorf("Error retrieving endpoints %s/%s: %v", service.ObjectMeta.Namespace, service.ObjectMeta.Name, err) continue } if len(endpoints.Subsets) == 0 { log.Warnf("Endpoints not found for %s/%s, falling back to Service ClusterIP", service.ObjectMeta.Namespace, service.ObjectMeta.Name) templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{ - URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(port.Port), + URL: protocol + "://" + service.Spec.ClusterIP + ":" + strconv.Itoa(int(port.Port)), Weight: 1, } } else { @@ -287,7 +205,7 @@ func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configur return &templateObjects, nil } -func endpointPortNumber(servicePort k8s.ServicePort, endpointPorts []k8s.EndpointPort) int { +func endpointPortNumber(servicePort v1.ServicePort, endpointPorts []v1.EndpointPort) int { if len(endpointPorts) > 0 { //name is optional if there is only one port port := endpointPorts[0] @@ -298,11 +216,11 @@ func endpointPortNumber(servicePort k8s.ServicePort, endpointPorts []k8s.Endpoin } return int(port.Port) } - return servicePort.Port + return int(servicePort.Port) } -func equalPorts(servicePort k8s.ServicePort, ingressPort k8s.IntOrString) bool { - if servicePort.Port == ingressPort.IntValue() { +func equalPorts(servicePort v1.ServicePort, ingressPort intstr.IntOrString) bool { + if int(servicePort.Port) == ingressPort.IntValue() { return true } if servicePort.Name != "" && servicePort.Name == ingressPort.String() { diff --git a/provider/kubernetes_test.go b/provider/kubernetes_test.go index 2a4e935f9..4148a72c2 100644 --- a/provider/kubernetes_test.go +++ b/provider/kubernetes_test.go @@ -2,29 +2,34 @@ package provider import ( "encoding/json" - "github.com/containous/traefik/provider/k8s" - "github.com/containous/traefik/types" "reflect" "testing" + + "k8s.io/client-go/1.5/pkg/api/v1" + "k8s.io/client-go/1.5/pkg/apis/extensions/v1beta1" + "k8s.io/client-go/1.5/pkg/util/intstr" + + "github.com/containous/traefik/provider/k8s" + "github.com/containous/traefik/types" ) func TestLoadIngresses(t *testing.T) { - ingresses := []k8s.Ingress{{ - ObjectMeta: k8s.ObjectMeta{ + ingresses := []*v1beta1.Ingress{{ + ObjectMeta: v1.ObjectMeta{ Namespace: "testing", }, - Spec: k8s.IngressSpec{ - Rules: []k8s.IngressRule{ + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ { Host: "foo", - IngressRuleValue: k8s.IngressRuleValue{ - HTTP: &k8s.HTTPIngressRuleValue{ - Paths: []k8s.HTTPIngressPath{ + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ { Path: "/bar", - Backend: k8s.IngressBackend{ + Backend: v1beta1.IngressBackend{ ServiceName: "service1", - ServicePort: k8s.FromInt(80), + ServicePort: intstr.FromInt(80), }, }, }, @@ -33,19 +38,19 @@ func TestLoadIngresses(t *testing.T) { }, { Host: "bar", - IngressRuleValue: k8s.IngressRuleValue{ - HTTP: &k8s.HTTPIngressRuleValue{ - Paths: []k8s.HTTPIngressPath{ + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ { - Backend: k8s.IngressBackend{ + Backend: v1beta1.IngressBackend{ ServiceName: "service3", - ServicePort: k8s.FromString("https"), + ServicePort: intstr.FromString("https"), }, }, { - Backend: k8s.IngressBackend{ + Backend: v1beta1.IngressBackend{ ServiceName: "service2", - ServicePort: k8s.FromInt(802), + ServicePort: intstr.FromInt(802), }, }, }, @@ -55,16 +60,16 @@ func TestLoadIngresses(t *testing.T) { }, }, }} - services := []k8s.Service{ + services := []*v1.Service{ { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Name: "service1", UID: "1", Namespace: "testing", }, - Spec: k8s.ServiceSpec{ + Spec: v1.ServiceSpec{ ClusterIP: "10.0.0.1", - Ports: []k8s.ServicePort{ + Ports: []v1.ServicePort{ { Port: 80, }, @@ -72,14 +77,14 @@ func TestLoadIngresses(t *testing.T) { }, }, { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Name: "service2", UID: "2", Namespace: "testing", }, - Spec: k8s.ServiceSpec{ + Spec: v1.ServiceSpec{ ClusterIP: "10.0.0.2", - Ports: []k8s.ServicePort{ + Ports: []v1.ServicePort{ { Port: 802, }, @@ -87,14 +92,14 @@ func TestLoadIngresses(t *testing.T) { }, }, { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Name: "service3", UID: "3", Namespace: "testing", }, - Spec: k8s.ServiceSpec{ + Spec: v1.ServiceSpec{ ClusterIP: "10.0.0.3", - Ports: []k8s.ServicePort{ + Ports: []v1.ServicePort{ { Name: "http", Port: 80, @@ -107,33 +112,33 @@ func TestLoadIngresses(t *testing.T) { }, }, } - endpoints := []k8s.Endpoints{ + endpoints := []*v1.Endpoints{ { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Name: "service1", UID: "1", Namespace: "testing", }, - Subsets: []k8s.EndpointSubset{ + Subsets: []v1.EndpointSubset{ { - Addresses: []k8s.EndpointAddress{ + Addresses: []v1.EndpointAddress{ { IP: "10.10.0.1", }, }, - Ports: []k8s.EndpointPort{ + Ports: []v1.EndpointPort{ { Port: 8080, }, }, }, { - Addresses: []k8s.EndpointAddress{ + Addresses: []v1.EndpointAddress{ { IP: "10.21.0.1", }, }, - Ports: []k8s.EndpointPort{ + Ports: []v1.EndpointPort{ { Port: 8080, }, @@ -142,19 +147,19 @@ func TestLoadIngresses(t *testing.T) { }, }, { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Name: "service3", UID: "3", Namespace: "testing", }, - Subsets: []k8s.EndpointSubset{ + Subsets: []v1.EndpointSubset{ { - Addresses: []k8s.EndpointAddress{ + Addresses: []v1.EndpointAddress{ { IP: "10.15.0.1", }, }, - Ports: []k8s.EndpointPort{ + Ports: []v1.EndpointPort{ { Name: "http", Port: 8080, @@ -166,12 +171,12 @@ func TestLoadIngresses(t *testing.T) { }, }, { - Addresses: []k8s.EndpointAddress{ + Addresses: []v1.EndpointAddress{ { IP: "10.15.0.2", }, }, - Ports: []k8s.EndpointPort{ + Ports: []v1.EndpointPort{ { Name: "http", Port: 9080, @@ -267,23 +272,23 @@ func TestLoadIngresses(t *testing.T) { } func TestRuleType(t *testing.T) { - ingresses := []k8s.Ingress{ + ingresses := []*v1beta1.Ingress{ { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Annotations: map[string]string{"traefik.frontend.rule.type": "PathPrefixStrip"}, //camel case }, - Spec: k8s.IngressSpec{ - Rules: []k8s.IngressRule{ + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ { Host: "foo1", - IngressRuleValue: k8s.IngressRuleValue{ - HTTP: &k8s.HTTPIngressRuleValue{ - Paths: []k8s.HTTPIngressPath{ + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ { Path: "/bar1", - Backend: k8s.IngressBackend{ + Backend: v1beta1.IngressBackend{ ServiceName: "service1", - ServicePort: k8s.FromInt(801), + ServicePort: intstr.FromInt(801), }, }, }, @@ -294,21 +299,21 @@ func TestRuleType(t *testing.T) { }, }, { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Annotations: map[string]string{"traefik.frontend.rule.type": "path"}, //lower case }, - Spec: k8s.IngressSpec{ - Rules: []k8s.IngressRule{ + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ { Host: "foo1", - IngressRuleValue: k8s.IngressRuleValue{ - HTTP: &k8s.HTTPIngressRuleValue{ - Paths: []k8s.HTTPIngressPath{ + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ { Path: "/bar2", - Backend: k8s.IngressBackend{ + Backend: v1beta1.IngressBackend{ ServiceName: "service1", - ServicePort: k8s.FromInt(801), + ServicePort: intstr.FromInt(801), }, }, }, @@ -319,21 +324,21 @@ func TestRuleType(t *testing.T) { }, }, { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Annotations: map[string]string{"traefik.frontend.rule.type": "PathPrefix"}, //path prefix }, - Spec: k8s.IngressSpec{ - Rules: []k8s.IngressRule{ + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ { Host: "foo2", - IngressRuleValue: k8s.IngressRuleValue{ - HTTP: &k8s.HTTPIngressRuleValue{ - Paths: []k8s.HTTPIngressPath{ + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ { Path: "/bar1", - Backend: k8s.IngressBackend{ + Backend: v1beta1.IngressBackend{ ServiceName: "service1", - ServicePort: k8s.FromInt(801), + ServicePort: intstr.FromInt(801), }, }, }, @@ -344,21 +349,21 @@ func TestRuleType(t *testing.T) { }, }, { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Annotations: map[string]string{"traefik.frontend.rule.type": "PathStrip"}, //path strip }, - Spec: k8s.IngressSpec{ - Rules: []k8s.IngressRule{ + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ { Host: "foo2", - IngressRuleValue: k8s.IngressRuleValue{ - HTTP: &k8s.HTTPIngressRuleValue{ - Paths: []k8s.HTTPIngressPath{ + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ { Path: "/bar2", - Backend: k8s.IngressBackend{ + Backend: v1beta1.IngressBackend{ ServiceName: "service1", - ServicePort: k8s.FromInt(801), + ServicePort: intstr.FromInt(801), }, }, }, @@ -369,21 +374,21 @@ func TestRuleType(t *testing.T) { }, }, { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Annotations: map[string]string{"traefik.frontend.rule.type": "PathXXStrip"}, //wrong rule }, - Spec: k8s.IngressSpec{ - Rules: []k8s.IngressRule{ + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ { Host: "foo1", - IngressRuleValue: k8s.IngressRuleValue{ - HTTP: &k8s.HTTPIngressRuleValue{ - Paths: []k8s.HTTPIngressPath{ + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ { Path: "/bar3", - Backend: k8s.IngressBackend{ + Backend: v1beta1.IngressBackend{ ServiceName: "service1", - ServicePort: k8s.FromInt(801), + ServicePort: intstr.FromInt(801), }, }, }, @@ -394,15 +399,15 @@ func TestRuleType(t *testing.T) { }, }, } - services := []k8s.Service{ + services := []*v1.Service{ { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Name: "service1", UID: "1", }, - Spec: k8s.ServiceSpec{ + Spec: v1.ServiceSpec{ ClusterIP: "10.0.0.1", - Ports: []k8s.ServicePort{ + Ports: []v1.ServicePort{ { Name: "http", Port: 801, @@ -495,22 +500,22 @@ func TestRuleType(t *testing.T) { } func TestGetPassHostHeader(t *testing.T) { - ingresses := []k8s.Ingress{{ - ObjectMeta: k8s.ObjectMeta{ + ingresses := []*v1beta1.Ingress{{ + ObjectMeta: v1.ObjectMeta{ Namespace: "awesome", }, - Spec: k8s.IngressSpec{ - Rules: []k8s.IngressRule{ + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ { Host: "foo", - IngressRuleValue: k8s.IngressRuleValue{ - HTTP: &k8s.HTTPIngressRuleValue{ - Paths: []k8s.HTTPIngressPath{ + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ { Path: "/bar", - Backend: k8s.IngressBackend{ + Backend: v1beta1.IngressBackend{ ServiceName: "service1", - ServicePort: k8s.FromInt(801), + ServicePort: intstr.FromInt(801), }, }, }, @@ -520,16 +525,16 @@ func TestGetPassHostHeader(t *testing.T) { }, }, }} - services := []k8s.Service{ + services := []*v1.Service{ { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Name: "service1", Namespace: "awesome", UID: "1", }, - Spec: k8s.ServiceSpec{ + Spec: v1.ServiceSpec{ ClusterIP: "10.0.0.1", - Ports: []k8s.ServicePort{ + Ports: []v1.ServicePort{ { Name: "http", Port: 801, @@ -587,22 +592,22 @@ func TestGetPassHostHeader(t *testing.T) { } func TestOnlyReferencesServicesFromOwnNamespace(t *testing.T) { - ingresses := []k8s.Ingress{ + ingresses := []*v1beta1.Ingress{ { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Namespace: "awesome", }, - Spec: k8s.IngressSpec{ - Rules: []k8s.IngressRule{ + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ { Host: "foo", - IngressRuleValue: k8s.IngressRuleValue{ - HTTP: &k8s.HTTPIngressRuleValue{ - Paths: []k8s.HTTPIngressPath{ + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ { - Backend: k8s.IngressBackend{ + Backend: v1beta1.IngressBackend{ ServiceName: "service", - ServicePort: k8s.FromInt(80), + ServicePort: intstr.FromInt(80), }, }, }, @@ -613,16 +618,16 @@ func TestOnlyReferencesServicesFromOwnNamespace(t *testing.T) { }, }, } - services := []k8s.Service{ + services := []*v1.Service{ { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Name: "service", UID: "1", Namespace: "awesome", }, - Spec: k8s.ServiceSpec{ + Spec: v1.ServiceSpec{ ClusterIP: "10.0.0.1", - Ports: []k8s.ServicePort{ + Ports: []v1.ServicePort{ { Name: "http", Port: 80, @@ -631,14 +636,14 @@ func TestOnlyReferencesServicesFromOwnNamespace(t *testing.T) { }, }, { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Name: "service", UID: "2", Namespace: "not-awesome", }, - Spec: k8s.ServiceSpec{ + Spec: v1.ServiceSpec{ ClusterIP: "10.0.0.2", - Ports: []k8s.ServicePort{ + Ports: []v1.ServicePort{ { Name: "http", Port: 80, @@ -693,23 +698,23 @@ func TestOnlyReferencesServicesFromOwnNamespace(t *testing.T) { } func TestLoadNamespacedIngresses(t *testing.T) { - ingresses := []k8s.Ingress{ + ingresses := []*v1beta1.Ingress{ { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Namespace: "awesome", }, - Spec: k8s.IngressSpec{ - Rules: []k8s.IngressRule{ + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ { Host: "foo", - IngressRuleValue: k8s.IngressRuleValue{ - HTTP: &k8s.HTTPIngressRuleValue{ - Paths: []k8s.HTTPIngressPath{ + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ { Path: "/bar", - Backend: k8s.IngressBackend{ + Backend: v1beta1.IngressBackend{ ServiceName: "service1", - ServicePort: k8s.FromInt(801), + ServicePort: intstr.FromInt(801), }, }, }, @@ -718,19 +723,19 @@ func TestLoadNamespacedIngresses(t *testing.T) { }, { Host: "bar", - IngressRuleValue: k8s.IngressRuleValue{ - HTTP: &k8s.HTTPIngressRuleValue{ - Paths: []k8s.HTTPIngressPath{ + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ { - Backend: k8s.IngressBackend{ + Backend: v1beta1.IngressBackend{ ServiceName: "service3", - ServicePort: k8s.FromInt(443), + ServicePort: intstr.FromInt(443), }, }, { - Backend: k8s.IngressBackend{ + Backend: v1beta1.IngressBackend{ ServiceName: "service2", - ServicePort: k8s.FromInt(802), + ServicePort: intstr.FromInt(802), }, }, }, @@ -741,21 +746,21 @@ func TestLoadNamespacedIngresses(t *testing.T) { }, }, { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Namespace: "not-awesome", }, - Spec: k8s.IngressSpec{ - Rules: []k8s.IngressRule{ + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ { Host: "baz", - IngressRuleValue: k8s.IngressRuleValue{ - HTTP: &k8s.HTTPIngressRuleValue{ - Paths: []k8s.HTTPIngressPath{ + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ { Path: "/baz", - Backend: k8s.IngressBackend{ + Backend: v1beta1.IngressBackend{ ServiceName: "service1", - ServicePort: k8s.FromInt(801), + ServicePort: intstr.FromInt(801), }, }, }, @@ -766,16 +771,16 @@ func TestLoadNamespacedIngresses(t *testing.T) { }, }, } - services := []k8s.Service{ + services := []*v1.Service{ { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Namespace: "awesome", Name: "service1", UID: "1", }, - Spec: k8s.ServiceSpec{ + Spec: v1.ServiceSpec{ ClusterIP: "10.0.0.1", - Ports: []k8s.ServicePort{ + Ports: []v1.ServicePort{ { Name: "http", Port: 801, @@ -784,14 +789,14 @@ func TestLoadNamespacedIngresses(t *testing.T) { }, }, { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Name: "service1", Namespace: "not-awesome", UID: "1", }, - Spec: k8s.ServiceSpec{ + Spec: v1.ServiceSpec{ ClusterIP: "10.0.0.1", - Ports: []k8s.ServicePort{ + Ports: []v1.ServicePort{ { Name: "http", Port: 801, @@ -800,14 +805,14 @@ func TestLoadNamespacedIngresses(t *testing.T) { }, }, { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Name: "service2", Namespace: "awesome", UID: "2", }, - Spec: k8s.ServiceSpec{ + Spec: v1.ServiceSpec{ ClusterIP: "10.0.0.2", - Ports: []k8s.ServicePort{ + Ports: []v1.ServicePort{ { Port: 802, }, @@ -815,14 +820,14 @@ func TestLoadNamespacedIngresses(t *testing.T) { }, }, { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Name: "service3", Namespace: "awesome", UID: "3", }, - Spec: k8s.ServiceSpec{ + Spec: v1.ServiceSpec{ ClusterIP: "10.0.0.3", - Ports: []k8s.ServicePort{ + Ports: []v1.ServicePort{ { Name: "http", Port: 443, @@ -906,23 +911,23 @@ func TestLoadNamespacedIngresses(t *testing.T) { } func TestLoadMultipleNamespacedIngresses(t *testing.T) { - ingresses := []k8s.Ingress{ + ingresses := []*v1beta1.Ingress{ { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Namespace: "awesome", }, - Spec: k8s.IngressSpec{ - Rules: []k8s.IngressRule{ + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ { Host: "foo", - IngressRuleValue: k8s.IngressRuleValue{ - HTTP: &k8s.HTTPIngressRuleValue{ - Paths: []k8s.HTTPIngressPath{ + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ { Path: "/bar", - Backend: k8s.IngressBackend{ + Backend: v1beta1.IngressBackend{ ServiceName: "service1", - ServicePort: k8s.FromInt(801), + ServicePort: intstr.FromInt(801), }, }, }, @@ -931,19 +936,19 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) { }, { Host: "bar", - IngressRuleValue: k8s.IngressRuleValue{ - HTTP: &k8s.HTTPIngressRuleValue{ - Paths: []k8s.HTTPIngressPath{ + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ { - Backend: k8s.IngressBackend{ + Backend: v1beta1.IngressBackend{ ServiceName: "service3", - ServicePort: k8s.FromInt(443), + ServicePort: intstr.FromInt(443), }, }, { - Backend: k8s.IngressBackend{ + Backend: v1beta1.IngressBackend{ ServiceName: "service2", - ServicePort: k8s.FromInt(802), + ServicePort: intstr.FromInt(802), }, }, }, @@ -954,21 +959,21 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) { }, }, { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Namespace: "somewhat-awesome", }, - Spec: k8s.IngressSpec{ - Rules: []k8s.IngressRule{ + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ { Host: "awesome", - IngressRuleValue: k8s.IngressRuleValue{ - HTTP: &k8s.HTTPIngressRuleValue{ - Paths: []k8s.HTTPIngressPath{ + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ { Path: "/quix", - Backend: k8s.IngressBackend{ + Backend: v1beta1.IngressBackend{ ServiceName: "service1", - ServicePort: k8s.FromInt(801), + ServicePort: intstr.FromInt(801), }, }, }, @@ -979,21 +984,21 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) { }, }, { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Namespace: "not-awesome", }, - Spec: k8s.IngressSpec{ - Rules: []k8s.IngressRule{ + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ { Host: "baz", - IngressRuleValue: k8s.IngressRuleValue{ - HTTP: &k8s.HTTPIngressRuleValue{ - Paths: []k8s.HTTPIngressPath{ + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ { Path: "/baz", - Backend: k8s.IngressBackend{ + Backend: v1beta1.IngressBackend{ ServiceName: "service1", - ServicePort: k8s.FromInt(801), + ServicePort: intstr.FromInt(801), }, }, }, @@ -1004,16 +1009,16 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) { }, }, } - services := []k8s.Service{ + services := []*v1.Service{ { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Name: "service1", Namespace: "awesome", UID: "1", }, - Spec: k8s.ServiceSpec{ + Spec: v1.ServiceSpec{ ClusterIP: "10.0.0.1", - Ports: []k8s.ServicePort{ + Ports: []v1.ServicePort{ { Name: "http", Port: 801, @@ -1022,14 +1027,14 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) { }, }, { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Namespace: "somewhat-awesome", Name: "service1", UID: "17", }, - Spec: k8s.ServiceSpec{ + Spec: v1.ServiceSpec{ ClusterIP: "10.0.0.4", - Ports: []k8s.ServicePort{ + Ports: []v1.ServicePort{ { Name: "http", Port: 801, @@ -1038,14 +1043,14 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) { }, }, { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Namespace: "awesome", Name: "service2", UID: "2", }, - Spec: k8s.ServiceSpec{ + Spec: v1.ServiceSpec{ ClusterIP: "10.0.0.2", - Ports: []k8s.ServicePort{ + Ports: []v1.ServicePort{ { Port: 802, }, @@ -1053,14 +1058,14 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) { }, }, { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Namespace: "awesome", Name: "service3", UID: "3", }, - Spec: k8s.ServiceSpec{ + Spec: v1.ServiceSpec{ ClusterIP: "10.0.0.3", - Ports: []k8s.ServicePort{ + Ports: []v1.ServicePort{ { Name: "http", Port: 443, @@ -1167,21 +1172,21 @@ func TestLoadMultipleNamespacedIngresses(t *testing.T) { } func TestHostlessIngress(t *testing.T) { - ingresses := []k8s.Ingress{{ - ObjectMeta: k8s.ObjectMeta{ + ingresses := []*v1beta1.Ingress{{ + ObjectMeta: v1.ObjectMeta{ Namespace: "awesome", }, - Spec: k8s.IngressSpec{ - Rules: []k8s.IngressRule{ + Spec: v1beta1.IngressSpec{ + Rules: []v1beta1.IngressRule{ { - IngressRuleValue: k8s.IngressRuleValue{ - HTTP: &k8s.HTTPIngressRuleValue{ - Paths: []k8s.HTTPIngressPath{ + IngressRuleValue: v1beta1.IngressRuleValue{ + HTTP: &v1beta1.HTTPIngressRuleValue{ + Paths: []v1beta1.HTTPIngressPath{ { Path: "/bar", - Backend: k8s.IngressBackend{ + Backend: v1beta1.IngressBackend{ ServiceName: "service1", - ServicePort: k8s.FromInt(801), + ServicePort: intstr.FromInt(801), }, }, }, @@ -1191,16 +1196,16 @@ func TestHostlessIngress(t *testing.T) { }, }, }} - services := []k8s.Service{ + services := []*v1.Service{ { - ObjectMeta: k8s.ObjectMeta{ + ObjectMeta: v1.ObjectMeta{ Name: "service1", Namespace: "awesome", UID: "1", }, - Spec: k8s.ServiceSpec{ + Spec: v1.ServiceSpec{ ClusterIP: "10.0.0.1", - Ports: []k8s.ServicePort{ + Ports: []v1.ServicePort{ { Name: "http", Port: 801, @@ -1255,42 +1260,45 @@ func TestHostlessIngress(t *testing.T) { } type clientMock struct { - ingresses []k8s.Ingress - services []k8s.Service - endpoints []k8s.Endpoints + ingresses []*v1beta1.Ingress + services []*v1.Service + endpoints []*v1.Endpoints watchChan chan interface{} } -func (c clientMock) GetIngresses(labelString string, predicate func(k8s.Ingress) bool) ([]k8s.Ingress, error) { - var ingresses []k8s.Ingress +func (c clientMock) GetIngresses(namespaces k8s.Namespaces) []*v1beta1.Ingress { + result := make([]*v1beta1.Ingress, 0, len(c.ingresses)) + for _, ingress := range c.ingresses { - if predicate(ingress) { - ingresses = append(ingresses, ingress) + if k8s.HasNamespace(ingress, namespaces) { + result = append(result, ingress) } } - return ingresses, nil + return result } -func (c clientMock) WatchIngresses(labelString string, predicate func(k8s.Ingress) bool, stopCh <-chan bool) (chan interface{}, chan error, error) { - return c.watchChan, make(chan error), nil + +func (c clientMock) WatchIngresses(labelSelector string, stopCh <-chan struct{}) chan interface{} { + return c.watchChan } -func (c clientMock) GetService(name, namespace string) (k8s.Service, error) { + +func (c clientMock) GetService(namespace, name string) (*v1.Service, bool, error) { for _, service := range c.services { if service.Namespace == namespace && service.Name == name { - return service, nil + return service, true, nil } } - return k8s.Service{}, nil + return &v1.Service{}, true, nil } -func (c clientMock) GetEndpoints(name, namespace string) (k8s.Endpoints, error) { +func (c clientMock) GetEndpoints(namespace, name string) (*v1.Endpoints, bool, error) { for _, endpoints := range c.endpoints { if endpoints.Namespace == namespace && endpoints.Name == name { - return endpoints, nil + return endpoints, true, nil } } - return k8s.Endpoints{}, nil + return &v1.Endpoints{}, true, nil } -func (c clientMock) WatchAll(labelString string, stopCh <-chan bool) (chan interface{}, chan error, error) { - return c.watchChan, make(chan error), nil +func (c clientMock) WatchAll(labelString string, stopCh <-chan bool) (chan interface{}, error) { + return c.watchChan, nil } diff --git a/script/generate b/script/generate index 539075810..44add124a 100755 --- a/script/generate +++ b/script/generate @@ -1,4 +1,10 @@ #!/bin/bash set -e +# Fix for http://stackoverflow.com/questions/37284423/glog-flag-redefined-error +# go test github.com/containous/traefik/provider +#*/github.com/containous/traefik/provider/_test/provider.test flag redefined: log_dir +#panic: */github.com/containous/traefik/provider/_test/provider.test flag redefined: log_dir +rm -rf vendor/k8s.io/client-go/1.5/vendor/github.com/golang/glog/ + go generate diff --git a/traefik.go b/traefik.go index 5cdef2497..142a232c3 100644 --- a/traefik.go +++ b/traefik.go @@ -19,7 +19,7 @@ import ( "github.com/containous/traefik/cluster" "github.com/containous/traefik/log" "github.com/containous/traefik/middlewares" - "github.com/containous/traefik/provider" + "github.com/containous/traefik/provider/k8s" "github.com/containous/traefik/types" "github.com/containous/traefik/version" "github.com/docker/libkv/store" @@ -144,7 +144,7 @@ Complete documentation is available at https://traefik.io`, f.AddParser(reflect.TypeOf(EntryPoints{}), &EntryPoints{}) f.AddParser(reflect.TypeOf(DefaultEntryPoints{}), &DefaultEntryPoints{}) f.AddParser(reflect.TypeOf(types.Constraints{}), &types.Constraints{}) - f.AddParser(reflect.TypeOf(provider.Namespaces{}), &provider.Namespaces{}) + f.AddParser(reflect.TypeOf(k8s.Namespaces{}), &k8s.Namespaces{}) f.AddParser(reflect.TypeOf([]acme.Domain{}), &acme.Domains{}) //add commands