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"
2015-10-09 05:18:16 +00:00
"crypto/tls"
2015-08-20 05:08:26 +00:00
"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-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-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-10-09 22:04:41 +00:00
"k8s.io/kubernetes/pkg/apis/extensions"
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/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"
2015-11-10 11:23:51 +00:00
"k8s.io/kubernetes/pkg/storage/etcd/etcdtest"
2015-11-17 13:35:00 +00:00
etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/tools"
2015-09-03 17:35:04 +00:00
"k8s.io/kubernetes/pkg/util"
2015-11-10 06:28:45 +00:00
"k8s.io/kubernetes/pkg/util/intstr"
2015-09-09 21:36:19 +00:00
"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.
2015-11-17 13:35:00 +00:00
func setUp ( t * testing . T ) ( Master , * etcdtesting . EtcdTestServer , Config , * assert . Assertions ) {
server := etcdtesting . NewEtcdTestClientServer ( t )
2014-11-02 20:52:31 +00:00
master := Master { }
config := Config { }
2015-09-15 03:55:18 +00:00
storageVersions := make ( map [ string ] string )
2015-09-30 07:56:51 +00:00
storageDestinations := NewStorageDestinations ( )
2015-11-17 13:35:00 +00:00
storageDestinations . AddAPIGroup (
"" , etcdstorage . NewEtcdStorage ( server . Client , testapi . Default . Codec ( ) , etcdtest . PathPrefix ( ) ) )
storageDestinations . AddAPIGroup (
"extensions" , etcdstorage . NewEtcdStorage ( server . Client , testapi . Extensions . Codec ( ) , etcdtest . PathPrefix ( ) ) )
2015-09-30 07:56:51 +00:00
config . StorageDestinations = storageDestinations
2015-09-15 03:55:18 +00:00
storageVersions [ "" ] = testapi . Default . Version ( )
2015-10-09 22:15:35 +00:00
storageVersions [ "extensions" ] = testapi . Extensions . 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-11-17 13:35:00 +00:00
return master , server , config , assert . New ( t )
2015-09-03 17:35:04 +00:00
}
// TestNew verifies that the New function returns a Master
// using the configuration properly.
func TestNew ( t * testing . T ) {
2015-11-17 13:35:00 +00:00
_ , etcdserver , config , assert := setUp ( t )
defer etcdserver . Terminate ( t )
2015-10-09 05:18:16 +00:00
2015-09-03 17:35:04 +00:00
config . KubeletClient = client . FakeKubeletClient { }
2015-10-09 05:18:16 +00:00
config . ProxyDialer = func ( network , addr string ) ( net . Conn , error ) { return nil , nil }
config . ProxyTLSClientConfig = & tls . Config { }
2015-09-03 17:35:04 +00:00
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 )
2015-10-13 00:40:37 +00:00
assert . Equal ( master . apiGroupVersionOverrides , config . APIGroupVersionOverrides )
2015-09-03 17:35:04 +00:00
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 )
2015-10-09 05:18:16 +00:00
assert . Equal ( master . tunneler , config . Tunneler )
// These functions should point to the same memory location
masterDialer , _ := util . Dialer ( master . proxyTransport )
masterDialerFunc := fmt . Sprintf ( "%p" , masterDialer )
configDialerFunc := fmt . Sprintf ( "%p" , config . ProxyDialer )
assert . Equal ( masterDialerFunc , configDialerFunc )
assert . Equal ( master . proxyTransport . ( * http . Transport ) . TLSClientConfig , config . ProxyTLSClientConfig )
2015-09-03 17:35:04 +00:00
}
// TestNewEtcdStorage verifies that the usage of NewEtcdStorage reacts properly when
// the correct data is input
func TestNewEtcdStorage ( t * testing . T ) {
2015-11-17 13:35:00 +00:00
etcdserver := etcdtesting . NewEtcdTestClientServer ( t )
defer etcdserver . Terminate ( t )
2015-09-03 17:35:04 +00:00
assert := assert . New ( t )
// Pass case
2015-11-17 13:35:00 +00:00
_ , err := NewEtcdStorage ( etcdserver . Client , 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-11-17 13:35:00 +00:00
_ , err = NewEtcdStorage ( etcdserver . Client , 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 ) {
2015-11-17 13:35:00 +00:00
master , etcdserver , config , assert := setUp ( t )
defer etcdserver . Terminate ( t )
2015-05-14 00:29:25 +00:00
servers := master . getServersToValidate ( & config )
2015-05-04 21:17:35 +00:00
2015-11-17 13:35:00 +00:00
// Expected servers to validate: scheduler, controller-manager and etcd.
assert . Equal ( 3 , len ( servers ) , "unexpected server list: %#v" , servers )
2015-09-03 17:35:04 +00:00
2015-11-17 13:35:00 +00:00
for _ , server := range [ ] string { "scheduler" , "controller-manager" , "etcd-0" } {
2015-05-04 21:17:35 +00:00
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 ) {
2015-11-17 13:35:00 +00:00
master , etcdserver , _ , assert := setUp ( t )
defer etcdserver . Terminate ( t )
2015-09-03 17:35:04 +00:00
version := master . api_v1 ( )
2015-11-12 20:20:20 +00:00
assert . Equal ( unversioned . GroupVersion { Version : "v1" } , version . GroupVersion , "Version was not v1: %s" , version . GroupVersion )
2015-09-03 17:35:04 +00:00
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
2015-11-17 13:35:00 +00:00
master , etcdserver , _ , assert := setUp ( t )
defer etcdserver . Terminate ( t )
2015-09-03 17:35:04 +00:00
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 )
}
2015-10-07 15:06:05 +00:00
// TestControllerServicePorts verifies master extraServicePorts are
// correctly copied into controller
func TestControllerServicePorts ( t * testing . T ) {
2015-11-17 13:35:00 +00:00
master , etcdserver , _ , assert := setUp ( t )
defer etcdserver . Terminate ( t )
2015-10-07 15:06:05 +00:00
master . namespaceRegistry = namespace . NewRegistry ( nil )
master . serviceRegistry = registrytest . NewServiceRegistry ( )
master . endpointRegistry = endpoint . NewRegistry ( nil )
master . extraServicePorts = [ ] api . ServicePort {
{
Name : "additional-port-1" ,
Port : 1000 ,
Protocol : api . ProtocolTCP ,
2015-11-10 06:28:45 +00:00
TargetPort : intstr . FromInt ( 1000 ) ,
2015-10-07 15:06:05 +00:00
} ,
{
Name : "additional-port-2" ,
Port : 1010 ,
Protocol : api . ProtocolTCP ,
2015-11-10 06:28:45 +00:00
TargetPort : intstr . FromInt ( 1010 ) ,
2015-10-07 15:06:05 +00:00
} ,
}
controller := master . NewBootstrapController ( )
assert . Equal ( 1000 , controller . ExtraServicePorts [ 0 ] . Port )
assert . Equal ( 1010 , controller . ExtraServicePorts [ 1 ] . Port )
}
2015-09-03 17:35:04 +00:00
// 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 ) {
2015-11-17 13:35:00 +00:00
master , etcdserver , _ , assert := setUp ( t )
defer etcdserver . Terminate ( t )
2015-09-03 17:35:04 +00:00
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 ) {
2015-11-17 13:35:00 +00:00
master , etcdserver , _ , assert := setUp ( t )
defer etcdserver . Terminate ( t )
2015-09-03 17:35:04 +00:00
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 ) {
2015-11-17 13:35:00 +00:00
master , etcdserver , _ , assert := setUp ( t )
defer etcdserver . Terminate ( t )
2015-09-03 17:35:04 +00:00
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 ) {
2015-11-17 13:35:00 +00:00
master , etcdserver , _ , assert := setUp ( t )
defer etcdserver . Terminate ( t )
2015-09-03 17:35:04 +00:00
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 )
}
// TestExpapi verifies that the unexported exapi creates
2015-10-09 01:33:22 +00:00
// the an experimental unversioned.APIGroupVersion.
2015-09-03 17:35:04 +00:00
func TestExpapi ( t * testing . T ) {
2015-11-17 13:35:00 +00:00
master , etcdserver , config , assert := setUp ( t )
defer etcdserver . Terminate ( t )
2015-09-03 17:35:04 +00:00
2015-11-12 20:20:20 +00:00
extensionsGroupMeta := latest . GroupOrDie ( "extensions" )
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-11-12 20:20:20 +00:00
assert . Equal ( expAPIGroup . Mapper , extensionsGroupMeta . RESTMapper )
assert . Equal ( expAPIGroup . Codec , extensionsGroupMeta . Codec )
assert . Equal ( expAPIGroup . Linker , extensionsGroupMeta . SelfLinker )
assert . Equal ( expAPIGroup . GroupVersion , unversioned . GroupVersion { Group : extensionsGroupMeta . Group , Version : extensionsGroupMeta . Version } )
2015-09-03 17:35:04 +00:00
}
// TestGetNodeAddresses verifies that proper results are returned
// when requesting node addresses.
func TestGetNodeAddresses ( t * testing . T ) {
2015-11-17 13:35:00 +00:00
master , etcdserver , _ , assert := setUp ( t )
defer etcdserver . Terminate ( t )
2015-09-03 17:35:04 +00:00
// Fail case (no addresses associated with nodes)
2015-10-27 13:47:58 +00:00
nodes , _ := master . nodeRegistry . ListNodes ( api . NewDefaultContext ( ) , nil )
2015-09-03 17:35:04 +00:00
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
2015-10-27 13:47:58 +00:00
nodes , _ = master . nodeRegistry . ListNodes ( api . NewDefaultContext ( ) , nil )
2015-09-03 17:35:04 +00:00
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
2015-10-27 13:47:58 +00:00
nodes , _ = master . nodeRegistry . ListNodes ( api . NewDefaultContext ( ) , nil )
2015-09-03 17:35:04 +00:00
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 )
}
2015-09-28 18:08:47 +00:00
func TestDiscoveryAtAPIS ( t * testing . T ) {
2015-11-17 13:35:00 +00:00
master , etcdserver , config , assert := setUp ( t )
defer etcdserver . Terminate ( t )
2015-09-28 18:08:47 +00:00
// ================= 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 )
2015-10-09 01:33:22 +00:00
groupList := unversioned . APIGroupList { }
2015-09-28 18:08:47 +00:00
assert . NoError ( decodeResponse ( resp , & groupList ) )
if err != nil {
t . Fatalf ( "unexpected error: %v" , err )
}
2015-10-09 22:14:03 +00:00
expectGroupName := "extensions"
2015-11-05 22:52:58 +00:00
expectVersions := [ ] unversioned . GroupVersionForDiscovery {
2015-09-28 18:08:47 +00:00
{
2015-10-09 22:15:35 +00:00
GroupVersion : testapi . Extensions . GroupAndVersion ( ) ,
Version : testapi . Extensions . Version ( ) ,
2015-09-28 18:08:47 +00:00
} ,
}
2015-11-05 22:52:58 +00:00
expectPreferredVersion := unversioned . GroupVersionForDiscovery {
2015-10-09 22:14:03 +00:00
GroupVersion : config . StorageVersions [ "extensions" ] ,
Version : apiutil . GetVersion ( config . StorageVersions [ "extensions" ] ) ,
2015-09-28 18:08:47 +00:00
}
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-11-17 14:21:42 +00:00
func initThirdParty ( t * testing . T , version string ) ( * Master , * etcdtesting . EtcdTestServer , * httptest . Server , * assert . Assertions ) {
2015-11-17 13:35:00 +00:00
master , etcdserver , _ , assert := setUp ( t )
2015-09-09 21:36:19 +00:00
master . thirdPartyResources = map [ string ] * thirdpartyresourcedatastorage . REST { }
2015-10-09 22:49:10 +00:00
api := & extensions . 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-10-09 22:49:10 +00:00
Versions : [ ] extensions . 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-11-17 14:21:42 +00:00
master . thirdPartyStorage = etcdstorage . NewEtcdStorage ( etcdserver . Client , testapi . Extensions . 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-11-17 14:21:42 +00:00
return & master , etcdserver , 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 {
2015-11-17 14:21:42 +00:00
func ( ) {
_ , etcdserver , server , assert := initThirdParty ( t , version )
defer server . Close ( )
defer etcdserver . Terminate ( t )
client := etcdserver . Client
if test . items != nil {
setupEtcdList ( client , "/ThirdPartyResourceData/company.com/foos/default" , test . items )
}
2015-08-20 05:08:26 +00:00
2015-11-17 14:21:42 +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-11-17 14:21:42 +00:00
assert . Equal ( http . StatusOK , resp . StatusCode )
2015-08-20 05:08:26 +00:00
2015-11-17 14:21:42 +00:00
data , err := ioutil . ReadAll ( resp . Body )
assert . NoError ( err )
2015-08-20 05:08:26 +00:00
2015-11-17 14:21:42 +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-11-17 14:21:42 +00:00
if test . items == nil {
if len ( list . Items ) != 0 {
t . Errorf ( "expected no items, saw: %v" , list . Items )
}
return
2015-09-09 21:36:19 +00:00
}
2015-09-03 17:35:04 +00:00
2015-11-17 14:21:42 +00:00
if len ( list . Items ) != len ( test . items ) {
t . Errorf ( "unexpected length: %d vs %d" , len ( list . Items ) , len ( test . items ) )
return
2015-09-09 21:36:19 +00:00
}
2015-11-17 14:21:42 +00:00
// The order of elements in LIST is not guaranteed.
mapping := make ( map [ string ] int )
for ix := range test . items {
mapping [ test . items [ ix ] . Name ] = ix
}
for ix := range list . Items {
// Copy things that are set dynamically on the server
expectedObj := test . items [ mapping [ list . Items [ ix ] . Name ] ]
expectedObj . SelfLink = list . Items [ ix ] . SelfLink
expectedObj . ResourceVersion = list . Items [ ix ] . ResourceVersion
expectedObj . Namespace = list . Items [ ix ] . Namespace
expectedObj . UID = list . Items [ ix ] . UID
expectedObj . CreationTimestamp = list . Items [ ix ] . CreationTimestamp
// We endure the order of items by sorting them (using 'mapping')
// so that this function passes.
if ! reflect . DeepEqual ( list . Items [ ix ] , expectedObj ) {
t . Errorf ( "expected:\n%#v\nsaw:\n%#v\n" , expectedObj , list . Items [ ix ] )
}
}
} ( )
2015-09-09 21:36:19 +00:00
}
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-10-09 22:49:10 +00:00
thirdPartyData := extensions . ThirdPartyResourceData {
2015-08-20 05:08:26 +00:00
ObjectMeta : api . ObjectMeta { Name : name } ,
Data : serial ,
}
2015-10-09 22:15:35 +00:00
return testapi . Extensions . Codec ( ) . Encode ( & thirdPartyData )
2015-08-20 05:08:26 +00:00
}
2015-11-17 14:21:42 +00:00
// TODO: Convert to storage.Interface.
2015-11-17 13:35:00 +00:00
func storeToEtcd ( client tools . EtcdClient , path , name string , obj interface { } ) error {
2015-08-20 05:08:26 +00:00
data , err := encodeToThirdParty ( name , obj )
if err != nil {
return err
}
2015-11-17 13:35:00 +00:00
_ , err = client . Set ( etcdtest . PathPrefix ( ) + path , string ( data ) , 0 )
2015-08-20 05:08:26 +00:00
return err
}
2015-11-17 14:21:42 +00:00
// TODO: Convert to storage.Interface.
func setupEtcdList ( client tools . EtcdClient , path string , list [ ] Foo ) error {
2015-09-09 21:36:19 +00:00
for _ , obj := range list {
2015-11-17 14:21:42 +00:00
if err := storeToEtcd ( client , path + "/" + obj . Name , obj . Name , obj ) ; err != nil {
2015-09-09 21:36:19 +00:00
return err
}
}
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-11-17 14:21:42 +00:00
_ , etcdserver , server , assert := initThirdParty ( t , version )
2015-08-20 05:08:26 +00:00
defer server . Close ( )
2015-11-17 14:21:42 +00:00
defer etcdserver . Terminate ( t )
2015-08-20 05:08:26 +00:00
2015-11-17 14:21:42 +00:00
client := etcdserver . Client
2015-08-20 05:08:26 +00:00
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-11-17 14:21:42 +00:00
if ! assert . NoError ( storeToEtcd ( client , "/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-11-17 14:21:42 +00:00
expectedObj . ResourceVersion = item . ResourceVersion
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-11-17 14:21:42 +00:00
_ , etcdserver , server , assert := initThirdParty ( t , version )
2015-08-20 05:08:26 +00:00
defer server . Close ( )
2015-11-17 14:21:42 +00:00
defer etcdserver . Terminate ( t )
2015-08-20 05:08:26 +00:00
2015-11-17 14:21:42 +00:00
client := etcdserver . Client
2015-08-20 05:08:26 +00:00
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
2015-11-17 14:21:42 +00:00
expectedObj . ResourceVersion = item . ResourceVersion
2015-09-03 00:13:38 +00:00
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
}
2015-11-17 14:21:42 +00:00
etcdResp , err := client . 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-10-09 22:15:35 +00:00
obj , err := testapi . Extensions . Codec ( ) . Decode ( [ ] byte ( etcdResp . Node . Value ) )
2015-09-11 06:37:26 +00:00
if err != nil {
t . Errorf ( "unexpected error: %v" , err )
}
2015-10-09 22:49:10 +00:00
thirdPartyObj , ok := obj . ( * extensions . 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-11-17 14:21:42 +00:00
_ , etcdserver , server , assert := initThirdParty ( t , version )
2015-08-20 05:08:26 +00:00
defer server . Close ( )
2015-11-17 14:21:42 +00:00
defer etcdserver . Terminate ( t )
2015-08-20 05:08:26 +00:00
2015-11-17 14:21:42 +00:00
client := etcdserver . Client
2015-08-20 05:08:26 +00:00
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-11-17 14:21:42 +00:00
if ! assert . NoError ( storeToEtcd ( client , "/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
2015-11-17 14:21:42 +00:00
expectedObj . ResourceVersion = item . ResourceVersion
2015-09-03 00:13:38 +00:00
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-11-17 14:21:42 +00:00
expectedDeletedKey := etcdtest . AddPrefix ( "ThirdPartyResourceData/company.com/foos/default/test" )
_ , err = client . Get ( expectedDeletedKey , false , false )
if ! etcdstorage . IsEtcdNotFound ( err ) {
t . Errorf ( "expected deletion didn't happen: %v" , err )
2015-08-20 05:08:26 +00:00
}
}
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 ) {
2015-11-17 14:21:42 +00:00
master , etcdserver , server , assert := initThirdParty ( t , version )
2015-09-09 21:36:19 +00:00
defer server . Close ( )
2015-11-17 14:21:42 +00:00
defer etcdserver . Terminate ( t )
2015-09-09 21:36:19 +00:00
2015-11-17 14:21:42 +00:00
client := etcdserver . Client
2015-09-09 21:36:19 +00:00
expectedObj := Foo {
ObjectMeta : api . ObjectMeta {
Name : "test" ,
} ,
TypeMeta : unversioned . TypeMeta {
Kind : "Foo" ,
} ,
SomeField : "test field" ,
OtherField : 10 ,
}
2015-11-17 14:21:42 +00:00
if ! assert . NoError ( storeToEtcd ( client , "/ThirdPartyResourceData/company.com/foos/default/test" , "test" , expectedObj ) ) {
2015-09-09 21:36:19 +00:00
t . FailNow ( )
return
}
secondObj := expectedObj
secondObj . Name = "bar"
2015-11-17 14:21:42 +00:00
if ! assert . NoError ( storeToEtcd ( client , "/ThirdPartyResourceData/company.com/foos/default/bar" , "bar" , secondObj ) ) {
2015-09-09 21:36:19 +00:00
t . FailNow ( )
return
}
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 )
}
2015-11-17 14:21:42 +00:00
expectedDeletedKeys := [ ] string {
etcdtest . AddPrefix ( "/ThirdPartyResourceData/company.com/foos/default/test" ) ,
etcdtest . AddPrefix ( "/ThirdPartyResourceData/company.com/foos/default/bar" ) ,
2015-09-09 21:36:19 +00:00
}
2015-11-17 14:21:42 +00:00
for _ , key := range expectedDeletedKeys {
_ , err := client . Get ( key , false , false )
if ! etcdstorage . IsEtcdNotFound ( err ) {
t . Errorf ( "expected deletion didn't happen: %v" , err )
}
2015-09-09 21:36:19 +00:00
}
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 ] )
}
}
}