Add unit test

Signed-off-by: Emile Vauge <emile@vauge.com>
This commit is contained in:
Emile Vauge 2016-04-20 13:26:51 +02:00
parent d82e1342fb
commit c0dd4c3209
No known key found for this signature in database
GPG key ID: D808B4C167352E59
8 changed files with 242 additions and 47 deletions

View file

@ -2,14 +2,14 @@
apiVersion: v1
kind: Service
metadata:
name: whoami-x
name: service1
labels:
app: whoami
spec:
type: NodePort
ports:
- port: 80
nodePort: 30301
nodePort: 30283
targetPort: 80
protocol: TCP
name: http
@ -19,24 +19,7 @@ spec:
apiVersion: v1
kind: Service
metadata:
name: whoami-default
labels:
app: whoami
spec:
type: NodePort
ports:
- port: 80
nodePort: 30302
targetPort: 80
protocol: TCP
name: http
selector:
app: whoami
---
apiVersion: v1
kind: Service
metadata:
name: whoami-y
name: service2
labels:
app: whoami
spec:
@ -50,6 +33,23 @@ spec:
selector:
app: whoami
---
apiVersion: v1
kind: Service
metadata:
name: service3
labels:
app: whoami
spec:
type: NodePort
ports:
- port: 80
nodePort: 30285
targetPort: 80
protocol: TCP
name: http
selector:
app: whoami
---
# A single RC matching all Services
apiVersion: v1
kind: ReplicationController
@ -72,7 +72,7 @@ spec:
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: whoamimap
name: whoamiIngress
spec:
rules:
- host: foo.localhost
@ -80,14 +80,14 @@ spec:
paths:
- path: /bar
backend:
serviceName: whoami-x
serviceName: service1
servicePort: 80
- host: bar.localhost
http:
paths:
- backend:
serviceName: whoami-y
serviceName: service2
servicePort: 80
- backend:
serviceName: whoami-x
serviceName: service3
servicePort: 80

View file

@ -19,13 +19,6 @@ spec:
- image: containous/traefik:k8s
name: traefik-ingress-lb
imagePullPolicy: Always
# livenessProbe:
# httpGet:
# path: /healthz
# port: 10249
# scheme: HTTP
# initialDelaySeconds: 30
# timeoutSeconds: 5
ports:
- containerPort: 80
hostPort: 80

View file

