API Machinery, Kubectl and tests

This commit is contained in:
Antoine Pelisse
2019-01-16 21:14:42 -08:00
parent 5869ce6dfe
commit 0e1d50e70f
89 changed files with 2908 additions and 278 deletions

View File

@@ -68,6 +68,9 @@ filegroup(
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
srcs = [
":package-srcs",
"//test/integration/apiserver/apply:all-srcs",
],
tags = ["automanaged"],
)

View File

@@ -56,6 +56,7 @@ func setupWithResources(t *testing.T, groupVersions []schema.GroupVersion, resou
resourceConfig.EnableResources(resources...)
masterConfig.ExtraConfig.APIResourceConfigSource = resourceConfig
}
masterConfig.GenericConfig.OpenAPIConfig = framework.DefaultOpenAPIConfig()
_, s, closeFn := framework.RunAMaster(masterConfig)
clientSet, err := clientset.NewForConfig(&restclient.Config{Host: s.URL})

View File

@@ -0,0 +1,35 @@
load("@io_bazel_rules_go//go:def.bzl", "go_test")
go_test(
name = "go_default_test",
srcs = [
"apply_test.go",
"main_test.go",
],
deps = [
"//pkg/master:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/features:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature/testing:go_default_library",
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
"//staging/src/k8s.io/client-go/rest:go_default_library",
"//test/integration/framework:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,206 @@
/*
Copyright 2018 The Kubernetes Authors.
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 apiserver
import (
"net/http/httptest"
"testing"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
genericfeatures "k8s.io/apiserver/pkg/features"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing"
clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/kubernetes/pkg/master"
"k8s.io/kubernetes/test/integration/framework"
)
func setup(t *testing.T, groupVersions ...schema.GroupVersion) (*httptest.Server, clientset.Interface, framework.CloseFunc) {
masterConfig := framework.NewIntegrationTestMasterConfig()
if len(groupVersions) > 0 {
resourceConfig := master.DefaultAPIResourceConfigSource()
resourceConfig.EnableVersions(groupVersions...)
masterConfig.ExtraConfig.APIResourceConfigSource = resourceConfig
}
masterConfig.GenericConfig.OpenAPIConfig = framework.DefaultOpenAPIConfig()
_, s, closeFn := framework.RunAMaster(masterConfig)
clientSet, err := clientset.NewForConfig(&restclient.Config{Host: s.URL})
if err != nil {
t.Fatalf("Error in create clientset: %v", err)
}
return s, clientSet, closeFn
}
// TestApplyAlsoCreates makes sure that PATCH requests with the apply content type
// will create the object if it doesn't already exist
// TODO: make a set of test cases in an easy-to-consume place (separate package?) so it's easy to test in both integration and e2e.
func TestApplyAlsoCreates(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
_, client, closeFn := setup(t)
defer closeFn()
_, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
Namespace("default").
Resource("pods").
Name("test-pod").
Body([]byte(`{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "test-pod"
},
"spec": {
"containers": [{
"name": "test-container",
"image": "test-image"
}]
}
}`)).
Do().
Get()
if err != nil {
t.Fatalf("Failed to create object using Apply patch: %v", err)
}
_, err = client.CoreV1().RESTClient().Get().Namespace("default").Resource("pods").Name("test-pod").Do().Get()
if err != nil {
t.Fatalf("Failed to retrieve object: %v", err)
}
}
// TestCreateOnApplyFailsWithUID makes sure that PATCH requests with the apply content type
// will not create the object if it doesn't already exist and it specifies a UID
func TestCreateOnApplyFailsWithUID(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
_, client, closeFn := setup(t)
defer closeFn()
_, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
Namespace("default").
Resource("pods").
Name("test-pod-uid").
Body([]byte(`{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "test-pod-uid",
"uid": "88e00824-7f0e-11e8-94a1-c8d3ffb15800"
},
"spec": {
"containers": [{
"name": "test-container",
"image": "test-image"
}]
}
}`)).
Do().
Get()
if !errors.IsConflict(err) {
t.Fatalf("Expected conflict error but got: %v", err)
}
}
func TestApplyUpdateApplyConflictForced(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
_, client, closeFn := setup(t)
defer closeFn()
obj := []byte(`{
"apiVersion": "apps/v1",
"kind": "Deployment",
"metadata": {
"name": "deployment",
"labels": {"app": "nginx"}
},
"spec": {
"replicas": 3,
"selector": {
"matchLabels": {
"app": "nginx"
}
},
"template": {
"metadata": {
"labels": {
"app": "nginx"
}
},
"spec": {
"containers": [{
"name": "nginx",
"image": "nginx:latest"
}]
}
}
}
}`)
_, err := client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
AbsPath("/apis/apps/v1").
Namespace("default").
Resource("deployments").
Name("deployment").
Body(obj).Do().Get()
if err != nil {
t.Fatalf("Failed to create object using Apply patch: %v", err)
}
_, err = client.CoreV1().RESTClient().Patch(types.MergePatchType).
AbsPath("/apis/extensions/v1beta1").
Namespace("default").
Resource("deployments").
Name("deployment").
Body([]byte(`{"spec":{"replicas": 5}}`)).Do().Get()
if err != nil {
t.Fatalf("Failed to patch object: %v", err)
}
_, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
AbsPath("/apis/apps/v1").
Namespace("default").
Resource("deployments").
Name("deployment").
Body([]byte(obj)).Do().Get()
if err == nil {
t.Fatalf("Expecting to get conflicts when applying object")
}
status, ok := err.(*errors.StatusError)
if !ok {
t.Fatalf("Expecting to get conflicts as API error")
}
if len(status.Status().Details.Causes) < 1 {
t.Fatalf("Expecting to get at least one conflict when applying object, got: %v", status.Status().Details.Causes)
}
_, err = client.CoreV1().RESTClient().Patch(types.ApplyPatchType).
AbsPath("/apis/apps/v1").
Namespace("default").
Resource("deployments").
Name("deployment").
Param("force", "true").
Body([]byte(obj)).Do().Get()
if err != nil {
t.Fatalf("Failed to apply object with force: %v", err)
}
}

View File

@@ -0,0 +1,27 @@
/*
Copyright 2017 The Kubernetes Authors.
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 apiserver
import (
"testing"
"k8s.io/kubernetes/test/integration/framework"
)
func TestMain(m *testing.M) {
framework.EtcdMain(m.Run)
}

View File

@@ -62,6 +62,7 @@ var kindWhiteList = sets.NewString(
"ListOptions",
"CreateOptions",
"UpdateOptions",
"PatchOptions",
"NodeProxyOptions",
"PodAttachOptions",
"PodExecOptions",

View File

@@ -72,6 +72,7 @@ go_test(
"//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authorization/authorizerfactory:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/features:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/registry/generic:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/util/feature/testing:go_default_library",

View File

@@ -27,8 +27,6 @@ import (
"testing"
"time"
"k8s.io/klog"
apiextensionsclient "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -37,12 +35,16 @@ import (
"k8s.io/apiserver/pkg/authentication/token/tokenfile"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
genericfeatures "k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/registry/generic"
utilfeature "k8s.io/apiserver/pkg/util/feature"
utilfeaturetesting "k8s.io/apiserver/pkg/util/feature/testing"
externalclientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
watchtools "k8s.io/client-go/tools/watch"
"k8s.io/client-go/transport"
csiclientset "k8s.io/csi-api/pkg/client/clientset/versioned"
"k8s.io/klog"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/api/testapi"
api "k8s.io/kubernetes/pkg/apis/core"
@@ -292,6 +294,8 @@ var (
)
func TestRBAC(t *testing.T) {
defer utilfeaturetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.ServerSideApply, true)()
superUser := "admin/system:masters"
tests := []struct {
@@ -478,6 +482,40 @@ func TestRBAC(t *testing.T) {
{"limitrange-updater", "PUT", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK},
},
},
// {
// bootstrapRoles: bootstrapRoles{
// clusterRoles: []rbacapi.ClusterRole{
// {
// ObjectMeta: metav1.ObjectMeta{Name: "allow-all"},
// Rules: []rbacapi.PolicyRule{ruleAllowAll},
// },
// {
// ObjectMeta: metav1.ObjectMeta{Name: "patch-limitranges"},
// Rules: []rbacapi.PolicyRule{
// rbacapi.NewRule("patch").Groups("").Resources("limitranges").RuleOrDie(),
// },
// },
// },
// clusterRoleBindings: []rbacapi.ClusterRoleBinding{
// {
// ObjectMeta: metav1.ObjectMeta{Name: "patch-limitranges"},
// Subjects: []rbacapi.Subject{
// {Kind: "User", Name: "limitrange-patcher"},
// },
// RoleRef: rbacapi.RoleRef{Kind: "ClusterRole", Name: "patch-limitranges"},
// },
// },
// },
// requests: []request{
// // Create the namespace used later in the test
// {superUser, "POST", "", "namespaces", "", "", limitRangeNamespace, http.StatusCreated},
// {"limitrange-patcher", "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusForbidden},
// {superUser, "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusCreated},
// {superUser, "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK},
// {"limitrange-patcher", "PATCH", "", "limitranges", "limitrange-namespace", "a", aLimitRange, http.StatusOK},
// },
// },
}
for i, tc := range tests {
@@ -494,6 +532,7 @@ func TestRBAC(t *testing.T) {
"nonescalating-rolebinding-writer": {Name: "nonescalating-rolebinding-writer"},
"pod-reader": {Name: "pod-reader"},
"limitrange-updater": {Name: "limitrange-updater"},
"limitrange-patcher": {Name: "limitrange-patcher"},
"user-with-no-permissions": {Name: "user-with-no-permissions"},
}))
_, s, closeFn := framework.RunAMaster(masterConfig)
@@ -530,6 +569,12 @@ func TestRBAC(t *testing.T) {
}
req, err := http.NewRequest(r.verb, s.URL+path, body)
// TODO: Un-comment this when Apply works again
// if r.verb == "PATCH" {
// // For patch operations, use the apply content type
// req.Header.Add("Content-Type", string(types.ApplyPatchType))
// }
if err != nil {
t.Fatalf("failed to create request: %v", err)
}

View File

@@ -58,7 +58,7 @@ func DryRunCreateTest(t *testing.T, rsc dynamic.ResourceInterface, obj *unstruct
func DryRunPatchTest(t *testing.T, rsc dynamic.ResourceInterface, name string) {
patch := []byte(`{"metadata":{"annotations":{"patch": "true"}}}`)
obj, err := rsc.Patch(name, types.MergePatchType, patch, metav1.UpdateOptions{DryRun: []string{metav1.DryRunAll}})
obj, err := rsc.Patch(name, types.MergePatchType, patch, metav1.PatchOptions{DryRun: []string{metav1.DryRunAll}})
if err != nil {
t.Fatalf("failed to dry-run patch object: %v", err)
}
@@ -97,7 +97,7 @@ func DryRunScalePatchTest(t *testing.T, rsc dynamic.ResourceInterface, name stri
replicas := getReplicasOrFail(t, obj)
patch := []byte(`{"spec":{"replicas":10}}`)
patchedObj, err := rsc.Patch(name, types.MergePatchType, patch, metav1.UpdateOptions{DryRun: []string{metav1.DryRunAll}}, "scale")
patchedObj, err := rsc.Patch(name, types.MergePatchType, patch, metav1.PatchOptions{DryRun: []string{metav1.DryRunAll}}, "scale")
if err != nil {
t.Fatalf("failed to dry-run patch object: %v", err)
}

View File

@@ -67,6 +67,7 @@ go_library(
"//vendor/github.com/go-openapi/spec:go_default_library",
"//vendor/github.com/pborman/uuid:go_default_library",
"//vendor/k8s.io/klog:go_default_library",
"//vendor/k8s.io/kube-openapi/pkg/common:go_default_library",
],
)

View File

@@ -27,8 +27,6 @@ import (
"github.com/go-openapi/spec"
"github.com/pborman/uuid"
"k8s.io/klog"
apps "k8s.io/api/apps/v1beta1"
auditreg "k8s.io/api/auditregistration/v1alpha1"
autoscaling "k8s.io/api/autoscaling/v1"
@@ -55,6 +53,8 @@ import (
"k8s.io/client-go/informers"
clientset "k8s.io/client-go/kubernetes"
restclient "k8s.io/client-go/rest"
"k8s.io/klog"
openapicommon "k8s.io/kube-openapi/pkg/common"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/api/testapi"
"k8s.io/kubernetes/pkg/apis/batch"
@@ -109,6 +109,24 @@ func (h *MasterHolder) SetMaster(m *master.Master) {
close(h.Initialized)
}
func DefaultOpenAPIConfig() *openapicommon.Config {
openAPIConfig := genericapiserver.DefaultOpenAPIConfig(openapi.GetOpenAPIDefinitions, openapinamer.NewDefinitionNamer(legacyscheme.Scheme))
openAPIConfig.Info = &spec.Info{
InfoProps: spec.InfoProps{
Title: "Kubernetes",
Version: "unversioned",
},
}
openAPIConfig.DefaultResponse = &spec.Response{
ResponseProps: spec.ResponseProps{
Description: "Default Response.",
},
}
openAPIConfig.GetDefinitions = openapi.GetOpenAPIDefinitions
return openAPIConfig
}
// startMasterOrDie starts a kubernetes master and an httpserver to handle api requests
func startMasterOrDie(masterConfig *master.Config, incomingServer *httptest.Server, masterReceiver MasterReceiver) (*master.Master, *httptest.Server, CloseFunc) {
var m *master.Master
@@ -138,19 +156,7 @@ func startMasterOrDie(masterConfig *master.Config, incomingServer *httptest.Serv
if masterConfig == nil {
masterConfig = NewMasterConfig()
masterConfig.GenericConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(openapi.GetOpenAPIDefinitions, openapinamer.NewDefinitionNamer(legacyscheme.Scheme))
masterConfig.GenericConfig.OpenAPIConfig.Info = &spec.Info{
InfoProps: spec.InfoProps{
Title: "Kubernetes",
Version: "unversioned",
},
}
masterConfig.GenericConfig.OpenAPIConfig.DefaultResponse = &spec.Response{
ResponseProps: spec.ResponseProps{
Description: "Default Response.",
},
}
masterConfig.GenericConfig.OpenAPIConfig.GetDefinitions = openapi.GetOpenAPIDefinitions
masterConfig.GenericConfig.OpenAPIConfig = DefaultOpenAPIConfig()
}
// set the loopback client config