mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-26 05:03:09 +00:00
e2e-kubeadm-refactor
This commit is contained in:
parent
3adae6ce2f
commit
cb306ae0b3
@ -9,24 +9,28 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
|||||||
go_test(
|
go_test(
|
||||||
name = "go_default_test",
|
name = "go_default_test",
|
||||||
srcs = [
|
srcs = [
|
||||||
|
"bootstrap_token_test.go",
|
||||||
|
"cluster_info_test.go",
|
||||||
|
"controlplane_nodes_test.go",
|
||||||
"e2e_kubeadm_suite_test.go",
|
"e2e_kubeadm_suite_test.go",
|
||||||
"kubeadm_test.go",
|
"kubeadm_config_test.go",
|
||||||
],
|
],
|
||||||
out = "e2e_kubeadm.test",
|
out = "e2e_kubeadm.test",
|
||||||
embed = [":go_default_library"],
|
embed = [":go_default_library"],
|
||||||
tags = ["e2e"],
|
tags = ["e2e"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//staging/src/k8s.io/api/authorization/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/api/rbac/v1:go_default_library",
|
"//staging/src/k8s.io/api/rbac/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
|
"//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
||||||
"//staging/src/k8s.io/client-go/rest:go_default_library",
|
|
||||||
"//staging/src/k8s.io/cluster-bootstrap/token/api:go_default_library",
|
"//staging/src/k8s.io/cluster-bootstrap/token/api:go_default_library",
|
||||||
"//test/e2e/framework:go_default_library",
|
"//test/e2e/framework:go_default_library",
|
||||||
"//vendor/github.com/onsi/ginkgo:go_default_library",
|
"//vendor/github.com/onsi/ginkgo:go_default_library",
|
||||||
"//vendor/github.com/onsi/gomega:go_default_library",
|
"//vendor/github.com/onsi/gomega:go_default_library",
|
||||||
"//vendor/github.com/spf13/pflag:go_default_library",
|
"//vendor/github.com/spf13/pflag:go_default_library",
|
||||||
|
"//vendor/gopkg.in/yaml.v2:go_default_library",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -50,11 +54,22 @@ filegroup(
|
|||||||
|
|
||||||
go_library(
|
go_library(
|
||||||
name = "go_default_library",
|
name = "go_default_library",
|
||||||
srcs = ["matchers.go"],
|
srcs = [
|
||||||
|
"bootstrap_signer.go",
|
||||||
|
"const.go",
|
||||||
|
"framework.go",
|
||||||
|
"util.go",
|
||||||
|
],
|
||||||
importpath = "k8s.io/kubernetes/test/e2e_kubeadm",
|
importpath = "k8s.io/kubernetes/test/e2e_kubeadm",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
|
"//staging/src/k8s.io/api/authorization/v1:go_default_library",
|
||||||
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
"//staging/src/k8s.io/api/core/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/api/rbac/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||||
|
"//staging/src/k8s.io/client-go/kubernetes:go_default_library",
|
||||||
|
"//test/e2e/framework:go_default_library",
|
||||||
|
"//vendor/github.com/onsi/ginkgo:go_default_library",
|
||||||
"//vendor/github.com/onsi/gomega:go_default_library",
|
"//vendor/github.com/onsi/gomega:go_default_library",
|
||||||
"//vendor/github.com/onsi/gomega/gstruct:go_default_library",
|
"//vendor/github.com/onsi/gomega/gstruct:go_default_library",
|
||||||
],
|
],
|
||||||
|
47
test/e2e_kubeadm/bootstrap_signer.go
Normal file
47
test/e2e_kubeadm/bootstrap_signer.go
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 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 e2e_kubeadm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/kubernetes/test/e2e/framework"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
bootstrapTokensSignerRoleName = "system:controller:bootstrap-signer"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Define container for all the test specification aimed at verifying
|
||||||
|
// that kubeadm creates the bootstrap signer
|
||||||
|
var _ = KubeadmDescribe("bootstrap signer", func() {
|
||||||
|
|
||||||
|
// Get an instance of the k8s test framework
|
||||||
|
f := framework.NewDefaultFramework("bootstrap token")
|
||||||
|
|
||||||
|
// Tests in this container are not expected to create new objects in the cluster
|
||||||
|
// so we are disabling the creation of a namespace in order to get a faster execution
|
||||||
|
f.SkipNamespaceCreation = true
|
||||||
|
|
||||||
|
It("should be active", func() {
|
||||||
|
//NB. this is technically implemented a part of the control-plane phase
|
||||||
|
// and more specifically if the controller manager is properly configured,
|
||||||
|
// the bootstrapsigner controller is activated and the system:controller:bootstrap-signer
|
||||||
|
// group will be automatically created
|
||||||
|
ExpectRole(f.ClientSet, kubeSystemNamespace, bootstrapTokensSignerRoleName)
|
||||||
|
})
|
||||||
|
})
|
82
test/e2e_kubeadm/bootstrap_token_test.go
Normal file
82
test/e2e_kubeadm/bootstrap_token_test.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 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 e2e_kubeadm
|
||||||
|
|
||||||
|
import (
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
rbacv1 "k8s.io/api/rbac/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/kubernetes/test/e2e/framework"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
bootstrapTokensGroup = "system:bootstrappers:kubeadm:default-node-token"
|
||||||
|
bootstrapTokensAllowPostCSRClusterRoleBinding = "kubeadm:kubelet-bootstrap"
|
||||||
|
bootstrapTokensAllowPostCSRClusterRoleName = "system:node-bootstrapper"
|
||||||
|
bootstrapTokensCSRAutoApprovalClusterRoleBinding = "kubeadm:node-autoapprove-bootstrap"
|
||||||
|
bootstrapTokensCSRAutoApprovalClusterRoleName = "system:certificates.k8s.io:certificatesigningrequests:nodeclient"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Define container for all the test specification aimed at verifying
|
||||||
|
// that kubeadm creates the bootstrap token, the system:bootstrappers:kubeadm:default-node-token group
|
||||||
|
// and that all the related RBAC rules are in place
|
||||||
|
var _ = KubeadmDescribe("bootstrap token", func() {
|
||||||
|
|
||||||
|
// Get an instance of the k8s test framework
|
||||||
|
f := framework.NewDefaultFramework("bootstrap token")
|
||||||
|
|
||||||
|
// Tests in this container are not expected to create new objects in the cluster
|
||||||
|
// so we are disabling the creation of a namespace in order to get a faster execution
|
||||||
|
f.SkipNamespaceCreation = true
|
||||||
|
|
||||||
|
It("should exist and be properly configured", func() {
|
||||||
|
secrets, err := f.ClientSet.CoreV1().
|
||||||
|
Secrets(kubeSystemNamespace).
|
||||||
|
List(metav1.ListOptions{})
|
||||||
|
framework.ExpectNoError(err, "error reading Secrets")
|
||||||
|
|
||||||
|
tokenNum := 0
|
||||||
|
for _, s := range secrets.Items {
|
||||||
|
if s.Type == corev1.SecretTypeBootstrapToken {
|
||||||
|
//TODO: might be we want to further check tokens (auth-extra-groups, usage etc)
|
||||||
|
tokenNum++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expect(tokenNum).Should(BeNumerically(">", 0), "At least one bootstrap token should exist")
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should be allowed to post CSR for kubelet certificates on joining nodes", func() {
|
||||||
|
ExpectClusterRoleBindingWithSubjectAndRole(f.ClientSet,
|
||||||
|
bootstrapTokensAllowPostCSRClusterRoleBinding,
|
||||||
|
rbacv1.GroupKind, bootstrapTokensGroup,
|
||||||
|
bootstrapTokensAllowPostCSRClusterRoleName,
|
||||||
|
)
|
||||||
|
//TODO: check if possible to verify "allowed to post CSR" using subject asses review as well
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should be allowed to auto approve CSR for kubelet certificates on joining nodes", func() {
|
||||||
|
ExpectClusterRoleBindingWithSubjectAndRole(f.ClientSet,
|
||||||
|
bootstrapTokensCSRAutoApprovalClusterRoleBinding,
|
||||||
|
rbacv1.GroupKind, bootstrapTokensGroup,
|
||||||
|
bootstrapTokensCSRAutoApprovalClusterRoleName,
|
||||||
|
)
|
||||||
|
//TODO: check if possible to verify "allowed to auto approve CSR" using subject asses review as well
|
||||||
|
})
|
||||||
|
})
|
78
test/e2e_kubeadm/cluster_info_test.go
Normal file
78
test/e2e_kubeadm/cluster_info_test.go
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 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 e2e_kubeadm
|
||||||
|
|
||||||
|
import (
|
||||||
|
authv1 "k8s.io/api/authorization/v1"
|
||||||
|
rbacv1 "k8s.io/api/rbac/v1"
|
||||||
|
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
|
||||||
|
"k8s.io/kubernetes/test/e2e/framework"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
clusterInfoConfigMapName = "cluster-info"
|
||||||
|
clusterInfoRoleName = "kubeadm:bootstrap-signer-clusterinfo"
|
||||||
|
clusterInfoRoleBindingName = clusterInfoRoleName
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
clusterInfoConfigMapResource = &authv1.ResourceAttributes{
|
||||||
|
Namespace: kubePublicNamespace,
|
||||||
|
Name: clusterInfoConfigMapName,
|
||||||
|
Resource: "configmaps",
|
||||||
|
Verb: "get",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Define container for all the test specification aimed at verifying
|
||||||
|
// that kubeadm creates the cluster-info ConfigMap, that it is properly configured
|
||||||
|
// and that all the related RBAC rules are in place
|
||||||
|
var _ = KubeadmDescribe("cluster-info ConfigMap", func() {
|
||||||
|
|
||||||
|
// Get an instance of the k8s test framework
|
||||||
|
f := framework.NewDefaultFramework("cluster-info")
|
||||||
|
|
||||||
|
// Tests in this container are not expected to create new objects in the cluster
|
||||||
|
// so we are disabling the creation of a namespace in order to get a faster execution
|
||||||
|
f.SkipNamespaceCreation = true
|
||||||
|
|
||||||
|
It("should exist and be properly configured", func() {
|
||||||
|
// Nb. this is technically implemented a part of the bootstrap-token phase
|
||||||
|
cm := GetConfigMap(f.ClientSet, kubePublicNamespace, clusterInfoConfigMapName)
|
||||||
|
|
||||||
|
Expect(cm.Data).To(HaveKey(HavePrefix(bootstrapapi.JWSSignatureKeyPrefix)))
|
||||||
|
Expect(cm.Data).To(HaveKey(bootstrapapi.KubeConfigKey))
|
||||||
|
|
||||||
|
//TODO: What else? server?
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should have related Role and RoleBinding", func() {
|
||||||
|
// Nb. this is technically implemented a part of the bootstrap-token phase
|
||||||
|
ExpectRole(f.ClientSet, kubePublicNamespace, clusterInfoRoleName)
|
||||||
|
ExpectRoleBinding(f.ClientSet, kubePublicNamespace, clusterInfoRoleBindingName)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should be accessible for anonymous", func() {
|
||||||
|
ExpectSubjectHasAccessToResource(f.ClientSet,
|
||||||
|
rbacv1.UserKind, anonymousUser,
|
||||||
|
clusterInfoConfigMapResource,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2018 The Kubernetes Authors.
|
Copyright 2019 The Kubernetes Authors.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@ -16,22 +16,11 @@ limitations under the License.
|
|||||||
|
|
||||||
package e2e_kubeadm
|
package e2e_kubeadm
|
||||||
|
|
||||||
import (
|
const (
|
||||||
"github.com/onsi/gomega"
|
kubePublicNamespace = "kube-public"
|
||||||
"github.com/onsi/gomega/gstruct"
|
kubeSystemNamespace = "kube-system"
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
|
anonymousUser = "system:anonymous"
|
||||||
|
|
||||||
|
nodesGroup = "system:nodes"
|
||||||
)
|
)
|
||||||
|
|
||||||
func subject(name, kind string) gomega.OmegaMatcher {
|
|
||||||
return gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
|
|
||||||
"Name": gomega.Equal(name),
|
|
||||||
"Kind": gomega.Equal(kind),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func taint(key string, effect corev1.TaintEffect) gomega.OmegaMatcher {
|
|
||||||
return gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
|
|
||||||
"Key": gomega.Equal(key),
|
|
||||||
"Effect": gomega.Equal(effect),
|
|
||||||
})
|
|
||||||
}
|
|
68
test/e2e_kubeadm/controlplane_nodes_test.go
Normal file
68
test/e2e_kubeadm/controlplane_nodes_test.go
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 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 e2e_kubeadm
|
||||||
|
|
||||||
|
import (
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/labels"
|
||||||
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/kubernetes/test/e2e/framework"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
controlPlaneTaint = "node-role.kubernetes.io/master"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Define container for all the test specification aimed at verifying
|
||||||
|
// that kubeadm configures the control-plane node as expected
|
||||||
|
var _ = KubeadmDescribe("control-plane node", func() {
|
||||||
|
|
||||||
|
// Get an instance of the k8s test framework
|
||||||
|
f := framework.NewDefaultFramework("control-plane node")
|
||||||
|
|
||||||
|
// Tests in this container are not expected to create new objects in the cluster
|
||||||
|
// so we are disabling the creation of a namespace in order to get a faster execution
|
||||||
|
f.SkipNamespaceCreation = true
|
||||||
|
|
||||||
|
// Important! please note that this test can't be run on single-node clusters
|
||||||
|
// in case you can skip this test with SKIP=multi-node
|
||||||
|
It("should be labelled and tainted [multi-node]", func() {
|
||||||
|
// get all control-plane nodes (and this implicitly checks that node are properly labeled)
|
||||||
|
controlPlanes := getControlPlaneNodes(f.ClientSet)
|
||||||
|
|
||||||
|
// checks if there is at least one control-plane node
|
||||||
|
Expect(controlPlanes.Items).NotTo(BeEmpty(), "at least one node with label %s should exist. if you are running test on a single-node cluster, you can skip this test with SKIP=multi-node", controlPlaneTaint)
|
||||||
|
|
||||||
|
// checks that the control-plane nodes have the expected taint
|
||||||
|
for _, cp := range controlPlanes.Items {
|
||||||
|
framework.ExpectNodeHasTaint(f.ClientSet, cp.GetName(), &corev1.Taint{Key: controlPlaneTaint, Effect: corev1.TaintEffectNoSchedule})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
func getControlPlaneNodes(c clientset.Interface) *corev1.NodeList {
|
||||||
|
selector := labels.Set{controlPlaneTaint: ""}.AsSelector()
|
||||||
|
masters, err := c.CoreV1().Nodes().
|
||||||
|
List(metav1.ListOptions{LabelSelector: selector.String()})
|
||||||
|
framework.ExpectNoError(err, "error reading control-plane nodes")
|
||||||
|
|
||||||
|
return masters
|
||||||
|
}
|
23
test/e2e_kubeadm/framework.go
Normal file
23
test/e2e_kubeadm/framework.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 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 e2e_kubeadm
|
||||||
|
|
||||||
|
import "k8s.io/kubernetes/test/e2e/framework"
|
||||||
|
|
||||||
|
func KubeadmDescribe(text string, body func()) bool {
|
||||||
|
return framework.KubeDescribe("[sig-cluster-lifecycle] [area-kubeadm] "+text, body)
|
||||||
|
}
|
117
test/e2e_kubeadm/kubeadm_config_test.go
Normal file
117
test/e2e_kubeadm/kubeadm_config_test.go
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2019 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 e2e_kubeadm
|
||||||
|
|
||||||
|
import (
|
||||||
|
yaml "gopkg.in/yaml.v2"
|
||||||
|
authv1 "k8s.io/api/authorization/v1"
|
||||||
|
rbacv1 "k8s.io/api/rbac/v1"
|
||||||
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/kubernetes/test/e2e/framework"
|
||||||
|
|
||||||
|
. "github.com/onsi/ginkgo"
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
kubeadmConfigName = "kubeadm-config"
|
||||||
|
kubeadmConfigRoleName = "kubeadm:nodes-kubeadm-config"
|
||||||
|
kubeadmConfigRoleBindingName = kubeadmConfigRoleName
|
||||||
|
kubeadmConfigClusterConfigurationConfigMapKey = "ClusterConfiguration"
|
||||||
|
kubeadmConfigClusterStatusConfigMapKey = "ClusterStatus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
kubeadmConfigConfigMapResource = &authv1.ResourceAttributes{
|
||||||
|
Namespace: kubeSystemNamespace,
|
||||||
|
Name: kubeadmConfigName,
|
||||||
|
Resource: "configmaps",
|
||||||
|
Verb: "get",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Define container for all the test specification aimed at verifying
|
||||||
|
// that kubeadm creates the cluster-info ConfigMap, that it is properly configured
|
||||||
|
// and that all the related RBAC rules are in place
|
||||||
|
var _ = KubeadmDescribe("kubeadm-config ConfigMap", func() {
|
||||||
|
|
||||||
|
// Get an instance of the k8s test framework
|
||||||
|
f := framework.NewDefaultFramework("kubeadm-config")
|
||||||
|
|
||||||
|
// Tests in this container are not expected to create new objects in the cluster
|
||||||
|
// so we are disabling the creation of a namespace in order to get a faster execution
|
||||||
|
f.SkipNamespaceCreation = true
|
||||||
|
|
||||||
|
It("should exist and be properly configured", func() {
|
||||||
|
cm := GetConfigMap(f.ClientSet, kubeSystemNamespace, kubeadmConfigName)
|
||||||
|
|
||||||
|
Expect(cm.Data).To(HaveKey(kubeadmConfigClusterConfigurationConfigMapKey))
|
||||||
|
Expect(cm.Data).To(HaveKey(kubeadmConfigClusterStatusConfigMapKey))
|
||||||
|
|
||||||
|
m := unmarshalYaml(cm.Data[kubeadmConfigClusterStatusConfigMapKey])
|
||||||
|
if _, ok := m["apiEndpoints"]; ok {
|
||||||
|
d := m["apiEndpoints"].(map[interface{}]interface{})
|
||||||
|
// get all control-plane nodes
|
||||||
|
controlPlanes := getControlPlaneNodes(f.ClientSet)
|
||||||
|
|
||||||
|
// checks that all the control-plane nodes are in the apiEndpoints list
|
||||||
|
for _, cp := range controlPlanes.Items {
|
||||||
|
if _, ok := d[cp.Name]; !ok {
|
||||||
|
framework.Failf("failed to get apiEndpoints for control-plane %s in %s", cp.Name, kubeadmConfigClusterStatusConfigMapKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
framework.Failf("failed to get apiEndpoints from %s", kubeadmConfigClusterStatusConfigMapKey)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should have related Role and RoleBinding", func() {
|
||||||
|
ExpectRole(f.ClientSet, kubeSystemNamespace, kubeadmConfigRoleName)
|
||||||
|
ExpectRoleBinding(f.ClientSet, kubeSystemNamespace, kubeadmConfigRoleBindingName)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should be accessible for bootstrap tokens", func() {
|
||||||
|
ExpectSubjectHasAccessToResource(f.ClientSet,
|
||||||
|
rbacv1.GroupKind, bootstrapTokensGroup,
|
||||||
|
kubeadmConfigConfigMapResource,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
It("should be accessible for for nodes", func() {
|
||||||
|
ExpectSubjectHasAccessToResource(f.ClientSet,
|
||||||
|
rbacv1.GroupKind, nodesGroup,
|
||||||
|
kubeadmConfigConfigMapResource,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
func getClusterConfiguration(c clientset.Interface) map[interface{}]interface{} {
|
||||||
|
cm := GetConfigMap(c, kubeSystemNamespace, kubeadmConfigName)
|
||||||
|
|
||||||
|
Expect(cm.Data).To(HaveKey(kubeadmConfigClusterConfigurationConfigMapKey))
|
||||||
|
|
||||||
|
return unmarshalYaml(cm.Data[kubeadmConfigClusterConfigurationConfigMapKey])
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalYaml(data string) map[interface{}]interface{} {
|
||||||
|
m := make(map[interface{}]interface{})
|
||||||
|
err := yaml.Unmarshal([]byte(data), &m)
|
||||||
|
if err != nil {
|
||||||
|
framework.Failf("error parsing %s ConfigMap: %v", kubeadmConfigName, err)
|
||||||
|
}
|
||||||
|
return m
|
||||||
|
}
|
@ -1,136 +0,0 @@
|
|||||||
/*
|
|
||||||
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 e2e_kubeadm
|
|
||||||
|
|
||||||
import (
|
|
||||||
corev1 "k8s.io/api/core/v1"
|
|
||||||
rbacv1 "k8s.io/api/rbac/v1"
|
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
||||||
"k8s.io/apimachinery/pkg/labels"
|
|
||||||
"k8s.io/client-go/kubernetes"
|
|
||||||
"k8s.io/client-go/rest"
|
|
||||||
bootstrapapi "k8s.io/cluster-bootstrap/token/api"
|
|
||||||
"k8s.io/kubernetes/test/e2e/framework"
|
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
|
||||||
. "github.com/onsi/gomega"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
masterTaint = "node-role.kubernetes.io/master"
|
|
||||||
kubeadmConfigNamespace = "kube-system"
|
|
||||||
kubeadmConfigName = "kubeadm-config"
|
|
||||||
clusterInfoNamespace = "kube-public"
|
|
||||||
clusterInfoName = "cluster-info"
|
|
||||||
bootstrapSignerRoleNamespace = "kube-system"
|
|
||||||
bootstrapSignerRoleName = "system:controller:bootstrap-signer"
|
|
||||||
)
|
|
||||||
|
|
||||||
var _ = framework.KubeDescribe("Kubeadm [Feature:Kubeadm]", func() {
|
|
||||||
f := framework.NewDefaultFramework("kubeadm")
|
|
||||||
|
|
||||||
Describe("kubeadm master", func() {
|
|
||||||
It("should be labelled and tainted", func() {
|
|
||||||
selector := labels.Set{masterTaint: ""}.AsSelector()
|
|
||||||
master, err := f.ClientSet.CoreV1().Nodes().
|
|
||||||
List(metav1.ListOptions{LabelSelector: selector.String()})
|
|
||||||
framework.ExpectNoError(err, "couldn't find a master node")
|
|
||||||
Expect(master.Items).NotTo(BeEmpty())
|
|
||||||
for _, master := range master.Items {
|
|
||||||
Expect(master.Spec.Taints).To(
|
|
||||||
ContainElement(taint(masterTaint, corev1.TaintEffectNoSchedule)),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Describe("kubeadm-config config map", func() {
|
|
||||||
It("should exist", func() {
|
|
||||||
_, err := f.ClientSet.CoreV1().
|
|
||||||
ConfigMaps(kubeadmConfigNamespace).
|
|
||||||
Get(kubeadmConfigName, metav1.GetOptions{})
|
|
||||||
framework.ExpectNoError(err)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Describe("cluster-info", func() {
|
|
||||||
It("should have expected keys", func() {
|
|
||||||
clientInfo, err := f.ClientSet.CoreV1().
|
|
||||||
ConfigMaps(clusterInfoNamespace).
|
|
||||||
Get(clusterInfoName, metav1.GetOptions{})
|
|
||||||
framework.ExpectNoError(err, "couldn't find config map")
|
|
||||||
|
|
||||||
Expect(clientInfo.Data).To(HaveKey(HavePrefix(bootstrapapi.JWSSignatureKeyPrefix)))
|
|
||||||
Expect(clientInfo.Data).To(HaveKey(bootstrapapi.KubeConfigKey))
|
|
||||||
})
|
|
||||||
|
|
||||||
It("should be public", func() {
|
|
||||||
cfg, err := framework.LoadConfig()
|
|
||||||
framework.ExpectNoError(err, "couldn't get config")
|
|
||||||
cfg = rest.AnonymousClientConfig(cfg)
|
|
||||||
client, err := kubernetes.NewForConfig(cfg)
|
|
||||||
framework.ExpectNoError(err, "couldn't create client")
|
|
||||||
|
|
||||||
_, err = client.CoreV1().ConfigMaps(clusterInfoNamespace).
|
|
||||||
Get(clusterInfoName, metav1.GetOptions{})
|
|
||||||
framework.ExpectNoError(err, "couldn't anonymously access config")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Describe("bootstrap signer RBAC role", func() {
|
|
||||||
It("should exist", func() {
|
|
||||||
_, err := f.ClientSet.RbacV1().
|
|
||||||
Roles(bootstrapSignerRoleNamespace).
|
|
||||||
Get(bootstrapSignerRoleName, metav1.GetOptions{})
|
|
||||||
framework.ExpectNoError(err, "doesn't exist")
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Describe("kubeadm:kubelet-bootstrap cluster role binding", func() {
|
|
||||||
It("should exist", func() {
|
|
||||||
binding, err := f.ClientSet.RbacV1().
|
|
||||||
ClusterRoleBindings().
|
|
||||||
Get("kubeadm:kubelet-bootstrap", metav1.GetOptions{})
|
|
||||||
framework.ExpectNoError(err, "couldn't get clusterrolebinding")
|
|
||||||
Expect(binding.Subjects).To(
|
|
||||||
ContainElement(subject(
|
|
||||||
"system:bootstrappers:kubeadm:default-node-token",
|
|
||||||
rbacv1.GroupKind,
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
Expect(binding.RoleRef.Name).To(Equal("system:node-bootstrapper"))
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
Describe("autoapproval for new bootstrap token", func() {
|
|
||||||
It("should create a clusterrolebinding", func() {
|
|
||||||
binding, err := f.ClientSet.RbacV1().
|
|
||||||
ClusterRoleBindings().
|
|
||||||
Get("kubeadm:node-autoapprove-bootstrap", metav1.GetOptions{})
|
|
||||||
framework.ExpectNoError(err, "couldn't get clusterrolebinding")
|
|
||||||
Expect(binding.Subjects).To(
|
|
||||||
ContainElement(subject(
|
|
||||||
"system:bootstrappers:kubeadm:default-node-token",
|
|
||||||
rbacv1.GroupKind,
|
|
||||||
)),
|
|
||||||
)
|
|
||||||
Expect(binding.RoleRef.Name).To(
|
|
||||||
Equal("system:certificates.k8s.io:certificatesigningrequests:nodeclient"),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
@ -99,7 +99,8 @@ func getBazelGinkgo() (string, error) {
|
|||||||
|
|
||||||
func execCommand(binary string, args ...string) error {
|
func execCommand(binary string, args ...string) error {
|
||||||
fmt.Printf("Running command: %v %v\n", binary, strings.Join(args, " "))
|
fmt.Printf("Running command: %v %v\n", binary, strings.Join(args, " "))
|
||||||
cmd := exec.Command(binary, args...)
|
cmd := exec.Command("sh", "-c", strings.Join(append([]string{binary}, args...), " "))
|
||||||
|
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdout = os.Stdout
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stderr = os.Stderr
|
||||||
return cmd.Run()
|
return cmd.Run()
|
||||||
|
125
test/e2e_kubeadm/util.go
Normal file
125
test/e2e_kubeadm/util.go
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
/*
|
||||||
|
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 e2e_kubeadm
|
||||||
|
|
||||||
|
import (
|
||||||
|
authv1 "k8s.io/api/authorization/v1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
rbacv1 "k8s.io/api/rbac/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
clientset "k8s.io/client-go/kubernetes"
|
||||||
|
"k8s.io/kubernetes/test/e2e/framework"
|
||||||
|
|
||||||
|
. "github.com/onsi/gomega"
|
||||||
|
"github.com/onsi/gomega/gstruct"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigMaps utils
|
||||||
|
|
||||||
|
func GetConfigMap(c clientset.Interface, namespace, name string) *corev1.ConfigMap {
|
||||||
|
r, err := c.CoreV1().
|
||||||
|
ConfigMaps(namespace).
|
||||||
|
Get(name, metav1.GetOptions{})
|
||||||
|
framework.ExpectNoError(err, "error getting ConfigMap %q from namespace %q", name, namespace)
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// RBAC utils
|
||||||
|
|
||||||
|
func ExpectRole(c clientset.Interface, namespace, name string) {
|
||||||
|
_, err := c.RbacV1().
|
||||||
|
Roles(namespace).
|
||||||
|
Get(name, metav1.GetOptions{})
|
||||||
|
framework.ExpectNoError(err, "error getting Role %q from namespace %q", name, namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExpectRoleBinding(c clientset.Interface, namespace, name string) {
|
||||||
|
_, err := c.RbacV1().
|
||||||
|
RoleBindings(namespace).
|
||||||
|
Get(name, metav1.GetOptions{})
|
||||||
|
framework.ExpectNoError(err, "error getting RoleBinding %q from namespace %q", name, namespace)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExpectClusterRole(c clientset.Interface, name string) {
|
||||||
|
_, err := c.RbacV1().
|
||||||
|
ClusterRoles().
|
||||||
|
Get(name, metav1.GetOptions{})
|
||||||
|
framework.ExpectNoError(err, "error getting ClusterRole %q", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExpectClusterRoleBinding(c clientset.Interface, name string) {
|
||||||
|
_, err := c.RbacV1().
|
||||||
|
ClusterRoleBindings().
|
||||||
|
Get(name, metav1.GetOptions{})
|
||||||
|
framework.ExpectNoError(err, "error getting ClusterRoleBindings %q", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExpectClusterRoleBindingWithSubjectAndRole(c clientset.Interface, name, subjectKind, subject, role string) {
|
||||||
|
binding, err := c.RbacV1().
|
||||||
|
ClusterRoleBindings().
|
||||||
|
Get(name, metav1.GetOptions{})
|
||||||
|
framework.ExpectNoError(err, "error getting ClusterRoleBindings %q", name)
|
||||||
|
Expect(binding.Subjects).To(
|
||||||
|
ContainElement(subjectMatcher(
|
||||||
|
subject,
|
||||||
|
subjectKind,
|
||||||
|
)),
|
||||||
|
"ClusterRole %q does not have %s %q as subject", name, subjectKind, subject,
|
||||||
|
)
|
||||||
|
Expect(binding.RoleRef.Name).To(
|
||||||
|
Equal(role),
|
||||||
|
"ClusterRole %q does not have %q as role", name, role,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExpectSubjectHasAccessToResource(c clientset.Interface, subjectKind, subject string, resource *authv1.ResourceAttributes) {
|
||||||
|
var sar *authv1.SubjectAccessReview
|
||||||
|
switch subjectKind {
|
||||||
|
case rbacv1.GroupKind:
|
||||||
|
sar = &authv1.SubjectAccessReview{
|
||||||
|
Spec: authv1.SubjectAccessReviewSpec{
|
||||||
|
Groups: []string{subject},
|
||||||
|
ResourceAttributes: resource,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
case rbacv1.UserKind:
|
||||||
|
fallthrough
|
||||||
|
case rbacv1.ServiceAccountKind:
|
||||||
|
sar = &authv1.SubjectAccessReview{
|
||||||
|
Spec: authv1.SubjectAccessReviewSpec{
|
||||||
|
User: subject,
|
||||||
|
ResourceAttributes: resource,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
framework.Failf("invalid subjectKind %s", subjectKind)
|
||||||
|
}
|
||||||
|
|
||||||
|
s, err := c.AuthorizationV1().SubjectAccessReviews().Create(sar)
|
||||||
|
framework.ExpectNoError(err, "error getting SubjectAccessReview for %s %s to resource %+v", subjectKind, subject, *sar.Spec.ResourceAttributes)
|
||||||
|
|
||||||
|
Expect(s.Status.Allowed).Should(BeTrue(), "%s %s has no access to resource %+v", subjectKind, subject, *sar.Spec.ResourceAttributes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// matchers
|
||||||
|
|
||||||
|
func subjectMatcher(name, kind string) OmegaMatcher {
|
||||||
|
return gstruct.MatchFields(gstruct.IgnoreExtras, gstruct.Fields{
|
||||||
|
"Name": Equal(name),
|
||||||
|
"Kind": Equal(kind),
|
||||||
|
})
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user