@ -21,7 +21,13 @@ const (
)
// Client is a client for the Kubernetes master.
type Client struct {
type Client interface {
GetIngresses(predicate func(Ingress) bool) ([]Ingress, error)
WatchIngresses(predicate func(Ingress) bool, stopCh <-chan bool) (chan interface{}, chan error, error)
GetServices(predicate func(Service) bool) ([]Service, error)
}
type clientImpl struct {
endpointURL string
tls *tls.Config
token string
@ -32,12 +38,12 @@ type Client struct {
// The provided host is an url (scheme://hostname[:port]) of a
// Kubernetes master without any path.
// The provided client is an authorized http.Client used to perform requests to the Kubernetes API master.
func NewClient(baseURL string, caCert []byte, token string) (*Client, error) {
func NewClient(baseURL string, caCert []byte, token string) (Client, error) {
validURL, err := url.Parse(baseURL)
if err != nil {
return nil, fmt.Errorf("failed to parse URL %q: %v", baseURL, err)
}
return &Client{
return &clientImpl{
endpointURL: strings.TrimSuffix(validURL.String(), "/"),
token: token,
caCert: caCert,
@ -45,7 +51,7 @@ func NewClient(baseURL string, caCert []byte, token string) (*Client, error) {
}
// GetIngresses returns all services in the cluster
func (c *Client) GetIngresses(predicate func(Ingress) bool) ([]Ingress, error) {
func (c *clientImpl) GetIngresses(predicate func(Ingress) bool) ([]Ingress, error) {
getURL := c.endpointURL + extentionsEndpoint + defaultIngress
request := gorequest.New().Get(getURL)
if len(c.token) > 0 {
@ -76,7 +82,7 @@ func (c *Client) GetIngresses(predicate func(Ingress) bool) ([]Ingress, error) {
}
// WatchIngresses returns all services in the cluster
func (c *Client) WatchIngresses(predicate func(Ingress) bool, stopCh <-chan bool) (chan interface{}, chan error, error) {
func (c *clientImpl) WatchIngresses(predicate func(Ingress) bool, stopCh <-chan bool) (chan interface{}, chan error, error) {
watchCh := make(chan interface{})
errCh := make(chan error)
@ -130,7 +136,7 @@ func (c *Client) WatchIngresses(predicate func(Ingress) bool, stopCh <-chan bool
}
// GetServices returns all services in the cluster
func (c *Client) GetServices(predicate func(Service) bool) ([]Service, error) {
func (c *clientImpl) GetServices(predicate func(Service) bool) ([]Service, error) {
getURL := c.endpointURL + APIEndpoint + defaultService
// Make request to Kubernetes API

View file

@ -249,6 +249,19 @@ type IntOrString struct {
StrVal string
}
// FromInt creates an IntOrString object with an int32 value. It is
// your responsibility not to call this method with a value greater
// than int32.
// TODO: convert to (val int32)
func FromInt(val int) IntOrString {
return IntOrString{Type: Int, IntVal: int32(val)}
}
// FromString creates an IntOrString object with a string value.
func FromString(val string) IntOrString {
return IntOrString{Type: String, StrVal: val}
}
// String returns the string value, or the Itoa of the int value.
func (intstr *IntOrString) String() string {
if intstr.Type == String {

View file

@ -23,7 +23,7 @@ type Kubernetes struct {
Endpoint string
}
func (provider *Kubernetes) createClient() (*k8s.Client, error) {
func (provider *Kubernetes) createClient() (k8s.Client, error) {
var token string
tokenBytes, err := ioutil.ReadFile(serviceAccountToken)
if err == nil {
@ -103,7 +103,7 @@ func (provider *Kubernetes) Provide(configurationChan chan<- types.ConfigMessage
return nil
}
func (provider *Kubernetes) loadIngresses(k8sClient *k8s.Client) (*types.Configuration, error) {
func (provider *Kubernetes) loadIngresses(k8sClient k8s.Client) (*types.Configuration, error) {
ingresses, err := k8sClient.GetIngresses(func(ingress k8s.Ingress) bool {
return true
})
@ -136,7 +136,7 @@ func (provider *Kubernetes) loadIngresses(k8sClient *k8s.Client) (*types.Configu
}
if len(pa.Path) > 0 {
templateObjects.Frontends[r.Host+pa.Path].Routes[pa.Path] = types.Route{
Rule: pa.Path,
Rule: "PathStrip:" + pa.Path,
}
}
services, err := k8sClient.GetServices(func(service k8s.Service) bool {
@ -151,13 +151,13 @@ func (provider *Kubernetes) loadIngresses(k8sClient *k8s.Client) (*types.Configu
for _, port := range service.Spec.Ports {
if port.Port == pa.Backend.ServicePort.IntValue() {
protocol = port.Name
templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{
URL: protocol + "://" + service.Spec.ClusterIP + ":" + pa.Backend.ServicePort.String(),
Weight: 1,
}
break
}
}
templateObjects.Backends[r.Host+pa.Path].Servers[string(service.UID)] = types.Server{
URL: protocol + "://" + service.Spec.ClusterIP + ":" + pa.Backend.ServicePort.String(),
Weight: 1,
}
}
}
}

View file

@ -1 +1,184 @@
package provider
import (
"github.com/containous/traefik/provider/k8s"
"github.com/containous/traefik/types"
"reflect"
"testing"
)
func TestLoadIngresses(t *testing.T) {
ingresses := []k8s.Ingress{{
Spec: k8s.IngressSpec{
Rules: []k8s.IngressRule{
{
Host: "foo",
IngressRuleValue: k8s.IngressRuleValue{
HTTP: &k8s.HTTPIngressRuleValue{
Paths: []k8s.HTTPIngressPath{
{
Path: "/bar",
Backend: k8s.IngressBackend{
ServiceName: "service1",
ServicePort: k8s.FromInt(801),
},
},
},
},
},
},
{
Host: "bar",
IngressRuleValue: k8s.IngressRuleValue{
HTTP: &k8s.HTTPIngressRuleValue{
Paths: []k8s.HTTPIngressPath{
{
Backend: k8s.IngressBackend{
ServiceName: "service3",
ServicePort: k8s.FromInt(803),
},
},
{
Backend: k8s.IngressBackend{
ServiceName: "service2",
ServicePort: k8s.FromInt(802),
},
},
},
},
},
},
},
},
}}
services := []k8s.Service{
{
ObjectMeta: k8s.ObjectMeta{
Name: "service1",
UID: "1",
},
Spec: k8s.ServiceSpec{
ClusterIP: "10.0.0.1",
Ports: []k8s.ServicePort{
{
Name: "http",
Port: 801,
},
},
},
},
{
ObjectMeta: k8s.ObjectMeta{
Name: "service2",
UID: "2",
},
Spec: k8s.ServiceSpec{
ClusterIP: "10.0.0.2",
Ports: []k8s.ServicePort{
{
Name: "http",
Port: 802,
},
},
},
},
{
ObjectMeta: k8s.ObjectMeta{
Name: "service3",
UID: "3",
},
Spec: k8s.ServiceSpec{
ClusterIP: "10.0.0.3",
Ports: []k8s.ServicePort{
{
Name: "http",
Port: 803,
},
},
},
},
}
watchChan := make(chan interface{})
client := clientMock{
ingresses: ingresses,
services: services,
watchChan: watchChan,
}
provider := Kubernetes{}
actual, err := provider.loadIngresses(client)
if err != nil {
t.Fatalf("error %+v", err)
}
expected := &types.Configuration{
Backends: map[string]*types.Backend{
"foo/bar": {
Servers: map[string]types.Server{
"1": {
URL: "http://10.0.0.1:801",
Weight: 1,
},
},
CircuitBreaker: nil,
LoadBalancer: nil,
},
"bar": {
Servers: map[string]types.Server{
"2": {
URL: "http://10.0.0.2:802",
Weight: 1,
},
"3": {
URL: "http://10.0.0.3:803",
Weight: 1,
},
},
CircuitBreaker: nil,
LoadBalancer: nil,
},
},
Frontends: map[string]*types.Frontend{
"foo/bar": {
Backend: "foo/bar",
Routes: map[string]types.Route{
"/bar": {
Rule: "PathStrip:/bar",
},
"foo": {
Rule: "Host:foo",
},
},
},
"bar": {
Backend: "bar",
Routes: map[string]types.Route{
"bar": {
Rule: "Host:bar",
},
},
},
},
}
if !reflect.DeepEqual(actual.Backends, expected.Backends) {
t.Fatalf("expected %+v, got %+v", expected.Backends, actual.Backends)
}
if !reflect.DeepEqual(actual.Frontends, expected.Frontends) {
t.Fatalf("expected %+v, got %+v", expected.Frontends, actual.Frontends)
}
}
type clientMock struct {
ingresses []k8s.Ingress
services []k8s.Service
watchChan chan interface{}
}
func (c clientMock) GetIngresses(predicate func(k8s.Ingress) bool) ([]k8s.Ingress, error) {
return c.ingresses, nil
}
func (c clientMock) WatchIngresses(predicate func(k8s.Ingress) bool, stopCh <-chan bool) (chan interface{}, chan error, error) {
return c.watchChan, make(chan error), nil
}
func (c clientMock) GetServices(predicate func(k8s.Service) bool) ([]k8s.Service, error) {
return c.services, nil
}

View file

@ -11,6 +11,6 @@
backend = "{{$frontend.Backend}}"
{{range $routeName, $route := $frontend.Routes}}
[frontends."{{$frontendName}}".routes."{{$routeName}}"]
rule = "PathStrip:{{$route.Rule}}"
rule = "{{$route.Rule}}"
{{end}}
{{end}}

View file

@ -40,10 +40,10 @@
</ul>
<ul class="nav navbar-nav navbar-right">
<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>
<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>
</ul>
</div>