Add unit test
Signed-off-by: Emile Vauge <emile@vauge.com>
This commit is contained in:
parent
d82e1342fb
commit
c0dd4c3209
8 changed files with 242 additions and 47 deletions
|
@ -2,14 +2,14 @@
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: whoami-x
|
name: service1
|
||||||
labels:
|
labels:
|
||||||
app: whoami
|
app: whoami
|
||||||
spec:
|
spec:
|
||||||
type: NodePort
|
type: NodePort
|
||||||
ports:
|
ports:
|
||||||
- port: 80
|
- port: 80
|
||||||
nodePort: 30301
|
nodePort: 30283
|
||||||
targetPort: 80
|
targetPort: 80
|
||||||
protocol: TCP
|
protocol: TCP
|
||||||
name: http
|
name: http
|
||||||
|
@ -19,24 +19,7 @@ spec:
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: Service
|
kind: Service
|
||||||
metadata:
|
metadata:
|
||||||
name: whoami-default
|
name: service2
|
||||||
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
|
|
||||||
labels:
|
labels:
|
||||||
app: whoami
|
app: whoami
|
||||||
spec:
|
spec:
|
||||||
|
@ -50,6 +33,23 @@ spec:
|
||||||
selector:
|
selector:
|
||||||
app: whoami
|
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
|
# A single RC matching all Services
|
||||||
apiVersion: v1
|
apiVersion: v1
|
||||||
kind: ReplicationController
|
kind: ReplicationController
|
||||||
|
@ -72,7 +72,7 @@ spec:
|
||||||
apiVersion: extensions/v1beta1
|
apiVersion: extensions/v1beta1
|
||||||
kind: Ingress
|
kind: Ingress
|
||||||
metadata:
|
metadata:
|
||||||
name: whoamimap
|
name: whoamiIngress
|
||||||
spec:
|
spec:
|
||||||
rules:
|
rules:
|
||||||
- host: foo.localhost
|
- host: foo.localhost
|
||||||
|
@ -80,14 +80,14 @@ spec:
|
||||||
paths:
|
paths:
|
||||||
- path: /bar
|
- path: /bar
|
||||||
backend:
|
backend:
|
||||||
serviceName: whoami-x
|
serviceName: service1
|
||||||
servicePort: 80
|
servicePort: 80
|
||||||
- host: bar.localhost
|
- host: bar.localhost
|
||||||
http:
|
http:
|
||||||
paths:
|
paths:
|
||||||
- backend:
|
- backend:
|
||||||
serviceName: whoami-y
|
serviceName: service2
|
||||||
servicePort: 80
|
servicePort: 80
|
||||||
- backend:
|
- backend:
|
||||||
serviceName: whoami-x
|
serviceName: service3
|
||||||
servicePort: 80
|
servicePort: 80
|
||||||
|
|
|
@ -19,13 +19,6 @@ spec:
|
||||||
- image: containous/traefik:k8s
|
- image: containous/traefik:k8s
|
||||||
name: traefik-ingress-lb
|
name: traefik-ingress-lb
|
||||||
imagePullPolicy: Always
|
imagePullPolicy: Always
|
||||||
# livenessProbe:
|
|
||||||
# httpGet:
|
|
||||||
# path: /healthz
|
|
||||||
# port: 10249
|
|
||||||
# scheme: HTTP
|
|
||||||
# initialDelaySeconds: 30
|
|
||||||
# timeoutSeconds: 5
|
|
||||||
ports:
|
ports:
|
||||||
- containerPort: 80
|
- containerPort: 80
|
||||||
hostPort: 80
|
hostPort: 80
|
||||||
|
|
|
@ -21,7 +21,13 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client is a client for the Kubernetes master.
|
// 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
|
endpointURL string
|
||||||
tls *tls.Config
|
tls *tls.Config
|
||||||
token string
|
token string
|
||||||
|
@ -32,12 +38,12 @@ type Client struct {
|
||||||
// The provided host is an url (scheme://hostname[:port]) of a
|
// The provided host is an url (scheme://hostname[:port]) of a
|
||||||
// Kubernetes master without any path.
|
// Kubernetes master without any path.
|
||||||
// The provided client is an authorized http.Client used to perform requests to the Kubernetes API master.
|
// 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)
|
validURL, err := url.Parse(baseURL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to parse URL %q: %v", baseURL, err)
|
return nil, fmt.Errorf("failed to parse URL %q: %v", baseURL, err)
|
||||||
}
|
}
|
||||||
return &Client{
|
return &clientImpl{
|
||||||
endpointURL: strings.TrimSuffix(validURL.String(), "/"),
|
endpointURL: strings.TrimSuffix(validURL.String(), "/"),
|
||||||
token: token,
|
token: token,
|
||||||
caCert: caCert,
|
caCert: caCert,
|
||||||
|
@ -45,7 +51,7 @@ func NewClient(baseURL string, caCert []byte, token string) (*Client, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetIngresses returns all services in the cluster
|
// 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
|
getURL := c.endpointURL + extentionsEndpoint + defaultIngress
|
||||||
request := gorequest.New().Get(getURL)
|
request := gorequest.New().Get(getURL)
|
||||||
if len(c.token) > 0 {
|
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
|
// 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{})
|
watchCh := make(chan interface{})
|
||||||
errCh := make(chan error)
|
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
|
// 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
|
getURL := c.endpointURL + APIEndpoint + defaultService
|
||||||
|
|
||||||
// Make request to Kubernetes API
|
// Make request to Kubernetes API
|
||||||
|
|
|
@ -249,6 +249,19 @@ type IntOrString struct {
|
||||||
StrVal string
|
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.
|
// String returns the string value, or the Itoa of the int value.
|
||||||
func (intstr *IntOrString) String() string {
|
func (intstr *IntOrString) String() string {
|
||||||
if intstr.Type == String {
|
if intstr.Type == String {
|
||||||
|
|
|
@ -23,7 +23,7 @@ type Kubernetes struct {
|
||||||
Endpoint string
|
Endpoint string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provider *Kubernetes) createClient() (*k8s.Client, error) {
|
func (provider *Kubernetes) createClient() (k8s.Client, error) {
|
||||||
var token string
|
var token string
|
||||||
tokenBytes, err := ioutil.ReadFile(serviceAccountToken)
|
tokenBytes, err := ioutil.ReadFile(serviceAccountToken)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -103,7 +103,7 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage
|
||||||
return nil
|
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 {
|
ingresses, err := k8sClient.GetIngresses(func(ingress k8s.Ingress) bool {
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
@ -136,7 +136,7 @@ func (provider *Kubernetes) loadIngresses(k8sClient *k8s.Client) (*types.Configu
|
||||||
}
|
}
|
||||||
if len(pa.Path) > 0 {
|
if len(pa.Path) > 0 {
|
||||||
templateObjects.Frontends[r.Host+pa.Path].Routes[pa.Path] = types.Route{
|
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 {
|
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 {
|
for _, port := range service.Spec.Ports {
|
||||||
if port.Port == pa.Backend.ServicePort.IntValue() {
|
if port.Port == pa.Backend.ServicePort.IntValue() {
|
||||||
protocol = port.Name
|
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
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{
|
|
||||||
URL: protocol + "://" + service.Spec.ClusterIP + ":" + pa.Backend.ServicePort.String(),
|
|
||||||
Weight: 1,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,184 @@
|
||||||
package provider
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -11,6 +11,6 @@
|
||||||
backend = "{{$frontend.Backend}}"
|
backend = "{{$frontend.Backend}}"
|
||||||
{{range $routeName, $route := $frontend.Routes}}
|
{{range $routeName, $route := $frontend.Routes}}
|
||||||
[frontends."{{$frontendName}}".routes."{{$routeName}}"]
|
[frontends."{{$frontendName}}".routes."{{$routeName}}"]
|
||||||
rule = "PathStrip:{{$route.Rule}}"
|
rule = "{{$route.Rule}}"
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -40,10 +40,10 @@
|
||||||
</ul>
|
</ul>
|
||||||
<ul class="nav navbar-nav navbar-right">
|
<ul class="nav navbar-nav navbar-right">
|
||||||
<li>
|
<li>
|
||||||
<a href="https://github.com/containous/traefik/blob/master/docs/index.md" target="_blank">Documentation</a>
|
<a href="https://docs.traefik.io" target="_blank">Documentation</a>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a href="http://traefik.io" target="_blank"><span class="traefik-blue">traefik.io</span></a>
|
<a href="https://traefik.io" target="_blank"><span class="traefik-blue">traefik.io</span></a>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
Loading…
Reference in a new issue