2015-06-11 22:55:25 +00:00
/ *
2016-06-03 00:25:58 +00:00
Copyright 2014 The Kubernetes Authors .
2015-06-11 22:55:25 +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 .
* /
2017-08-07 20:48:13 +00:00
// OWNER = sig/network
2017-07-10 17:03:40 +00:00
2017-08-07 20:48:13 +00:00
package network
2015-06-11 22:55:25 +00:00
import (
"fmt"
2015-07-13 07:58:18 +00:00
"math"
2015-06-18 23:25:58 +00:00
"net/http"
2015-06-11 22:55:25 +00:00
"strings"
2015-06-18 23:25:58 +00:00
"sync"
2015-06-11 22:55:25 +00:00
"time"
2017-06-22 18:24:23 +00:00
"k8s.io/api/core/v1"
2017-01-13 17:48:50 +00:00
"k8s.io/apimachinery/pkg/api/errors"
2017-01-17 03:38:19 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2017-01-27 20:42:17 +00:00
"k8s.io/apimachinery/pkg/util/intstr"
2017-01-11 14:09:48 +00:00
"k8s.io/apimachinery/pkg/util/net"
2017-06-23 20:56:37 +00:00
clientset "k8s.io/client-go/kubernetes"
2017-07-11 01:10:34 +00:00
"k8s.io/kubernetes/pkg/api/testapi"
2016-04-07 17:21:31 +00:00
"k8s.io/kubernetes/test/e2e/framework"
2016-10-12 11:37:37 +00:00
testutils "k8s.io/kubernetes/test/utils"
2017-08-29 08:32:08 +00:00
imageutils "k8s.io/kubernetes/test/utils/image"
2015-06-11 22:55:25 +00:00
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
2015-06-18 23:25:58 +00:00
const (
// Try all the proxy tests this many times (to catch even rare flakes).
proxyAttempts = 20
// Only print this many characters of the response (to keep the logs
// legible).
maxDisplayBodyLen = 100
2016-03-14 21:46:54 +00:00
// We have seen one of these calls take just over 15 seconds, so putting this at 30.
proxyHTTPCallTimeout = 30 * time . Second
2015-06-18 23:25:58 +00:00
)
2017-08-07 20:48:13 +00:00
var _ = SIGDescribe ( "Proxy" , func ( ) {
2017-07-11 01:10:34 +00:00
version := testapi . Groups [ v1 . GroupName ] . GroupVersion ( ) . Version
2016-05-26 00:30:12 +00:00
Context ( "version " + version , func ( ) {
options := framework . FrameworkOptions {
ClientQPS : - 1.0 ,
}
f := framework . NewFramework ( "proxy" , options , nil )
prefix := "/api/" + version
// Port here has to be kept in sync with default kubelet port.
2017-10-23 18:30:05 +00:00
/ *
Testname : proxy - prefix - node - logs - port
Description : Ensure that proxy on node logs works with generic top
level prefix proxy and explicit kubelet port .
* /
2017-10-26 17:46:09 +00:00
framework . ConformanceIt ( "should proxy logs on node with explicit kubelet port " , func ( ) { nodeProxyTest ( f , prefix + "/proxy/nodes/" , ":10250/logs/" ) } )
2017-10-23 18:30:05 +00:00
/ *
Testname : proxy - prefix - node - logs
Description : Ensure that proxy on node logs works with generic top
level prefix proxy .
* /
2017-10-26 17:46:09 +00:00
framework . ConformanceIt ( "should proxy logs on node " , func ( ) { nodeProxyTest ( f , prefix + "/proxy/nodes/" , "/logs/" ) } )
2016-05-26 00:30:12 +00:00
It ( "should proxy to cadvisor" , func ( ) { nodeProxyTest ( f , prefix + "/proxy/nodes/" , ":4194/containers/" ) } )
2017-10-23 18:30:05 +00:00
/ *
Testname : proxy - subresource - node - logs - port
Description : Ensure that proxy on node logs works with node proxy
subresource and explicit kubelet port .
* /
2017-10-26 17:46:09 +00:00
framework . ConformanceIt ( "should proxy logs on node with explicit kubelet port using proxy subresource " , func ( ) { nodeProxyTest ( f , prefix + "/nodes/" , ":10250/proxy/logs/" ) } )
2017-10-23 18:30:05 +00:00
/ *
Testname : proxy - subresource - node - logs
Description : Ensure that proxy on node logs works with node proxy
subresource .
* /
2017-10-26 17:46:09 +00:00
framework . ConformanceIt ( "should proxy logs on node using proxy subresource " , func ( ) { nodeProxyTest ( f , prefix + "/nodes/" , "/proxy/logs/" ) } )
2016-05-26 00:30:12 +00:00
It ( "should proxy to cadvisor using proxy subresource" , func ( ) { nodeProxyTest ( f , prefix + "/nodes/" , ":4194/proxy/containers/" ) } )
// using the porter image to serve content, access the content
// (of multiple pods?) from multiple (endpoints/services?)
2017-10-23 18:30:05 +00:00
/ *
Testname : proxy - service - pod
Description : Ensure that proxy through a service and a pod works with
both generic top level prefix proxy and proxy subresource .
* /
2017-10-26 17:46:09 +00:00
framework . ConformanceIt ( "should proxy through a service and a pod " , func ( ) {
2016-05-26 00:30:12 +00:00
start := time . Now ( )
labels := map [ string ] string { "proxy-service-target" : "true" }
2017-10-25 15:54:32 +00:00
service , err := f . ClientSet . CoreV1 ( ) . Services ( f . Namespace . Name ) . Create ( & v1 . Service {
2017-01-17 03:38:19 +00:00
ObjectMeta : metav1 . ObjectMeta {
2016-05-26 00:30:12 +00:00
GenerateName : "proxy-service-" ,
} ,
2016-11-18 20:55:17 +00:00
Spec : v1 . ServiceSpec {
2016-05-26 00:30:12 +00:00
Selector : labels ,
2016-11-18 20:55:17 +00:00
Ports : [ ] v1 . ServicePort {
2016-05-26 00:30:12 +00:00
{
Name : "portname1" ,
Port : 80 ,
TargetPort : intstr . FromString ( "dest1" ) ,
} ,
{
Name : "portname2" ,
Port : 81 ,
TargetPort : intstr . FromInt ( 162 ) ,
} ,
{
Name : "tlsportname1" ,
Port : 443 ,
TargetPort : intstr . FromString ( "tlsdest1" ) ,
} ,
{
Name : "tlsportname2" ,
Port : 444 ,
TargetPort : intstr . FromInt ( 462 ) ,
} ,
2015-10-09 06:25:25 +00:00
} ,
2015-06-11 22:55:25 +00:00
} ,
2016-05-26 00:30:12 +00:00
} )
Expect ( err ) . NotTo ( HaveOccurred ( ) )
// Make an RC with a single pod. The 'porter' image is
// a simple server which serves the values of the
// environmental variables below.
By ( "starting an echo server on multiple ports" )
2016-11-18 20:55:17 +00:00
pods := [ ] * v1 . Pod { }
2016-05-26 00:30:12 +00:00
cfg := testutils . RCConfig {
2016-11-18 20:55:17 +00:00
Client : f . ClientSet ,
InternalClient : f . InternalClientset ,
2017-08-29 08:32:08 +00:00
Image : imageutils . GetE2EImage ( imageutils . Porter ) ,
2016-11-18 20:55:17 +00:00
Name : service . Name ,
Namespace : f . Namespace . Name ,
Replicas : 1 ,
PollInterval : time . Second ,
2016-05-26 00:30:12 +00:00
Env : map [ string ] string {
"SERVE_PORT_80" : ` <a href="/rewriteme">test</a> ` ,
"SERVE_PORT_1080" : ` <a href="/rewriteme">test</a> ` ,
"SERVE_PORT_160" : "foo" ,
"SERVE_PORT_162" : "bar" ,
"SERVE_TLS_PORT_443" : ` <a href="/tlsrewriteme">test</a> ` ,
"SERVE_TLS_PORT_460" : ` tls baz ` ,
"SERVE_TLS_PORT_462" : ` tls qux ` ,
} ,
Ports : map [ string ] int {
"dest1" : 160 ,
"dest2" : 162 ,
"tlsdest1" : 460 ,
"tlsdest2" : 462 ,
} ,
2016-11-18 20:55:17 +00:00
ReadinessProbe : & v1 . Probe {
Handler : v1 . Handler {
HTTPGet : & v1 . HTTPGetAction {
2016-05-26 00:30:12 +00:00
Port : intstr . FromInt ( 80 ) ,
} ,
2016-02-23 01:43:25 +00:00
} ,
2016-05-26 00:30:12 +00:00
InitialDelaySeconds : 1 ,
TimeoutSeconds : 5 ,
PeriodSeconds : 10 ,
2016-02-23 01:43:25 +00:00
} ,
2016-05-26 00:30:12 +00:00
Labels : labels ,
CreatedPods : & pods ,
}
Expect ( framework . RunRC ( cfg ) ) . NotTo ( HaveOccurred ( ) )
2016-11-18 20:55:17 +00:00
defer framework . DeleteRCAndPods ( f . ClientSet , f . InternalClientset , f . Namespace . Name , cfg . Name )
2015-06-11 22:55:25 +00:00
2017-05-12 14:22:46 +00:00
Expect ( framework . WaitForEndpoint ( f . ClientSet , f . Namespace . Name , service . Name ) ) . NotTo ( HaveOccurred ( ) )
2015-06-11 22:55:25 +00:00
2016-05-26 00:30:12 +00:00
// table constructors
// Try proxying through the service and directly to through the pod.
svcProxyURL := func ( scheme , port string ) string {
return prefix + "/proxy/namespaces/" + f . Namespace . Name + "/services/" + net . JoinSchemeNamePort ( scheme , service . Name , port )
}
subresourceServiceProxyURL := func ( scheme , port string ) string {
return prefix + "/namespaces/" + f . Namespace . Name + "/services/" + net . JoinSchemeNamePort ( scheme , service . Name , port ) + "/proxy"
}
podProxyURL := func ( scheme , port string ) string {
return prefix + "/proxy/namespaces/" + f . Namespace . Name + "/pods/" + net . JoinSchemeNamePort ( scheme , pods [ 0 ] . Name , port )
}
subresourcePodProxyURL := func ( scheme , port string ) string {
return prefix + "/namespaces/" + f . Namespace . Name + "/pods/" + net . JoinSchemeNamePort ( scheme , pods [ 0 ] . Name , port ) + "/proxy"
}
2016-07-12 16:30:33 +00:00
2016-05-26 00:30:12 +00:00
// construct the table
expectations := map [ string ] string {
svcProxyURL ( "" , "portname1" ) + "/" : "foo" ,
svcProxyURL ( "" , "80" ) + "/" : "foo" ,
svcProxyURL ( "" , "portname2" ) + "/" : "bar" ,
svcProxyURL ( "" , "81" ) + "/" : "bar" ,
svcProxyURL ( "http" , "portname1" ) + "/" : "foo" ,
svcProxyURL ( "http" , "80" ) + "/" : "foo" ,
svcProxyURL ( "http" , "portname2" ) + "/" : "bar" ,
svcProxyURL ( "http" , "81" ) + "/" : "bar" ,
svcProxyURL ( "https" , "tlsportname1" ) + "/" : "tls baz" ,
svcProxyURL ( "https" , "443" ) + "/" : "tls baz" ,
svcProxyURL ( "https" , "tlsportname2" ) + "/" : "tls qux" ,
svcProxyURL ( "https" , "444" ) + "/" : "tls qux" ,
subresourceServiceProxyURL ( "" , "portname1" ) + "/" : "foo" ,
subresourceServiceProxyURL ( "http" , "portname1" ) + "/" : "foo" ,
subresourceServiceProxyURL ( "" , "portname2" ) + "/" : "bar" ,
subresourceServiceProxyURL ( "http" , "portname2" ) + "/" : "bar" ,
subresourceServiceProxyURL ( "https" , "tlsportname1" ) + "/" : "tls baz" ,
subresourceServiceProxyURL ( "https" , "tlsportname2" ) + "/" : "tls qux" ,
podProxyURL ( "" , "1080" ) + "/" : ` <a href=" ` + podProxyURL ( "" , "1080" ) + ` /rewriteme">test</a> ` ,
podProxyURL ( "" , "160" ) + "/" : "foo" ,
podProxyURL ( "" , "162" ) + "/" : "bar" ,
podProxyURL ( "http" , "1080" ) + "/" : ` <a href=" ` + podProxyURL ( "http" , "1080" ) + ` /rewriteme">test</a> ` ,
podProxyURL ( "http" , "160" ) + "/" : "foo" ,
podProxyURL ( "http" , "162" ) + "/" : "bar" ,
subresourcePodProxyURL ( "" , "" ) + "/" : ` <a href=" ` + subresourcePodProxyURL ( "" , "" ) + ` /rewriteme">test</a> ` ,
subresourcePodProxyURL ( "" , "1080" ) + "/" : ` <a href=" ` + subresourcePodProxyURL ( "" , "1080" ) + ` /rewriteme">test</a> ` ,
subresourcePodProxyURL ( "http" , "1080" ) + "/" : ` <a href=" ` + subresourcePodProxyURL ( "http" , "1080" ) + ` /rewriteme">test</a> ` ,
subresourcePodProxyURL ( "" , "160" ) + "/" : "foo" ,
subresourcePodProxyURL ( "http" , "160" ) + "/" : "foo" ,
subresourcePodProxyURL ( "" , "162" ) + "/" : "bar" ,
subresourcePodProxyURL ( "http" , "162" ) + "/" : "bar" ,
subresourcePodProxyURL ( "https" , "443" ) + "/" : ` <a href=" ` + subresourcePodProxyURL ( "https" , "443" ) + ` /tlsrewriteme">test</a> ` ,
subresourcePodProxyURL ( "https" , "460" ) + "/" : "tls baz" ,
subresourcePodProxyURL ( "https" , "462" ) + "/" : "tls qux" ,
// TODO: below entries don't work, but I believe we should make them work.
// podPrefix + ":dest1": "foo",
// podPrefix + ":dest2": "bar",
}
2015-06-11 22:55:25 +00:00
2016-05-26 00:30:12 +00:00
wg := sync . WaitGroup { }
errs := [ ] string { }
errLock := sync . Mutex { }
recordError := func ( s string ) {
errLock . Lock ( )
defer errLock . Unlock ( )
errs = append ( errs , s )
}
d := time . Since ( start )
framework . Logf ( "setup took %v, starting test cases" , d )
numberTestCases := len ( expectations )
totalAttempts := numberTestCases * proxyAttempts
By ( fmt . Sprintf ( "running %v cases, %v attempts per case, %v total attempts" , numberTestCases , proxyAttempts , totalAttempts ) )
for i := 0 ; i < proxyAttempts ; i ++ {
wg . Add ( numberTestCases )
for path , val := range expectations {
go func ( i int , path , val string ) {
defer wg . Done ( )
// this runs the test case
body , status , d , err := doProxy ( f , path , i )
if err != nil {
if serr , ok := err . ( * errors . StatusError ) ; ok {
recordError ( fmt . Sprintf ( "%v (%v; %v): path %v gave status error: %+v" ,
i , status , d , path , serr . Status ( ) ) )
} else {
recordError ( fmt . Sprintf ( "%v: path %v gave error: %v" , i , path , err ) )
}
return
2016-05-27 14:03:28 +00:00
}
2016-05-26 00:30:12 +00:00
if status != http . StatusOK {
recordError ( fmt . Sprintf ( "%v: path %v gave status: %v" , i , path , status ) )
}
if e , a := val , string ( body ) ; e != a {
recordError ( fmt . Sprintf ( "%v: path %v: wanted %v, got %v" , i , path , e , a ) )
}
if d > proxyHTTPCallTimeout {
recordError ( fmt . Sprintf ( "%v: path %v took %v > %v" , i , path , d , proxyHTTPCallTimeout ) )
}
} ( i , path , val )
}
wg . Wait ( )
2015-06-11 22:55:25 +00:00
}
2016-05-26 00:30:12 +00:00
if len ( errs ) != 0 {
2017-10-25 15:54:32 +00:00
body , err := f . ClientSet . CoreV1 ( ) . Pods ( f . Namespace . Name ) . GetLogs ( pods [ 0 ] . Name , & v1 . PodLogOptions { } ) . Do ( ) . Raw ( )
2016-05-26 00:30:12 +00:00
if err != nil {
framework . Logf ( "Error getting logs for pod %s: %v" , pods [ 0 ] . Name , err )
} else {
framework . Logf ( "Pod %s has the following error logs: %s" , pods [ 0 ] . Name , body )
}
2016-05-24 13:40:55 +00:00
2017-05-09 22:39:35 +00:00
framework . Failf ( strings . Join ( errs , "\n" ) )
2016-05-26 00:30:12 +00:00
}
} )
2015-06-11 22:55:25 +00:00
} )
2016-05-26 00:30:12 +00:00
} )
2015-06-11 22:55:25 +00:00
2016-07-12 16:30:33 +00:00
func doProxy ( f * framework . Framework , path string , i int ) ( body [ ] byte , statusCode int , d time . Duration , err error ) {
2015-06-18 23:25:58 +00:00
// About all of the proxy accesses in this file:
// * AbsPath is used because it preserves the trailing '/'.
// * Do().Raw() is used (instead of DoRaw()) because it will turn an
// error from apiserver proxy into an actual error, and there is no
// chance of the things we are talking to being confused for an error
// that apiserver would have emitted.
start := time . Now ( )
2017-10-25 15:54:32 +00:00
body , err = f . ClientSet . CoreV1 ( ) . RESTClient ( ) . Get ( ) . AbsPath ( path ) . Do ( ) . StatusCode ( & statusCode ) . Raw ( )
2015-06-18 23:25:58 +00:00
d = time . Since ( start )
if len ( body ) > 0 {
2016-07-12 16:30:33 +00:00
framework . Logf ( "(%v) %v: %s (%v; %v)" , i , path , truncate ( body , maxDisplayBodyLen ) , statusCode , d )
2015-11-07 21:05:52 +00:00
} else {
2016-04-07 17:21:31 +00:00
framework . Logf ( "%v: %s (%v; %v)" , path , "no body" , statusCode , d )
2015-06-18 23:25:58 +00:00
}
return
}
func truncate ( b [ ] byte , maxLen int ) [ ] byte {
if len ( b ) <= maxLen - 3 {
return b
}
b2 := append ( [ ] byte ( nil ) , b [ : maxLen - 3 ] ... )
b2 = append ( b2 , '.' , '.' , '.' )
return b2
}
2016-10-19 13:55:39 +00:00
func pickNode ( cs clientset . Interface ) ( string , error ) {
2015-12-10 14:35:58 +00:00
// TODO: investigate why it doesn't work on master Node.
2016-10-19 13:55:39 +00:00
nodes := framework . GetReadySchedulableNodesOrDie ( cs )
2015-06-11 22:55:25 +00:00
if len ( nodes . Items ) == 0 {
return "" , fmt . Errorf ( "no nodes exist, can't test node proxy" )
}
return nodes . Items [ 0 ] . Name , nil
}
2015-06-18 23:25:58 +00:00
2016-04-07 17:21:31 +00:00
func nodeProxyTest ( f * framework . Framework , prefix , nodeDest string ) {
2016-10-19 13:55:39 +00:00
node , err := pickNode ( f . ClientSet )
2015-06-18 23:25:58 +00:00
Expect ( err ) . NotTo ( HaveOccurred ( ) )
2015-07-15 12:31:38 +00:00
// TODO: Change it to test whether all requests succeeded when requests
// not reaching Kubelet issue is debugged.
2015-07-13 07:58:18 +00:00
serviceUnavailableErrors := 0
2015-06-18 23:25:58 +00:00
for i := 0 ; i < proxyAttempts ; i ++ {
2016-07-12 16:30:33 +00:00
_ , status , d , err := doProxy ( f , prefix + node + nodeDest , i )
2015-07-13 07:58:18 +00:00
if status == http . StatusServiceUnavailable {
2016-04-07 17:21:31 +00:00
framework . Logf ( "Failed proxying node logs due to service unavailable: %v" , err )
2015-07-13 07:58:18 +00:00
time . Sleep ( time . Second )
serviceUnavailableErrors ++
} else {
Expect ( err ) . NotTo ( HaveOccurred ( ) )
Expect ( status ) . To ( Equal ( http . StatusOK ) )
2016-03-14 21:46:54 +00:00
Expect ( d ) . To ( BeNumerically ( "<" , proxyHTTPCallTimeout ) )
2015-07-13 07:58:18 +00:00
}
2015-06-18 23:25:58 +00:00
}
2015-07-15 12:31:38 +00:00
if serviceUnavailableErrors > 0 {
2016-04-07 17:21:31 +00:00
framework . Logf ( "error: %d requests to proxy node logs failed" , serviceUnavailableErrors )
2015-07-15 12:31:38 +00:00
}
2015-07-13 07:58:18 +00:00
maxFailures := int ( math . Floor ( 0.1 * float64 ( proxyAttempts ) ) )
Expect ( serviceUnavailableErrors ) . To ( BeNumerically ( "<" , maxFailures ) )
2015-06-18 23:25:58 +00:00
}