k3s/pkg/master/master_test.go

1057 lines
34 KiB
Go
Raw Normal View History

/*
Copyright 2014 The Kubernetes Authors All rights reserved.
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 (
"bytes"
"crypto/tls"
"encoding/json"
2015-09-03 17:35:04 +00:00
"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"
"reflect"
"strings"
"testing"
2015-08-05 22:03:47 +00:00
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/apiserver"
utilnet "k8s.io/kubernetes/pkg/util/net"
"k8s.io/kubernetes/pkg/util/sets"
apiv1 "k8s.io/kubernetes/pkg/api/v1"
"k8s.io/kubernetes/pkg/apimachinery/registered"
2016-04-15 22:30:15 +00:00
"k8s.io/kubernetes/pkg/apis/apps"
appsapi "k8s.io/kubernetes/pkg/apis/apps"
2016-02-08 14:03:59 +00:00
"k8s.io/kubernetes/pkg/apis/autoscaling"
autoscalingapiv1 "k8s.io/kubernetes/pkg/apis/autoscaling/v1"
2016-02-17 22:06:35 +00:00
"k8s.io/kubernetes/pkg/apis/batch"
batchapiv1 "k8s.io/kubernetes/pkg/apis/batch/v1"
2015-10-09 22:04:41 +00:00
"k8s.io/kubernetes/pkg/apis/extensions"
extensionsapiv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
"k8s.io/kubernetes/pkg/genericapiserver"
"k8s.io/kubernetes/pkg/kubelet/client"
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"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/storage"
2015-08-05 22:03:47 +00:00
etcdstorage "k8s.io/kubernetes/pkg/storage/etcd"
"k8s.io/kubernetes/pkg/storage/etcd/etcdtest"
etcdtesting "k8s.io/kubernetes/pkg/storage/etcd/testing"
"k8s.io/kubernetes/pkg/util/intstr"
"github.com/stretchr/testify/assert"
"golang.org/x/net/context"
)
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, *etcdtesting.EtcdTestServer, Config, *assert.Assertions) {
server := etcdtesting.NewEtcdTestClientServer(t)
master := &Master{
GenericAPIServer: &genericapiserver.GenericAPIServer{},
}
config := Config{
Config: &genericapiserver.Config{},
}
etcdConfig := etcdstorage.EtcdConfig{
Prefix: etcdtest.PathPrefix(),
CAFile: server.CAFile,
KeyFile: server.KeyFile,
CertFile: server.CertFile,
}
for _, url := range server.ClientURLs {
etcdConfig.ServerList = append(etcdConfig.ServerList, url.String())
}
resourceEncoding := genericapiserver.NewDefaultResourceEncodingConfig()
resourceEncoding.SetVersionEncoding(api.GroupName, *testapi.Default.GroupVersion(), unversioned.GroupVersion{Group: api.GroupName, Version: runtime.APIVersionInternal})
resourceEncoding.SetVersionEncoding(autoscaling.GroupName, *testapi.Autoscaling.GroupVersion(), unversioned.GroupVersion{Group: autoscaling.GroupName, Version: runtime.APIVersionInternal})
resourceEncoding.SetVersionEncoding(batch.GroupName, *testapi.Batch.GroupVersion(), unversioned.GroupVersion{Group: batch.GroupName, Version: runtime.APIVersionInternal})
resourceEncoding.SetVersionEncoding(apps.GroupName, *testapi.Apps.GroupVersion(), unversioned.GroupVersion{Group: apps.GroupName, Version: runtime.APIVersionInternal})
resourceEncoding.SetVersionEncoding(extensions.GroupName, *testapi.Extensions.GroupVersion(), unversioned.GroupVersion{Group: extensions.GroupName, Version: runtime.APIVersionInternal})
storageFactory := genericapiserver.NewDefaultStorageFactory(etcdConfig, api.Codecs, resourceEncoding, DefaultAPIResourceConfigSource())
config.StorageFactory = storageFactory
config.APIResourceConfigSource = DefaultAPIResourceConfigSource()
config.PublicAddress = net.ParseIP("192.168.10.4")
config.Serializer = api.Codecs
2015-09-03 17:35:04 +00:00
config.KubeletClient = client.FakeKubeletClient{}
2016-02-03 01:29:17 +00:00
config.APIPrefix = "/api"
config.APIGroupPrefix = "/apis"
config.APIResourceConfigSource = DefaultAPIResourceConfigSource()
config.ProxyDialer = func(network, addr string) (net.Conn, error) { return nil, nil }
config.ProxyTLSClientConfig = &tls.Config{}
2016-03-24 22:44:31 +00:00
// TODO: this is kind of hacky. The trouble is that the sync loop
// runs in a go-routine and there is no way to validate in the test
// that the sync routine has actually run. The right answer here
// is probably to add some sort of callback that we can register
// to validate that it's actually been run, but for now we don't
// run the sync routine and register types manually.
2016-03-10 04:06:31 +00:00
config.disableThirdPartyControllerForTesting = true
master.nodeRegistry = registrytest.NewNodeRegistry([]string{"node1", "node2"}, api.NodeResources{})
return master, server, config, assert.New(t)
}
func newMaster(t *testing.T) (*Master, *etcdtesting.EtcdTestServer, Config, *assert.Assertions) {
_, etcdserver, config, assert := setUp(t)
master, err := New(&config)
if err != nil {
t.Fatalf("Error in bringing up the master: %v", err)
}
return master, etcdserver, config, assert
}
// limitedAPIResourceConfigSource only enables the core group, the extensions group, the batch group, and the autoscaling group.
func limitedAPIResourceConfigSource() *genericapiserver.ResourceConfig {
ret := genericapiserver.NewResourceConfig()
2016-04-15 22:30:15 +00:00
ret.EnableVersions(apiv1.SchemeGroupVersion, extensionsapiv1beta1.SchemeGroupVersion, batchapiv1.SchemeGroupVersion, appsapi.SchemeGroupVersion, autoscalingapiv1.SchemeGroupVersion)
return ret
}
// newLimitedMaster only enables the core group, the extensions group, the batch group, and the autoscaling group.
func newLimitedMaster(t *testing.T) (*Master, *etcdtesting.EtcdTestServer, Config, *assert.Assertions) {
_, etcdserver, config, assert := setUp(t)
config.APIResourceConfigSource = limitedAPIResourceConfigSource()
master, err := New(&config)
if err != nil {
t.Fatalf("Error in bringing up the master: %v", err)
}
return master, etcdserver, config, assert
}
// TestNew verifies that the New function returns a Master
// using the configuration properly.
func TestNew(t *testing.T) {
master, etcdserver, config, assert := newMaster(t)
defer etcdserver.Terminate(t)
2015-09-03 17:35:04 +00:00
// Verify many of the variables match their config counterparts
assert.Equal(master.enableCoreControllers, config.EnableCoreControllers)
assert.Equal(master.tunneler, config.Tunneler)
assert.Equal(master.APIPrefix, config.APIPrefix)
assert.Equal(master.APIGroupPrefix, config.APIGroupPrefix)
assert.Equal(master.RequestContextMapper, config.RequestContextMapper)
assert.Equal(master.MasterCount, config.MasterCount)
assert.Equal(master.ClusterIP, config.PublicAddress)
assert.Equal(master.PublicReadWritePort, config.ReadWritePort)
assert.Equal(master.ServiceReadWriteIP, config.ServiceReadWriteIP)
// These functions should point to the same memory location
masterDialer, _ := utilnet.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
}
// TestNamespaceSubresources ensures the namespace subresource parsing in apiserver/handlers.go doesn't drift
func TestNamespaceSubresources(t *testing.T) {
master, etcdserver, _, _ := newMaster(t)
defer etcdserver.Terminate(t)
expectedSubresources := apiserver.NamespaceSubResourcesForTest
foundSubresources := sets.NewString()
for k := range master.v1ResourcesStorage {
parts := strings.Split(k, "/")
if len(parts) == 2 && parts[0] == "namespaces" {
foundSubresources.Insert(parts[1])
}
}
if !reflect.DeepEqual(expectedSubresources.List(), foundSubresources.List()) {
t.Errorf("Expected namespace subresources %#v, got %#v. Update apiserver/handlers.go#namespaceSubresources", expectedSubresources.List(), foundSubresources.List())
}
}
2015-09-03 17:35:04 +00:00
// TestGetServersToValidate verifies the unexported getServersToValidate function
func TestGetServersToValidate(t *testing.T) {
master, etcdserver, config, assert := setUp(t)
defer etcdserver.Terminate(t)
2015-05-14 00:29:25 +00:00
servers := master.getServersToValidate(&config)
// 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
for _, server := range []string{"scheduler", "controller-manager", "etcd-0"} {
if _, ok := servers[server]; !ok {
t.Errorf("server list missing: %s", server)
}
}
}
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")
}
// 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, etcdserver, _, assert := setUp(t)
defer etcdserver.Terminate(t)
portRange := utilnet.PortRange{Base: 10, Size: 10}
2015-09-03 17:35:04 +00:00
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
2015-09-03 17:35:04 +00:00
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-09-03 17:35:04 +00:00
}
// TestControllerServicePorts verifies master extraServicePorts are
// correctly copied into controller
func TestControllerServicePorts(t *testing.T) {
master, etcdserver, _, assert := setUp(t)
defer etcdserver.Terminate(t)
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,
TargetPort: intstr.FromInt(1000),
},
{
Name: "additional-port-2",
Port: 1010,
Protocol: api.ProtocolTCP,
TargetPort: intstr.FromInt(1010),
},
}
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
// TestGetNodeAddresses verifies that proper results are returned
// when requesting node addresses.
func TestGetNodeAddresses(t *testing.T) {
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)
}
// Because we need to be backwards compatible with release 1.1, at endpoints
// that exist in release 1.1, the responses should have empty APIVersion.
func TestAPIVersionOfDiscoveryEndpoints(t *testing.T) {
master, etcdserver, _, assert := newMaster(t)
defer etcdserver.Terminate(t)
server := httptest.NewServer(master.HandlerContainer.ServeMux)
// /api exists in release-1.1
resp, err := http.Get(server.URL + "/api")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
apiVersions := unversioned.APIVersions{}
assert.NoError(decodeResponse(resp, &apiVersions))
assert.Equal(apiVersions.APIVersion, "")
// /api/v1 exists in release-1.1
resp, err = http.Get(server.URL + "/api/v1")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
resourceList := unversioned.APIResourceList{}
assert.NoError(decodeResponse(resp, &resourceList))
assert.Equal(resourceList.APIVersion, "")
// /apis exists in release-1.1
resp, err = http.Get(server.URL + "/apis")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
groupList := unversioned.APIGroupList{}
assert.NoError(decodeResponse(resp, &groupList))
assert.Equal(groupList.APIVersion, "")
// /apis/extensions exists in release-1.1
resp, err = http.Get(server.URL + "/apis/extensions")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
group := unversioned.APIGroup{}
assert.NoError(decodeResponse(resp, &group))
assert.Equal(group.APIVersion, "")
// /apis/extensions/v1beta1 exists in release-1.1
resp, err = http.Get(server.URL + "/apis/extensions/v1beta1")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
resourceList = unversioned.APIResourceList{}
assert.NoError(decodeResponse(resp, &resourceList))
assert.Equal(resourceList.APIVersion, "")
// /apis/autoscaling doesn't exist in release-1.1, so the APIVersion field
// should be non-empty in the results returned by the server.
resp, err = http.Get(server.URL + "/apis/autoscaling")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
group = unversioned.APIGroup{}
assert.NoError(decodeResponse(resp, &group))
assert.Equal(group.APIVersion, "v1")
// apis/autoscaling/v1 doesn't exist in release-1.1, so the APIVersion field
// should be non-empty in the results returned by the server.
resp, err = http.Get(server.URL + "/apis/autoscaling/v1")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
resourceList = unversioned.APIResourceList{}
assert.NoError(decodeResponse(resp, &resourceList))
assert.Equal(resourceList.APIVersion, "v1")
}
2015-09-28 18:08:47 +00:00
func TestDiscoveryAtAPIS(t *testing.T) {
master, etcdserver, _, assert := newLimitedMaster(t)
defer etcdserver.Terminate(t)
2015-09-28 18:08:47 +00:00
server := httptest.NewServer(master.HandlerContainer.ServeMux)
resp, err := http.Get(server.URL + "/apis")
if !assert.NoError(err) {
t.Errorf("unexpected error: %v", err)
}
2015-09-28 18:08:47 +00:00
assert.Equal(http.StatusOK, resp.StatusCode)
2015-09-28 18:08:47 +00:00
groupList := unversioned.APIGroupList{}
assert.NoError(decodeResponse(resp, &groupList))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
2016-04-15 22:30:15 +00:00
expectGroupNames := sets.NewString(autoscaling.GroupName, batch.GroupName, apps.GroupName, extensions.GroupName)
expectVersions := map[string][]unversioned.GroupVersionForDiscovery{
autoscaling.GroupName: {
2016-02-19 18:31:12 +00:00
{
GroupVersion: testapi.Autoscaling.GroupVersion().String(),
Version: testapi.Autoscaling.GroupVersion().Version,
2016-02-19 18:31:12 +00:00
},
},
batch.GroupName: {
2016-02-19 18:31:12 +00:00
{
GroupVersion: testapi.Batch.GroupVersion().String(),
Version: testapi.Batch.GroupVersion().Version,
2016-02-19 18:31:12 +00:00
},
},
2016-04-15 22:30:15 +00:00
apps.GroupName: {
{
GroupVersion: testapi.Apps.GroupVersion().String(),
Version: testapi.Apps.GroupVersion().Version,
},
},
extensions.GroupName: {
2016-02-19 18:31:12 +00:00
{
GroupVersion: testapi.Extensions.GroupVersion().String(),
Version: testapi.Extensions.GroupVersion().Version,
2016-02-19 18:31:12 +00:00
},
},
}
expectPreferredVersion := map[string]unversioned.GroupVersionForDiscovery{
autoscaling.GroupName: {
GroupVersion: registered.GroupOrDie(autoscaling.GroupName).GroupVersion.String(),
Version: registered.GroupOrDie(autoscaling.GroupName).GroupVersion.Version,
},
batch.GroupName: {
GroupVersion: registered.GroupOrDie(batch.GroupName).GroupVersion.String(),
Version: registered.GroupOrDie(batch.GroupName).GroupVersion.Version,
},
2016-04-15 22:30:15 +00:00
apps.GroupName: {
GroupVersion: registered.GroupOrDie(apps.GroupName).GroupVersion.String(),
Version: registered.GroupOrDie(apps.GroupName).GroupVersion.Version,
2016-04-15 22:30:15 +00:00
},
extensions.GroupName: {
GroupVersion: registered.GroupOrDie(extensions.GroupName).GroupVersion.String(),
Version: registered.GroupOrDie(extensions.GroupName).GroupVersion.Version,
},
}
assert.Equal(3, len(groupList.Groups))
for _, group := range groupList.Groups {
if !expectGroupNames.Has(group.Name) {
t.Errorf("got unexpected group %s", group.Name)
}
assert.Equal(expectVersions[group.Name], group.Versions)
assert.Equal(expectPreferredVersion[group.Name], group.PreferredVersion)
}
thirdPartyGV := unversioned.GroupVersionForDiscovery{GroupVersion: "company.com/v1", Version: "v1"}
master.addThirdPartyResourceStorage("/apis/company.com/v1", nil,
unversioned.APIGroup{
Name: "company.com",
Versions: []unversioned.GroupVersionForDiscovery{thirdPartyGV},
PreferredVersion: thirdPartyGV,
})
resp, err = http.Get(server.URL + "/apis")
if !assert.NoError(err) {
t.Errorf("unexpected error: %v", err)
}
assert.Equal(http.StatusOK, resp.StatusCode)
assert.NoError(decodeResponse(resp, &groupList))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
assert.Equal(4, len(groupList.Groups))
expectGroupNames.Insert("company.com")
expectVersions["company.com"] = []unversioned.GroupVersionForDiscovery{thirdPartyGV}
expectPreferredVersion["company.com"] = thirdPartyGV
for _, group := range groupList.Groups {
if !expectGroupNames.Has(group.Name) {
t.Errorf("got unexpected group %s", group.Name)
2016-02-19 18:31:12 +00:00
}
assert.Equal(expectVersions[group.Name], group.Versions)
assert.Equal(expectPreferredVersion[group.Name], group.PreferredVersion)
}
2015-09-28 18:08:47 +00:00
}
2015-09-01 05:28:08 +00:00
var versionsToTest = []string{"v1", "v3"}
type Foo struct {
unversioned.TypeMeta `json:",inline"`
api.ObjectMeta `json:"metadata,omitempty" description:"standard object metadata"`
SomeField string `json:"someField"`
OtherField int `json:"otherField"`
}
type FooList struct {
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"`
Items []Foo `json:"items"`
}
func initThirdParty(t *testing.T, version string) (*Master, *etcdtesting.EtcdTestServer, *httptest.Server, *assert.Assertions) {
2016-02-03 01:29:17 +00:00
master, etcdserver, _, assert := newMaster(t)
2015-10-09 22:49:10 +00:00
api := &extensions.ThirdPartyResource{
2015-08-19 18:02:01 +00:00
ObjectMeta: api.ObjectMeta{
Name: "foo.company.com",
2015-08-19 18:02:01 +00:00
},
2015-10-09 22:49:10 +00:00
Versions: []extensions.APIVersion{
{
2016-03-10 04:06:31 +00:00
Name: version,
2015-08-19 18:02:01 +00:00
},
},
}
_, master.ServiceClusterIPRange, _ = net.ParseCIDR("10.0.0.0/24")
if !assert.NoError(master.InstallThirdPartyResource(api)) {
t.FailNow()
2015-08-19 18:02:01 +00:00
}
server := httptest.NewServer(master.HandlerContainer.ServeMux)
2016-02-03 01:29:17 +00:00
return master, etcdserver, server, assert
}
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) {
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 {
func() {
master, etcdserver, server, assert := initThirdParty(t, version)
// TODO: Uncomment when fix #19254
// defer server.Close()
defer etcdserver.Terminate(t)
if test.items != nil {
2016-03-25 13:24:58 +00:00
err := createThirdPartyList(master.thirdPartyStorage, "/ThirdPartyResourceData/company.com/foos/default", test.items)
2016-03-10 04:06:31 +00:00
if !assert.NoError(err) {
return
}
}
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
assert.Equal(http.StatusOK, resp.StatusCode)
data, err := ioutil.ReadAll(resp.Body)
assert.NoError(err)
list := FooList{}
if err = json.Unmarshal(data, &list); err != nil {
t.Errorf("unexpected error: %v", err)
}
if test.items == nil {
if len(list.Items) != 0 {
t.Errorf("expected no items, saw: %v", list.Items)
}
return
}
2015-09-03 17:35:04 +00:00
if len(list.Items) != len(test.items) {
2016-02-25 06:07:54 +00:00
t.Fatalf("unexpected length: %d vs %d", len(list.Items), len(test.items))
}
// 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])
}
}
}()
}
}
func encodeToThirdParty(name string, obj interface{}) (runtime.Object, error) {
serial, err := json.Marshal(obj)
if err != nil {
return nil, err
}
2015-10-09 22:49:10 +00:00
thirdPartyData := extensions.ThirdPartyResourceData{
ObjectMeta: api.ObjectMeta{Name: name},
Data: serial,
}
return &thirdPartyData, nil
}
2016-03-25 13:24:58 +00:00
func createThirdPartyObject(s storage.Interface, path, name string, obj interface{}) error {
data, err := encodeToThirdParty(name, obj)
if err != nil {
return err
}
2016-03-25 13:24:58 +00:00
return s.Create(context.TODO(), etcdtest.AddPrefix(path), data, nil, 0)
}
2016-03-25 13:24:58 +00:00
func createThirdPartyList(s storage.Interface, path string, list []Foo) error {
for _, obj := range list {
2016-03-25 13:24:58 +00:00
if err := createThirdPartyObject(s, path+"/"+obj.Name, obj.Name, obj); err != nil {
return err
}
}
return nil
}
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) {
master, etcdserver, server, assert := initThirdParty(t, version)
// TODO: Uncomment when fix #19254
// defer server.Close()
defer etcdserver.Terminate(t)
expectedObj := Foo{
ObjectMeta: api.ObjectMeta{
Name: "test",
},
TypeMeta: unversioned.TypeMeta{
2015-09-01 05:28:08 +00:00
Kind: "Foo",
APIVersion: version,
},
SomeField: "test field",
OtherField: 10,
}
2016-03-25 13:24:58 +00:00
if !assert.NoError(createThirdPartyObject(master.thirdPartyStorage, "/ThirdPartyResourceData/company.com/foos/default/test", "test", expectedObj)) {
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) {
return
}
2015-09-03 17:35:04 +00:00
assert.Equal(http.StatusOK, resp.StatusCode)
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)
}
// Fill in data that the apiserver injects
expectedObj.SelfLink = item.SelfLink
expectedObj.ResourceVersion = item.ResourceVersion
2015-09-03 17:35:04 +00:00
if !assert.True(reflect.DeepEqual(item, expectedObj)) {
t.Errorf("expected:\n%#v\nsaw:\n%#v\n", expectedObj, item)
}
}
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) {
master, etcdserver, server, assert := initThirdParty(t, version)
// TODO: Uncomment when fix #19254
// defer server.Close()
defer etcdserver.Terminate(t)
inputObj := Foo{
ObjectMeta: api.ObjectMeta{
Name: "test",
},
TypeMeta: unversioned.TypeMeta{
2015-09-01 05:28:08 +00:00
Kind: "Foo",
APIVersion: "company.com/" + version,
},
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) {
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) {
2016-02-25 06:07:54 +00:00
t.Fatalf("unexpected error: %v", err)
}
2015-09-03 17:35:04 +00:00
assert.Equal(http.StatusCreated, resp.StatusCode)
item := Foo{}
2015-09-03 17:35:04 +00:00
assert.NoError(decodeResponse(resp, &item))
// fill in fields set by the apiserver
expectedObj := inputObj
expectedObj.SelfLink = item.SelfLink
expectedObj.ResourceVersion = item.ResourceVersion
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)) {
t.Errorf("expected:\n%v\nsaw:\n%v\n", expectedObj, item)
}
thirdPartyObj := extensions.ThirdPartyResourceData{}
err = master.thirdPartyStorage.Get(
context.TODO(), etcdtest.AddPrefix("/ThirdPartyResourceData/company.com/foos/default/test"),
&thirdPartyObj, false)
2015-09-03 17:35:04 +00:00
if !assert.NoError(err) {
2015-09-01 05:28:08 +00:00
t.FailNow()
}
2015-09-03 17:35:04 +00:00
item = Foo{}
2015-09-03 17:35:04 +00:00
assert.NoError(json.Unmarshal(thirdPartyObj.Data, &item))
2015-09-03 17:35:04 +00:00
if !assert.True(reflect.DeepEqual(item, inputObj)) {
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) {
master, etcdserver, server, assert := initThirdParty(t, version)
// TODO: Uncomment when fix #19254
// defer server.Close()
defer etcdserver.Terminate(t)
expectedObj := Foo{
ObjectMeta: api.ObjectMeta{
Name: "test",
Namespace: "default",
},
TypeMeta: unversioned.TypeMeta{
2015-08-21 21:24:16 +00:00
Kind: "Foo",
},
SomeField: "test field",
OtherField: 10,
}
2016-03-25 13:24:58 +00:00
if !assert.NoError(createThirdPartyObject(master.thirdPartyStorage, "/ThirdPartyResourceData/company.com/foos/default/test", "test", expectedObj)) {
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) {
return
}
2015-09-03 17:35:04 +00:00
assert.Equal(http.StatusOK, resp.StatusCode)
item := Foo{}
2015-09-03 17:35:04 +00:00
assert.NoError(decodeResponse(resp, &item))
// Fill in fields set by the apiserver
expectedObj.SelfLink = item.SelfLink
expectedObj.ResourceVersion = item.ResourceVersion
expectedObj.Namespace = item.Namespace
2015-09-03 17:35:04 +00:00
if !assert.True(reflect.DeepEqual(item, expectedObj)) {
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) {
return
}
2015-09-03 17:35:04 +00:00
assert.Equal(http.StatusOK, resp.StatusCode)
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) {
return
}
2015-09-03 17:35:04 +00:00
assert.Equal(http.StatusNotFound, resp.StatusCode)
expectedDeletedKey := etcdtest.AddPrefix("ThirdPartyResourceData/company.com/foos/default/test")
thirdPartyObj := extensions.ThirdPartyResourceData{}
err = master.thirdPartyStorage.Get(
context.TODO(), expectedDeletedKey, &thirdPartyObj, false)
if !storage.IsNotFound(err) {
t.Errorf("expected deletion didn't happen: %v", err)
}
}
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
}
client := &http.Client{}
return client.Do(req)
2015-08-19 18:02:01 +00:00
}
2016-02-25 06:07:54 +00:00
func TestInstallThirdPartyAPIListOptions(t *testing.T) {
for _, version := range versionsToTest {
testInstallThirdPartyAPIListOptionsForVersion(t, version)
}
}
func testInstallThirdPartyAPIListOptionsForVersion(t *testing.T, version string) {
_, etcdserver, server, assert := initThirdParty(t, version)
// TODO: Uncomment when fix #19254
// defer server.Close()
defer etcdserver.Terminate(t)
// send a GET request with query parameter
resp, err := httpGetWithRV(server.URL + "/apis/company.com/" + version + "/namespaces/default/foos")
if !assert.NoError(err) {
t.Fatalf("unexpected error: %v", err)
}
assert.Equal(http.StatusOK, resp.StatusCode)
}
func httpGetWithRV(url string) (*http.Response, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
q := req.URL.Query()
// resourceversion is part of a ListOptions
q.Add("resourceversion", "0")
req.URL.RawQuery = q.Encode()
client := &http.Client{}
return client.Do(req)
}
func TestInstallThirdPartyResourceRemove(t *testing.T) {
for _, version := range versionsToTest {
testInstallThirdPartyResourceRemove(t, version)
}
}
func testInstallThirdPartyResourceRemove(t *testing.T, version string) {
master, etcdserver, server, assert := initThirdParty(t, version)
// TODO: Uncomment when fix #19254
// defer server.Close()
defer etcdserver.Terminate(t)
expectedObj := Foo{
ObjectMeta: api.ObjectMeta{
Name: "test",
},
TypeMeta: unversioned.TypeMeta{
Kind: "Foo",
},
SomeField: "test field",
OtherField: 10,
}
2016-03-25 13:24:58 +00:00
if !assert.NoError(createThirdPartyObject(master.thirdPartyStorage, "/ThirdPartyResourceData/company.com/foos/default/test", "test", expectedObj)) {
t.FailNow()
return
}
secondObj := expectedObj
secondObj.Name = "bar"
2016-03-25 13:24:58 +00:00
if !assert.NoError(createThirdPartyObject(master.thirdPartyStorage, "/ThirdPartyResourceData/company.com/foos/default/bar", "bar", secondObj)) {
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)
}
expectedDeletedKeys := []string{
etcdtest.AddPrefix("/ThirdPartyResourceData/company.com/foos/default/test"),
etcdtest.AddPrefix("/ThirdPartyResourceData/company.com/foos/default/bar"),
}
for _, key := range expectedDeletedKeys {
thirdPartyObj := extensions.ThirdPartyResourceData{}
err := master.thirdPartyStorage.Get(context.TODO(), key, &thirdPartyObj, false)
if !storage.IsNotFound(err) {
t.Errorf("expected deletion didn't happen: %v", err)
}
}
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])
}
}
}
2016-02-25 22:13:28 +00:00
func TestThirdPartyDiscovery(t *testing.T) {
for _, version := range versionsToTest {
testThirdPartyDiscovery(t, version)
}
}
func testThirdPartyDiscovery(t *testing.T, version string) {
_, etcdserver, server, assert := initThirdParty(t, version)
// TODO: Uncomment when fix #19254
// defer server.Close()
defer etcdserver.Terminate(t)
resp, err := http.Get(server.URL + "/apis/company.com/")
if !assert.NoError(err) {
return
}
assert.Equal(http.StatusOK, resp.StatusCode)
group := unversioned.APIGroup{}
assert.NoError(decodeResponse(resp, &group))
assert.Equal(group.APIVersion, "v1")
assert.Equal(group.Kind, "APIGroup")
assert.Equal(group.Name, "company.com")
expectedVersion := unversioned.GroupVersionForDiscovery{
GroupVersion: "company.com/" + version,
Version: version,
}
assert.Equal(group.Versions, []unversioned.GroupVersionForDiscovery{expectedVersion})
assert.Equal(group.PreferredVersion, expectedVersion)
2016-02-25 22:13:28 +00:00
resp, err = http.Get(server.URL + "/apis/company.com/" + version)
if !assert.NoError(err) {
return
}
assert.Equal(http.StatusOK, resp.StatusCode)
resourceList := unversioned.APIResourceList{}
assert.NoError(decodeResponse(resp, &resourceList))
assert.Equal(resourceList.APIVersion, "v1")
assert.Equal(resourceList.Kind, "APIResourceList")
assert.Equal(resourceList.GroupVersion, "company.com/"+version)
assert.Equal(resourceList.APIResources, []unversioned.APIResource{
{
Name: "foos",
Namespaced: true,
Kind: "Foo",
},
})
}