2015-01-13 02:11:27 +00:00
/ *
2015-05-01 16:19:44 +00:00
Copyright 2014 The Kubernetes Authors All rights reserved .
2015-01-13 02:11:27 +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 .
* /
package e2e
import (
"fmt"
2015-03-21 00:35:43 +00:00
"io/ioutil"
2015-05-22 23:01:35 +00:00
"math/rand"
2015-03-21 00:35:43 +00:00
"net/http"
2015-02-18 19:30:18 +00:00
"sort"
2015-03-21 00:35:43 +00:00
"strings"
2015-01-13 02:11:27 +00:00
"time"
"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
2015-07-10 20:38:37 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/api/errors"
2015-01-13 02:11:27 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/client"
2015-05-20 15:59:34 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/fields"
"github.com/GoogleCloudPlatform/kubernetes/pkg/labels"
2015-05-22 23:01:35 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
2015-06-11 00:11:02 +00:00
"github.com/GoogleCloudPlatform/kubernetes/pkg/util/wait"
2015-01-28 00:38:48 +00:00
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
2015-01-13 02:11:27 +00:00
)
2015-05-22 23:01:35 +00:00
// This should match whatever the default/configured range is
2015-06-22 17:48:48 +00:00
var ServiceNodePortRange = util . PortRange { Base : 30000 , Size : 2768 }
2015-05-22 23:01:35 +00:00
2015-02-05 13:50:07 +00:00
var _ = Describe ( "Services" , func ( ) {
var c * client . Client
2015-03-27 23:52:18 +00:00
// Use these in tests. They're unique for each test to prevent name collisions.
2015-05-08 17:07:32 +00:00
var namespaces [ 2 ] string
2015-02-05 13:50:07 +00:00
BeforeEach ( func ( ) {
2015-02-09 15:10:02 +00:00
var err error
c , err = loadClient ( )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2015-05-08 17:07:32 +00:00
By ( "Building a namespace api objects" )
for i := range namespaces {
namespacePtr , err := createTestingNS ( fmt . Sprintf ( "service-%d" , i ) , c )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
namespaces [ i ] = namespacePtr . Name
}
} )
AfterEach ( func ( ) {
for _ , ns := range namespaces {
By ( fmt . Sprintf ( "Destroying namespace %v" , ns ) )
if err := c . Namespaces ( ) . Delete ( ns ) ; err != nil {
Failf ( "Couldn't delete namespace %s: %s" , ns , err )
}
}
2015-02-05 13:50:07 +00:00
} )
2015-05-14 01:50:28 +00:00
// TODO: We get coverage of TCP/UDP and multi-port services through the DNS test. We should have a simpler test for multi-port TCP here.
2015-05-13 23:13:19 +00:00
It ( "should provide secure master service" , func ( ) {
_ , err := c . Services ( api . NamespaceDefault ) . Get ( "kubernetes" )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2015-02-05 14:05:45 +00:00
} )
2015-02-09 20:37:56 +00:00
2015-06-16 01:40:11 +00:00
It ( "should serve a basic endpoint from pods" , func ( ) {
2015-02-18 19:30:18 +00:00
serviceName := "endpoint-test2"
2015-05-08 17:07:32 +00:00
ns := namespaces [ 0 ]
2015-02-09 20:37:56 +00:00
labels := map [ string ] string {
"foo" : "bar" ,
"baz" : "blah" ,
}
defer func ( ) {
err := c . Services ( ns ) . Delete ( serviceName )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
} ( )
service := & api . Service {
ObjectMeta : api . ObjectMeta {
Name : serviceName ,
} ,
Spec : api . ServiceSpec {
2015-03-13 15:16:41 +00:00
Selector : labels ,
Ports : [ ] api . ServicePort { {
Port : 80 ,
TargetPort : util . NewIntOrStringFromInt ( 80 ) ,
} } ,
2015-02-09 20:37:56 +00:00
} ,
}
_ , err := c . Services ( ns ) . Create ( service )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2015-05-08 17:07:32 +00:00
validateEndpointsOrFail ( c , ns , serviceName , map [ string ] [ ] int { } )
2015-02-09 20:37:56 +00:00
2015-02-23 22:57:08 +00:00
var names [ ] string
2015-02-09 20:37:56 +00:00
defer func ( ) {
for _ , name := range names {
2015-04-28 12:21:57 +00:00
err := c . Pods ( ns ) . Delete ( name , nil )
2015-02-09 20:37:56 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
}
} ( )
2015-02-23 22:57:08 +00:00
name1 := "test1"
2015-05-08 17:07:32 +00:00
addEndpointPodOrFail ( c , ns , name1 , labels , [ ] api . ContainerPort { { ContainerPort : 80 } } )
2015-02-23 22:57:08 +00:00
names = append ( names , name1 )
2015-05-08 17:07:32 +00:00
validateEndpointsOrFail ( c , ns , serviceName , map [ string ] [ ] int { name1 : { 80 } } )
2015-02-09 20:37:56 +00:00
name2 := "test2"
2015-05-08 17:07:32 +00:00
addEndpointPodOrFail ( c , ns , name2 , labels , [ ] api . ContainerPort { { ContainerPort : 80 } } )
2015-02-09 20:37:56 +00:00
names = append ( names , name2 )
2015-05-08 17:07:32 +00:00
validateEndpointsOrFail ( c , ns , serviceName , map [ string ] [ ] int { name1 : { 80 } , name2 : { 80 } } )
2015-02-09 20:37:56 +00:00
2015-04-28 12:21:57 +00:00
err = c . Pods ( ns ) . Delete ( name1 , nil )
2015-02-09 20:37:56 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
names = [ ] string { name2 }
2015-05-08 17:07:32 +00:00
validateEndpointsOrFail ( c , ns , serviceName , map [ string ] [ ] int { name2 : { 80 } } )
2015-02-09 20:37:56 +00:00
2015-04-28 12:21:57 +00:00
err = c . Pods ( ns ) . Delete ( name2 , nil )
2015-02-09 20:37:56 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
names = [ ] string { }
2015-05-08 17:07:32 +00:00
validateEndpointsOrFail ( c , ns , serviceName , map [ string ] [ ] int { } )
2015-06-16 01:40:11 +00:00
} )
2015-05-08 17:07:32 +00:00
2015-06-16 01:40:11 +00:00
It ( "should serve multiport endpoints from pods" , func ( ) {
2015-05-08 17:07:32 +00:00
// repacking functionality is intentionally not tested here - it's better to test it in an integration test.
serviceName := "multi-endpoint-test"
ns := namespaces [ 0 ]
defer func ( ) {
err := c . Services ( ns ) . Delete ( serviceName )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
} ( )
labels := map [ string ] string { "foo" : "bar" }
svc1port := "svc1"
svc2port := "svc2"
service := & api . Service {
ObjectMeta : api . ObjectMeta {
Name : serviceName ,
} ,
Spec : api . ServiceSpec {
Selector : labels ,
Ports : [ ] api . ServicePort {
{
Name : "portname1" ,
Port : 80 ,
TargetPort : util . NewIntOrStringFromString ( svc1port ) ,
} ,
{
Name : "portname2" ,
Port : 81 ,
TargetPort : util . NewIntOrStringFromString ( svc2port ) ,
} ,
} ,
} ,
}
_ , err := c . Services ( ns ) . Create ( service )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
port1 := 100
port2 := 101
validateEndpointsOrFail ( c , ns , serviceName , map [ string ] [ ] int { } )
var names [ ] string
defer func ( ) {
for _ , name := range names {
err := c . Pods ( ns ) . Delete ( name , nil )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
}
} ( )
containerPorts1 := [ ] api . ContainerPort {
{
Name : svc1port ,
ContainerPort : port1 ,
} ,
}
containerPorts2 := [ ] api . ContainerPort {
{
Name : svc2port ,
ContainerPort : port2 ,
} ,
}
podname1 := "podname1"
addEndpointPodOrFail ( c , ns , podname1 , labels , containerPorts1 )
names = append ( names , podname1 )
validateEndpointsOrFail ( c , ns , serviceName , map [ string ] [ ] int { podname1 : { port1 } } )
podname2 := "podname2"
addEndpointPodOrFail ( c , ns , podname2 , labels , containerPorts2 )
names = append ( names , podname2 )
validateEndpointsOrFail ( c , ns , serviceName , map [ string ] [ ] int { podname1 : { port1 } , podname2 : { port2 } } )
podname3 := "podname3"
addEndpointPodOrFail ( c , ns , podname3 , labels , append ( containerPorts1 , containerPorts2 ... ) )
names = append ( names , podname3 )
validateEndpointsOrFail ( c , ns , serviceName , map [ string ] [ ] int { podname1 : { port1 } , podname2 : { port2 } , podname3 : { port1 , port2 } } )
err = c . Pods ( ns ) . Delete ( podname1 , nil )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
names = [ ] string { podname2 , podname3 }
validateEndpointsOrFail ( c , ns , serviceName , map [ string ] [ ] int { podname2 : { port2 } , podname3 : { port1 , port2 } } )
err = c . Pods ( ns ) . Delete ( podname2 , nil )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
names = [ ] string { podname3 }
validateEndpointsOrFail ( c , ns , serviceName , map [ string ] [ ] int { podname3 : { port1 , port2 } } )
err = c . Pods ( ns ) . Delete ( podname3 , nil )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
names = [ ] string { }
validateEndpointsOrFail ( c , ns , serviceName , map [ string ] [ ] int { } )
2015-06-16 01:40:11 +00:00
} )
2015-03-20 21:04:41 +00:00
2015-03-21 00:35:43 +00:00
It ( "should be able to create a functioning external load balancer" , func ( ) {
2015-06-22 21:14:54 +00:00
// requires ExternalLoadBalancer
SkipUnlessProviderIs ( "gce" , "gke" , "aws" )
2015-04-22 21:02:59 +00:00
2015-03-21 00:35:43 +00:00
serviceName := "external-lb-test"
2015-05-08 17:07:32 +00:00
ns := namespaces [ 0 ]
2015-05-22 23:01:35 +00:00
t := NewWebserverTest ( c , ns , serviceName )
defer func ( ) {
defer GinkgoRecover ( )
errs := t . Cleanup ( )
if len ( errs ) != 0 {
Failf ( "errors in cleanup: %v" , errs )
}
} ( )
2015-07-09 05:02:10 +00:00
inboundPort := 3000
2015-05-22 23:01:35 +00:00
service := t . BuildServiceSpec ( )
service . Spec . Type = api . ServiceTypeLoadBalancer
2015-07-09 05:02:10 +00:00
service . Spec . Ports [ 0 ] . Port = inboundPort
service . Spec . Ports [ 0 ] . TargetPort = util . NewIntOrStringFromInt ( 80 )
2015-03-21 00:35:43 +00:00
By ( "creating service " + serviceName + " with external load balancer in namespace " + ns )
2015-05-22 23:01:35 +00:00
result , err := t . CreateService ( service )
2015-03-21 00:35:43 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2015-03-24 17:32:43 +00:00
// Wait for the load balancer to be created asynchronously, which is
2015-05-22 23:01:35 +00:00
// currently indicated by ingress point(s) being added to the status.
result , err = waitForLoadBalancerIngress ( c , serviceName , ns )
2015-04-01 02:55:23 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2015-05-22 21:33:29 +00:00
if len ( result . Status . LoadBalancer . Ingress ) != 1 {
Failf ( "got unexpected number (%v) of ingress points for externally load balanced service: %v" , result . Status . LoadBalancer . Ingress , result )
}
ingress := result . Status . LoadBalancer . Ingress [ 0 ]
2015-05-22 23:01:35 +00:00
if len ( result . Spec . Ports ) != 1 {
Failf ( "got unexpected len(Spec.Ports) for LoadBalancer service: %v" , result )
2015-03-21 00:35:43 +00:00
}
2015-05-22 23:01:35 +00:00
port := result . Spec . Ports [ 0 ]
if port . NodePort == 0 {
Failf ( "got unexpected Spec.Ports[0].nodePort for LoadBalancer service: %v" , result )
}
if ! ServiceNodePortRange . Contains ( port . NodePort ) {
Failf ( "got unexpected (out-of-range) port for LoadBalancer service: %v" , result )
2015-03-21 00:35:43 +00:00
}
By ( "creating pod to be part of service " + serviceName )
2015-06-17 07:13:26 +00:00
t . CreateWebserverRC ( 1 )
2015-03-21 00:35:43 +00:00
2015-05-22 23:01:35 +00:00
By ( "hitting the pod through the service's NodePort" )
testReachable ( pickMinionIP ( c ) , port . NodePort )
2015-03-21 00:35:43 +00:00
2015-05-22 23:01:35 +00:00
By ( "hitting the pod through the service's external load balancer" )
2015-07-09 05:02:10 +00:00
testLoadBalancerReachable ( ingress , inboundPort )
2015-03-21 00:35:43 +00:00
} )
2015-05-20 15:59:34 +00:00
It ( "should be able to create a functioning NodePort service" , func ( ) {
serviceName := "nodeportservice-test"
ns := namespaces [ 0 ]
2015-05-22 23:01:35 +00:00
t := NewWebserverTest ( c , ns , serviceName )
defer func ( ) {
defer GinkgoRecover ( )
errs := t . Cleanup ( )
if len ( errs ) != 0 {
Failf ( "errors in cleanup: %v" , errs )
}
} ( )
service := t . BuildServiceSpec ( )
service . Spec . Type = api . ServiceTypeNodePort
By ( "creating service " + serviceName + " with type=NodePort in namespace " + ns )
2015-05-20 15:59:34 +00:00
result , err := c . Services ( ns ) . Create ( service )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
defer func ( ns , serviceName string ) { // clean up when we're done
By ( "deleting service " + serviceName + " in namespace " + ns )
err := c . Services ( ns ) . Delete ( serviceName )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
} ( ns , serviceName )
if len ( result . Spec . Ports ) != 1 {
Failf ( "got unexpected number (%d) of Ports for NodePort service: %v" , len ( result . Spec . Ports ) , result )
}
nodePort := result . Spec . Ports [ 0 ] . NodePort
if nodePort == 0 {
Failf ( "got unexpected nodePort (%d) on Ports[0] for NodePort service: %v" , nodePort , result )
}
2015-05-22 23:01:35 +00:00
if ! ServiceNodePortRange . Contains ( nodePort ) {
Failf ( "got unexpected (out-of-range) port for NodePort service: %v" , result )
}
By ( "creating pod to be part of service " + serviceName )
2015-06-17 07:13:26 +00:00
t . CreateWebserverRC ( 1 )
2015-05-22 23:01:35 +00:00
By ( "hitting the pod through the service's NodePort" )
ip := pickMinionIP ( c )
testReachable ( ip , nodePort )
} )
It ( "should be able to change the type and nodeport settings of a service" , func ( ) {
2015-07-09 16:47:12 +00:00
// requires ExternalLoadBalancer
SkipUnlessProviderIs ( "gce" , "gke" , "aws" )
2015-05-22 23:01:35 +00:00
serviceName := "mutability-service-test"
ns := namespaces [ 0 ]
2015-05-20 15:59:34 +00:00
2015-05-22 23:01:35 +00:00
t := NewWebserverTest ( c , ns , serviceName )
defer func ( ) {
defer GinkgoRecover ( )
errs := t . Cleanup ( )
if len ( errs ) != 0 {
Failf ( "errors in cleanup: %v" , errs )
}
} ( )
service := t . BuildServiceSpec ( )
By ( "creating service " + serviceName + " with type unspecified in namespace " + t . Namespace )
service , err := t . CreateService ( service )
2015-05-20 15:59:34 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2015-05-22 23:01:35 +00:00
if service . Spec . Type != api . ServiceTypeClusterIP {
Failf ( "got unexpected Spec.Type for default service: %v" , service )
}
if len ( service . Spec . Ports ) != 1 {
Failf ( "got unexpected len(Spec.Ports) for default service: %v" , service )
}
port := service . Spec . Ports [ 0 ]
if port . NodePort != 0 {
Failf ( "got unexpected Spec.Ports[0].nodePort for default service: %v" , service )
}
if len ( service . Status . LoadBalancer . Ingress ) != 0 {
Failf ( "got unexpected len(Status.LoadBalancer.Ingresss) for default service: %v" , service )
2015-05-20 15:59:34 +00:00
}
2015-05-22 23:01:35 +00:00
By ( "creating pod to be part of service " + t . ServiceName )
2015-06-17 07:13:26 +00:00
t . CreateWebserverRC ( 1 )
2015-05-22 23:01:35 +00:00
By ( "changing service " + serviceName + " to type=NodePort" )
2015-07-10 20:38:37 +00:00
service , err = updateService ( c , ns , serviceName , func ( s * api . Service ) {
s . Spec . Type = api . ServiceTypeNodePort
} )
2015-05-22 23:01:35 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
if service . Spec . Type != api . ServiceTypeNodePort {
Failf ( "got unexpected Spec.Type for NodePort service: %v" , service )
}
if len ( service . Spec . Ports ) != 1 {
Failf ( "got unexpected len(Spec.Ports) for NodePort service: %v" , service )
}
port = service . Spec . Ports [ 0 ]
if port . NodePort == 0 {
Failf ( "got unexpected Spec.Ports[0].nodePort for NodePort service: %v" , service )
}
if ! ServiceNodePortRange . Contains ( port . NodePort ) {
Failf ( "got unexpected (out-of-range) port for NodePort service: %v" , service )
2015-05-20 15:59:34 +00:00
}
2015-05-22 23:01:35 +00:00
if len ( service . Status . LoadBalancer . Ingress ) != 0 {
Failf ( "got unexpected len(Status.LoadBalancer.Ingresss) for NodePort service: %v" , service )
}
By ( "hitting the pod through the service's NodePort" )
ip := pickMinionIP ( c )
nodePort1 := port . NodePort // Save for later!
testReachable ( ip , nodePort1 )
By ( "changing service " + serviceName + " to type=LoadBalancer" )
service . Spec . Type = api . ServiceTypeLoadBalancer
2015-07-10 20:38:37 +00:00
service , err = updateService ( c , ns , serviceName , func ( s * api . Service ) {
s . Spec . Type = api . ServiceTypeLoadBalancer
} )
2015-05-22 23:01:35 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
// Wait for the load balancer to be created asynchronously
service , err = waitForLoadBalancerIngress ( c , serviceName , ns )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
if service . Spec . Type != api . ServiceTypeLoadBalancer {
Failf ( "got unexpected Spec.Type for LoadBalancer service: %v" , service )
}
if len ( service . Spec . Ports ) != 1 {
Failf ( "got unexpected len(Spec.Ports) for LoadBalancer service: %v" , service )
}
port = service . Spec . Ports [ 0 ]
if port . NodePort != nodePort1 {
Failf ( "got unexpected Spec.Ports[0].nodePort for LoadBalancer service: %v" , service )
}
if len ( service . Status . LoadBalancer . Ingress ) != 1 {
Failf ( "got unexpected len(Status.LoadBalancer.Ingresss) for LoadBalancer service: %v" , service )
}
ingress1 := service . Status . LoadBalancer . Ingress [ 0 ]
if ingress1 . IP == "" && ingress1 . Hostname == "" {
Failf ( "got unexpected Status.LoadBalancer.Ingresss[0] for LoadBalancer service: %v" , service )
}
By ( "hitting the pod through the service's NodePort" )
ip = pickMinionIP ( c )
testReachable ( ip , nodePort1 )
By ( "hitting the pod through the service's LoadBalancer" )
testLoadBalancerReachable ( ingress1 , 80 )
By ( "changing service " + serviceName + " update NodePort" )
nodePort2 := nodePort1 - 1
if ! ServiceNodePortRange . Contains ( nodePort2 ) {
//Check for (unlikely) assignment at bottom of range
nodePort2 = nodePort1 + 1
}
2015-07-10 20:38:37 +00:00
service , err = updateService ( c , ns , serviceName , func ( s * api . Service ) {
s . Spec . Ports [ 0 ] . NodePort = nodePort2
} )
2015-05-22 23:01:35 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
if service . Spec . Type != api . ServiceTypeLoadBalancer {
Failf ( "got unexpected Spec.Type for updated-NodePort service: %v" , service )
}
if len ( service . Spec . Ports ) != 1 {
Failf ( "got unexpected len(Spec.Ports) for updated-NodePort service: %v" , service )
}
port = service . Spec . Ports [ 0 ]
if port . NodePort != nodePort2 {
Failf ( "got unexpected Spec.Ports[0].nodePort for NodePort service: %v" , service )
}
if len ( service . Status . LoadBalancer . Ingress ) != 1 {
Failf ( "got unexpected len(Status.LoadBalancer.Ingresss) for NodePort service: %v" , service )
}
ingress2 := service . Status . LoadBalancer . Ingress [ 0 ]
2015-07-09 17:31:30 +00:00
// TODO: Fix the issue here: https://github.com/GoogleCloudPlatform/kubernetes/issues/11002
if providerIs ( "aws" ) {
// TODO: Make this less of a hack (or fix the underlying bug)
time . Sleep ( time . Second * 120 )
service , err = waitForLoadBalancerIngress ( c , serviceName , ns )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
// We don't want the ingress point to change, but we should verify that the new ingress point still works
ingress2 = service . Status . LoadBalancer . Ingress [ 0 ]
Expect ( ingress1 ) . NotTo ( Equal ( ingress2 ) )
} else {
Expect ( ingress1 ) . To ( Equal ( ingress2 ) )
}
2015-05-22 23:01:35 +00:00
By ( "hitting the pod through the service's updated NodePort" )
testReachable ( ip , nodePort2 )
By ( "hitting the pod through the service's LoadBalancer" )
testLoadBalancerReachable ( ingress2 , 80 )
By ( "checking the old NodePort is closed" )
testNotReachable ( ip , nodePort1 )
By ( "changing service " + serviceName + " back to type=ClusterIP" )
2015-07-10 20:38:37 +00:00
service , err = updateService ( c , ns , serviceName , func ( s * api . Service ) {
s . Spec . Type = api . ServiceTypeClusterIP
s . Spec . Ports [ 0 ] . NodePort = 0
} )
2015-05-22 23:01:35 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
if service . Spec . Type != api . ServiceTypeClusterIP {
Failf ( "got unexpected Spec.Type for back-to-ClusterIP service: %v" , service )
}
if len ( service . Spec . Ports ) != 1 {
Failf ( "got unexpected len(Spec.Ports) for back-to-ClusterIP service: %v" , service )
}
port = service . Spec . Ports [ 0 ]
if port . NodePort != 0 {
Failf ( "got unexpected Spec.Ports[0].nodePort for back-to-ClusterIP service: %v" , service )
}
// Wait for the load balancer to be destroyed asynchronously
service , err = waitForLoadBalancerDestroy ( c , serviceName , ns )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
if len ( service . Status . LoadBalancer . Ingress ) != 0 {
Failf ( "got unexpected len(Status.LoadBalancer.Ingresss) for back-to-ClusterIP service: %v" , service )
}
By ( "checking the NodePort (original) is closed" )
ip = pickMinionIP ( c )
testNotReachable ( ip , nodePort1 )
By ( "checking the NodePort (updated) is closed" )
ip = pickMinionIP ( c )
testNotReachable ( ip , nodePort2 )
By ( "checking the LoadBalancer is closed" )
testLoadBalancerNotReachable ( ingress2 , 80 )
} )
It ( "should release the load balancer when Type goes from LoadBalancer -> NodePort" , func ( ) {
2015-07-09 16:47:12 +00:00
// requires ExternalLoadBalancer
SkipUnlessProviderIs ( "gce" , "gke" , "aws" )
2015-05-22 23:01:35 +00:00
serviceName := "service-release-lb"
ns := namespaces [ 0 ]
t := NewWebserverTest ( c , ns , serviceName )
defer func ( ) {
defer GinkgoRecover ( )
errs := t . Cleanup ( )
if len ( errs ) != 0 {
Failf ( "errors in cleanup: %v" , errs )
}
} ( )
service := t . BuildServiceSpec ( )
service . Spec . Type = api . ServiceTypeLoadBalancer
By ( "creating service " + serviceName + " with type LoadBalancer" )
service , err := t . CreateService ( service )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
By ( "creating pod to be part of service " + t . ServiceName )
2015-06-17 07:13:26 +00:00
t . CreateWebserverRC ( 1 )
2015-05-22 23:01:35 +00:00
if service . Spec . Type != api . ServiceTypeLoadBalancer {
Failf ( "got unexpected Spec.Type for LoadBalancer service: %v" , service )
}
if len ( service . Spec . Ports ) != 1 {
Failf ( "got unexpected len(Spec.Ports) for LoadBalancer service: %v" , service )
}
nodePort := service . Spec . Ports [ 0 ] . NodePort
if nodePort == 0 {
Failf ( "got unexpected Spec.Ports[0].NodePort for LoadBalancer service: %v" , service )
}
// Wait for the load balancer to be created asynchronously
service , err = waitForLoadBalancerIngress ( c , serviceName , ns )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
if len ( service . Status . LoadBalancer . Ingress ) != 1 {
Failf ( "got unexpected len(Status.LoadBalancer.Ingresss) for LoadBalancer service: %v" , service )
}
ingress := service . Status . LoadBalancer . Ingress [ 0 ]
if ingress . IP == "" && ingress . Hostname == "" {
Failf ( "got unexpected Status.LoadBalancer.Ingresss[0] for LoadBalancer service: %v" , service )
}
By ( "hitting the pod through the service's NodePort" )
ip := pickMinionIP ( c )
testReachable ( ip , nodePort )
By ( "hitting the pod through the service's LoadBalancer" )
testLoadBalancerReachable ( ingress , 80 )
By ( "changing service " + serviceName + " to type=NodePort" )
2015-07-10 20:38:37 +00:00
service , err = updateService ( c , ns , serviceName , func ( s * api . Service ) {
s . Spec . Type = api . ServiceTypeNodePort
} )
2015-05-22 23:01:35 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
if service . Spec . Type != api . ServiceTypeNodePort {
Failf ( "got unexpected Spec.Type for NodePort service: %v" , service )
}
if len ( service . Spec . Ports ) != 1 {
Failf ( "got unexpected len(Spec.Ports) for NodePort service: %v" , service )
}
if service . Spec . Ports [ 0 ] . NodePort != nodePort {
Failf ( "got unexpected Spec.Ports[0].NodePort for NodePort service: %v" , service )
}
// Wait for the load balancer to be created asynchronously
service , err = waitForLoadBalancerDestroy ( c , serviceName , ns )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
if len ( service . Status . LoadBalancer . Ingress ) != 0 {
Failf ( "got unexpected len(Status.LoadBalancer.Ingresss) for NodePort service: %v" , service )
}
By ( "hitting the pod through the service's NodePort" )
testReachable ( ip , nodePort )
By ( "checking the LoadBalancer is closed" )
testLoadBalancerNotReachable ( ingress , 80 )
} )
It ( "should prevent NodePort collisions" , func ( ) {
serviceName := "nodeport-collision"
serviceName2 := serviceName + "2"
ns := namespaces [ 0 ]
t := NewWebserverTest ( c , ns , serviceName )
defer func ( ) {
defer GinkgoRecover ( )
errs := t . Cleanup ( )
if len ( errs ) != 0 {
Failf ( "errors in cleanup: %v" , errs )
}
} ( )
service := t . BuildServiceSpec ( )
service . Spec . Type = api . ServiceTypeNodePort
By ( "creating service " + serviceName + " with type NodePort in namespace " + ns )
result , err := t . CreateService ( service )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
if result . Spec . Type != api . ServiceTypeNodePort {
Failf ( "got unexpected Spec.Type for new service: %v" , result )
}
if len ( result . Spec . Ports ) != 1 {
Failf ( "got unexpected len(Spec.Ports) for new service: %v" , result )
}
port := result . Spec . Ports [ 0 ]
if port . NodePort == 0 {
Failf ( "got unexpected Spec.Ports[0].nodePort for new service: %v" , result )
}
By ( "creating service " + serviceName + " with conflicting NodePort" )
service2 := t . BuildServiceSpec ( )
service2 . Name = serviceName2
service2 . Spec . Type = api . ServiceTypeNodePort
service2 . Spec . Ports [ 0 ] . NodePort = port . NodePort
By ( "creating service " + serviceName2 + " with conflicting NodePort" )
result2 , err := t . CreateService ( service2 )
if err == nil {
Failf ( "Created service with conflicting NodePort: %v" , result2 )
}
expectedErr := fmt . Sprintf ( "Service \"%s\" is invalid: spec.ports[0].nodePort: invalid value '%d': provided port is already allocated" , serviceName2 , port . NodePort )
Expect ( fmt . Sprintf ( "%v" , err ) ) . To ( Equal ( expectedErr ) )
By ( "deleting original service " + serviceName + " with type NodePort in namespace " + ns )
err = t . DeleteService ( serviceName )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
By ( "creating service " + serviceName2 + " with no-longer-conflicting NodePort" )
_ , err = t . CreateService ( service2 )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
} )
It ( "should check NodePort out-of-range" , func ( ) {
serviceName := "nodeport-range-test"
ns := namespaces [ 0 ]
t := NewWebserverTest ( c , ns , serviceName )
2015-05-20 15:59:34 +00:00
defer func ( ) {
defer GinkgoRecover ( )
2015-05-22 23:01:35 +00:00
errs := t . Cleanup ( )
if len ( errs ) != 0 {
Failf ( "errors in cleanup: %v" , errs )
}
2015-05-20 15:59:34 +00:00
} ( )
2015-05-22 23:01:35 +00:00
service := t . BuildServiceSpec ( )
service . Spec . Type = api . ServiceTypeNodePort
By ( "creating service " + serviceName + " with type NodePort in namespace " + ns )
service , err := t . CreateService ( service )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
if service . Spec . Type != api . ServiceTypeNodePort {
Failf ( "got unexpected Spec.Type for new service: %v" , service )
}
if len ( service . Spec . Ports ) != 1 {
Failf ( "got unexpected len(Spec.Ports) for new service: %v" , service )
}
port := service . Spec . Ports [ 0 ]
if port . NodePort == 0 {
Failf ( "got unexpected Spec.Ports[0].nodePort for new service: %v" , service )
}
if ! ServiceNodePortRange . Contains ( port . NodePort ) {
Failf ( "got unexpected (out-of-range) port for new service: %v" , service )
2015-05-20 15:59:34 +00:00
}
2015-05-22 23:01:35 +00:00
outOfRangeNodePort := 0
for {
outOfRangeNodePort = 1 + rand . Intn ( 65535 )
if ! ServiceNodePortRange . Contains ( outOfRangeNodePort ) {
2015-05-20 15:59:34 +00:00
break
}
}
2015-05-22 23:01:35 +00:00
By ( fmt . Sprintf ( "changing service " + serviceName + " to out-of-range NodePort %d" , outOfRangeNodePort ) )
2015-07-10 20:38:37 +00:00
result , err := updateService ( c , ns , serviceName , func ( s * api . Service ) {
s . Spec . Ports [ 0 ] . NodePort = outOfRangeNodePort
} )
2015-05-22 23:01:35 +00:00
if err == nil {
Failf ( "failed to prevent update of service with out-of-range NodePort: %v" , result )
}
expectedErr := fmt . Sprintf ( "Service \"%s\" is invalid: spec.ports[0].nodePort: invalid value '%d': provided port is not in the valid range" , serviceName , outOfRangeNodePort )
Expect ( fmt . Sprintf ( "%v" , err ) ) . To ( Equal ( expectedErr ) )
By ( "deleting original service " + serviceName )
err = t . DeleteService ( serviceName )
2015-05-20 15:59:34 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2015-05-22 23:01:35 +00:00
By ( fmt . Sprintf ( "creating service " + serviceName + " with out-of-range NodePort %d" , outOfRangeNodePort ) )
service = t . BuildServiceSpec ( )
service . Spec . Type = api . ServiceTypeNodePort
service . Spec . Ports [ 0 ] . NodePort = outOfRangeNodePort
service , err = t . CreateService ( service )
if err == nil {
Failf ( "failed to prevent create of service with out-of-range NodePort (%d): %v" , outOfRangeNodePort , service )
}
Expect ( fmt . Sprintf ( "%v" , err ) ) . To ( Equal ( expectedErr ) )
} )
It ( "should release NodePorts on delete" , func ( ) {
serviceName := "nodeport-reuse"
ns := namespaces [ 0 ]
t := NewWebserverTest ( c , ns , serviceName )
defer func ( ) {
defer GinkgoRecover ( )
errs := t . Cleanup ( )
if len ( errs ) != 0 {
Failf ( "errors in cleanup: %v" , errs )
}
} ( )
service := t . BuildServiceSpec ( )
service . Spec . Type = api . ServiceTypeNodePort
By ( "creating service " + serviceName + " with type NodePort in namespace " + ns )
service , err := t . CreateService ( service )
2015-05-20 15:59:34 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2015-05-22 23:01:35 +00:00
if service . Spec . Type != api . ServiceTypeNodePort {
Failf ( "got unexpected Spec.Type for new service: %v" , service )
}
if len ( service . Spec . Ports ) != 1 {
Failf ( "got unexpected len(Spec.Ports) for new service: %v" , service )
2015-05-20 15:59:34 +00:00
}
2015-05-22 23:01:35 +00:00
port := service . Spec . Ports [ 0 ]
if port . NodePort == 0 {
Failf ( "got unexpected Spec.Ports[0].nodePort for new service: %v" , service )
2015-05-20 15:59:34 +00:00
}
2015-05-22 23:01:35 +00:00
if ! ServiceNodePortRange . Contains ( port . NodePort ) {
Failf ( "got unexpected (out-of-range) port for new service: %v" , service )
}
port1 := port . NodePort
By ( "deleting original service " + serviceName )
err = t . DeleteService ( serviceName )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
By ( fmt . Sprintf ( "creating service " + serviceName + " with same NodePort %d" , port1 ) )
service = t . BuildServiceSpec ( )
service . Spec . Type = api . ServiceTypeNodePort
service . Spec . Ports [ 0 ] . NodePort = port1
service , err = t . CreateService ( service )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2015-05-20 15:59:34 +00:00
} )
2015-03-20 21:04:41 +00:00
It ( "should correctly serve identically named services in different namespaces on different external IP addresses" , func ( ) {
2015-06-22 21:14:54 +00:00
// requires ExternalLoadBalancer
SkipUnlessProviderIs ( "gce" , "gke" , "aws" )
2015-04-22 21:02:59 +00:00
2015-05-08 17:07:32 +00:00
serviceNames := [ ] string { "s0" } // Could add more here, but then it takes longer.
2015-02-18 19:30:18 +00:00
labels := map [ string ] string {
"key0" : "value0" ,
"key1" : "value1" ,
}
service := & api . Service {
ObjectMeta : api . ObjectMeta { } ,
Spec : api . ServiceSpec {
2015-03-13 15:16:41 +00:00
Selector : labels ,
Ports : [ ] api . ServicePort { {
Port : 80 ,
TargetPort : util . NewIntOrStringFromInt ( 80 ) ,
} } ,
2015-05-22 21:49:26 +00:00
Type : api . ServiceTypeLoadBalancer ,
2015-02-18 19:30:18 +00:00
} ,
}
2015-03-20 21:04:41 +00:00
2015-05-22 21:33:29 +00:00
ingressPoints := [ ] string { }
2015-02-18 19:30:18 +00:00
for _ , namespace := range namespaces {
for _ , serviceName := range serviceNames {
service . ObjectMeta . Name = serviceName
service . ObjectMeta . Namespace = namespace
By ( "creating service " + serviceName + " in namespace " + namespace )
2015-04-01 02:55:23 +00:00
_ , err := c . Services ( namespace ) . Create ( service )
2015-02-18 19:30:18 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
defer func ( namespace , serviceName string ) { // clean up when we're done
By ( "deleting service " + serviceName + " in namespace " + namespace )
err := c . Services ( namespace ) . Delete ( serviceName )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
} ( namespace , serviceName )
2015-04-01 02:55:23 +00:00
}
}
for _ , namespace := range namespaces {
for _ , serviceName := range serviceNames {
2015-05-22 23:01:35 +00:00
result , err := waitForLoadBalancerIngress ( c , serviceName , namespace )
2015-04-01 02:55:23 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2015-05-22 21:33:29 +00:00
for i := range result . Status . LoadBalancer . Ingress {
ingress := result . Status . LoadBalancer . Ingress [ i ] . IP
if ingress == "" {
ingress = result . Status . LoadBalancer . Ingress [ i ] . Hostname
}
ingressPoints = append ( ingressPoints , ingress ) // Save 'em to check uniqueness
}
2015-02-18 19:30:18 +00:00
}
}
2015-05-22 21:33:29 +00:00
validateUniqueOrFail ( ingressPoints )
2015-03-20 21:04:41 +00:00
} )
2015-01-28 00:38:48 +00:00
} )
2015-02-09 20:37:56 +00:00
2015-07-10 20:38:37 +00:00
// updateService fetches a service, calls the update function on it,
// and then attempts to send the updated service. It retries up to 2
// times in the face of timeouts and conflicts.
func updateService ( c * client . Client , namespace , serviceName string , update func ( * api . Service ) ) ( * api . Service , error ) {
var service * api . Service
var err error
for i := 0 ; i < 3 ; i ++ {
service , err = c . Services ( namespace ) . Get ( serviceName )
if err != nil {
return service , err
}
update ( service )
service , err = c . Services ( namespace ) . Update ( service )
if ! errors . IsConflict ( err ) && ! errors . IsServerTimeout ( err ) {
return service , err
}
}
return service , err
}
2015-05-22 23:01:35 +00:00
func waitForLoadBalancerIngress ( c * client . Client , serviceName , namespace string ) ( * api . Service , error ) {
2015-06-12 18:26:27 +00:00
// TODO: once support ticket 21807001 is resolved, reduce this timeout back to something reasonable
const timeout = 20 * time . Minute
2015-04-01 02:55:23 +00:00
var service * api . Service
2015-05-22 23:01:35 +00:00
By ( fmt . Sprintf ( "waiting up to %v for service %s in namespace %s to have a LoadBalancer ingress point" , timeout , serviceName , namespace ) )
2015-05-19 18:17:32 +00:00
for start := time . Now ( ) ; time . Since ( start ) < timeout ; time . Sleep ( 5 * time . Second ) {
2015-04-01 02:55:23 +00:00
service , err := c . Services ( namespace ) . Get ( serviceName )
if err != nil {
Logf ( "Get service failed, ignoring for 5s: %v" , err )
2015-05-19 18:17:32 +00:00
continue
2015-04-01 02:55:23 +00:00
}
2015-05-22 21:33:29 +00:00
if len ( service . Status . LoadBalancer . Ingress ) > 0 {
2015-05-19 18:17:32 +00:00
return service , nil
2015-04-01 02:55:23 +00:00
}
2015-05-22 23:01:35 +00:00
Logf ( "Waiting for service %s in namespace %s to have a LoadBalancer ingress point (%v)" , serviceName , namespace , time . Since ( start ) )
}
return service , fmt . Errorf ( "service %s in namespace %s doesn't have a LoadBalancer ingress point after %.2f seconds" , serviceName , namespace , timeout . Seconds ( ) )
}
func waitForLoadBalancerDestroy ( c * client . Client , serviceName , namespace string ) ( * api . Service , error ) {
2015-06-12 18:26:27 +00:00
// TODO: once support ticket 21807001 is resolved, reduce this timeout back to something reasonable
const timeout = 10 * time . Minute
2015-05-22 23:01:35 +00:00
var service * api . Service
By ( fmt . Sprintf ( "waiting up to %v for service %s in namespace %s to have no LoadBalancer ingress points" , timeout , serviceName , namespace ) )
for start := time . Now ( ) ; time . Since ( start ) < timeout ; time . Sleep ( 5 * time . Second ) {
service , err := c . Services ( namespace ) . Get ( serviceName )
if err != nil {
Logf ( "Get service failed, ignoring for 5s: %v" , err )
continue
}
if len ( service . Status . LoadBalancer . Ingress ) == 0 {
return service , nil
}
Logf ( "Waiting for service %s in namespace %s to have no LoadBalancer ingress points (%v)" , serviceName , namespace , time . Since ( start ) )
2015-04-01 02:55:23 +00:00
}
2015-05-22 23:01:35 +00:00
return service , fmt . Errorf ( "service %s in namespace %s still has LoadBalancer ingress points after %.2f seconds" , serviceName , namespace , timeout . Seconds ( ) )
2015-04-01 02:55:23 +00:00
}
2015-02-18 19:30:18 +00:00
func validateUniqueOrFail ( s [ ] string ) {
By ( fmt . Sprintf ( "validating unique: %v" , s ) )
sort . Strings ( s )
var prev string
for i , elem := range s {
if i > 0 && elem == prev {
Fail ( "duplicate found: " + elem )
}
prev = elem
}
}
2015-05-08 17:07:32 +00:00
func getPortsByIp ( subsets [ ] api . EndpointSubset ) map [ string ] [ ] int {
m := make ( map [ string ] [ ] int )
2015-03-20 21:24:43 +00:00
for _ , ss := range subsets {
for _ , port := range ss . Ports {
2015-05-08 17:07:32 +00:00
for _ , addr := range ss . Addresses {
Logf ( "Found IP %v and port %v" , addr . IP , port . Port )
if _ , ok := m [ addr . IP ] ; ! ok {
m [ addr . IP ] = make ( [ ] int , 0 )
2015-03-20 21:24:43 +00:00
}
2015-05-08 17:07:32 +00:00
m [ addr . IP ] = append ( m [ addr . IP ] , port . Port )
2015-03-20 21:24:43 +00:00
}
2015-02-09 20:37:56 +00:00
}
}
2015-05-08 17:07:32 +00:00
return m
2015-03-20 21:24:43 +00:00
}
2015-02-09 20:37:56 +00:00
2015-05-08 17:07:32 +00:00
func translatePodNameToIpOrFail ( c * client . Client , ns string , expectedEndpoints map [ string ] [ ] int ) map [ string ] [ ] int {
portsByIp := make ( map [ string ] [ ] int )
for name , portList := range expectedEndpoints {
2015-02-09 20:37:56 +00:00
pod , err := c . Pods ( ns ) . Get ( name )
if err != nil {
2015-02-23 22:57:08 +00:00
Failf ( "failed to get pod %s, that's pretty weird. validation failed: %s" , name , err )
2015-02-09 20:37:56 +00:00
}
2015-05-08 17:07:32 +00:00
portsByIp [ pod . Status . PodIP ] = portList
2015-02-18 19:30:18 +00:00
By ( fmt . Sprintf ( "" ) )
2015-02-09 20:37:56 +00:00
}
2015-05-08 17:07:32 +00:00
By ( fmt . Sprintf ( "successfully translated pod names to ips: %v -> %v on namespace %s" , expectedEndpoints , portsByIp , ns ) )
return portsByIp
}
func validatePortsOrFail ( endpoints map [ string ] [ ] int , expectedEndpoints map [ string ] [ ] int ) {
if len ( endpoints ) != len ( expectedEndpoints ) {
// should not happen because we check this condition before
Failf ( "invalid number of endpoints got %v, expected %v" , endpoints , expectedEndpoints )
}
for ip := range expectedEndpoints {
if _ , ok := endpoints [ ip ] ; ! ok {
Failf ( "endpoint %v not found" , ip )
}
if len ( endpoints [ ip ] ) != len ( expectedEndpoints [ ip ] ) {
Failf ( "invalid list of ports for ip %v. Got %v, expected %v" , ip , endpoints [ ip ] , expectedEndpoints [ ip ] )
}
sort . Ints ( endpoints [ ip ] )
sort . Ints ( expectedEndpoints [ ip ] )
for index := range endpoints [ ip ] {
if endpoints [ ip ] [ index ] != expectedEndpoints [ ip ] [ index ] {
Failf ( "invalid list of ports for ip %v. Got %v, expected %v" , ip , endpoints [ ip ] , expectedEndpoints [ ip ] )
}
}
}
2015-02-09 20:37:56 +00:00
}
2015-07-01 21:58:37 +00:00
func validateEndpointsOrFail ( c * client . Client , namespace , serviceName string , expectedEndpoints map [ string ] [ ] int ) {
By ( fmt . Sprintf ( "Waiting up to %v for service %s in namespace %s to expose endpoints %v" , serviceStartTimeout , serviceName , namespace , expectedEndpoints ) )
for start := time . Now ( ) ; time . Since ( start ) < serviceStartTimeout ; time . Sleep ( 5 * time . Second ) {
endpoints , err := c . Endpoints ( namespace ) . Get ( serviceName )
if err != nil {
Logf ( "Get endpoints failed (%v elapsed, ignoring for 5s): %v" , time . Since ( start ) , err )
continue
}
Logf ( "Found endpoints %v" , endpoints )
2015-05-08 17:07:32 +00:00
2015-07-01 21:58:37 +00:00
portsByIp := getPortsByIp ( endpoints . Subsets )
Logf ( "Found ports by ip %v" , portsByIp )
2015-05-08 17:07:32 +00:00
2015-07-01 21:58:37 +00:00
if len ( portsByIp ) == len ( expectedEndpoints ) {
expectedPortsByIp := translatePodNameToIpOrFail ( c , namespace , expectedEndpoints )
validatePortsOrFail ( portsByIp , expectedPortsByIp )
By ( fmt . Sprintf ( "Successfully validated that service %s in namespace %s exposes endpoints %v (%v elapsed)" , serviceName , namespace , expectedEndpoints , time . Since ( start ) ) )
return
2015-02-09 20:37:56 +00:00
}
2015-07-01 21:58:37 +00:00
Logf ( "Unexpected number of endpoints: found %v, expected %v (%v elapsed, ignoring for 5s)" , portsByIp , expectedEndpoints , time . Since ( start ) )
2015-05-19 18:17:32 +00:00
}
2015-07-01 21:58:37 +00:00
Failf ( "Timed out waiting for service %s in namespace %s to expose endpoints %v (%v elapsed)" , serviceName , namespace , expectedEndpoints , serviceStartTimeout )
2015-02-09 20:37:56 +00:00
}
2015-05-08 17:07:32 +00:00
func addEndpointPodOrFail ( c * client . Client , ns , name string , labels map [ string ] string , containerPorts [ ] api . ContainerPort ) {
2015-02-18 19:30:18 +00:00
By ( fmt . Sprintf ( "Adding pod %v in namespace %v" , name , ns ) )
2015-02-09 20:37:56 +00:00
pod := & api . Pod {
ObjectMeta : api . ObjectMeta {
Name : name ,
Labels : labels ,
} ,
Spec : api . PodSpec {
Containers : [ ] api . Container {
{
Name : "test" ,
2015-03-31 17:25:20 +00:00
Image : "gcr.io/google_containers/pause" ,
2015-05-08 17:07:32 +00:00
Ports : containerPorts ,
2015-02-09 20:37:56 +00:00
} ,
} ,
} ,
}
_ , err := c . Pods ( ns ) . Create ( pod )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
}
2015-05-20 15:59:34 +00:00
func collectAddresses ( nodes * api . NodeList , addressType api . NodeAddressType ) [ ] string {
ips := [ ] string { }
for i := range nodes . Items {
item := & nodes . Items [ i ]
for j := range item . Status . Addresses {
nodeAddress := & item . Status . Addresses [ j ]
if nodeAddress . Type == addressType {
ips = append ( ips , nodeAddress . Address )
}
}
}
return ips
}
func getMinionPublicIps ( c * client . Client ) ( [ ] string , error ) {
nodes , err := c . Nodes ( ) . List ( labels . Everything ( ) , fields . Everything ( ) )
if err != nil {
return nil , err
}
ips := collectAddresses ( nodes , api . NodeExternalIP )
if len ( ips ) == 0 {
ips = collectAddresses ( nodes , api . NodeLegacyHostIP )
}
return ips , nil
}
2015-05-22 23:01:35 +00:00
func pickMinionIP ( c * client . Client ) string {
publicIps , err := getMinionPublicIps ( c )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
if len ( publicIps ) == 0 {
Failf ( "got unexpected number (%d) of public IPs" , len ( publicIps ) )
}
ip := publicIps [ 0 ]
return ip
}
func testLoadBalancerReachable ( ingress api . LoadBalancerIngress , port int ) {
ip := ingress . IP
if ip == "" {
ip = ingress . Hostname
}
testReachable ( ip , port )
}
func testLoadBalancerNotReachable ( ingress api . LoadBalancerIngress , port int ) {
ip := ingress . IP
if ip == "" {
ip = ingress . Hostname
}
testNotReachable ( ip , port )
}
func testReachable ( ip string , port int ) {
url := fmt . Sprintf ( "http://%s:%d" , ip , port )
if ip == "" {
2015-06-11 00:11:02 +00:00
Failf ( "Got empty IP for reachability check (%s)" , url )
2015-05-22 23:01:35 +00:00
}
if port == 0 {
2015-06-11 00:11:02 +00:00
Failf ( "Got port==0 for reachability check (%s)" , url )
2015-05-22 23:01:35 +00:00
}
2015-06-25 07:51:38 +00:00
desc := fmt . Sprintf ( "the url %s to be reachable" , url )
By ( fmt . Sprintf ( "Waiting up to %v for %s" , podStartTimeout , desc ) )
2015-06-17 07:13:26 +00:00
start := time . Now ( )
2015-06-25 07:51:38 +00:00
err := wait . Poll ( poll , podStartTimeout , func ( ) ( bool , error ) {
2015-06-11 00:11:02 +00:00
resp , err := httpGetNoConnectionPool ( url )
if err != nil {
2015-06-17 07:13:26 +00:00
Logf ( "Got error waiting for reachability of %s: %v (%v)" , url , err , time . Since ( start ) )
2015-06-11 00:11:02 +00:00
return false , nil
2015-05-22 23:01:35 +00:00
}
2015-06-11 00:11:02 +00:00
defer resp . Body . Close ( )
body , err := ioutil . ReadAll ( resp . Body )
if err != nil {
Logf ( "Got error reading response from %s: %v" , url , err )
return false , nil
}
if resp . StatusCode != 200 {
return false , fmt . Errorf ( "received non-success return status %q trying to access %s; got body: %s" , resp . Status , url , string ( body ) )
}
if ! strings . Contains ( string ( body ) , "test-webserver" ) {
return false , fmt . Errorf ( "received response body without expected substring 'test-webserver': %s" , string ( body ) )
}
Logf ( "Successfully reached %v" , url )
return true , nil
2015-06-25 07:51:38 +00:00
} )
Expect ( err ) . NotTo ( HaveOccurred ( ) , "Error waiting for %s" , desc )
2015-05-22 23:01:35 +00:00
}
func testNotReachable ( ip string , port int ) {
url := fmt . Sprintf ( "http://%s:%d" , ip , port )
if ip == "" {
2015-06-11 00:11:02 +00:00
Failf ( "Got empty IP for non-reachability check (%s)" , url )
2015-05-22 23:01:35 +00:00
}
if port == 0 {
2015-06-11 00:11:02 +00:00
Failf ( "Got port==0 for non-reachability check (%s)" , url )
2015-05-22 23:01:35 +00:00
}
2015-06-25 07:51:38 +00:00
desc := fmt . Sprintf ( "the url %s to be *not* reachable" , url )
By ( fmt . Sprintf ( "Waiting up to %v for %s" , podStartTimeout , desc ) )
err := wait . Poll ( poll , podStartTimeout , func ( ) ( bool , error ) {
2015-06-11 00:11:02 +00:00
resp , err := httpGetNoConnectionPool ( url )
2015-05-22 23:01:35 +00:00
if err != nil {
2015-06-25 07:51:38 +00:00
Logf ( "Successfully waited for %s" , desc )
2015-06-11 00:11:02 +00:00
return true , nil
2015-05-22 23:01:35 +00:00
}
2015-06-11 00:11:02 +00:00
defer resp . Body . Close ( )
body , err := ioutil . ReadAll ( resp . Body )
if err != nil {
Logf ( "Expecting %s to be unreachable but was reachable and got an error reading response: %v" , url , err )
return false , nil
}
Logf ( "Able to reach service %s when should no longer have been reachable, status:%d and body: %s" , url , resp . Status , string ( body ) )
return false , nil
2015-06-25 07:51:38 +00:00
} )
Expect ( err ) . NotTo ( HaveOccurred ( ) , "Error waiting for %s" , desc )
2015-05-22 23:01:35 +00:00
}
// Does an HTTP GET, but does not reuse TCP connections
// This masks problems where the iptables rule has changed, but we don't see it
2015-05-23 12:20:49 +00:00
// This is intended for relatively quick requests (status checks), so we set a short (5 seconds) timeout
2015-05-22 23:01:35 +00:00
func httpGetNoConnectionPool ( url string ) ( * http . Response , error ) {
tr := & http . Transport {
DisableKeepAlives : true ,
}
client := & http . Client {
Transport : tr ,
2015-05-23 12:20:49 +00:00
Timeout : 5 * time . Second ,
2015-05-22 23:01:35 +00:00
}
return client . Get ( url )
}
// Simple helper class to avoid too much boilerplate in tests
type WebserverTest struct {
ServiceName string
Namespace string
Client * client . Client
TestId string
Labels map [ string ] string
2015-06-17 07:13:26 +00:00
rcs map [ string ] bool
2015-05-22 23:01:35 +00:00
services map [ string ] bool
2015-06-17 07:13:26 +00:00
name string
image string
2015-05-22 23:01:35 +00:00
}
func NewWebserverTest ( client * client . Client , namespace string , serviceName string ) * WebserverTest {
t := & WebserverTest { }
t . Client = client
t . Namespace = namespace
t . ServiceName = serviceName
t . TestId = t . ServiceName + "-" + string ( util . NewUUID ( ) )
t . Labels = map [ string ] string {
"testid" : t . TestId ,
}
2015-06-17 07:13:26 +00:00
t . rcs = make ( map [ string ] bool )
2015-05-22 23:01:35 +00:00
t . services = make ( map [ string ] bool )
2015-06-17 07:13:26 +00:00
t . name = "webserver"
t . image = "gcr.io/google_containers/test-webserver"
2015-05-22 23:01:35 +00:00
2015-06-17 07:13:26 +00:00
return t
2015-05-22 23:01:35 +00:00
}
// Build default config for a service (which can then be changed)
func ( t * WebserverTest ) BuildServiceSpec ( ) * api . Service {
service := & api . Service {
ObjectMeta : api . ObjectMeta {
Name : t . ServiceName ,
} ,
Spec : api . ServiceSpec {
Selector : t . Labels ,
Ports : [ ] api . ServicePort { {
Port : 80 ,
TargetPort : util . NewIntOrStringFromInt ( 80 ) ,
} } ,
} ,
}
return service
}
2015-06-17 07:13:26 +00:00
// CreateWebserverRC creates rc-backed pods with the well-known webserver
// configuration and records it for cleanup.
func ( t * WebserverTest ) CreateWebserverRC ( replicas int ) * api . ReplicationController {
rcSpec := rcByName ( t . name , replicas , t . image , t . Labels )
rcAct , err := t . createRC ( rcSpec )
2015-05-22 23:01:35 +00:00
if err != nil {
2015-06-17 07:13:26 +00:00
Failf ( "Failed to create rc %s: %v" , rcSpec . Name , err )
}
if err := verifyPods ( t . Client , t . Namespace , t . name , false , replicas ) ; err != nil {
Failf ( "Failed to create %d pods with name %s: %v" , replicas , t . name , err )
2015-05-22 23:01:35 +00:00
}
2015-06-17 07:13:26 +00:00
return rcAct
2015-05-22 23:01:35 +00:00
}
2015-06-17 07:13:26 +00:00
// createRC creates a replication controller and records it for cleanup.
func ( t * WebserverTest ) createRC ( rc * api . ReplicationController ) ( * api . ReplicationController , error ) {
rc , err := t . Client . ReplicationControllers ( t . Namespace ) . Create ( rc )
2015-05-22 23:01:35 +00:00
if err == nil {
2015-06-17 07:13:26 +00:00
t . rcs [ rc . Name ] = true
2015-05-22 23:01:35 +00:00
}
2015-06-17 07:13:26 +00:00
return rc , err
2015-05-22 23:01:35 +00:00
}
// Create a service, and record it for cleanup
func ( t * WebserverTest ) CreateService ( service * api . Service ) ( * api . Service , error ) {
result , err := t . Client . Services ( t . Namespace ) . Create ( service )
if err == nil {
t . services [ service . Name ] = true
}
return result , err
}
// Delete a service, and remove it from the cleanup list
func ( t * WebserverTest ) DeleteService ( serviceName string ) error {
err := t . Client . Services ( t . Namespace ) . Delete ( serviceName )
if err == nil {
delete ( t . services , serviceName )
}
return err
}
func ( t * WebserverTest ) Cleanup ( ) [ ] error {
var errs [ ] error
2015-06-17 07:13:26 +00:00
for rcName := range t . rcs {
By ( "stopping RC " + rcName + " in namespace " + t . Namespace )
// First, resize the RC to 0.
old , err := t . Client . ReplicationControllers ( t . Namespace ) . Get ( rcName )
2015-05-22 23:01:35 +00:00
if err != nil {
errs = append ( errs , err )
}
2015-06-17 07:13:26 +00:00
old . Spec . Replicas = 0
if _ , err := t . Client . ReplicationControllers ( t . Namespace ) . Update ( old ) ; err != nil {
errs = append ( errs , err )
}
// TODO(mbforbes): Wait.
// Then, delete the RC altogether.
if err := t . Client . ReplicationControllers ( t . Namespace ) . Delete ( rcName ) ; err != nil {
errs = append ( errs , err )
}
2015-05-22 23:01:35 +00:00
}
for serviceName := range t . services {
By ( "deleting service " + serviceName + " in namespace " + t . Namespace )
err := t . Client . Services ( t . Namespace ) . Delete ( serviceName )
if err != nil {
errs = append ( errs , err )
}
}
return errs
}