2014-11-02 20:52:31 +00:00
/ *
2015-05-01 16:19:44 +00:00
Copyright 2014 The Kubernetes Authors All rights reserved .
2014-11-02 20:52:31 +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 master
import (
2015-08-20 05:08:26 +00:00
"bytes"
"encoding/json"
2015-09-03 17:35:04 +00:00
"errors"
"fmt"
2015-08-19 18:02:01 +00:00
"io/ioutil"
2015-09-03 17:35:04 +00:00
"net"
2015-08-19 18:02:01 +00:00
"net/http"
"net/http/httptest"
2015-09-03 17:35:04 +00:00
"os"
"path/filepath"
2015-08-20 05:08:26 +00:00
"reflect"
2015-09-09 21:36:19 +00:00
"strings"
2014-11-02 20:52:31 +00:00
"testing"
2015-09-03 17:35:04 +00:00
"time"
2014-11-02 20:52:31 +00:00
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/latest"
2015-09-03 17:35:04 +00:00
"k8s.io/kubernetes/pkg/api/meta"
2015-09-11 00:20:53 +00:00
"k8s.io/kubernetes/pkg/api/testapi"
2015-09-09 21:59:11 +00:00
"k8s.io/kubernetes/pkg/api/unversioned"
2015-09-28 18:08:47 +00:00
apiutil "k8s.io/kubernetes/pkg/api/util"
2015-09-03 17:35:04 +00:00
"k8s.io/kubernetes/pkg/api/v1"
2015-09-09 22:46:06 +00:00
"k8s.io/kubernetes/pkg/apis/experimental"
2015-09-09 22:49:26 +00:00
"k8s.io/kubernetes/pkg/apiserver"
client "k8s.io/kubernetes/pkg/client/unversioned"
2015-09-03 17:35:04 +00:00
"k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/registry/endpoint"
"k8s.io/kubernetes/pkg/registry/namespace"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/registry/registrytest"
2015-09-09 21:36:19 +00:00
thirdpartyresourcedatastorage "k8s.io/kubernetes/pkg/registry/thirdpartyresourcedata/etcd"
2015-08-05 22:03:47 +00:00
etcdstorage "k8s.io/kubernetes/pkg/storage/etcd"
"k8s.io/kubernetes/pkg/tools"
"k8s.io/kubernetes/pkg/tools/etcdtest"
2015-09-03 17:35:04 +00:00
"k8s.io/kubernetes/pkg/util"
2015-09-09 21:36:19 +00:00
"github.com/coreos/go-etcd/etcd"
"github.com/emicklei/go-restful"
"github.com/stretchr/testify/assert"
2014-11-02 20:52:31 +00:00
)
2015-09-03 17:35:04 +00:00
// setUp is a convience function for setting up for (most) tests.
func setUp ( t * testing . T ) ( Master , Config , * assert . Assertions ) {
2014-11-02 20:52:31 +00:00
master := Master { }
config := Config { }
fakeClient := tools . NewFakeEtcdClient ( t )
fakeClient . Machines = [ ] string { "http://machine1:4001" , "http://machine2" , "http://machine3:4003" }
2015-09-15 03:55:18 +00:00
storageVersions := make ( map [ string ] string )
2015-09-30 07:56:51 +00:00
storageDestinations := NewStorageDestinations ( )
storageDestinations . AddAPIGroup ( "" , etcdstorage . NewEtcdStorage ( fakeClient , testapi . Default . Codec ( ) , etcdtest . PathPrefix ( ) ) )
storageDestinations . AddAPIGroup ( "experimental" , etcdstorage . NewEtcdStorage ( fakeClient , testapi . Experimental . Codec ( ) , etcdtest . PathPrefix ( ) ) )
config . StorageDestinations = storageDestinations
2015-09-15 03:55:18 +00:00
storageVersions [ "" ] = testapi . Default . Version ( )
2015-09-28 18:08:47 +00:00
storageVersions [ "experimental" ] = testapi . Experimental . GroupAndVersion ( )
2015-09-15 03:55:18 +00:00
config . StorageVersions = storageVersions
2015-09-09 14:18:17 +00:00
master . nodeRegistry = registrytest . NewNodeRegistry ( [ ] string { "node1" , "node2" } , api . NodeResources { } )
2014-11-02 20:52:31 +00:00
2015-09-03 17:35:04 +00:00
return master , config , assert . New ( t )
}
// TestNew verifies that the New function returns a Master
// using the configuration properly.
func TestNew ( t * testing . T ) {
_ , config , assert := setUp ( t )
config . KubeletClient = client . FakeKubeletClient { }
master := New ( & config )
// Verify many of the variables match their config counterparts
assert . Equal ( master . enableCoreControllers , config . EnableCoreControllers )
assert . Equal ( master . enableLogsSupport , config . EnableLogsSupport )
assert . Equal ( master . enableUISupport , config . EnableUISupport )
assert . Equal ( master . enableSwaggerSupport , config . EnableSwaggerSupport )
assert . Equal ( master . enableSwaggerSupport , config . EnableSwaggerSupport )
assert . Equal ( master . enableProfiling , config . EnableProfiling )
assert . Equal ( master . apiPrefix , config . APIPrefix )
2015-09-14 22:30:32 +00:00
assert . Equal ( master . apiGroupPrefix , config . APIGroupPrefix )
2015-09-03 17:35:04 +00:00
assert . Equal ( master . corsAllowedOriginList , config . CorsAllowedOriginList )
assert . Equal ( master . authenticator , config . Authenticator )
assert . Equal ( master . authorizer , config . Authorizer )
assert . Equal ( master . admissionControl , config . AdmissionControl )
assert . Equal ( master . v1 , ! config . DisableV1 )
assert . Equal ( master . exp , config . EnableExp )
assert . Equal ( master . requestContextMapper , config . RequestContextMapper )
assert . Equal ( master . cacheTimeout , config . CacheTimeout )
assert . Equal ( master . masterCount , config . MasterCount )
assert . Equal ( master . externalHost , config . ExternalHost )
assert . Equal ( master . clusterIP , config . PublicAddress )
assert . Equal ( master . publicReadWritePort , config . ReadWritePort )
assert . Equal ( master . serviceReadWriteIP , config . ServiceReadWriteIP )
assert . Equal ( master . installSSHKey , config . InstallSSHKey )
}
// TestNewEtcdStorage verifies that the usage of NewEtcdStorage reacts properly when
// the correct data is input
func TestNewEtcdStorage ( t * testing . T ) {
assert := assert . New ( t )
fakeClient := tools . NewFakeEtcdClient ( t )
// Pass case
2015-09-12 22:27:05 +00:00
_ , err := NewEtcdStorage ( fakeClient , latest . GroupOrDie ( "" ) . InterfacesFor , testapi . Default . Version ( ) , etcdtest . PathPrefix ( ) )
2015-09-03 17:35:04 +00:00
assert . NoError ( err , "Unable to create etcdstorage: %s" , err )
// Fail case
errorFunc := func ( apiVersion string ) ( * meta . VersionInterfaces , error ) { return nil , errors . New ( "ERROR" ) }
2015-09-12 22:27:05 +00:00
_ , err = NewEtcdStorage ( fakeClient , errorFunc , testapi . Default . Version ( ) , etcdtest . PathPrefix ( ) )
2015-09-03 17:35:04 +00:00
assert . Error ( err , "NewEtcdStorage should have failed" )
}
// TestGetServersToValidate verifies the unexported getServersToValidate function
func TestGetServersToValidate ( t * testing . T ) {
master , config , assert := setUp ( t )
2015-05-14 00:29:25 +00:00
servers := master . getServersToValidate ( & config )
2015-05-04 21:17:35 +00:00
2015-09-03 17:35:04 +00:00
assert . Equal ( 5 , len ( servers ) , "unexpected server list: %#v" , servers )
2015-05-04 21:17:35 +00:00
for _ , server := range [ ] string { "scheduler" , "controller-manager" , "etcd-0" , "etcd-1" , "etcd-2" } {
if _ , ok := servers [ server ] ; ! ok {
t . Errorf ( "server list missing: %s" , server )
}
}
2014-11-02 20:52:31 +00:00
}
2015-07-28 08:10:48 +00:00
2015-09-03 17:35:04 +00:00
// TestFindExternalAddress verifies both pass and fail cases for the unexported
// findExternalAddress function
2015-07-28 08:10:48 +00:00
func TestFindExternalAddress ( t * testing . T ) {
2015-09-03 17:35:04 +00:00
assert := assert . New ( t )
2015-07-28 08:10:48 +00:00
expectedIP := "172.0.0.1"
nodes := [ ] * api . Node { new ( api . Node ) , new ( api . Node ) , new ( api . Node ) }
nodes [ 0 ] . Status . Addresses = [ ] api . NodeAddress { { "ExternalIP" , expectedIP } }
nodes [ 1 ] . Status . Addresses = [ ] api . NodeAddress { { "LegacyHostIP" , expectedIP } }
nodes [ 2 ] . Status . Addresses = [ ] api . NodeAddress { { "ExternalIP" , expectedIP } , { "LegacyHostIP" , "172.0.0.2" } }
2015-09-03 17:35:04 +00:00
// Pass Case
2015-07-28 08:10:48 +00:00
for _ , node := range nodes {
ip , err := findExternalAddress ( node )
2015-09-03 17:35:04 +00:00
assert . NoError ( err , "error getting node external address" )
assert . Equal ( expectedIP , ip , "expected ip to be %s, but was %s" , expectedIP , ip )
2015-07-28 08:10:48 +00:00
}
2015-09-03 17:35:04 +00:00
// Fail case
2015-07-28 08:10:48 +00:00
_ , err := findExternalAddress ( new ( api . Node ) )
2015-09-03 17:35:04 +00:00
assert . Error ( err , "expected findExternalAddress to fail on a node with missing ip information" )
}
// TestApi_v1 verifies that the unexported api_v1 function does indeed
// utilize the correct Version and Codec.
func TestApi_v1 ( t * testing . T ) {
master , _ , assert := setUp ( t )
version := master . api_v1 ( )
assert . Equal ( "v1" , version . Version , "Version was not v1: %s" , version . Version )
assert . Equal ( v1 . Codec , version . Codec , "version.Codec was not for v1: %s" , version . Codec )
for k , v := range master . storage {
assert . Contains ( version . Storage , v , "Value %s not found (key: %s)" , k , v )
2015-07-28 08:10:48 +00:00
}
}
2015-08-19 18:02:01 +00:00
2015-09-03 17:35:04 +00:00
// TestNewBootstrapController verifies master fields are properly copied into controller
func TestNewBootstrapController ( t * testing . T ) {
// Tests a subset of inputs to ensure they are set properly in the controller
master , _ , assert := setUp ( t )
portRange := util . PortRange { Base : 10 , Size : 10 }
master . namespaceRegistry = namespace . NewRegistry ( nil )
master . serviceRegistry = registrytest . NewServiceRegistry ( )
master . endpointRegistry = endpoint . NewRegistry ( nil )
master . serviceNodePortRange = portRange
master . masterCount = 1
master . serviceReadWritePort = 1000
master . publicReadWritePort = 1010
controller := master . NewBootstrapController ( )
assert . Equal ( controller . NamespaceRegistry , master . namespaceRegistry )
assert . Equal ( controller . EndpointRegistry , master . endpointRegistry )
assert . Equal ( controller . ServiceRegistry , master . serviceRegistry )
assert . Equal ( controller . ServiceNodePortRange , portRange )
assert . Equal ( controller . MasterCount , master . masterCount )
assert . Equal ( controller . ServicePort , master . serviceReadWritePort )
assert . Equal ( controller . PublicServicePort , master . publicReadWritePort )
}
// TestNewHandlerContainer verifies that NewHandlerContainer uses the
// mux provided
func TestNewHandlerContainer ( t * testing . T ) {
assert := assert . New ( t )
mux := http . NewServeMux ( )
container := NewHandlerContainer ( mux )
assert . Equal ( mux , container . ServeMux , "ServerMux's do not match" )
}
// TestHandleWithAuth verifies HandleWithAuth adds the path
// to the muxHelper.RegisteredPaths.
func TestHandleWithAuth ( t * testing . T ) {
master , _ , assert := setUp ( t )
mh := apiserver . MuxHelper { Mux : http . NewServeMux ( ) }
master . muxHelper = & mh
handler := func ( r http . ResponseWriter , w * http . Request ) { w . Write ( nil ) }
master . HandleWithAuth ( "/test" , http . HandlerFunc ( handler ) )
assert . Contains ( master . muxHelper . RegisteredPaths , "/test" , "Path not found in muxHelper" )
}
// TestHandleFuncWithAuth verifies HandleFuncWithAuth adds the path
// to the muxHelper.RegisteredPaths.
func TestHandleFuncWithAuth ( t * testing . T ) {
master , _ , assert := setUp ( t )
mh := apiserver . MuxHelper { Mux : http . NewServeMux ( ) }
master . muxHelper = & mh
handler := func ( r http . ResponseWriter , w * http . Request ) { w . Write ( nil ) }
master . HandleFuncWithAuth ( "/test" , handler )
assert . Contains ( master . muxHelper . RegisteredPaths , "/test" , "Path not found in muxHelper" )
}
// TestInstallSwaggerAPI verifies that the swagger api is added
// at the proper endpoint.
func TestInstallSwaggerAPI ( t * testing . T ) {
master , _ , assert := setUp ( t )
mux := http . NewServeMux ( )
master . handlerContainer = NewHandlerContainer ( mux )
// Ensure swagger isn't installed without the call
ws := master . handlerContainer . RegisteredWebServices ( )
if ! assert . Equal ( len ( ws ) , 0 ) {
for x := range ws {
assert . NotEqual ( "/swaggerapi" , ws [ x ] . RootPath ( ) , "SwaggerAPI was installed without a call to InstallSwaggerAPI()" )
}
}
// Install swagger and test
master . InstallSwaggerAPI ( )
ws = master . handlerContainer . RegisteredWebServices ( )
if assert . NotEqual ( 0 , len ( ws ) , "SwaggerAPI not installed." ) {
assert . Equal ( "/swaggerapi/" , ws [ 0 ] . RootPath ( ) , "SwaggerAPI did not install to the proper path. %s != /swaggerapi" , ws [ 0 ] . RootPath ( ) )
}
// Empty externalHost verification
mux = http . NewServeMux ( )
master . handlerContainer = NewHandlerContainer ( mux )
master . externalHost = ""
master . clusterIP = net . IPv4 ( 10 , 10 , 10 , 10 )
master . publicReadWritePort = 1010
master . InstallSwaggerAPI ( )
if assert . NotEqual ( 0 , len ( ws ) , "SwaggerAPI not installed." ) {
assert . Equal ( "/swaggerapi/" , ws [ 0 ] . RootPath ( ) , "SwaggerAPI did not install to the proper path. %s != /swaggerapi" , ws [ 0 ] . RootPath ( ) )
}
}
// TestDefaultAPIGroupVersion verifies that the unexported defaultAPIGroupVersion
// creates the expected APIGroupVersion based off of master.
func TestDefaultAPIGroupVersion ( t * testing . T ) {
master , _ , assert := setUp ( t )
master . dialer = func ( network , addr string ) ( net . Conn , error ) { return nil , nil }
apiGroup := master . defaultAPIGroupVersion ( )
assert . Equal ( apiGroup . Root , master . apiPrefix )
assert . Equal ( apiGroup . Admit , master . admissionControl )
assert . Equal ( apiGroup . Context , master . requestContextMapper )
assert . Equal ( apiGroup . MinRequestTimeout , master . minRequestTimeout )
// These functions should be different instances of the same function
groupDialerFunc := fmt . Sprintf ( "%+v" , apiGroup . ProxyDialerFn )
masterDialerFunc := fmt . Sprintf ( "%+v" , master . dialer )
assert . Equal ( groupDialerFunc , masterDialerFunc )
}
// TestExpapi verifies that the unexported exapi creates
// the an experimental api APIGroupVersion.
func TestExpapi ( t * testing . T ) {
master , config , assert := setUp ( t )
2015-09-09 22:46:06 +00:00
expAPIGroup := master . experimental ( & config )
2015-09-17 05:15:05 +00:00
assert . Equal ( expAPIGroup . Root , master . apiGroupPrefix )
2015-09-12 22:27:05 +00:00
assert . Equal ( expAPIGroup . Mapper , latest . GroupOrDie ( "experimental" ) . RESTMapper )
assert . Equal ( expAPIGroup . Codec , latest . GroupOrDie ( "experimental" ) . Codec )
assert . Equal ( expAPIGroup . Linker , latest . GroupOrDie ( "experimental" ) . SelfLinker )
2015-09-17 05:15:05 +00:00
assert . Equal ( expAPIGroup . Version , latest . GroupOrDie ( "experimental" ) . GroupVersion )
2015-09-03 17:35:04 +00:00
}
// TestSecondsSinceSync verifies that proper results are returned
// when checking the time between syncs
func TestSecondsSinceSync ( t * testing . T ) {
master , _ , assert := setUp ( t )
master . lastSync = time . Date ( 2015 , time . January , 1 , 1 , 1 , 1 , 1 , time . UTC ) . Unix ( )
// Nano Second. No difference.
master . clock = & util . FakeClock { Time : time . Date ( 2015 , time . January , 1 , 1 , 1 , 1 , 2 , time . UTC ) }
assert . Equal ( int64 ( 0 ) , master . secondsSinceSync ( ) )
// Second
master . clock = & util . FakeClock { Time : time . Date ( 2015 , time . January , 1 , 1 , 1 , 2 , 1 , time . UTC ) }
assert . Equal ( int64 ( 1 ) , master . secondsSinceSync ( ) )
// Minute
master . clock = & util . FakeClock { Time : time . Date ( 2015 , time . January , 1 , 1 , 2 , 1 , 1 , time . UTC ) }
assert . Equal ( int64 ( 60 ) , master . secondsSinceSync ( ) )
// Hour
master . clock = & util . FakeClock { Time : time . Date ( 2015 , time . January , 1 , 2 , 1 , 1 , 1 , time . UTC ) }
assert . Equal ( int64 ( 3600 ) , master . secondsSinceSync ( ) )
// Day
master . clock = & util . FakeClock { Time : time . Date ( 2015 , time . January , 2 , 1 , 1 , 1 , 1 , time . UTC ) }
assert . Equal ( int64 ( 86400 ) , master . secondsSinceSync ( ) )
// Month
master . clock = & util . FakeClock { Time : time . Date ( 2015 , time . February , 1 , 1 , 1 , 1 , 1 , time . UTC ) }
assert . Equal ( int64 ( 2678400 ) , master . secondsSinceSync ( ) )
// Future Month. Should be -Month.
master . lastSync = time . Date ( 2015 , time . February , 1 , 1 , 1 , 1 , 1 , time . UTC ) . Unix ( )
master . clock = & util . FakeClock { Time : time . Date ( 2015 , time . January , 1 , 1 , 1 , 1 , 1 , time . UTC ) }
assert . Equal ( int64 ( - 2678400 ) , master . secondsSinceSync ( ) )
}
// TestGetNodeAddresses verifies that proper results are returned
// when requesting node addresses.
func TestGetNodeAddresses ( t * testing . T ) {
master , _ , assert := setUp ( t )
// Fail case (no addresses associated with nodes)
nodes , _ := master . nodeRegistry . ListNodes ( api . NewDefaultContext ( ) , labels . Everything ( ) , fields . Everything ( ) )
addrs , err := master . getNodeAddresses ( )
assert . Error ( err , "getNodeAddresses should have caused an error as there are no addresses." )
assert . Equal ( [ ] string ( nil ) , addrs )
// Pass case with External type IP
nodes , _ = master . nodeRegistry . ListNodes ( api . NewDefaultContext ( ) , labels . Everything ( ) , fields . Everything ( ) )
for index := range nodes . Items {
nodes . Items [ index ] . Status . Addresses = [ ] api . NodeAddress { { Type : api . NodeExternalIP , Address : "127.0.0.1" } }
}
addrs , err = master . getNodeAddresses ( )
assert . NoError ( err , "getNodeAddresses should not have returned an error." )
assert . Equal ( [ ] string { "127.0.0.1" , "127.0.0.1" } , addrs )
// Pass case with LegacyHost type IP
nodes , _ = master . nodeRegistry . ListNodes ( api . NewDefaultContext ( ) , labels . Everything ( ) , fields . Everything ( ) )
for index := range nodes . Items {
nodes . Items [ index ] . Status . Addresses = [ ] api . NodeAddress { { Type : api . NodeLegacyHostIP , Address : "127.0.0.2" } }
}
addrs , err = master . getNodeAddresses ( )
assert . NoError ( err , "getNodeAddresses failback should not have returned an error." )
assert . Equal ( [ ] string { "127.0.0.2" , "127.0.0.2" } , addrs )
}
// TestRefreshTunnels verifies that the function errors when no addresses
// are associated with nodes
func TestRefreshTunnels ( t * testing . T ) {
master , _ , assert := setUp ( t )
// Fail case (no addresses associated with nodes)
assert . Error ( master . refreshTunnels ( "test" , "/tmp/undefined" ) )
// TODO: pass case without needing actual connections?
}
// TestIsTunnelSyncHealthy verifies that the 600 second lag test
// is honored.
func TestIsTunnelSyncHealthy ( t * testing . T ) {
master , _ , assert := setUp ( t )
// Pass case: 540 second lag
master . lastSync = time . Date ( 2015 , time . January , 1 , 1 , 1 , 1 , 1 , time . UTC ) . Unix ( )
master . clock = & util . FakeClock { Time : time . Date ( 2015 , time . January , 1 , 1 , 9 , 1 , 1 , time . UTC ) }
err := master . IsTunnelSyncHealthy ( nil )
assert . NoError ( err , "IsTunnelSyncHealthy() should not have returned an error." )
// Fail case: 720 second lag
master . clock = & util . FakeClock { Time : time . Date ( 2015 , time . January , 1 , 1 , 12 , 1 , 1 , time . UTC ) }
err = master . IsTunnelSyncHealthy ( nil )
assert . Error ( err , "IsTunnelSyncHealthy() should have returned an error." )
}
// generateTempFile creates a temporary file path
func generateTempFilePath ( prefix string ) string {
tmpPath , _ := filepath . Abs ( fmt . Sprintf ( "%s/%s-%d" , os . TempDir ( ) , prefix , time . Now ( ) . Unix ( ) ) )
return tmpPath
}
// TestGenerateSSHKey verifies that SSH key generation does indeed
// generate keys even with keys already exist.
func TestGenerateSSHKey ( t * testing . T ) {
master , _ , assert := setUp ( t )
privateKey := generateTempFilePath ( "private" )
publicKey := generateTempFilePath ( "public" )
// Make sure we have no test keys laying around
os . Remove ( privateKey )
os . Remove ( publicKey )
// Pass case: Sunny day case
err := master . generateSSHKey ( "unused" , privateKey , publicKey )
assert . NoError ( err , "generateSSHKey should not have retuend an error: %s" , err )
// Pass case: PrivateKey exists test case
os . Remove ( publicKey )
err = master . generateSSHKey ( "unused" , privateKey , publicKey )
assert . NoError ( err , "generateSSHKey should not have retuend an error: %s" , err )
// Pass case: PublicKey exists test case
os . Remove ( privateKey )
err = master . generateSSHKey ( "unused" , privateKey , publicKey )
assert . NoError ( err , "generateSSHKey should not have retuend an error: %s" , err )
// Make sure we have no test keys laying around
os . Remove ( privateKey )
os . Remove ( publicKey )
// TODO: testing error cases where the file can not be removed?
}
2015-09-28 18:08:47 +00:00
func TestDiscoveryAtAPIS ( t * testing . T ) {
master , config , assert := setUp ( t )
master . exp = true
// ================= preparation for master.init() ======================
portRange := util . PortRange { Base : 10 , Size : 10 }
master . serviceNodePortRange = portRange
_ , ipnet , err := net . ParseCIDR ( "192.168.1.1/24" )
if ! assert . NoError ( err ) {
t . Errorf ( "unexpected error: %v" , err )
}
master . serviceClusterIPRange = ipnet
mh := apiserver . MuxHelper { Mux : http . NewServeMux ( ) }
master . muxHelper = & mh
master . rootWebService = new ( restful . WebService )
master . handlerContainer = restful . NewContainer ( )
master . mux = http . NewServeMux ( )
master . requestContextMapper = api . NewRequestContextMapper ( )
// ======================= end of preparation ===========================
master . init ( & config )
server := httptest . NewServer ( master . handlerContainer . ServeMux )
resp , err := http . Get ( server . URL + "/apis" )
if ! assert . NoError ( err ) {
t . Errorf ( "unexpected error: %v" , err )
}
assert . Equal ( http . StatusOK , resp . StatusCode )
groupList := api . APIGroupList { }
assert . NoError ( decodeResponse ( resp , & groupList ) )
if err != nil {
t . Fatalf ( "unexpected error: %v" , err )
}
expectGroupName := "experimental"
expectVersions := [ ] api . GroupVersion {
{
GroupVersion : testapi . Experimental . GroupAndVersion ( ) ,
Version : testapi . Experimental . Version ( ) ,
} ,
}
expectPreferredVersion := api . GroupVersion {
GroupVersion : config . StorageVersions [ "experimental" ] ,
Version : apiutil . GetVersion ( config . StorageVersions [ "experimental" ] ) ,
}
assert . Equal ( expectGroupName , groupList . Groups [ 0 ] . Name )
assert . Equal ( expectVersions , groupList . Groups [ 0 ] . Versions )
assert . Equal ( expectPreferredVersion , groupList . Groups [ 0 ] . PreferredVersion )
}
2015-09-01 05:28:08 +00:00
var versionsToTest = [ ] string { "v1" , "v3" }
2015-08-20 05:08:26 +00:00
type Foo struct {
2015-09-09 21:59:11 +00:00
unversioned . TypeMeta ` json:",inline" `
api . ObjectMeta ` json:"metadata,omitempty" description:"standard object metadata" `
2015-08-20 05:08:26 +00:00
SomeField string ` json:"someField" `
OtherField int ` json:"otherField" `
}
type FooList struct {
2015-09-09 21:59:11 +00:00
unversioned . TypeMeta ` json:",inline" `
unversioned . ListMeta ` json:"metadata,omitempty" description:"standard list metadata; see http://releases.k8s.io/HEAD/docs/devel/api-conventions.md#metadata" `
2015-08-20 05:08:26 +00:00
2015-09-09 21:36:19 +00:00
Items [ ] Foo ` json:"items" `
2015-08-20 05:08:26 +00:00
}
2015-09-09 21:36:19 +00:00
func initThirdParty ( t * testing . T , version string ) ( * Master , * tools . FakeEtcdClient , * httptest . Server , * assert . Assertions ) {
2015-09-03 17:35:04 +00:00
master , _ , assert := setUp ( t )
2015-09-09 21:36:19 +00:00
master . thirdPartyResources = map [ string ] * thirdpartyresourcedatastorage . REST { }
2015-09-09 22:46:06 +00:00
api := & experimental . ThirdPartyResource {
2015-08-19 18:02:01 +00:00
ObjectMeta : api . ObjectMeta {
2015-08-20 05:08:26 +00:00
Name : "foo.company.com" ,
2015-08-19 18:02:01 +00:00
} ,
2015-09-09 22:46:06 +00:00
Versions : [ ] experimental . APIVersion {
2015-08-20 05:08:26 +00:00
{
2015-08-19 18:02:01 +00:00
APIGroup : "group" ,
2015-09-01 05:28:08 +00:00
Name : version ,
2015-08-19 18:02:01 +00:00
} ,
} ,
}
master . handlerContainer = restful . NewContainer ( )
2015-08-20 05:08:26 +00:00
fakeClient := tools . NewFakeEtcdClient ( t )
fakeClient . Machines = [ ] string { "http://machine1:4001" , "http://machine2" , "http://machine3:4003" }
2015-09-11 06:37:26 +00:00
master . thirdPartyStorage = etcdstorage . NewEtcdStorage ( fakeClient , testapi . Experimental . Codec ( ) , etcdtest . PathPrefix ( ) )
2015-08-20 05:08:26 +00:00
2015-09-09 21:36:19 +00:00
if ! assert . NoError ( master . InstallThirdPartyResource ( api ) ) {
2015-08-20 05:08:26 +00:00
t . FailNow ( )
2015-08-19 18:02:01 +00:00
}
2015-08-20 05:08:26 +00:00
2015-08-19 18:02:01 +00:00
server := httptest . NewServer ( master . handlerContainer . ServeMux )
2015-09-09 21:36:19 +00:00
return & master , fakeClient , server , assert
2015-08-20 05:08:26 +00:00
}
func TestInstallThirdPartyAPIList ( t * testing . T ) {
2015-09-01 05:28:08 +00:00
for _ , version := range versionsToTest {
testInstallThirdPartyAPIListVersion ( t , version )
}
}
func testInstallThirdPartyAPIListVersion ( t * testing . T , version string ) {
2015-09-09 21:36:19 +00:00
tests := [ ] struct {
items [ ] Foo
} {
{ } ,
{
items : [ ] Foo { } ,
} ,
{
items : [ ] Foo {
{
ObjectMeta : api . ObjectMeta {
Name : "test" ,
} ,
TypeMeta : unversioned . TypeMeta {
Kind : "Foo" ,
APIVersion : version ,
} ,
SomeField : "test field" ,
OtherField : 10 ,
} ,
{
ObjectMeta : api . ObjectMeta {
Name : "bar" ,
} ,
TypeMeta : unversioned . TypeMeta {
Kind : "Foo" ,
APIVersion : version ,
} ,
SomeField : "test field another" ,
OtherField : 20 ,
} ,
} ,
} ,
}
for _ , test := range tests {
_ , fakeClient , server , assert := initThirdParty ( t , version )
defer server . Close ( )
2015-08-20 05:08:26 +00:00
2015-09-09 21:36:19 +00:00
if test . items == nil {
fakeClient . ExpectNotFoundGet ( etcdtest . PathPrefix ( ) + "/ThirdPartyResourceData/company.com/foos/default" )
} else {
setupEtcdList ( fakeClient , "/ThirdPartyResourceData/company.com/foos/default" , test . items )
}
2015-08-20 05:08:26 +00:00
2015-09-09 21:36:19 +00:00
resp , err := http . Get ( server . URL + "/apis/company.com/" + version + "/namespaces/default/foos" )
if ! assert . NoError ( err ) {
return
}
defer resp . Body . Close ( )
2015-09-03 17:35:04 +00:00
2015-09-09 21:36:19 +00:00
assert . Equal ( http . StatusOK , resp . StatusCode )
2015-08-20 05:08:26 +00:00
2015-09-09 21:36:19 +00:00
data , err := ioutil . ReadAll ( resp . Body )
assert . NoError ( err )
2015-08-20 05:08:26 +00:00
2015-09-09 21:36:19 +00:00
list := FooList { }
if err = json . Unmarshal ( data , & list ) ; err != nil {
t . Errorf ( "unexpected error: %v" , err )
}
2015-08-20 05:08:26 +00:00
2015-09-09 21:36:19 +00:00
if test . items == nil {
if len ( list . Items ) != 0 {
t . Errorf ( "expected no items, saw: %v" , list . Items )
}
continue
}
2015-09-03 17:35:04 +00:00
2015-09-09 21:36:19 +00:00
if len ( list . Items ) != len ( test . items ) {
t . Errorf ( "unexpected length: %d vs %d" , len ( list . Items ) , len ( test . items ) )
continue
}
for ix := range list . Items {
// Copy things that are set dynamically on the server
expectedObj := test . items [ ix ]
expectedObj . SelfLink = list . Items [ ix ] . SelfLink
expectedObj . Namespace = list . Items [ ix ] . Namespace
expectedObj . UID = list . Items [ ix ] . UID
expectedObj . CreationTimestamp = list . Items [ ix ] . CreationTimestamp
if ! reflect . DeepEqual ( list . Items [ ix ] , expectedObj ) {
t . Errorf ( "expected:\n%#v\nsaw:\n%#v\n" , expectedObj , list . Items [ ix ] )
}
}
}
2015-08-20 05:08:26 +00:00
}
func encodeToThirdParty ( name string , obj interface { } ) ( [ ] byte , error ) {
serial , err := json . Marshal ( obj )
if err != nil {
return nil , err
}
2015-09-09 22:46:06 +00:00
thirdPartyData := experimental . ThirdPartyResourceData {
2015-08-20 05:08:26 +00:00
ObjectMeta : api . ObjectMeta { Name : name } ,
Data : serial ,
}
2015-09-22 17:53:30 +00:00
return testapi . Experimental . Codec ( ) . Encode ( & thirdPartyData )
2015-08-20 05:08:26 +00:00
}
func storeToEtcd ( fakeClient * tools . FakeEtcdClient , path , name string , obj interface { } ) error {
data , err := encodeToThirdParty ( name , obj )
if err != nil {
return err
}
_ , err = fakeClient . Set ( etcdtest . PathPrefix ( ) + path , string ( data ) , 0 )
return err
}
2015-09-09 21:36:19 +00:00
func setupEtcdList ( fakeClient * tools . FakeEtcdClient , path string , list [ ] Foo ) error {
resp := tools . EtcdResponseWithError {
R : & etcd . Response {
Node : & etcd . Node { } ,
} ,
}
for _ , obj := range list {
data , err := encodeToThirdParty ( obj . Name , obj )
if err != nil {
return err
}
resp . R . Node . Nodes = append ( resp . R . Node . Nodes , & etcd . Node { Value : string ( data ) } )
}
fakeClient . Data [ etcdtest . PathPrefix ( ) + path ] = resp
return nil
}
2015-08-20 05:08:26 +00:00
func decodeResponse ( resp * http . Response , obj interface { } ) error {
defer resp . Body . Close ( )
data , err := ioutil . ReadAll ( resp . Body )
if err != nil {
return err
}
if err := json . Unmarshal ( data , obj ) ; err != nil {
return err
}
return nil
}
func TestInstallThirdPartyAPIGet ( t * testing . T ) {
2015-09-01 05:28:08 +00:00
for _ , version := range versionsToTest {
testInstallThirdPartyAPIGetVersion ( t , version )
}
}
func testInstallThirdPartyAPIGetVersion ( t * testing . T , version string ) {
2015-09-09 21:36:19 +00:00
_ , fakeClient , server , assert := initThirdParty ( t , version )
2015-08-20 05:08:26 +00:00
defer server . Close ( )
expectedObj := Foo {
ObjectMeta : api . ObjectMeta {
Name : "test" ,
} ,
2015-09-09 21:59:11 +00:00
TypeMeta : unversioned . TypeMeta {
2015-09-01 05:28:08 +00:00
Kind : "Foo" ,
APIVersion : version ,
2015-08-20 05:08:26 +00:00
} ,
SomeField : "test field" ,
OtherField : 10 ,
}
2015-09-03 17:35:04 +00:00
if ! assert . NoError ( storeToEtcd ( fakeClient , "/ThirdPartyResourceData/company.com/foos/default/test" , "test" , expectedObj ) ) {
2015-08-20 05:08:26 +00:00
t . FailNow ( )
return
}
2015-09-14 20:37:40 +00:00
resp , err := http . Get ( server . URL + "/apis/company.com/" + version + "/namespaces/default/foos/test" )
2015-09-03 17:35:04 +00:00
if ! assert . NoError ( err ) {
2015-08-20 05:08:26 +00:00
return
}
2015-09-03 17:35:04 +00:00
assert . Equal ( http . StatusOK , resp . StatusCode )
2015-08-20 05:08:26 +00:00
item := Foo { }
2015-09-03 17:35:04 +00:00
assert . NoError ( decodeResponse ( resp , & item ) )
if ! assert . False ( reflect . DeepEqual ( item , expectedObj ) ) {
t . Errorf ( "expected objects to not be equal:\n%v\nsaw:\n%v\n" , expectedObj , item )
2015-08-20 05:08:26 +00:00
}
2015-09-03 00:13:38 +00:00
// Fill in data that the apiserver injects
expectedObj . SelfLink = item . SelfLink
2015-09-03 17:35:04 +00:00
if ! assert . True ( reflect . DeepEqual ( item , expectedObj ) ) {
2015-09-03 00:13:38 +00:00
t . Errorf ( "expected:\n%#v\nsaw:\n%#v\n" , expectedObj , item )
2015-08-20 05:08:26 +00:00
}
}
func TestInstallThirdPartyAPIPost ( t * testing . T ) {
2015-09-01 05:28:08 +00:00
for _ , version := range versionsToTest {
testInstallThirdPartyAPIPostForVersion ( t , version )
}
}
func testInstallThirdPartyAPIPostForVersion ( t * testing . T , version string ) {
2015-09-09 21:36:19 +00:00
_ , fakeClient , server , assert := initThirdParty ( t , version )
2015-08-20 05:08:26 +00:00
defer server . Close ( )
inputObj := Foo {
ObjectMeta : api . ObjectMeta {
Name : "test" ,
} ,
2015-09-09 21:59:11 +00:00
TypeMeta : unversioned . TypeMeta {
2015-09-01 05:28:08 +00:00
Kind : "Foo" ,
2015-09-29 21:36:47 +00:00
APIVersion : "company.com/" + version ,
2015-08-20 05:08:26 +00:00
} ,
SomeField : "test field" ,
OtherField : 10 ,
}
2015-08-21 21:24:16 +00:00
data , err := json . Marshal ( inputObj )
2015-09-03 17:35:04 +00:00
if ! assert . NoError ( err ) {
2015-08-20 05:08:26 +00:00
return
}
2015-09-14 20:37:40 +00:00
resp , err := http . Post ( server . URL + "/apis/company.com/" + version + "/namespaces/default/foos" , "application/json" , bytes . NewBuffer ( data ) )
2015-09-03 17:35:04 +00:00
if ! assert . NoError ( err ) {
2015-09-14 20:37:40 +00:00
t . Errorf ( "unexpected error: %v" , err )
2015-08-20 05:08:26 +00:00
return
}
2015-09-03 17:35:04 +00:00
assert . Equal ( http . StatusCreated , resp . StatusCode )
2015-08-20 05:08:26 +00:00
item := Foo { }
2015-09-03 17:35:04 +00:00
assert . NoError ( decodeResponse ( resp , & item ) )
2015-08-20 05:08:26 +00:00
2015-09-03 00:13:38 +00:00
// fill in fields set by the apiserver
expectedObj := inputObj
expectedObj . SelfLink = item . SelfLink
expectedObj . Namespace = item . Namespace
expectedObj . UID = item . UID
expectedObj . CreationTimestamp = item . CreationTimestamp
2015-09-03 17:35:04 +00:00
if ! assert . True ( reflect . DeepEqual ( item , expectedObj ) ) {
2015-09-03 00:13:38 +00:00
t . Errorf ( "expected:\n%v\nsaw:\n%v\n" , expectedObj , item )
2015-08-20 05:08:26 +00:00
}
etcdResp , err := fakeClient . Get ( etcdtest . PathPrefix ( ) + "/ThirdPartyResourceData/company.com/foos/default/test" , false , false )
2015-09-03 17:35:04 +00:00
if ! assert . NoError ( err ) {
2015-09-01 05:28:08 +00:00
t . FailNow ( )
2015-08-20 05:08:26 +00:00
}
2015-09-03 17:35:04 +00:00
2015-09-11 06:37:26 +00:00
obj , err := testapi . Experimental . Codec ( ) . Decode ( [ ] byte ( etcdResp . Node . Value ) )
if err != nil {
t . Errorf ( "unexpected error: %v" , err )
}
2015-09-09 22:46:06 +00:00
thirdPartyObj , ok := obj . ( * experimental . ThirdPartyResourceData )
2015-09-11 06:37:26 +00:00
if ! ok {
2015-08-20 05:08:26 +00:00
t . Errorf ( "unexpected object: %v" , obj )
}
item = Foo { }
2015-09-03 17:35:04 +00:00
assert . NoError ( json . Unmarshal ( thirdPartyObj . Data , & item ) )
2015-08-20 05:08:26 +00:00
2015-09-03 17:35:04 +00:00
if ! assert . True ( reflect . DeepEqual ( item , inputObj ) ) {
2015-08-20 05:08:26 +00:00
t . Errorf ( "expected:\n%v\nsaw:\n%v\n" , inputObj , item )
}
}
func TestInstallThirdPartyAPIDelete ( t * testing . T ) {
2015-09-01 05:28:08 +00:00
for _ , version := range versionsToTest {
testInstallThirdPartyAPIDeleteVersion ( t , version )
}
}
func testInstallThirdPartyAPIDeleteVersion ( t * testing . T , version string ) {
2015-09-09 21:36:19 +00:00
_ , fakeClient , server , assert := initThirdParty ( t , version )
2015-08-20 05:08:26 +00:00
defer server . Close ( )
expectedObj := Foo {
ObjectMeta : api . ObjectMeta {
2015-09-03 00:13:38 +00:00
Name : "test" ,
Namespace : "default" ,
2015-08-20 05:08:26 +00:00
} ,
2015-09-09 21:59:11 +00:00
TypeMeta : unversioned . TypeMeta {
2015-08-21 21:24:16 +00:00
Kind : "Foo" ,
2015-08-20 05:08:26 +00:00
} ,
SomeField : "test field" ,
OtherField : 10 ,
}
2015-09-03 17:35:04 +00:00
if ! assert . NoError ( storeToEtcd ( fakeClient , "/ThirdPartyResourceData/company.com/foos/default/test" , "test" , expectedObj ) ) {
2015-08-20 05:08:26 +00:00
t . FailNow ( )
return
}
2015-09-14 20:37:40 +00:00
resp , err := http . Get ( server . URL + "/apis/company.com/" + version + "/namespaces/default/foos/test" )
2015-09-03 17:35:04 +00:00
if ! assert . NoError ( err ) {
2015-08-20 05:08:26 +00:00
return
}
2015-09-03 17:35:04 +00:00
assert . Equal ( http . StatusOK , resp . StatusCode )
2015-08-20 05:08:26 +00:00
item := Foo { }
2015-09-03 17:35:04 +00:00
assert . NoError ( decodeResponse ( resp , & item ) )
2015-08-20 05:08:26 +00:00
2015-09-03 00:13:38 +00:00
// Fill in fields set by the apiserver
expectedObj . SelfLink = item . SelfLink
expectedObj . Namespace = item . Namespace
2015-09-03 17:35:04 +00:00
if ! assert . True ( reflect . DeepEqual ( item , expectedObj ) ) {
2015-08-20 05:08:26 +00:00
t . Errorf ( "expected:\n%v\nsaw:\n%v\n" , expectedObj , item )
}
2015-09-14 20:37:40 +00:00
resp , err = httpDelete ( server . URL + "/apis/company.com/" + version + "/namespaces/default/foos/test" )
2015-09-03 17:35:04 +00:00
if ! assert . NoError ( err ) {
2015-08-20 05:08:26 +00:00
return
}
2015-09-03 17:35:04 +00:00
assert . Equal ( http . StatusOK , resp . StatusCode )
2015-08-20 05:08:26 +00:00
2015-09-14 20:37:40 +00:00
resp , err = http . Get ( server . URL + "/apis/company.com/" + version + "/namespaces/default/foos/test" )
2015-09-03 17:35:04 +00:00
if ! assert . NoError ( err ) {
2015-08-20 05:08:26 +00:00
return
}
2015-09-03 17:35:04 +00:00
assert . Equal ( http . StatusNotFound , resp . StatusCode )
2015-08-20 05:08:26 +00:00
expectDeletedKeys := [ ] string { etcdtest . PathPrefix ( ) + "/ThirdPartyResourceData/company.com/foos/default/test" }
2015-09-03 17:35:04 +00:00
if ! assert . True ( reflect . DeepEqual ( fakeClient . DeletedKeys , expectDeletedKeys ) ) {
2015-08-20 05:08:26 +00:00
t . Errorf ( "unexpected deleted keys: %v" , fakeClient . DeletedKeys )
}
}
func httpDelete ( url string ) ( * http . Response , error ) {
req , err := http . NewRequest ( "DELETE" , url , nil )
if err != nil {
return nil , err
2015-08-19 18:02:01 +00:00
}
2015-08-20 05:08:26 +00:00
client := & http . Client { }
return client . Do ( req )
2015-08-19 18:02:01 +00:00
}
2015-09-09 21:36:19 +00:00
func TestInstallThirdPartyResourceRemove ( t * testing . T ) {
for _ , version := range versionsToTest {
testInstallThirdPartyResourceRemove ( t , version )
}
}
func testInstallThirdPartyResourceRemove ( t * testing . T , version string ) {
master , fakeClient , server , assert := initThirdParty ( t , version )
defer server . Close ( )
expectedObj := Foo {
ObjectMeta : api . ObjectMeta {
Name : "test" ,
} ,
TypeMeta : unversioned . TypeMeta {
Kind : "Foo" ,
} ,
SomeField : "test field" ,
OtherField : 10 ,
}
if ! assert . NoError ( storeToEtcd ( fakeClient , "/ThirdPartyResourceData/company.com/foos/default/test" , "test" , expectedObj ) ) {
t . FailNow ( )
return
}
secondObj := expectedObj
secondObj . Name = "bar"
if ! assert . NoError ( storeToEtcd ( fakeClient , "/ThirdPartyResourceData/company.com/foos/default/bar" , "bar" , secondObj ) ) {
t . FailNow ( )
return
}
setupEtcdList ( fakeClient , "/ThirdPartyResourceData/company.com/foos/default" , [ ] Foo { expectedObj , secondObj } )
resp , err := http . Get ( server . URL + "/apis/company.com/" + version + "/namespaces/default/foos/test" )
if ! assert . NoError ( err ) {
t . FailNow ( )
return
}
if resp . StatusCode != http . StatusOK {
t . Errorf ( "unexpected status: %v" , resp )
}
item := Foo { }
if err := decodeResponse ( resp , & item ) ; err != nil {
t . Errorf ( "unexpected error: %v" , err )
}
// TODO: validate etcd set things here
item . ObjectMeta = expectedObj . ObjectMeta
if ! assert . True ( reflect . DeepEqual ( item , expectedObj ) ) {
t . Errorf ( "expected:\n%v\nsaw:\n%v\n" , expectedObj , item )
}
path := makeThirdPartyPath ( "company.com" )
master . RemoveThirdPartyResource ( path )
resp , err = http . Get ( server . URL + "/apis/company.com/" + version + "/namespaces/default/foos/test" )
if ! assert . NoError ( err ) {
return
}
if resp . StatusCode != http . StatusNotFound {
t . Errorf ( "unexpected status: %v" , resp )
}
expectDeletedKeys := [ ] string {
etcdtest . PathPrefix ( ) + "/ThirdPartyResourceData/company.com/foos/default/test" ,
etcdtest . PathPrefix ( ) + "/ThirdPartyResourceData/company.com/foos/default/bar" ,
}
if ! assert . True ( reflect . DeepEqual ( fakeClient . DeletedKeys , expectDeletedKeys ) ) {
t . Errorf ( "unexpected deleted keys: %v" , fakeClient . DeletedKeys )
}
installed := master . ListThirdPartyResources ( )
if len ( installed ) != 0 {
t . Errorf ( "Resource(s) still installed: %v" , installed )
}
services := master . handlerContainer . RegisteredWebServices ( )
for ix := range services {
if strings . HasPrefix ( services [ ix ] . RootPath ( ) , "/apis/company.com" ) {
t . Errorf ( "Web service still installed at %s: %#v" , services [ ix ] . RootPath ( ) , services [ ix ] )
}
}
}