2015-03-24 17:32:43 +00:00
|
|
|
/*
|
2015-05-01 16:19:44 +00:00
|
|
|
Copyright 2015 The Kubernetes Authors All rights reserved.
|
2015-03-24 17:32:43 +00:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2015-10-10 03:58:57 +00:00
|
|
|
package service
|
2015-03-24 17:32:43 +00:00
|
|
|
|
|
|
|
import (
|
2015-04-22 20:54:44 +00:00
|
|
|
"reflect"
|
2015-03-24 17:32:43 +00:00
|
|
|
"testing"
|
|
|
|
|
2015-08-05 22:03:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/api"
|
2016-02-16 22:16:45 +00:00
|
|
|
"k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake"
|
2015-10-19 21:08:35 +00:00
|
|
|
fakecloud "k8s.io/kubernetes/pkg/cloudprovider/providers/fake"
|
2015-08-05 22:03:47 +00:00
|
|
|
"k8s.io/kubernetes/pkg/types"
|
2015-03-24 17:32:43 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
const region = "us-central"
|
|
|
|
|
2015-05-22 21:49:26 +00:00
|
|
|
func newService(name string, uid types.UID, serviceType api.ServiceType) *api.Service {
|
|
|
|
return &api.Service{ObjectMeta: api.ObjectMeta{Name: name, Namespace: "namespace", UID: uid}, Spec: api.ServiceSpec{Type: serviceType}}
|
2015-04-22 20:54:44 +00:00
|
|
|
}
|
|
|
|
|
2015-03-24 17:32:43 +00:00
|
|
|
func TestCreateExternalLoadBalancer(t *testing.T) {
|
|
|
|
table := []struct {
|
|
|
|
service *api.Service
|
|
|
|
expectErr bool
|
|
|
|
expectCreateAttempt bool
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
service: &api.Service{
|
|
|
|
ObjectMeta: api.ObjectMeta{
|
|
|
|
Name: "no-external-balancer",
|
|
|
|
Namespace: "default",
|
|
|
|
},
|
|
|
|
Spec: api.ServiceSpec{
|
2015-05-22 21:49:26 +00:00
|
|
|
Type: api.ServiceTypeClusterIP,
|
2015-03-24 17:32:43 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
expectErr: false,
|
|
|
|
expectCreateAttempt: false,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
service: &api.Service{
|
|
|
|
ObjectMeta: api.ObjectMeta{
|
|
|
|
Name: "udp-service",
|
|
|
|
Namespace: "default",
|
|
|
|
},
|
|
|
|
Spec: api.ServiceSpec{
|
|
|
|
Ports: []api.ServicePort{{
|
|
|
|
Port: 80,
|
|
|
|
Protocol: api.ProtocolUDP,
|
|
|
|
}},
|
2015-05-22 21:49:26 +00:00
|
|
|
Type: api.ServiceTypeLoadBalancer,
|
2015-03-24 17:32:43 +00:00
|
|
|
},
|
|
|
|
},
|
2015-09-28 20:57:58 +00:00
|
|
|
expectErr: false,
|
|
|
|
expectCreateAttempt: true,
|
2015-03-24 17:32:43 +00:00
|
|
|
},
|
|
|
|
{
|
|
|
|
service: &api.Service{
|
|
|
|
ObjectMeta: api.ObjectMeta{
|
|
|
|
Name: "basic-service1",
|
|
|
|
Namespace: "default",
|
|
|
|
},
|
|
|
|
Spec: api.ServiceSpec{
|
|
|
|
Ports: []api.ServicePort{{
|
|
|
|
Port: 80,
|
|
|
|
Protocol: api.ProtocolTCP,
|
|
|
|
}},
|
2015-05-22 21:49:26 +00:00
|
|
|
Type: api.ServiceTypeLoadBalancer,
|
2015-03-24 17:32:43 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
expectErr: false,
|
|
|
|
expectCreateAttempt: true,
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, item := range table {
|
2015-10-19 21:08:35 +00:00
|
|
|
cloud := &fakecloud.FakeCloud{}
|
2015-03-24 17:32:43 +00:00
|
|
|
cloud.Region = region
|
2016-01-29 06:34:08 +00:00
|
|
|
client := &fake.Clientset{}
|
2015-05-03 06:32:21 +00:00
|
|
|
controller := New(cloud, client, "test-cluster")
|
2015-03-24 17:32:43 +00:00
|
|
|
controller.init()
|
2015-07-06 21:37:46 +00:00
|
|
|
cloud.Calls = nil // ignore any cloud calls made in init()
|
|
|
|
client.ClearActions() // ignore any client calls made in init()
|
2015-08-08 01:52:23 +00:00
|
|
|
err, _ := controller.createLoadBalancerIfNeeded(types.NamespacedName{Namespace: "foo", Name: "bar"}, item.service, nil)
|
2015-03-24 17:32:43 +00:00
|
|
|
if !item.expectErr && err != nil {
|
|
|
|
t.Errorf("unexpected error: %v", err)
|
|
|
|
} else if item.expectErr && err == nil {
|
|
|
|
t.Errorf("expected error creating %v, got nil", item.service)
|
|
|
|
}
|
2015-07-06 21:37:46 +00:00
|
|
|
actions := client.Actions()
|
2015-03-24 17:32:43 +00:00
|
|
|
if !item.expectCreateAttempt {
|
|
|
|
if len(cloud.Calls) > 0 {
|
|
|
|
t.Errorf("unexpected cloud provider calls: %v", cloud.Calls)
|
|
|
|
}
|
2015-07-06 21:37:46 +00:00
|
|
|
if len(actions) > 0 {
|
|
|
|
t.Errorf("unexpected client actions: %v", actions)
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
|
|
|
} else {
|
2015-10-19 21:08:35 +00:00
|
|
|
var balancer *fakecloud.FakeBalancer
|
2015-06-13 15:58:39 +00:00
|
|
|
for k := range cloud.Balancers {
|
|
|
|
if balancer == nil {
|
|
|
|
b := cloud.Balancers[k]
|
|
|
|
balancer = &b
|
|
|
|
} else {
|
|
|
|
t.Errorf("expected one load balancer to be created, got %v", cloud.Balancers)
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if balancer == nil {
|
|
|
|
t.Errorf("expected one load balancer to be created, got none")
|
|
|
|
} else if balancer.Name != controller.loadBalancerName(item.service) ||
|
|
|
|
balancer.Region != region ||
|
|
|
|
balancer.Ports[0].Port != item.service.Spec.Ports[0].Port {
|
|
|
|
t.Errorf("created load balancer has incorrect parameters: %v", balancer)
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
|
|
|
actionFound := false
|
2015-07-06 21:37:46 +00:00
|
|
|
for _, action := range actions {
|
2016-04-13 22:33:15 +00:00
|
|
|
if action.GetVerb() == "update" && action.GetResource().Resource == "services" {
|
2015-03-24 17:32:43 +00:00
|
|
|
actionFound = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !actionFound {
|
2015-07-06 21:37:46 +00:00
|
|
|
t.Errorf("expected updated service to be sent to client, got these actions instead: %v", actions)
|
2015-03-24 17:32:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-04-22 20:54:44 +00:00
|
|
|
// TODO: Finish converting and update comments
|
|
|
|
func TestUpdateNodesInExternalLoadBalancer(t *testing.T) {
|
|
|
|
hosts := []string{"node0", "node1", "node73"}
|
|
|
|
table := []struct {
|
|
|
|
services []*api.Service
|
2015-10-19 21:08:35 +00:00
|
|
|
expectedUpdateCalls []fakecloud.FakeUpdateBalancerCall
|
2015-04-22 20:54:44 +00:00
|
|
|
}{
|
|
|
|
{
|
|
|
|
// No services present: no calls should be made.
|
|
|
|
services: []*api.Service{},
|
|
|
|
expectedUpdateCalls: nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// Services do not have external load balancers: no calls should be made.
|
|
|
|
services: []*api.Service{
|
2015-05-22 21:49:26 +00:00
|
|
|
newService("s0", "111", api.ServiceTypeClusterIP),
|
2015-05-20 15:59:34 +00:00
|
|
|
newService("s1", "222", api.ServiceTypeNodePort),
|
2015-04-22 20:54:44 +00:00
|
|
|
},
|
|
|
|
expectedUpdateCalls: nil,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// Services does have an external load balancer: one call should be made.
|
|
|
|
services: []*api.Service{
|
2015-05-22 21:49:26 +00:00
|
|
|
newService("s0", "333", api.ServiceTypeLoadBalancer),
|
2015-04-22 20:54:44 +00:00
|
|
|
},
|
2015-10-19 21:08:35 +00:00
|
|
|
expectedUpdateCalls: []fakecloud.FakeUpdateBalancerCall{
|
Change LoadBalancer methods to take api.Service
This is a better abstraction than passing in specific pieces of the
Service that each of the cloudproviders may or may not need. For
instance, many of the providers don't need a region, yet this is passed
in. Similarly many of the providers want a string IP for the load
balancer, but it passes in a converted net ip. Affinity is unused by
AWS. A provider change may also require adding a new parameter which has
an effect on all other cloud provider implementations.
Further, this will simplify adding provider specific load balancer
options, such as with labels or some other metadata. For example, we
could add labels for configuring the details of an AWS elastic load
balancer, such as idle timeout on connections, whether it is
internal or external, cross-zone load balancing, and so on.
Authors: @chbatey, @jsravn
2016-02-17 11:36:50 +00:00
|
|
|
{newService("s0", "333", api.ServiceTypeLoadBalancer), hosts},
|
2015-04-22 20:54:44 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// Three services have an external load balancer: three calls.
|
|
|
|
services: []*api.Service{
|
2015-05-22 21:49:26 +00:00
|
|
|
newService("s0", "444", api.ServiceTypeLoadBalancer),
|
|
|
|
newService("s1", "555", api.ServiceTypeLoadBalancer),
|
|
|
|
newService("s2", "666", api.ServiceTypeLoadBalancer),
|
2015-04-22 20:54:44 +00:00
|
|
|
},
|
2015-10-19 21:08:35 +00:00
|
|
|
expectedUpdateCalls: []fakecloud.FakeUpdateBalancerCall{
|
Change LoadBalancer methods to take api.Service
This is a better abstraction than passing in specific pieces of the
Service that each of the cloudproviders may or may not need. For
instance, many of the providers don't need a region, yet this is passed
in. Similarly many of the providers want a string IP for the load
balancer, but it passes in a converted net ip. Affinity is unused by
AWS. A provider change may also require adding a new parameter which has
an effect on all other cloud provider implementations.
Further, this will simplify adding provider specific load balancer
options, such as with labels or some other metadata. For example, we
could add labels for configuring the details of an AWS elastic load
balancer, such as idle timeout on connections, whether it is
internal or external, cross-zone load balancing, and so on.
Authors: @chbatey, @jsravn
2016-02-17 11:36:50 +00:00
|
|
|
{newService("s0", "444", api.ServiceTypeLoadBalancer), hosts},
|
|
|
|
{newService("s1", "555", api.ServiceTypeLoadBalancer), hosts},
|
|
|
|
{newService("s2", "666", api.ServiceTypeLoadBalancer), hosts},
|
2015-04-22 20:54:44 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
// Two services have an external load balancer and two don't: two calls.
|
|
|
|
services: []*api.Service{
|
2015-05-20 15:59:34 +00:00
|
|
|
newService("s0", "777", api.ServiceTypeNodePort),
|
2015-05-22 21:49:26 +00:00
|
|
|
newService("s1", "888", api.ServiceTypeLoadBalancer),
|
|
|
|
newService("s3", "999", api.ServiceTypeLoadBalancer),
|
|
|
|
newService("s4", "123", api.ServiceTypeClusterIP),
|
2015-04-22 20:54:44 +00:00
|
|
|
},
|
2015-10-19 21:08:35 +00:00
|
|
|
expectedUpdateCalls: []fakecloud.FakeUpdateBalancerCall{
|
Change LoadBalancer methods to take api.Service
This is a better abstraction than passing in specific pieces of the
Service that each of the cloudproviders may or may not need. For
instance, many of the providers don't need a region, yet this is passed
in. Similarly many of the providers want a string IP for the load
balancer, but it passes in a converted net ip. Affinity is unused by
AWS. A provider change may also require adding a new parameter which has
an effect on all other cloud provider implementations.
Further, this will simplify adding provider specific load balancer
options, such as with labels or some other metadata. For example, we
could add labels for configuring the details of an AWS elastic load
balancer, such as idle timeout on connections, whether it is
internal or external, cross-zone load balancing, and so on.
Authors: @chbatey, @jsravn
2016-02-17 11:36:50 +00:00
|
|
|
{newService("s1", "888", api.ServiceTypeLoadBalancer), hosts},
|
|
|
|
{newService("s3", "999", api.ServiceTypeLoadBalancer), hosts},
|
2015-04-22 20:54:44 +00:00
|
|
|
},
|
|
|
|
},
|
2015-05-18 05:36:48 +00:00
|
|
|
{
|
|
|
|
// One service has an external load balancer and one is nil: one call.
|
|
|
|
services: []*api.Service{
|
2015-05-22 21:49:26 +00:00
|
|
|
newService("s0", "234", api.ServiceTypeLoadBalancer),
|
2015-05-18 05:36:48 +00:00
|
|
|
nil,
|
|
|
|
},
|
2015-10-19 21:08:35 +00:00
|
|
|
expectedUpdateCalls: []fakecloud.FakeUpdateBalancerCall{
|
Change LoadBalancer methods to take api.Service
This is a better abstraction than passing in specific pieces of the
Service that each of the cloudproviders may or may not need. For
instance, many of the providers don't need a region, yet this is passed
in. Similarly many of the providers want a string IP for the load
balancer, but it passes in a converted net ip. Affinity is unused by
AWS. A provider change may also require adding a new parameter which has
an effect on all other cloud provider implementations.
Further, this will simplify adding provider specific load balancer
options, such as with labels or some other metadata. For example, we
could add labels for configuring the details of an AWS elastic load
balancer, such as idle timeout on connections, whether it is
internal or external, cross-zone load balancing, and so on.
Authors: @chbatey, @jsravn
2016-02-17 11:36:50 +00:00
|
|
|
{newService("s0", "234", api.ServiceTypeLoadBalancer), hosts},
|
2015-05-18 05:36:48 +00:00
|
|
|
},
|
|
|
|
},
|
2015-04-22 20:54:44 +00:00
|
|
|
}
|
|
|
|
for _, item := range table {
|
2015-10-19 21:08:35 +00:00
|
|
|
cloud := &fakecloud.FakeCloud{}
|
2015-04-22 20:54:44 +00:00
|
|
|
|
|
|
|
cloud.Region = region
|
2016-01-29 06:34:08 +00:00
|
|
|
client := &fake.Clientset{}
|
2015-05-03 06:32:21 +00:00
|
|
|
controller := New(cloud, client, "test-cluster2")
|
2015-04-22 20:54:44 +00:00
|
|
|
controller.init()
|
|
|
|
cloud.Calls = nil // ignore any cloud calls made in init()
|
|
|
|
|
|
|
|
var services []*cachedService
|
|
|
|
for _, service := range item.services {
|
2015-06-16 22:59:03 +00:00
|
|
|
services = append(services, &cachedService{lastState: service, appliedState: service})
|
2015-04-22 20:54:44 +00:00
|
|
|
}
|
|
|
|
if err := controller.updateLoadBalancerHosts(services, hosts); err != nil {
|
|
|
|
t.Errorf("unexpected error: %v", err)
|
|
|
|
}
|
|
|
|
if !reflect.DeepEqual(item.expectedUpdateCalls, cloud.UpdateCalls) {
|
|
|
|
t.Errorf("expected update calls mismatch, expected %+v, got %+v", item.expectedUpdateCalls, cloud.UpdateCalls)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-28 17:07:24 +00:00
|
|
|
func TestHostsFromNodeList(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
nodes *api.NodeList
|
|
|
|
expectedHosts []string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
nodes: &api.NodeList{},
|
|
|
|
expectedHosts: []string{},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
nodes: &api.NodeList{
|
|
|
|
Items: []api.Node{
|
|
|
|
{
|
|
|
|
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
|
|
|
Status: api.NodeStatus{Phase: api.NodeRunning},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ObjectMeta: api.ObjectMeta{Name: "bar"},
|
|
|
|
Status: api.NodeStatus{Phase: api.NodeRunning},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedHosts: []string{"foo", "bar"},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
nodes: &api.NodeList{
|
|
|
|
Items: []api.Node{
|
|
|
|
{
|
|
|
|
ObjectMeta: api.ObjectMeta{Name: "foo"},
|
|
|
|
Status: api.NodeStatus{Phase: api.NodeRunning},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ObjectMeta: api.ObjectMeta{Name: "bar"},
|
|
|
|
Status: api.NodeStatus{Phase: api.NodeRunning},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
ObjectMeta: api.ObjectMeta{Name: "unschedulable"},
|
|
|
|
Spec: api.NodeSpec{Unschedulable: true},
|
|
|
|
Status: api.NodeStatus{Phase: api.NodeRunning},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectedHosts: []string{"foo", "bar"},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, test := range tests {
|
|
|
|
hosts := hostsFromNodeList(test.nodes)
|
|
|
|
if !reflect.DeepEqual(hosts, test.expectedHosts) {
|
|
|
|
t.Errorf("expected: %v, saw: %v", test.expectedHosts, hosts)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestGetNodeConditionPredicate(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
node api.Node
|
|
|
|
expectAccept bool
|
|
|
|
name string
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
node: api.Node{},
|
|
|
|
expectAccept: false,
|
|
|
|
name: "empty",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
node: api.Node{
|
|
|
|
Status: api.NodeStatus{
|
|
|
|
Conditions: []api.NodeCondition{
|
|
|
|
{Type: api.NodeReady, Status: api.ConditionTrue},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectAccept: true,
|
|
|
|
name: "basic",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
node: api.Node{
|
|
|
|
Spec: api.NodeSpec{Unschedulable: true},
|
|
|
|
Status: api.NodeStatus{
|
|
|
|
Conditions: []api.NodeCondition{
|
|
|
|
{Type: api.NodeReady, Status: api.ConditionTrue},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
expectAccept: false,
|
|
|
|
name: "unschedulable",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
pred := getNodeConditionPredicate()
|
|
|
|
for _, test := range tests {
|
|
|
|
accept := pred(test.node)
|
|
|
|
if accept != test.expectAccept {
|
|
|
|
t.Errorf("Test failed for %s, expected %v, saw %v", test.name, test.expectAccept, accept)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-03-24 17:32:43 +00:00
|
|
|
// TODO(a-robinson): Add tests for update/sync/delete.
|