vendor update for CSI 0.3.0

This commit is contained in:
gman
2018-07-18 16:47:22 +02:00
parent 6f484f92fc
commit 8ea659f0d5
6810 changed files with 438061 additions and 193861 deletions

View File

@ -10,10 +10,10 @@ go_library(
name = "go_default_library",
srcs = [
"arguments.go",
"cgroupdriver.go",
"copy.go",
"endpoint.go",
"error.go",
"etcd.go",
"marshal.go",
"template.go",
"version.go",
@ -21,8 +21,8 @@ go_library(
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util",
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/preflight:go_default_library",
"//vendor/github.com/coreos/etcd/clientv3:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//vendor/gopkg.in/yaml.v2:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
@ -30,6 +30,7 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/validation:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library",
"//vendor/k8s.io/utils/exec:go_default_library",
],
)
@ -37,15 +38,20 @@ go_test(
name = "go_default_test",
srcs = [
"arguments_test.go",
"cgroupdriver_test.go",
"endpoint_test.go",
"error_test.go",
"marshal_test.go",
"template_test.go",
"version_test.go",
],
embed = [":go_default_library"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/preflight:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/scheme:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
],
)
@ -64,10 +70,10 @@ filegroup(
"//cmd/kubeadm/app/util/audit:all-srcs",
"//cmd/kubeadm/app/util/config:all-srcs",
"//cmd/kubeadm/app/util/dryrun:all-srcs",
"//cmd/kubeadm/app/util/etcd:all-srcs",
"//cmd/kubeadm/app/util/kubeconfig:all-srcs",
"//cmd/kubeadm/app/util/pubkeypin:all-srcs",
"//cmd/kubeadm/app/util/staticpod:all-srcs",
"//cmd/kubeadm/app/util/token:all-srcs",
],
tags = ["automanaged"],
)

View File

@ -19,6 +19,8 @@ go_library(
deps = [
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/util:go_default_library",
"//pkg/kubelet/apis:go_default_library",
"//pkg/kubelet/types:go_default_library",
"//pkg/registry/core/service/ipallocator:go_default_library",
"//vendor/k8s.io/api/apps/v1:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
@ -27,8 +29,10 @@ go_library(
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
"//vendor/k8s.io/client-go/dynamic:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",

View File

@ -34,7 +34,7 @@ import (
// ClientBackedDryRunGetter implements the DryRunGetter interface for use in NewDryRunClient() and proxies all GET and LIST requests to the backing API server reachable via rest.Config
type ClientBackedDryRunGetter struct {
client clientset.Interface
dynClientPool dynamic.ClientPool
dynamicClient dynamic.Interface
}
// InitDryRunGetter should implement the DryRunGetter interface
@ -46,10 +46,14 @@ func NewClientBackedDryRunGetter(config *rest.Config) (*ClientBackedDryRunGetter
if err != nil {
return nil, err
}
dynamicClient, err := dynamic.NewForConfig(config)
if err != nil {
return nil, err
}
return &ClientBackedDryRunGetter{
client: client,
dynClientPool: dynamic.NewDynamicClientPool(config),
dynamicClient: dynamicClient,
}, nil
}
@ -68,22 +72,15 @@ func NewClientBackedDryRunGetterFromKubeconfig(file string) (*ClientBackedDryRun
// HandleGetAction handles GET actions to the dryrun clientset this interface supports
func (clg *ClientBackedDryRunGetter) HandleGetAction(action core.GetAction) (bool, runtime.Object, error) {
rc, err := clg.actionToResourceClient(action)
unstructuredObj, err := clg.dynamicClient.Resource(action.GetResource()).Namespace(action.GetNamespace()).Get(action.GetName(), metav1.GetOptions{})
// Inform the user that the requested object wasn't found.
printIfNotExists(err)
if err != nil {
return true, nil, err
}
unversionedObj, err := rc.Get(action.GetName(), metav1.GetOptions{})
newObj, err := decodeUnstructuredIntoAPIObject(action, unstructuredObj)
if err != nil {
return true, nil, err
}
// If the unversioned object does not have .apiVersion; the inner object is probably nil
if len(unversionedObj.GetAPIVersion()) == 0 {
return true, nil, apierrors.NewNotFound(action.GetResource().GroupResource(), action.GetName())
}
newObj, err := decodeUnversionedIntoAPIObject(action, unversionedObj)
if err != nil {
fmt.Printf("error after decode: %v %v\n", unversionedObj, err)
fmt.Printf("error after decode: %v %v\n", unstructuredObj, err)
return true, nil, err
}
return true, newObj, err
@ -91,27 +88,18 @@ func (clg *ClientBackedDryRunGetter) HandleGetAction(action core.GetAction) (boo
// HandleListAction handles LIST actions to the dryrun clientset this interface supports
func (clg *ClientBackedDryRunGetter) HandleListAction(action core.ListAction) (bool, runtime.Object, error) {
rc, err := clg.actionToResourceClient(action)
if err != nil {
return true, nil, err
}
listOpts := metav1.ListOptions{
LabelSelector: action.GetListRestrictions().Labels.String(),
FieldSelector: action.GetListRestrictions().Fields.String(),
}
unversionedList, err := rc.List(listOpts)
unstructuredList, err := clg.dynamicClient.Resource(action.GetResource()).Namespace(action.GetNamespace()).List(listOpts)
if err != nil {
return true, nil, err
}
// If the runtime.Object here is nil, we should return successfully with no result
if unversionedList == nil {
return true, unversionedList, nil
}
newObj, err := decodeUnversionedIntoAPIObject(action, unversionedList)
newObj, err := decodeUnstructuredIntoAPIObject(action, unstructuredList)
if err != nil {
fmt.Printf("error after decode: %v %v\n", unversionedList, err)
fmt.Printf("error after decode: %v %v\n", unstructuredList, err)
return true, nil, err
}
return true, newObj, err
@ -122,28 +110,10 @@ func (clg *ClientBackedDryRunGetter) Client() clientset.Interface {
return clg.client
}
// actionToResourceClient returns the ResourceInterface for the given action
// First; the function gets the right API group interface from the resource type. The API group struct behind the interface
// returned may be cached in the dynamic client pool. Then, an APIResource object is constructed so that it can be passed to
// dynamic.Interface's Resource() function, which will give us the final ResourceInterface to query
func (clg *ClientBackedDryRunGetter) actionToResourceClient(action core.Action) (dynamic.ResourceInterface, error) {
dynIface, err := clg.dynClientPool.ClientForGroupVersionResource(action.GetResource())
if err != nil {
return nil, err
}
apiResource := &metav1.APIResource{
Name: action.GetResource().Resource,
Namespaced: action.GetNamespace() != "",
}
return dynIface.Resource(apiResource, action.GetNamespace()), nil
}
// decodeUnversionedIntoAPIObject converts the *unversioned.Unversioned object returned from the dynamic client
// to bytes; and then decodes it back _to an external api version (k8s.io/api vs k8s.io/kubernetes/pkg/api*)_ using the normal API machinery
func decodeUnversionedIntoAPIObject(action core.Action, unversionedObj runtime.Object) (runtime.Object, error) {
objBytes, err := json.Marshal(unversionedObj)
func decodeUnstructuredIntoAPIObject(action core.Action, unstructuredObj runtime.Unstructured) (runtime.Object, error) {
objBytes, err := json.Marshal(unstructuredObj)
if err != nil {
return nil, err
}
@ -153,3 +123,9 @@ func decodeUnversionedIntoAPIObject(action core.Action, unversionedObj runtime.O
}
return newObj, nil
}
func printIfNotExists(err error) {
if apierrors.IsNotFound(err) {
fmt.Println("[dryrun] The GET request didn't yield any result, the API Server returned a NotFound error.")
}
}

View File

@ -55,6 +55,18 @@ type DryRunClientOptions struct {
PrintGETAndLIST bool
}
// GetDefaultDryRunClientOptions returns the default DryRunClientOptions values
func GetDefaultDryRunClientOptions(drg DryRunGetter, w io.Writer) DryRunClientOptions {
return DryRunClientOptions{
Writer: w,
Getter: drg,
PrependReactors: []core.Reactor{},
AppendReactors: []core.Reactor{},
MarshalFunc: DefaultMarshalFunc,
PrintGETAndLIST: false,
}
}
// actionWithName is the generic interface for an action that has a name associated with it
// This just makes it easier to catch all actions that has a name; instead of hard-coding all request that has it associated
type actionWithName interface {
@ -71,14 +83,7 @@ type actionWithObject interface {
// NewDryRunClient is a wrapper for NewDryRunClientWithOpts using some default values
func NewDryRunClient(drg DryRunGetter, w io.Writer) clientset.Interface {
return NewDryRunClientWithOpts(DryRunClientOptions{
Writer: w,
Getter: drg,
PrependReactors: []core.Reactor{},
AppendReactors: []core.Reactor{},
MarshalFunc: DefaultMarshalFunc,
PrintGETAndLIST: false,
})
return NewDryRunClientWithOpts(GetDefaultDryRunClientOptions(drg, w))
}
// NewDryRunClientWithOpts returns a clientset.Interface that can be used normally for talking to the Kubernetes API.

View File

@ -17,6 +17,7 @@ limitations under the License.
package apiclient
import (
"encoding/json"
"fmt"
apps "k8s.io/api/apps/v1"
@ -24,7 +25,12 @@ import (
rbac "k8s.io/api/rbac/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/strategicpatch"
"k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeletapis "k8s.io/kubernetes/pkg/kubelet/apis"
)
// TODO: We should invent a dynamic mechanism for this using the dynamic client instead of hard-coding these functions per-type
@ -44,6 +50,21 @@ func CreateOrUpdateConfigMap(client clientset.Interface, cm *v1.ConfigMap) error
return nil
}
// CreateOrRetainConfigMap creates a ConfigMap if the target resource doesn't exist. If the resource exists already, this function will retain the resource instead.
func CreateOrRetainConfigMap(client clientset.Interface, cm *v1.ConfigMap, configMapName string) error {
if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Get(configMapName, metav1.GetOptions{}); err != nil {
if !apierrors.IsNotFound(err) {
return nil
}
if _, err := client.CoreV1().ConfigMaps(cm.ObjectMeta.Namespace).Create(cm); err != nil {
if !apierrors.IsAlreadyExists(err) {
return fmt.Errorf("unable to create configmap: %v", err)
}
}
}
return nil
}
// CreateOrUpdateSecret creates a Secret if the target resource doesn't exist. If the resource exists already, this function will update the resource instead.
func CreateOrUpdateSecret(client clientset.Interface, secret *v1.Secret) error {
if _, err := client.CoreV1().Secrets(secret.ObjectMeta.Namespace).Create(secret); err != nil {
@ -171,3 +192,49 @@ func CreateOrUpdateClusterRoleBinding(client clientset.Interface, clusterRoleBin
}
return nil
}
// PatchNode tries to patch a node using the following client, executing patchFn for the actual mutating logic
func PatchNode(client clientset.Interface, nodeName string, patchFn func(*v1.Node)) error {
// Loop on every false return. Return with an error if raised. Exit successfully if true is returned.
return wait.Poll(constants.APICallRetryInterval, constants.PatchNodeTimeout, func() (bool, error) {
// First get the node object
n, err := client.CoreV1().Nodes().Get(nodeName, metav1.GetOptions{})
if err != nil {
return false, nil
}
// The node may appear to have no labels at first,
// so we wait for it to get hostname label.
if _, found := n.ObjectMeta.Labels[kubeletapis.LabelHostname]; !found {
return false, nil
}
oldData, err := json.Marshal(n)
if err != nil {
return false, err
}
// Execute the mutating function
patchFn(n)
newData, err := json.Marshal(n)
if err != nil {
return false, err
}
patchBytes, err := strategicpatch.CreateTwoWayMergePatch(oldData, newData, v1.Node{})
if err != nil {
return false, err
}
if _, err := client.CoreV1().Nodes().Patch(n.Name, types.StrategicMergePatchType, patchBytes); err != nil {
if apierrors.IsConflict(err) {
fmt.Println("[patchnode] Temporarily unable to update node metadata due to conflict (will retry)")
return false, nil
}
return false, err
}
return true, nil
})
}

View File

@ -132,9 +132,7 @@ func (idr *InitDryRunGetter) handleGetNode(action core.GetAction) (bool, runtime
Labels: map[string]string{
"kubernetes.io/hostname": idr.masterName,
},
},
Spec: v1.NodeSpec{
ExternalID: idr.masterName,
Annotations: map[string]string{},
},
}, nil
}

View File

@ -46,7 +46,7 @@ func TestHandleGetAction(t *testing.T) {
{
action: core.NewRootGetAction(schema.GroupVersionResource{Version: "v1", Resource: "nodes"}, masterName),
expectedHandled: true,
expectedObjectJSON: []byte(`{"metadata":{"name":"master-foo","creationTimestamp":null,"labels":{"kubernetes.io/hostname":"master-foo"}},"spec":{"externalID":"master-foo"},"status":{"daemonEndpoints":{"kubeletEndpoint":{"Port":0}},"nodeInfo":{"machineID":"","systemUUID":"","bootID":"","kernelVersion":"","osImage":"","containerRuntimeVersion":"","kubeletVersion":"","kubeProxyVersion":"","operatingSystem":"","architecture":""}}}`),
expectedObjectJSON: []byte(`{"metadata":{"name":"master-foo","creationTimestamp":null,"labels":{"kubernetes.io/hostname":"master-foo"}},"spec":{},"status":{"daemonEndpoints":{"kubeletEndpoint":{"Port":0}},"nodeInfo":{"machineID":"","systemUUID":"","bootID":"","kernelVersion":"","osImage":"","containerRuntimeVersion":"","kubeletVersion":"","kubeProxyVersion":"","operatingSystem":"","architecture":""}}}`),
expectedErr: false,
},
{

View File

@ -17,8 +17,6 @@ limitations under the License.
package apiclient
import (
"crypto/sha256"
"encoding/json"
"fmt"
"io"
"net/http"
@ -31,6 +29,7 @@ import (
"k8s.io/apimachinery/pkg/util/wait"
clientset "k8s.io/client-go/kubernetes"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
)
// Waiter is an interface for waiting for criteria in Kubernetes to happen
@ -43,11 +42,11 @@ type Waiter interface {
WaitForPodToDisappear(staticPodName string) error
// WaitForStaticPodSingleHash fetches sha256 hash for the control plane static pod
WaitForStaticPodSingleHash(nodeName string, component string) (string, error)
// WaitForStaticPodHashChange waits for the given static pod component's static pod hash to get updated.
// By doing that we can be sure that the kubelet has restarted the given Static Pod
WaitForStaticPodHashChange(nodeName, component, previousHash string) error
// WaitForStaticPodControlPlaneHashes fetches sha256 hashes for the control plane static pods
WaitForStaticPodControlPlaneHashes(nodeName string) (map[string]string, error)
// WaitForStaticPodControlPlaneHashChange waits for the given static pod component's static pod hash to get updated.
// By doing that we can be sure that the kubelet has restarted the given Static Pod
WaitForStaticPodControlPlaneHashChange(nodeName, component, previousHash string) error
// WaitForHealthyKubelet blocks until the kubelet /healthz endpoint returns 'ok'
WaitForHealthyKubelet(initalTimeout time.Duration, healthzEndpoint string) error
// SetTimeout adjusts the timeout to the specified duration
@ -194,17 +193,17 @@ func (w *KubeWaiter) WaitForStaticPodSingleHash(nodeName string, component strin
return componentPodHash, err
}
// WaitForStaticPodControlPlaneHashChange blocks until it timeouts or notices that the Mirror Pod (for the Static Pod, respectively) has changed
// WaitForStaticPodHashChange blocks until it timeouts or notices that the Mirror Pod (for the Static Pod, respectively) has changed
// This implicitly means this function blocks until the kubelet has restarted the Static Pod in question
func (w *KubeWaiter) WaitForStaticPodControlPlaneHashChange(nodeName, component, previousHash string) error {
func (w *KubeWaiter) WaitForStaticPodHashChange(nodeName, component, previousHash string) error {
return wait.PollImmediate(constants.APICallRetryInterval, w.timeout, func() (bool, error) {
hashes, err := getStaticPodControlPlaneHashes(w.client, nodeName)
hash, err := getStaticPodSingleHash(w.client, nodeName, component)
if err != nil {
return false, nil
}
// We should continue polling until the UID changes
if hashes[component] == previousHash {
if hash == previousHash {
return false, nil
}
@ -235,12 +234,9 @@ func getStaticPodSingleHash(client clientset.Interface, nodeName string, compone
return "", err
}
podBytes, err := json.Marshal(staticPod)
if err != nil {
return "", err
}
return fmt.Sprintf("%x", sha256.Sum256(podBytes)), nil
staticPodHash := staticPod.Annotations[kubetypes.ConfigHashAnnotationKey]
fmt.Printf("Static pod: %s hash: %s\n", staticPodName, staticPodHash)
return staticPodHash, nil
}
// TryRunCommand runs a function a maximum of failureThreshold times, and retries on error. If failureThreshold is hit; the last error is returned

View File

@ -18,17 +18,32 @@ package util
import (
"fmt"
"sort"
"strings"
)
// BuildArgumentListFromMap takes two string-string maps, one with the base arguments and one with optional override arguments
// BuildArgumentListFromMap takes two string-string maps, one with the base arguments and one
// with optional override arguments. In the return list override arguments will precede base
// arguments
func BuildArgumentListFromMap(baseArguments map[string]string, overrideArguments map[string]string) []string {
var command []string
for k, v := range overrideArguments {
var keys []string
for k := range overrideArguments {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
v := overrideArguments[k]
// values of "" are allowed as well
command = append(command, fmt.Sprintf("--%s=%s", k, v))
}
for k, v := range baseArguments {
keys = []string{}
for k := range baseArguments {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
v := baseArguments[k]
if _, overrideExists := overrideArguments[k]; !overrideExists {
command = append(command, fmt.Sprintf("--%s=%s", k, v))
}

View File

@ -39,8 +39,8 @@ func TestBuildArgumentListFromMap(t *testing.T) {
},
expected: []string{
"--admission-control=NamespaceLifecycle,LimitRanger",
"--insecure-bind-address=127.0.0.1",
"--allow-privileged=true",
"--insecure-bind-address=127.0.0.1",
},
},
{ // add an argument that is not in base
@ -53,8 +53,8 @@ func TestBuildArgumentListFromMap(t *testing.T) {
},
expected: []string{
"--admission-control=NamespaceLifecycle,LimitRanger",
"--insecure-bind-address=127.0.0.1",
"--allow-privileged=true",
"--insecure-bind-address=127.0.0.1",
},
},
{ // allow empty strings in base
@ -68,8 +68,8 @@ func TestBuildArgumentListFromMap(t *testing.T) {
},
expected: []string{
"--admission-control=NamespaceLifecycle,LimitRanger",
"--insecure-bind-address=127.0.0.1",
"--allow-privileged=true",
"--insecure-bind-address=127.0.0.1",
"--something-that-allows-empty-string=",
},
},
@ -85,17 +85,15 @@ func TestBuildArgumentListFromMap(t *testing.T) {
},
expected: []string{
"--admission-control=NamespaceLifecycle,LimitRanger",
"--insecure-bind-address=127.0.0.1",
"--allow-privileged=true",
"--something-that-allows-empty-string=",
"--allow-privileged=true",
"--insecure-bind-address=127.0.0.1",
},
},
}
for _, rt := range tests {
actual := BuildArgumentListFromMap(rt.base, rt.overrides)
sort.Strings(actual)
sort.Strings(rt.expected)
if !reflect.DeepEqual(actual, rt.expected) {
t.Errorf("failed BuildArgumentListFromMap:\nexpected:\n%v\nsaw:\n%v", rt.expected, actual)
}

View File

@ -8,8 +8,10 @@ go_library(
deps = [
"//cmd/kubeadm/app/util:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/audit/install:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1:go_default_library",
"//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library",
],
)
@ -18,8 +20,9 @@ go_test(
srcs = ["utils_test.go"],
embed = [":go_default_library"],
deps = [
"//pkg/kubectl/scheme:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/audit/install:go_default_library",
"//vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1:go_default_library",
],
)

View File

@ -23,8 +23,10 @@ import (
"path/filepath"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apiserver/pkg/apis/audit/install"
auditv1beta1 "k8s.io/apiserver/pkg/apis/audit/v1beta1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/kubernetes/cmd/kubeadm/app/util"
)
@ -32,7 +34,7 @@ import (
func CreateDefaultAuditLogPolicy(policyFile string) error {
policy := auditv1beta1.Policy{
TypeMeta: metav1.TypeMeta{
APIVersion: "audit.k8s.io/v1beta1",
APIVersion: auditv1beta1.SchemeGroupVersion.String(),
Kind: "Policy",
},
Rules: []auditv1beta1.PolicyRule{
@ -50,11 +52,15 @@ func writePolicyToDisk(policyFile string, policy *auditv1beta1.Policy) error {
return fmt.Errorf("failed to create directory %q: %v", filepath.Dir(policyFile), err)
}
// Registers auditv1beta1 with the runtime Scheme
auditv1beta1.AddToScheme(scheme.Scheme)
scheme := runtime.NewScheme()
// Registers the API group with the scheme and adds types to a scheme
install.Install(scheme)
codecs := serializer.NewCodecFactory(scheme)
// writes the policy to disk
serialized, err := util.MarshalToYaml(policy, auditv1beta1.SchemeGroupVersion)
serialized, err := util.MarshalToYamlForCodecs(policy, auditv1beta1.SchemeGroupVersion, codecs)
if err != nil {
return fmt.Errorf("failed to marshal audit policy to YAML: %v", err)
}

View File

@ -23,8 +23,9 @@ import (
"testing"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/apiserver/pkg/apis/audit/install"
auditv1beta1 "k8s.io/apiserver/pkg/apis/audit/v1beta1"
"k8s.io/kubernetes/pkg/kubectl/scheme"
)
func cleanup(t *testing.T, path string) {
@ -50,8 +51,11 @@ func TestCreateDefaultAuditLogPolicy(t *testing.T) {
if err != nil {
t.Fatalf("failed to read %v: %v", auditPolicyFile, err)
}
scheme := runtime.NewScheme()
install.Install(scheme)
codecs := serializer.NewCodecFactory(scheme)
policy := auditv1beta1.Policy{}
err = runtime.DecodeInto(scheme.Codecs.UniversalDecoder(), policyBytes, &policy)
err = runtime.DecodeInto(codecs.UniversalDecoder(), policyBytes, &policy)
if err != nil {
t.Fatalf("failed to decode written policy: %v", err)
}

View File

@ -0,0 +1,75 @@
/*
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 util
import (
"fmt"
"strings"
utilsexec "k8s.io/utils/exec"
)
// TODO: add support for detecting the cgroup driver for CRI other than
// Docker. Currently only Docker driver detection is supported:
// Discussion:
// https://github.com/kubernetes/kubeadm/issues/844
// GetCgroupDriverDocker runs 'docker info' to obtain the docker cgroup driver
func GetCgroupDriverDocker(execer utilsexec.Interface) (string, error) {
info, err := callDockerInfo(execer)
if err != nil {
return "", err
}
return getCgroupDriverFromDockerInfo(info)
}
func validateCgroupDriver(driver string) error {
if driver != "cgroupfs" && driver != "systemd" {
return fmt.Errorf("unknown cgroup driver %q", driver)
}
return nil
}
// TODO: Docker 1.13 has a new way to obatain the cgroup driver:
// docker info -f "{{.CgroupDriver}}
// If the minimum supported Docker version in K8s becomes 1.13, move to
// this syntax.
func callDockerInfo(execer utilsexec.Interface) (string, error) {
out, err := execer.Command("docker", "info").Output()
if err != nil {
return "", fmt.Errorf("cannot execute 'docker info': %v", err)
}
return string(out), nil
}
func getCgroupDriverFromDockerInfo(info string) (string, error) {
lineSeparator := ": "
prefix := "Cgroup Driver"
for _, line := range strings.Split(info, "\n") {
if !strings.Contains(line, prefix+lineSeparator) {
continue
}
lineSplit := strings.Split(line, lineSeparator)
// At this point len(lineSplit) is always >= 2
driver := lineSplit[1]
if err := validateCgroupDriver(driver); err != nil {
return "", err
}
return driver, nil
}
return "", fmt.Errorf("cgroup driver is not defined in 'docker info'")
}

View File

@ -0,0 +1,68 @@
/*
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 util
import (
"testing"
)
func TestGetCgroupDriverDocker(t *testing.T) {
testCases := []struct {
name string
info string
expectedError bool
}{
{
name: "valid: value is 'cgroupfs'",
info: `Cgroup Driver: cgroupfs`,
expectedError: false,
},
{
name: "valid: value is 'systemd'",
info: `Cgroup Driver: systemd`,
expectedError: false,
},
{
name: "invalid: missing 'Cgroup Driver' key and value",
info: "",
expectedError: true,
},
{
name: "invalid: only a 'Cgroup Driver' key is present",
info: `Cgroup Driver`,
expectedError: true,
},
{
name: "invalid: empty 'Cgroup Driver' value",
info: `Cgroup Driver: `,
expectedError: true,
},
{
name: "invalid: unknown 'Cgroup Driver' value",
info: `Cgroup Driver: invalid-value`,
expectedError: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
if _, err := getCgroupDriverFromDockerInfo(tc.info); (err != nil) != tc.expectedError {
t.Fatalf("expected error: %v, saw: %v, error: %v", tc.expectedError, (err != nil), err)
}
})
}
}

View File

@ -8,27 +8,52 @@ load(
go_library(
name = "go_default_library",
srcs = ["masterconfig.go"],
srcs = [
"cluster.go",
"masterconfig.go",
"nodeconfig.go",
],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/config",
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/scheme:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha2:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/util:go_default_library",
"//cmd/kubeadm/app/util/token:go_default_library",
"//pkg/api/legacyscheme:go_default_library",
"//pkg/util/node:go_default_library",
"//pkg/util/version:go_default_library",
"//vendor/github.com/golang/glog:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
"//vendor/k8s.io/client-go/tools/bootstrap/token/util:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["masterconfig_test.go"],
srcs = [
"masterconfig_test.go",
"nodeconfig_test.go",
],
data = glob(["testdata/**"]),
embed = [":go_default_library"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/scheme:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
"//cmd/kubeadm/app/apis/kubeadm/v1alpha2:go_default_library",
"//cmd/kubeadm/app/util:go_default_library",
"//vendor/github.com/pmezard/go-difflib/difflib:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
],
)
filegroup(

View File

@ -0,0 +1,65 @@
/*
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 config
import (
"fmt"
"io"
"io/ioutil"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientset "k8s.io/client-go/kubernetes"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
)
// TODO: Add unit tests for this file
// FetchConfigFromFileOrCluster fetches configuration required for upgrading your cluster from a file (which has precedence) or a ConfigMap in the cluster
func FetchConfigFromFileOrCluster(client clientset.Interface, w io.Writer, logPrefix, cfgPath string) (*kubeadmapi.MasterConfiguration, error) {
// Load the configuration from a file or the cluster
configBytes, err := loadConfigurationBytes(client, w, logPrefix, cfgPath)
if err != nil {
return nil, err
}
// Take the versioned configuration populated from the file or ConfigMap, convert it to internal, default and validate
return BytesToInternalConfig(configBytes)
}
// loadConfigurationBytes loads the configuration byte slice from either a file or the cluster ConfigMap
func loadConfigurationBytes(client clientset.Interface, w io.Writer, logPrefix, cfgPath string) ([]byte, error) {
// The config file has the highest priority
if cfgPath != "" {
fmt.Fprintf(w, "[%s] Reading configuration options from a file: %s\n", logPrefix, cfgPath)
return ioutil.ReadFile(cfgPath)
}
fmt.Fprintf(w, "[%s] Reading configuration from the cluster...\n", logPrefix)
configMap, err := client.CoreV1().ConfigMaps(metav1.NamespaceSystem).Get(constants.MasterConfigurationConfigMap, metav1.GetOptions{})
if apierrors.IsNotFound(err) {
// Return the apierror directly so the caller of this function can know what type of error occurred and act based on that
return []byte{}, err
} else if err != nil {
return []byte{}, fmt.Errorf("an unexpected error happened when trying to get the ConfigMap %q in the %s namespace: %v", constants.MasterConfigurationConfigMap, metav1.NamespaceSystem, err)
}
fmt.Fprintf(w, "[%s] FYI: You can look at this config file with 'kubectl -n %s get cm %s -oyaml'\n", logPrefix, metav1.NamespaceSystem, constants.MasterConfigurationConfigMap)
return []byte(configMap.Data[constants.MasterConfigurationConfigMapKey]), nil
}

View File

@ -14,72 +14,86 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package config
import (
"fmt"
"io/ioutil"
"net"
"strings"
"github.com/golang/glog"
"k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
netutil "k8s.io/apimachinery/pkg/util/net"
bootstraputil "k8s.io/client-go/tools/bootstrap/token/util"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
tokenutil "k8s.io/kubernetes/cmd/kubeadm/app/util/token"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/util/node"
"k8s.io/kubernetes/pkg/util/version"
)
// SetInitDynamicDefaults checks and sets configuration values for Master node
// SetInitDynamicDefaults checks and sets configuration values for the MasterConfiguration object
func SetInitDynamicDefaults(cfg *kubeadmapi.MasterConfiguration) error {
// validate cfg.API.AdvertiseAddress.
addressIP := net.ParseIP(cfg.API.AdvertiseAddress)
if addressIP == nil && cfg.API.AdvertiseAddress != "" {
return fmt.Errorf("couldn't use \"%s\" as \"apiserver-advertise-address\", must be ipv4 or ipv6 address", cfg.API.AdvertiseAddress)
}
// Choose the right address for the API Server to advertise. If the advertise address is localhost or 0.0.0.0, the default interface's IP address is used
// This is the same logic as the API Server uses
ip, err := netutil.ChooseBindAddress(net.ParseIP(cfg.API.AdvertiseAddress))
ip, err := netutil.ChooseBindAddress(addressIP)
if err != nil {
return err
}
cfg.API.AdvertiseAddress = ip.String()
ip = net.ParseIP(cfg.API.AdvertiseAddress)
if ip.To4() != nil {
cfg.KubeProxy.Config.BindAddress = kubeadmapiext.DefaultProxyBindAddressv4
cfg.KubeProxy.Config.BindAddress = kubeadmapiv1alpha2.DefaultProxyBindAddressv4
} else {
cfg.KubeProxy.Config.BindAddress = kubeadmapiext.DefaultProxyBindAddressv6
cfg.KubeProxy.Config.BindAddress = kubeadmapiv1alpha2.DefaultProxyBindAddressv6
}
// Resolve possible version labels and validate version string
err = NormalizeKubernetesVersion(cfg)
if err != nil {
if err := NormalizeKubernetesVersion(cfg); err != nil {
return err
}
if cfg.Token == "" {
var err error
cfg.Token, err = tokenutil.GenerateToken()
// Downcase SANs. Some domain names (like ELBs) have capitals in them.
LowercaseSANs(cfg.APIServerCertSANs)
// Populate the .Token field with a random value if unset
// We do this at this layer, and not the API defaulting layer
// because of possible security concerns, and more practically
// because we can't return errors in the API object defaulting
// process but here we can.
for i, bt := range cfg.BootstrapTokens {
if bt.Token != nil && len(bt.Token.String()) > 0 {
continue
}
tokenStr, err := bootstraputil.GenerateBootstrapToken()
if err != nil {
return fmt.Errorf("couldn't generate random token: %v", err)
}
token, err := kubeadmapi.NewBootstrapTokenString(tokenStr)
if err != nil {
return err
}
cfg.BootstrapTokens[i].Token = token
}
cfg.NodeName = node.GetHostname(cfg.NodeName)
cfg.NodeRegistration.Name = node.GetHostname(cfg.NodeRegistration.Name)
return nil
}
// TryLoadMasterConfiguration tries to loads a Master configuration from the given file (if defined)
func TryLoadMasterConfiguration(cfgPath string, cfg *kubeadmapiext.MasterConfiguration) error {
if cfgPath != "" {
b, err := ioutil.ReadFile(cfgPath)
if err != nil {
return fmt.Errorf("unable to read config from %q [%v]", cfgPath, err)
}
if err := runtime.DecodeInto(legacyscheme.Codecs.UniversalDecoder(), b, cfg); err != nil {
return fmt.Errorf("unable to decode config from %q [%v]", cfgPath, err)
}
// Only if the slice is nil, we should append the master taint. This allows the user to specify an empty slice for no default master taint
if cfg.NodeRegistration.Taints == nil {
cfg.NodeRegistration.Taints = []v1.Taint{kubeadmconstants.MasterTaint}
}
return nil
@ -90,29 +104,93 @@ func TryLoadMasterConfiguration(cfgPath string, cfg *kubeadmapiext.MasterConfigu
// Then the external, versioned configuration is defaulted and converted to the internal type.
// Right thereafter, the configuration is defaulted again with dynamic values (like IP addresses of a machine, etc)
// Lastly, the internal config is validated and returned.
func ConfigFileAndDefaultsToInternalConfig(cfgPath string, defaultversionedcfg *kubeadmapiext.MasterConfiguration) (*kubeadmapi.MasterConfiguration, error) {
func ConfigFileAndDefaultsToInternalConfig(cfgPath string, defaultversionedcfg *kubeadmapiv1alpha2.MasterConfiguration) (*kubeadmapi.MasterConfiguration, error) {
internalcfg := &kubeadmapi.MasterConfiguration{}
// Loads configuration from config file, if provided
// Nb. --config overrides command line flags
if err := TryLoadMasterConfiguration(cfgPath, defaultversionedcfg); err != nil {
return nil, err
if cfgPath != "" {
// Loads configuration from config file, if provided
// Nb. --config overrides command line flags
glog.V(1).Infoln("loading configuration from the given file")
b, err := ioutil.ReadFile(cfgPath)
if err != nil {
return nil, fmt.Errorf("unable to read config from %q [%v]", cfgPath, err)
}
return BytesToInternalConfig(b)
}
// Takes passed flags into account; the defaulting is executed once again enforcing assignement of
// static default values to cfg only for values not provided with flags
legacyscheme.Scheme.Default(defaultversionedcfg)
legacyscheme.Scheme.Convert(defaultversionedcfg, internalcfg, nil)
// Applies dynamic defaults to settings not provided with flags
if err := SetInitDynamicDefaults(internalcfg); err != nil {
return nil, err
kubeadmscheme.Scheme.Default(defaultversionedcfg)
kubeadmscheme.Scheme.Convert(defaultversionedcfg, internalcfg, nil)
return defaultAndValidate(internalcfg)
}
// BytesToInternalConfig converts a byte array to an internal, defaulted and validated configuration object
func BytesToInternalConfig(b []byte) (*kubeadmapi.MasterConfiguration, error) {
internalcfg := &kubeadmapi.MasterConfiguration{}
decoded, err := kubeadmutil.LoadYAML(b)
if err != nil {
return nil, fmt.Errorf("unable to decode config from bytes: %v", err)
}
// Validates cfg (flags/configs + defaults + dynamic defaults)
if err := validation.ValidateMasterConfiguration(internalcfg).ToAggregate(); err != nil {
// As there was a bug in kubeadm v1.10 and earlier that made the YAML uploaded to the cluster configmap NOT have metav1.TypeMeta information
// we need to populate this here manually. If kind or apiVersion is empty, we know the apiVersion is v1alpha1, as by the time kubeadm had this bug,
// it could only write
// TODO: Remove this "hack" in v1.12 when we know the ConfigMap always contains v1alpha2 content written by kubeadm v1.11. Also, we will drop support for
// v1alpha1 in v1.12
kind := decoded["kind"]
apiVersion := decoded["apiVersion"]
if kind == nil || len(kind.(string)) == 0 {
decoded["kind"] = "MasterConfiguration"
}
if apiVersion == nil || len(apiVersion.(string)) == 0 {
decoded["apiVersion"] = kubeadmapiv1alpha1.SchemeGroupVersion.String()
}
// Between v1.9 and v1.10 the proxy componentconfig in the v1alpha1 MasterConfiguration changed unexpectedly, which broke unmarshalling out-of-the-box
// Hence, we need to workaround this bug in the v1alpha1 API
if decoded["apiVersion"] == kubeadmapiv1alpha1.SchemeGroupVersion.String() {
v1alpha1cfg := &kubeadmapiv1alpha1.MasterConfiguration{}
if err := kubeadmapiv1alpha1.Migrate(decoded, v1alpha1cfg, kubeadmscheme.Codecs); err != nil {
return nil, fmt.Errorf("unable to migrate config from previous version: %v", err)
}
// Default and convert to the internal version
kubeadmscheme.Scheme.Default(v1alpha1cfg)
kubeadmscheme.Scheme.Convert(v1alpha1cfg, internalcfg, nil)
} else if decoded["apiVersion"] == kubeadmapiv1alpha2.SchemeGroupVersion.String() {
v1alpha2cfg := &kubeadmapiv1alpha2.MasterConfiguration{}
if err := runtime.DecodeInto(kubeadmscheme.Codecs.UniversalDecoder(), b, v1alpha2cfg); err != nil {
return nil, fmt.Errorf("unable to decode config: %v", err)
}
// Default and convert to the internal version
kubeadmscheme.Scheme.Default(v1alpha2cfg)
kubeadmscheme.Scheme.Convert(v1alpha2cfg, internalcfg, nil)
} else {
// TODO: Add support for an upcoming v1alpha2 API
// TODO: In the future, we can unmarshal any two or more external types into the internal object directly using the following syntax.
// Long-term we don't need this if/else clause. In the future this will do
// runtime.DecodeInto(kubeadmscheme.Codecs.UniversalDecoder(kubeadmapiv1alpha2.SchemeGroupVersion, kubeadmapiv2alpha3.SchemeGroupVersion), b, internalcfg)
return nil, fmt.Errorf("unknown API version for kubeadm configuration")
}
return defaultAndValidate(internalcfg)
}
func defaultAndValidate(cfg *kubeadmapi.MasterConfiguration) (*kubeadmapi.MasterConfiguration, error) {
// Applies dynamic defaults to settings not provided with flags
if err := SetInitDynamicDefaults(cfg); err != nil {
return nil, err
}
return internalcfg, nil
// Validates cfg (flags/configs + defaults + dynamic defaults)
if err := validation.ValidateMasterConfiguration(cfg).ToAggregate(); err != nil {
return nil, err
}
return cfg, nil
}
// NormalizeKubernetesVersion resolves version labels, sets alternative
@ -141,3 +219,14 @@ func NormalizeKubernetesVersion(cfg *kubeadmapi.MasterConfiguration) error {
}
return nil
}
// LowercaseSANs can be used to force all SANs to be lowercase so it passes IsDNS1123Subdomain
func LowercaseSANs(sans []string) {
for i, san := range sans {
lowercase := strings.ToLower(san)
if lowercase != san {
glog.V(1).Infof("lowercasing SAN %q to %q", san, lowercase)
sans[i] = lowercase
}
}
}

View File

@ -1,5 +1,5 @@
/*
Copyright 2017 The Kubernetes Authors.
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.
@ -14,4 +14,212 @@ See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd
package config
import (
"bytes"
"io/ioutil"
"testing"
"github.com/pmezard/go-difflib/difflib"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
)
const (
master_v1alpha1YAML = "testdata/conversion/master/v1alpha1.yaml"
master_v1alpha1WithoutTypeMetaYAML = "testdata/conversion/master/v1alpha1_without_TypeMeta.yaml"
master_v1alpha2YAML = "testdata/conversion/master/v1alpha2.yaml"
master_internalYAML = "testdata/conversion/master/internal.yaml"
master_incompleteYAML = "testdata/defaulting/master/incomplete.yaml"
master_defaultedYAML = "testdata/defaulting/master/defaulted.yaml"
master_invalidYAML = "testdata/validation/invalid_mastercfg.yaml"
master_beforeUpgradeYAML = "testdata/v1alpha1_upgrade/before.yaml"
master_afterUpgradeYAML = "testdata/v1alpha1_upgrade/after.yaml"
)
func diff(expected, actual []byte) string {
// Write out the diff
var diffBytes bytes.Buffer
difflib.WriteUnifiedDiff(&diffBytes, difflib.UnifiedDiff{
A: difflib.SplitLines(string(expected)),
B: difflib.SplitLines(string(actual)),
FromFile: "expected",
ToFile: "actual",
Context: 3,
})
return diffBytes.String()
}
func TestConfigFileAndDefaultsToInternalConfig(t *testing.T) {
var tests = []struct {
name, in, out string
groupVersion schema.GroupVersion
expectedErr bool
}{
// These tests are reading one file, loading it using ConfigFileAndDefaultsToInternalConfig that all of kubeadm is using for unmarshal of our API types,
// and then marshals the internal object to the expected groupVersion
{ // v1alpha1 (faulty) -> internal
name: "v1alpha1WithoutTypeMetaToInternal",
in: master_v1alpha1WithoutTypeMetaYAML,
out: master_internalYAML,
groupVersion: kubeadm.SchemeGroupVersion,
},
{ // v1alpha1 -> internal
name: "v1alpha1ToInternal",
in: master_v1alpha1YAML,
out: master_internalYAML,
groupVersion: kubeadm.SchemeGroupVersion,
},
{ // v1alpha2 -> internal
name: "v1alpha2ToInternal",
in: master_v1alpha2YAML,
out: master_internalYAML,
groupVersion: kubeadm.SchemeGroupVersion,
},
{ // v1alpha1 (faulty) -> internal -> v1alpha2
name: "v1alpha1WithoutTypeMetaTov1alpha2",
in: master_v1alpha1WithoutTypeMetaYAML,
out: master_v1alpha2YAML,
groupVersion: v1alpha2.SchemeGroupVersion,
},
{ // v1alpha1 -> internal -> v1alpha2
name: "v1alpha1Tov1alpha2",
in: master_v1alpha1YAML,
out: master_v1alpha2YAML,
groupVersion: v1alpha2.SchemeGroupVersion,
},
// These tests are reading one file that has only a subset of the fields populated, loading it using ConfigFileAndDefaultsToInternalConfig,
// and then marshals the internal object to the expected groupVersion
{ // v1alpha1 (faulty) -> default -> validate -> internal -> v1alpha2
name: "incompleteYAMLToDefaultedv1alpha2",
in: master_incompleteYAML,
out: master_defaultedYAML,
groupVersion: v1alpha2.SchemeGroupVersion,
},
{ // v1alpha1 (faulty) -> validation should fail
name: "invalidYAMLShouldFail",
in: master_invalidYAML,
expectedErr: true,
},
}
for _, rt := range tests {
t.Run(rt.name, func(t2 *testing.T) {
internalcfg, err := ConfigFileAndDefaultsToInternalConfig(rt.in, &v1alpha2.MasterConfiguration{})
if err != nil {
if rt.expectedErr {
return
}
t2.Fatalf("couldn't unmarshal test data: %v", err)
}
actual, err := kubeadmutil.MarshalToYamlForCodecs(internalcfg, rt.groupVersion, scheme.Codecs)
if err != nil {
t2.Fatalf("couldn't marshal internal object: %v", err)
}
expected, err := ioutil.ReadFile(rt.out)
if err != nil {
t2.Fatalf("couldn't read test data: %v", err)
}
if !bytes.Equal(expected, actual) {
t2.Errorf("the expected and actual output differs.\n\tin: %s\n\tout: %s\n\tgroupversion: %s\n\tdiff: \n%s\n",
rt.in, rt.out, rt.groupVersion.String(), diff(expected, actual))
}
})
}
}
// TestUpgrade tests reading a faulty YAML representation of the MasterConfiguration object (as found in kubeadm clusters <= v1.9.x),
// fixes the problems internally and verifies the marshalled output is the expected output
func TestUpgrade(t *testing.T) {
before, err := ioutil.ReadFile(master_beforeUpgradeYAML)
if err != nil {
t.Fatalf("couldn't read test data: %v", err)
}
afterExpected, err := ioutil.ReadFile(master_afterUpgradeYAML)
if err != nil {
t.Fatalf("couldn't read test data: %v", err)
}
decoded, err := kubeadmutil.LoadYAML(before)
if err != nil {
t.Fatalf("couldn't unmarshal test yaml: %v", err)
}
scheme := runtime.NewScheme()
v1alpha1.AddToScheme(scheme)
codecs := serializer.NewCodecFactory(scheme)
obj := &v1alpha1.MasterConfiguration{}
if err := v1alpha1.Migrate(decoded, obj, codecs); err != nil {
t.Fatalf("couldn't decode migrated object: %v", err)
}
afterActual, err := kubeadmutil.MarshalToYamlForCodecs(obj, v1alpha1.SchemeGroupVersion, codecs)
if err != nil {
t.Fatalf("couldn't marshal object: %v", err)
}
if !bytes.Equal(afterExpected, afterActual) {
t.Errorf("v1alpha1 object after unmarshal, conversion and marshal didn't match expected value.\n\tdiff: \n%s\n", diff(afterExpected, afterActual))
}
}
func TestLowercaseSANs(t *testing.T) {
tests := []struct {
name string
in []string
out []string
}{
{
name: "empty struct",
},
{
name: "already lowercase",
in: []string{"example.k8s.io"},
out: []string{"example.k8s.io"},
},
{
name: "ip addresses and uppercase",
in: []string{"EXAMPLE.k8s.io", "10.100.0.1"},
out: []string{"example.k8s.io", "10.100.0.1"},
},
{
name: "punycode and uppercase",
in: []string{"xn--7gq663byk9a.xn--fiqz9s", "ANOTHEREXAMPLE.k8s.io"},
out: []string{"xn--7gq663byk9a.xn--fiqz9s", "anotherexample.k8s.io"},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
cfg := &v1alpha2.MasterConfiguration{
APIServerCertSANs: test.in,
}
LowercaseSANs(cfg.APIServerCertSANs)
if len(cfg.APIServerCertSANs) != len(test.out) {
t.Fatalf("expected %d elements, got %d", len(test.out), len(cfg.APIServerCertSANs))
}
for i, expected := range test.out {
if cfg.APIServerCertSANs[i] != expected {
t.Errorf("expected element %d to be %q, got %q", i, expected, cfg.APIServerCertSANs[i])
}
}
})
}
}

View File

@ -0,0 +1,71 @@
/*
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 config
import (
"fmt"
"io/ioutil"
"github.com/golang/glog"
"k8s.io/apimachinery/pkg/runtime"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
kubeadmapiv1alpha2 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
"k8s.io/kubernetes/pkg/util/node"
)
// SetJoinDynamicDefaults checks and sets configuration values for the NodeConfiguration object
func SetJoinDynamicDefaults(cfg *kubeadmapi.NodeConfiguration) error {
cfg.NodeRegistration.Name = node.GetHostname(cfg.NodeRegistration.Name)
return nil
}
// NodeConfigFileAndDefaultsToInternalConfig
func NodeConfigFileAndDefaultsToInternalConfig(cfgPath string, defaultversionedcfg *kubeadmapiv1alpha2.NodeConfiguration) (*kubeadmapi.NodeConfiguration, error) {
internalcfg := &kubeadmapi.NodeConfiguration{}
if cfgPath != "" {
// Loads configuration from config file, if provided
// Nb. --config overrides command line flags
glog.V(1).Infoln("loading configuration from the given file")
b, err := ioutil.ReadFile(cfgPath)
if err != nil {
return nil, fmt.Errorf("unable to read config from %q [%v]", cfgPath, err)
}
runtime.DecodeInto(kubeadmscheme.Codecs.UniversalDecoder(kubeadmapiv1alpha1.SchemeGroupVersion, kubeadmapiv1alpha2.SchemeGroupVersion), b, internalcfg)
} else {
// Takes passed flags into account; the defaulting is executed once again enforcing assignement of
// static default values to cfg only for values not provided with flags
kubeadmscheme.Scheme.Default(defaultversionedcfg)
kubeadmscheme.Scheme.Convert(defaultversionedcfg, internalcfg, nil)
}
// Applies dynamic defaults to settings not provided with flags
if err := SetJoinDynamicDefaults(internalcfg); err != nil {
return nil, err
}
// Validates cfg (flags/configs + defaults)
if err := validation.ValidateNodeConfiguration(internalcfg).ToAggregate(); err != nil {
return nil, err
}
return internalcfg, nil
}

View File

@ -0,0 +1,108 @@
/*
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 config
import (
"bytes"
"io/ioutil"
"testing"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha2"
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
)
const (
node_v1alpha1YAML = "testdata/conversion/node/v1alpha1.yaml"
node_v1alpha2YAML = "testdata/conversion/node/v1alpha2.yaml"
node_internalYAML = "testdata/conversion/node/internal.yaml"
node_incompleteYAML = "testdata/defaulting/node/incomplete.yaml"
node_defaultedYAML = "testdata/defaulting/node/defaulted.yaml"
node_invalidYAML = "testdata/validation/invalid_nodecfg.yaml"
)
func TestNodeConfigFileAndDefaultsToInternalConfig(t *testing.T) {
var tests = []struct {
name, in, out string
groupVersion schema.GroupVersion
expectedErr bool
}{
// These tests are reading one file, loading it using NodeConfigFileAndDefaultsToInternalConfig that all of kubeadm is using for unmarshal of our API types,
// and then marshals the internal object to the expected groupVersion
{ // v1alpha1 -> internal
name: "v1alpha1ToInternal",
in: node_v1alpha1YAML,
out: node_internalYAML,
groupVersion: kubeadm.SchemeGroupVersion,
},
{ // v1alpha2 -> internal
name: "v1alpha2ToInternal",
in: node_v1alpha2YAML,
out: node_internalYAML,
groupVersion: kubeadm.SchemeGroupVersion,
},
{ // v1alpha1 -> internal -> v1alpha2
name: "v1alpha1WithoutTypeMetaTov1alpha2",
in: node_v1alpha1YAML,
out: node_v1alpha2YAML,
groupVersion: v1alpha2.SchemeGroupVersion,
},
// These tests are reading one file that has only a subset of the fields populated, loading it using NodeConfigFileAndDefaultsToInternalConfig,
// and then marshals the internal object to the expected groupVersion
{ // v1alpha1 -> default -> validate -> internal -> v1alpha2
name: "incompleteYAMLToDefaulted",
in: node_incompleteYAML,
out: node_defaultedYAML,
groupVersion: v1alpha2.SchemeGroupVersion,
},
{ // v1alpha1 (faulty) -> validation should fail
name: "invalidYAMLShouldFail",
in: node_invalidYAML,
expectedErr: true,
},
}
for _, rt := range tests {
t.Run(rt.name, func(t2 *testing.T) {
internalcfg, err := NodeConfigFileAndDefaultsToInternalConfig(rt.in, &v1alpha2.NodeConfiguration{})
if err != nil {
if rt.expectedErr {
return
}
t2.Fatalf("couldn't unmarshal test data: %v", err)
}
actual, err := kubeadmutil.MarshalToYamlForCodecs(internalcfg, rt.groupVersion, scheme.Codecs)
if err != nil {
t2.Fatalf("couldn't marshal internal object: %v", err)
}
expected, err := ioutil.ReadFile(rt.out)
if err != nil {
t2.Fatalf("couldn't read test data: %v", err)
}
if !bytes.Equal(expected, actual) {
t2.Errorf("the expected and actual output differs.\n\tin: %s\n\tout: %s\n\tgroupversion: %s\n\tdiff: \n%s\n",
rt.in, rt.out, rt.groupVersion.String(), diff(expected, actual))
}
})
}
}

View File

@ -0,0 +1,161 @@
API:
AdvertiseAddress: 192.168.2.2
BindPort: 6443
ControlPlaneEndpoint: ""
APIServerCertSANs: null
APIServerExtraArgs:
authorization-mode: Node,RBAC,Webhook
APIServerExtraVolumes: null
AuditPolicyConfiguration:
LogDir: /var/log/kubernetes/audit
LogMaxAge: 2
Path: ""
BootstrapTokens:
- Description: ""
Expires: null
Groups:
- system:bootstrappers:kubeadm:default-node-token
TTL: 24h0m0s
Token: s73ybu.6tw6wnqgp5z0wb77
Usages:
- signing
- authentication
CIImageRepository: ""
CertificatesDir: /etc/kubernetes/pki
ClusterName: kubernetes
ControllerManagerExtraArgs: null
ControllerManagerExtraVolumes: null
Etcd:
External: null
Local:
DataDir: /var/lib/etcd
ExtraArgs: null
Image: ""
PeerCertSANs: null
ServerCertSANs: null
FeatureGates: null
ImageRepository: k8s.gcr.io
KubeProxy:
Config:
bindAddress: 0.0.0.0
clientConnection:
acceptContentTypes: ""
burst: 10
contentType: application/vnd.kubernetes.protobuf
kubeconfig: /var/lib/kube-proxy/kubeconfig.conf
qps: 5
clusterCIDR: ""
configSyncPeriod: 15m0s
conntrack:
max: null
maxPerCore: 32768
min: 131072
tcpCloseWaitTimeout: 1h0m0s
tcpEstablishedTimeout: 24h0m0s
enableProfiling: false
featureGates:
ServiceNodeExclusion: true
SupportIPVSProxyMode: true
healthzBindAddress: 0.0.0.0:10256
hostnameOverride: ""
iptables:
masqueradeAll: false
masqueradeBit: 14
minSyncPeriod: 0s
syncPeriod: 30s
ipvs:
excludeCIDRs: null
minSyncPeriod: 0s
scheduler: ""
syncPeriod: 30s
metricsBindAddress: 127.0.0.1:10249
mode: ""
nodePortAddresses: null
oomScoreAdj: -999
portRange: ""
resourceContainer: /kube-proxy
udpIdleTimeout: 250ms
KubeletConfiguration:
BaseConfig:
address: 0.0.0.0
authentication:
anonymous:
enabled: false
webhook:
cacheTTL: 2m0s
enabled: true
x509:
clientCAFile: /etc/kubernetes/pki/ca.crt
authorization:
mode: Webhook
webhook:
cacheAuthorizedTTL: 5m0s
cacheUnauthorizedTTL: 30s
cgroupDriver: cgroupfs
cgroupsPerQOS: true
clusterDNS:
- 10.96.0.10
clusterDomain: cluster.local
containerLogMaxFiles: 5
containerLogMaxSize: 10Mi
contentType: application/vnd.kubernetes.protobuf
cpuCFSQuota: true
cpuManagerPolicy: none
cpuManagerReconcilePeriod: 10s
enableControllerAttachDetach: true
enableDebuggingHandlers: true
enforceNodeAllocatable:
- pods
eventBurst: 10
eventRecordQPS: 5
evictionHard:
imagefs.available: 15%
memory.available: 100Mi
nodefs.available: 10%
nodefs.inodesFree: 5%
evictionPressureTransitionPeriod: 5m0s
failSwapOn: true
fileCheckFrequency: 20s
hairpinMode: promiscuous-bridge
healthzBindAddress: 127.0.0.1
healthzPort: 10248
httpCheckFrequency: 20s
imageGCHighThresholdPercent: 85
imageGCLowThresholdPercent: 80
imageMinimumGCAge: 2m0s
iptablesDropBit: 15
iptablesMasqueradeBit: 14
kubeAPIBurst: 10
kubeAPIQPS: 5
makeIPTablesUtilChains: true
maxOpenFiles: 1000000
maxPods: 110
nodeStatusUpdateFrequency: 10s
oomScoreAdj: -999
podPidsLimit: -1
port: 10250
registryBurst: 10
registryPullQPS: 5
resolvConf: /etc/resolv.conf
rotateCertificates: true
runtimeRequestTimeout: 2m0s
serializeImagePulls: true
staticPodPath: /etc/kubernetes/manifests
streamingConnectionIdleTimeout: 4h0m0s
syncFrequency: 1m0s
volumeStatsAggPeriod: 1m0s
KubernetesVersion: v1.10.2
Networking:
DNSDomain: cluster.local
PodSubnet: ""
ServiceSubnet: 10.96.0.0/12
NodeRegistration:
CRISocket: /var/run/dockershim.sock
KubeletExtraArgs: null
Name: master-1
Taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
SchedulerExtraArgs: null
SchedulerExtraVolumes: null
UnifiedControlPlaneImage: ""

View File

@ -0,0 +1,149 @@
api:
advertiseAddress: 192.168.2.2
bindPort: 6443
controlPlaneEndpoint: ""
apiVersion: kubeadm.k8s.io/v1alpha1
auditPolicy:
logDir: /var/log/kubernetes/audit
logMaxAge: 2
path: ""
authorizationModes:
- Node
- RBAC
- Webhook
certificatesDir: /etc/kubernetes/pki
cloudProvider: ""
clusterName: kubernetes
criSocket: /var/run/dockershim.sock
etcd:
caFile: ""
certFile: ""
dataDir: /var/lib/etcd
endpoints: null
image: ""
keyFile: ""
imageRepository: k8s.gcr.io
kind: MasterConfiguration
kubeProxy:
config:
bindAddress: 0.0.0.0
clientConnection:
acceptContentTypes: ""
burst: 10
contentType: application/vnd.kubernetes.protobuf
kubeconfig: /var/lib/kube-proxy/kubeconfig.conf
qps: 5
clusterCIDR: ""
configSyncPeriod: 15m0s
conntrack:
max: null
maxPerCore: 32768
min: 131072
tcpCloseWaitTimeout: 1h0m0s
tcpEstablishedTimeout: 24h0m0s
enableProfiling: false
featureGates:
ServiceNodeExclusion: true
SupportIPVSProxyMode: true
healthzBindAddress: 0.0.0.0:10256
hostnameOverride: ""
iptables:
masqueradeAll: false
masqueradeBit: 14
minSyncPeriod: 0s
syncPeriod: 30s
ipvs:
excludeCIDRs: null
minSyncPeriod: 0s
scheduler: ""
syncPeriod: 30s
metricsBindAddress: 127.0.0.1:10249
mode: ""
nodePortAddresses: null
oomScoreAdj: -999
portRange: ""
resourceContainer: /kube-proxy
udpIdleTimeout: 250ms
kubeletConfiguration:
baseConfig:
address: 0.0.0.0
authentication:
anonymous:
enabled: false
webhook:
cacheTTL: 2m0s
enabled: true
x509:
clientCAFile: /etc/kubernetes/pki/ca.crt
authorization:
mode: Webhook
webhook:
cacheAuthorizedTTL: 5m0s
cacheUnauthorizedTTL: 30s
cgroupDriver: cgroupfs
cgroupsPerQOS: true
clusterDNS:
- 10.96.0.10
clusterDomain: cluster.local
containerLogMaxFiles: 5
containerLogMaxSize: 10Mi
contentType: application/vnd.kubernetes.protobuf
cpuCFSQuota: true
cpuManagerPolicy: none
cpuManagerReconcilePeriod: 10s
enableControllerAttachDetach: true
enableDebuggingHandlers: true
enforceNodeAllocatable:
- pods
eventBurst: 10
eventRecordQPS: 5
evictionHard:
imagefs.available: 15%
memory.available: 100Mi
nodefs.available: 10%
nodefs.inodesFree: 5%
evictionPressureTransitionPeriod: 5m0s
failSwapOn: true
fileCheckFrequency: 20s
hairpinMode: promiscuous-bridge
healthzBindAddress: 127.0.0.1
healthzPort: 10248
httpCheckFrequency: 20s
imageGCHighThresholdPercent: 85
imageGCLowThresholdPercent: 80
imageMinimumGCAge: 2m0s
iptablesDropBit: 15
iptablesMasqueradeBit: 14
kubeAPIBurst: 10
kubeAPIQPS: 5
makeIPTablesUtilChains: true
maxOpenFiles: 1000000
maxPods: 110
nodeStatusUpdateFrequency: 10s
oomScoreAdj: -999
podPidsLimit: -1
port: 10250
registryBurst: 10
registryPullQPS: 5
resolvConf: /etc/resolv.conf
runtimeRequestTimeout: 2m0s
serializeImagePulls: true
staticPodPath: /etc/kubernetes/manifests
streamingConnectionIdleTimeout: 4h0m0s
syncFrequency: 1m0s
volumeStatsAggPeriod: 1m0s
kubernetesVersion: v1.10.2
networking:
dnsDomain: cluster.local
podSubnet: ""
serviceSubnet: 10.96.0.0/12
nodeName: master-1
privilegedPods: false
token: s73ybu.6tw6wnqgp5z0wb77
tokenGroups:
- system:bootstrappers:kubeadm:default-node-token
tokenTTL: 24h0m0s
tokenUsages:
- signing
- authentication
unifiedControlPlaneImage: ""

View File

@ -0,0 +1,146 @@
# This file don't have TypeMeta set. kubeadm should then unmarshal it as a apiVersion=kubeadm.k8s.io/v1alpha1 and kind=MasterConfiguration
api:
advertiseAddress: 192.168.2.2
bindPort: 6443
controlPlaneEndpoint: ""
auditPolicy:
logDir: /var/log/kubernetes/audit
logMaxAge: 2
path: ""
authorizationModes:
- Node
- RBAC
- Webhook
certificatesDir: /etc/kubernetes/pki
cloudProvider: ""
clusterName: kubernetes
criSocket: /var/run/dockershim.sock
etcd:
caFile: ""
certFile: ""
dataDir: /var/lib/etcd
endpoints: null
image: ""
keyFile: ""
imageRepository: k8s.gcr.io
kubeProxy:
config:
bindAddress: 0.0.0.0
clientConnection:
acceptContentTypes: ""
burst: 10
contentType: application/vnd.kubernetes.protobuf
kubeconfig: /var/lib/kube-proxy/kubeconfig.conf
qps: 5
clusterCIDR: ""
configSyncPeriod: 15m0s
conntrack:
max: null
maxPerCore: 32768
min: 131072
tcpCloseWaitTimeout: 1h0m0s
tcpEstablishedTimeout: 24h0m0s
enableProfiling: false
featureGates: "SupportIPVSProxyMode=true,ServiceNodeExclusion=true"
healthzBindAddress: 0.0.0.0:10256
hostnameOverride: ""
iptables:
masqueradeAll: false
masqueradeBit: 14
minSyncPeriod: 0s
syncPeriod: 30s
ipvs:
excludeCIDRs: null
minSyncPeriod: 0s
scheduler: ""
syncPeriod: 30s
metricsBindAddress: 127.0.0.1:10249
mode: ""
nodePortAddresses: null
oomScoreAdj: -999
portRange: ""
resourceContainer: /kube-proxy
udpIdleTimeout: 250ms
kubeletConfiguration:
baseConfig:
address: 0.0.0.0
authentication:
anonymous:
enabled: false
webhook:
cacheTTL: 2m0s
enabled: true
x509:
clientCAFile: /etc/kubernetes/pki/ca.crt
authorization:
mode: Webhook
webhook:
cacheAuthorizedTTL: 5m0s
cacheUnauthorizedTTL: 30s
cgroupDriver: cgroupfs
cgroupsPerQOS: true
clusterDNS:
- 10.96.0.10
clusterDomain: cluster.local
containerLogMaxFiles: 5
containerLogMaxSize: 10Mi
contentType: application/vnd.kubernetes.protobuf
cpuCFSQuota: true
cpuManagerPolicy: none
cpuManagerReconcilePeriod: 10s
enableControllerAttachDetach: true
enableDebuggingHandlers: true
enforceNodeAllocatable:
- pods
eventBurst: 10
eventRecordQPS: 5
evictionHard:
imagefs.available: 15%
memory.available: 100Mi
nodefs.available: 10%
nodefs.inodesFree: 5%
evictionPressureTransitionPeriod: 5m0s
failSwapOn: true
fileCheckFrequency: 20s
hairpinMode: promiscuous-bridge
healthzBindAddress: 127.0.0.1
healthzPort: 10248
httpCheckFrequency: 20s
imageGCHighThresholdPercent: 85
imageGCLowThresholdPercent: 80
imageMinimumGCAge: 2m0s
iptablesDropBit: 15
iptablesMasqueradeBit: 14
kubeAPIBurst: 10
kubeAPIQPS: 5
makeIPTablesUtilChains: true
maxOpenFiles: 1000000
maxPods: 110
nodeStatusUpdateFrequency: 10s
oomScoreAdj: -999
podPidsLimit: -1
port: 10250
registryBurst: 10
registryPullQPS: 5
resolvConf: /etc/resolv.conf
runtimeRequestTimeout: 2m0s
serializeImagePulls: true
staticPodPath: /etc/kubernetes/manifests
streamingConnectionIdleTimeout: 4h0m0s
syncFrequency: 1m0s
volumeStatsAggPeriod: 1m0s
kubernetesVersion: v1.10.2
networking:
dnsDomain: cluster.local
podSubnet: ""
serviceSubnet: 10.96.0.0/12
nodeName: master-1
privilegedPods: false
token: s73ybu.6tw6wnqgp5z0wb77
tokenGroups:
- system:bootstrappers:kubeadm:default-node-token
tokenTTL: 24h0m0s
tokenUsages:
- signing
- authentication
unifiedControlPlaneImage: ""

View File

@ -0,0 +1,148 @@
api:
advertiseAddress: 192.168.2.2
bindPort: 6443
controlPlaneEndpoint: ""
apiServerExtraArgs:
authorization-mode: Node,RBAC,Webhook
apiVersion: kubeadm.k8s.io/v1alpha2
auditPolicy:
logDir: /var/log/kubernetes/audit
logMaxAge: 2
path: ""
bootstrapTokens:
- groups:
- system:bootstrappers:kubeadm:default-node-token
token: s73ybu.6tw6wnqgp5z0wb77
ttl: 24h0m0s
usages:
- signing
- authentication
certificatesDir: /etc/kubernetes/pki
clusterName: kubernetes
etcd:
local:
dataDir: /var/lib/etcd
image: ""
imageRepository: k8s.gcr.io
kind: MasterConfiguration
kubeProxy:
config:
bindAddress: 0.0.0.0
clientConnection:
acceptContentTypes: ""
burst: 10
contentType: application/vnd.kubernetes.protobuf
kubeconfig: /var/lib/kube-proxy/kubeconfig.conf
qps: 5
clusterCIDR: ""
configSyncPeriod: 15m0s
conntrack:
max: null
maxPerCore: 32768
min: 131072
tcpCloseWaitTimeout: 1h0m0s
tcpEstablishedTimeout: 24h0m0s
enableProfiling: false
featureGates:
ServiceNodeExclusion: true
SupportIPVSProxyMode: true
healthzBindAddress: 0.0.0.0:10256
hostnameOverride: ""
iptables:
masqueradeAll: false
masqueradeBit: 14
minSyncPeriod: 0s
syncPeriod: 30s
ipvs:
excludeCIDRs: null
minSyncPeriod: 0s
scheduler: ""
syncPeriod: 30s
metricsBindAddress: 127.0.0.1:10249
mode: ""
nodePortAddresses: null
oomScoreAdj: -999
portRange: ""
resourceContainer: /kube-proxy
udpIdleTimeout: 250ms
kubeletConfiguration:
baseConfig:
address: 0.0.0.0
authentication:
anonymous:
enabled: false
webhook:
cacheTTL: 2m0s
enabled: true
x509:
clientCAFile: /etc/kubernetes/pki/ca.crt
authorization:
mode: Webhook
webhook:
cacheAuthorizedTTL: 5m0s
cacheUnauthorizedTTL: 30s
cgroupDriver: cgroupfs
cgroupsPerQOS: true
clusterDNS:
- 10.96.0.10
clusterDomain: cluster.local
containerLogMaxFiles: 5
containerLogMaxSize: 10Mi
contentType: application/vnd.kubernetes.protobuf
cpuCFSQuota: true
cpuManagerPolicy: none
cpuManagerReconcilePeriod: 10s
enableControllerAttachDetach: true
enableDebuggingHandlers: true
enforceNodeAllocatable:
- pods
eventBurst: 10
eventRecordQPS: 5
evictionHard:
imagefs.available: 15%
memory.available: 100Mi
nodefs.available: 10%
nodefs.inodesFree: 5%
evictionPressureTransitionPeriod: 5m0s
failSwapOn: true
fileCheckFrequency: 20s
hairpinMode: promiscuous-bridge
healthzBindAddress: 127.0.0.1
healthzPort: 10248
httpCheckFrequency: 20s
imageGCHighThresholdPercent: 85
imageGCLowThresholdPercent: 80
imageMinimumGCAge: 2m0s
iptablesDropBit: 15
iptablesMasqueradeBit: 14
kubeAPIBurst: 10
kubeAPIQPS: 5
makeIPTablesUtilChains: true
maxOpenFiles: 1000000
maxPods: 110
nodeStatusUpdateFrequency: 10s
oomScoreAdj: -999
podPidsLimit: -1
port: 10250
registryBurst: 10
registryPullQPS: 5
resolvConf: /etc/resolv.conf
rotateCertificates: true
runtimeRequestTimeout: 2m0s
serializeImagePulls: true
staticPodPath: /etc/kubernetes/manifests
streamingConnectionIdleTimeout: 4h0m0s
syncFrequency: 1m0s
volumeStatsAggPeriod: 1m0s
kubernetesVersion: v1.10.2
networking:
dnsDomain: cluster.local
podSubnet: ""
serviceSubnet: 10.96.0.0/12
nodeRegistration:
criSocket: /var/run/dockershim.sock
name: master-1
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
unifiedControlPlaneImage: ""

View File

@ -0,0 +1,17 @@
CACertPath: /etc/kubernetes/pki/ca.crt
ClusterName: kubernetes
DiscoveryFile: ""
DiscoveryTimeout: 5m0s
DiscoveryToken: abcdef.0123456789abcdef
DiscoveryTokenAPIServers:
- kube-apiserver:6443
DiscoveryTokenCACertHashes: null
DiscoveryTokenUnsafeSkipCAVerification: true
FeatureGates: null
NodeRegistration:
CRISocket: /var/run/dockershim.sock
KubeletExtraArgs: null
Name: master-1
Taints: null
TLSBootstrapToken: abcdef.0123456789abcdef
Token: abcdef.0123456789abcdef

View File

@ -0,0 +1,14 @@
apiVersion: kubeadm.k8s.io/v1alpha1
kind: NodeConfiguration
caCertPath: /etc/kubernetes/pki/ca.crt
clusterName: kubernetes
criSocket: /var/run/dockershim.sock
discoveryFile: ""
discoveryTimeout: 5m0s
discoveryToken: abcdef.0123456789abcdef
discoveryTokenAPIServers:
- kube-apiserver:6443
discoveryTokenUnsafeSkipCAVerification: true
nodeName: master-1
tlsBootstrapToken: abcdef.0123456789abcdef
token: abcdef.0123456789abcdef

View File

@ -0,0 +1,15 @@
apiVersion: kubeadm.k8s.io/v1alpha2
caCertPath: /etc/kubernetes/pki/ca.crt
clusterName: kubernetes
discoveryFile: ""
discoveryTimeout: 5m0s
discoveryToken: abcdef.0123456789abcdef
discoveryTokenAPIServers:
- kube-apiserver:6443
discoveryTokenUnsafeSkipCAVerification: true
kind: NodeConfiguration
nodeRegistration:
criSocket: /var/run/dockershim.sock
name: master-1
tlsBootstrapToken: abcdef.0123456789abcdef
token: abcdef.0123456789abcdef

View File

@ -0,0 +1,143 @@
api:
advertiseAddress: 192.168.2.2
bindPort: 6443
controlPlaneEndpoint: ""
apiVersion: kubeadm.k8s.io/v1alpha2
auditPolicy:
logDir: /var/log/kubernetes/audit
logMaxAge: 2
path: ""
bootstrapTokens:
- groups:
- system:bootstrappers:kubeadm:default-node-token
token: s73ybu.6tw6wnqgp5z0wb77
ttl: 24h0m0s
usages:
- signing
- authentication
certificatesDir: /var/lib/kubernetes/pki
clusterName: kubernetes
etcd:
local:
dataDir: /var/lib/etcd
image: ""
imageRepository: my-company.com
kind: MasterConfiguration
kubeProxy:
config:
bindAddress: 0.0.0.0
clientConnection:
acceptContentTypes: ""
burst: 10
contentType: application/vnd.kubernetes.protobuf
kubeconfig: /var/lib/kube-proxy/kubeconfig.conf
qps: 5
clusterCIDR: ""
configSyncPeriod: 15m0s
conntrack:
max: null
maxPerCore: 32768
min: 131072
tcpCloseWaitTimeout: 1h0m0s
tcpEstablishedTimeout: 24h0m0s
enableProfiling: false
healthzBindAddress: 0.0.0.0:10256
hostnameOverride: ""
iptables:
masqueradeAll: false
masqueradeBit: 14
minSyncPeriod: 0s
syncPeriod: 30s
ipvs:
excludeCIDRs: null
minSyncPeriod: 0s
scheduler: ""
syncPeriod: 30s
metricsBindAddress: 127.0.0.1:10249
mode: ""
nodePortAddresses: null
oomScoreAdj: -999
portRange: ""
resourceContainer: /kube-proxy
udpIdleTimeout: 250ms
kubeletConfiguration:
baseConfig:
address: 0.0.0.0
authentication:
anonymous:
enabled: false
webhook:
cacheTTL: 2m0s
enabled: true
x509:
clientCAFile: /etc/kubernetes/pki/ca.crt
authorization:
mode: Webhook
webhook:
cacheAuthorizedTTL: 5m0s
cacheUnauthorizedTTL: 30s
cgroupDriver: cgroupfs
cgroupsPerQOS: true
clusterDNS:
- 10.192.0.10
clusterDomain: cluster.global
containerLogMaxFiles: 5
containerLogMaxSize: 10Mi
contentType: application/vnd.kubernetes.protobuf
cpuCFSQuota: true
cpuManagerPolicy: none
cpuManagerReconcilePeriod: 10s
enableControllerAttachDetach: true
enableDebuggingHandlers: true
enforceNodeAllocatable:
- pods
eventBurst: 10
eventRecordQPS: 5
evictionHard:
imagefs.available: 15%
memory.available: 100Mi
nodefs.available: 10%
nodefs.inodesFree: 5%
evictionPressureTransitionPeriod: 5m0s
failSwapOn: true
fileCheckFrequency: 20s
hairpinMode: promiscuous-bridge
healthzBindAddress: 127.0.0.1
healthzPort: 10248
httpCheckFrequency: 20s
imageGCHighThresholdPercent: 85
imageGCLowThresholdPercent: 80
imageMinimumGCAge: 2m0s
iptablesDropBit: 15
iptablesMasqueradeBit: 14
kubeAPIBurst: 10
kubeAPIQPS: 5
makeIPTablesUtilChains: true
maxOpenFiles: 1000000
maxPods: 110
nodeStatusUpdateFrequency: 10s
oomScoreAdj: -999
podPidsLimit: -1
port: 10250
registryBurst: 10
registryPullQPS: 5
resolvConf: /etc/resolv.conf
rotateCertificates: true
runtimeRequestTimeout: 2m0s
serializeImagePulls: true
staticPodPath: /etc/kubernetes/manifests
streamingConnectionIdleTimeout: 4h0m0s
syncFrequency: 1m0s
volumeStatsAggPeriod: 1m0s
kubernetesVersion: v1.10.2
networking:
dnsDomain: cluster.global
podSubnet: ""
serviceSubnet: 10.196.0.0/12
nodeRegistration:
criSocket: /var/run/criruntime.sock
name: master-1
taints:
- effect: NoSchedule
key: node-role.kubernetes.io/master
unifiedControlPlaneImage: ""

View File

@ -0,0 +1,15 @@
# This file _should_ set TypeMeta, but at some point earlier we supported deserializing MasterConfigurations without TypeMeta, so we need to support that as long as we
# support the v1alpha1 API. In the meantime kubeadm will treat this as v1alpha1 automatically when unmarshalling.
api:
advertiseAddress: 192.168.2.2
bindPort: 6443
certificatesDir: /var/lib/kubernetes/pki
clusterName: kubernetes
criSocket: /var/run/criruntime.sock
imageRepository: my-company.com
kubernetesVersion: v1.10.2
networking:
dnsDomain: cluster.global
serviceSubnet: 10.196.0.0/12
nodeName: master-1
token: s73ybu.6tw6wnqgp5z0wb77

View File

@ -0,0 +1,15 @@
apiVersion: kubeadm.k8s.io/v1alpha2
caCertPath: /etc/kubernetes/pki/ca.crt
clusterName: kubernetes
discoveryFile: ""
discoveryTimeout: 5m0s
discoveryToken: abcdef.0123456789abcdef
discoveryTokenAPIServers:
- kube-apiserver:6443
discoveryTokenUnsafeSkipCAVerification: true
kind: NodeConfiguration
nodeRegistration:
criSocket: /var/run/dockershim.sock
name: thegopher
tlsBootstrapToken: abcdef.0123456789abcdef
token: abcdef.0123456789abcdef

View File

@ -0,0 +1,7 @@
apiVersion: kubeadm.k8s.io/v1alpha1
kind: NodeConfiguration
discoveryTokenAPIServers:
- kube-apiserver:6443
discoveryTokenUnsafeSkipCAVerification: true
nodeName: thegopher
token: abcdef.0123456789abcdef

View File

@ -0,0 +1,73 @@
api:
advertiseAddress: 172.31.93.180
bindPort: 6443
controlPlaneEndpoint: ""
apiVersion: kubeadm.k8s.io/v1alpha1
auditPolicy:
logDir: ""
path: ""
authorizationModes:
- Node
- RBAC
certificatesDir: /etc/kubernetes/pki
cloudProvider: aws
etcd:
caFile: ""
certFile: ""
dataDir: /var/lib/etcd
endpoints: null
image: ""
keyFile: ""
imageRepository: gcr.io/google_containers
kind: MasterConfiguration
kubeProxy:
config:
bindAddress: 0.0.0.0
clientConnection:
acceptContentTypes: ""
burst: 10
contentType: application/vnd.kubernetes.protobuf
kubeconfig: /var/lib/kube-proxy/kubeconfig.conf
qps: 5
clusterCIDR: 192.168.0.0/16
configSyncPeriod: 15m0s
conntrack:
max: null
maxPerCore: 32768
min: 131072
tcpCloseWaitTimeout: 1h0m0s
tcpEstablishedTimeout: 24h0m0s
enableProfiling: false
featureGates:
ServiceNodeExclusion: true
SupportIPVSProxyMode: true
healthzBindAddress: 0.0.0.0:10256
hostnameOverride: ""
iptables:
masqueradeAll: false
masqueradeBit: 14
minSyncPeriod: 0s
syncPeriod: 30s
ipvs:
excludeCIDRs: null
minSyncPeriod: 0s
scheduler: ""
syncPeriod: 30s
metricsBindAddress: 127.0.0.1:10249
mode: ""
nodePortAddresses: null
oomScoreAdj: -999
portRange: ""
resourceContainer: /kube-proxy
udpIdleTimeout: 0s
kubeletConfiguration: {}
kubernetesVersion: v1.9.6
networking:
dnsDomain: cluster.local
podSubnet: 192.168.0.0/16
serviceSubnet: 10.96.0.0/12
nodeName: ip-172-31-93-180.ec2.internal
privilegedPods: false
token: 8d69af.cd3e1c58f6228dfc
tokenTTL: 24h0m0s
unifiedControlPlaneImage: ""

View File

@ -0,0 +1,64 @@
# This MasterConfiguration object is wrong in two ways: it hasn't TypeMeta set, and .kubeProxy.config.featureGates is a string as it was in v1.9
# In v1.10 however, it changed in an inbackwards-compatible way to a map[string]string, so we have to workaround that to unmarshal this object
api:
advertiseAddress: 172.31.93.180
bindPort: 6443
authorizationModes:
- Node
- RBAC
certificatesDir: /etc/kubernetes/pki
cloudProvider: aws
etcd:
caFile: ""
certFile: ""
dataDir: /var/lib/etcd
endpoints: null
image: ""
keyFile: ""
imageRepository: gcr.io/google_containers
kubeProxy:
config:
bindAddress: 0.0.0.0
clientConnection:
acceptContentTypes: ""
burst: 10
contentType: application/vnd.kubernetes.protobuf
kubeconfig: /var/lib/kube-proxy/kubeconfig.conf
qps: 5
clusterCIDR: 192.168.0.0/16
configSyncPeriod: 15m0s
conntrack:
max: null
maxPerCore: 32768
min: 131072
tcpCloseWaitTimeout: 1h0m0s
tcpEstablishedTimeout: 24h0m0s
enableProfiling: false
featureGates: "SupportIPVSProxyMode=true,ServiceNodeExclusion=true"
healthzBindAddress: 0.0.0.0:10256
hostnameOverride: ""
iptables:
masqueradeAll: false
masqueradeBit: 14
minSyncPeriod: 0s
syncPeriod: 30s
ipvs:
minSyncPeriod: 0s
scheduler: ""
syncPeriod: 30s
metricsBindAddress: 127.0.0.1:10249
mode: ""
oomScoreAdj: -999
portRange: ""
resourceContainer: /kube-proxy
udpTimeoutMilliseconds: 250ms
kubeletConfiguration: {}
kubernetesVersion: v1.9.6
networking:
dnsDomain: cluster.local
podSubnet: 192.168.0.0/16
serviceSubnet: 10.96.0.0/12
nodeName: ip-172-31-93-180.ec2.internal
token: 8d69af.cd3e1c58f6228dfc
tokenTTL: 24h0m0s
unifiedControlPlaneImage: ""

View File

@ -0,0 +1,12 @@
api:
bindPort: 0
certificatesDir: relativepath
clusterName: kubernetes
criSocket: relativepath
imageRepository: my-company.com
kubernetesVersion: v1.10.2
networking:
dnsDomain: cluster.GLOBAL
serviceSubnet: 10.196.1000.0/100
nodeName: MASTER
token: s7bu.6tw6wn

View File

@ -0,0 +1,11 @@
apiVersion: kubeadm.k8s.io/v1alpha1
kind: NodeConfiguration
caCertPath: relativepath
criSocket: relativepath
discoveryFile: relativepath
discoveryTimeout: not-a-time
discoveryTokenAPIServers:
- INVALID_URL
discoveryTokenUnsafeSkipCAVerification: false
nodeName: NODE-1
token: invalidtoken

View File

@ -20,6 +20,7 @@ import (
"fmt"
"io"
"io/ioutil"
"path/filepath"
"time"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@ -43,6 +44,13 @@ func NewFileToPrint(realPath, printPath string) FileToPrint {
}
}
// PrintDryRunFile is a helper method around PrintDryRunFiles
func PrintDryRunFile(fileName, realDir, printDir string, w io.Writer) error {
return PrintDryRunFiles([]FileToPrint{
NewFileToPrint(filepath.Join(realDir, fileName), filepath.Join(printDir, fileName)),
}, w)
}
// PrintDryRunFiles prints the contents of the FileToPrints given to it to the writer w
func PrintDryRunFiles(files []FileToPrint, w io.Writer) error {
errs := []error{}
@ -81,7 +89,7 @@ func NewWaiter() apiclient.Waiter {
// WaitForAPI just returns a dummy nil, to indicate that the program should just proceed
func (w *Waiter) WaitForAPI() error {
fmt.Println("[dryrun] Would wait for the API Server's /healthz endpoint to return 'ok'")
fmt.Println("[dryrun] Would wait for the API Server's /healthz endpoint to return 'ok'")
return nil
}
@ -99,15 +107,14 @@ func (w *Waiter) WaitForPodToDisappear(podName string) error {
// WaitForHealthyKubelet blocks until the kubelet /healthz endpoint returns 'ok'
func (w *Waiter) WaitForHealthyKubelet(_ time.Duration, healthzEndpoint string) error {
fmt.Printf("[dryrun] Would make sure the kubelet %q endpoint is healthy\n", healthzEndpoint)
fmt.Printf("[dryrun] Would make sure the kubelet %q endpoint is healthy\n", healthzEndpoint)
return nil
}
// SetTimeout is a no-op; we don't wait in this implementation
func (w *Waiter) SetTimeout(_ time.Duration) {}
// WaitForStaticPodControlPlaneHashes returns an empty hash for all control plane images; WaitForStaticPodControlPlaneHashChange won't block in any case
// but the empty strings there are needed
// WaitForStaticPodControlPlaneHashes returns an empty hash for all control plane images;
func (w *Waiter) WaitForStaticPodControlPlaneHashes(_ string) (map[string]string, error) {
return map[string]string{
constants.KubeAPIServer: "",
@ -122,7 +129,7 @@ func (w *Waiter) WaitForStaticPodSingleHash(_ string, _ string) (string, error)
return "", nil
}
// WaitForStaticPodControlPlaneHashChange returns a dummy nil error in order for the flow to just continue as we're dryrunning
func (w *Waiter) WaitForStaticPodControlPlaneHashChange(_, _, _ string) error {
// WaitForStaticPodHashChange returns a dummy nil error in order for the flow to just continue as we're dryrunning
func (w *Waiter) WaitForStaticPodHashChange(_, _, _ string) error {
return nil
}

View File

@ -19,45 +19,101 @@ package util
import (
"fmt"
"net"
"net/url"
"strconv"
"k8s.io/apimachinery/pkg/util/validation"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
// GetMasterEndpoint returns a properly formatted Master Endpoint
// or passes the error from GetMasterHostPort.
func GetMasterEndpoint(cfg *kubeadmapi.MasterConfiguration) (string, error) {
hostPort, err := GetMasterHostPort(cfg)
if err != nil {
return "", err
// GetMasterEndpoint returns a properly formatted endpoint for the control plane built according following rules:
// - If the api.ControlPlaneEndpoint is defined, use it.
// - if the api.ControlPlaneEndpoint is defined but without a port number, use the api.ControlPlaneEndpoint + api.BindPort is used.
// - Otherwise, in case the api.ControlPlaneEndpoint is not defined, use the api.AdvertiseAddress + the api.BindPort.
func GetMasterEndpoint(api *kubeadmapi.API) (string, error) {
// parse the bind port
var bindPort = strconv.Itoa(int(api.BindPort))
if _, err := parsePort(bindPort); err != nil {
return "", fmt.Errorf("invalid value %q given for api.bindPort: %s", api.BindPort, err)
}
return fmt.Sprintf("https://%s", hostPort), nil
// parse the AdvertiseAddress
var ip = net.ParseIP(api.AdvertiseAddress)
if ip == nil {
return "", fmt.Errorf("invalid value `%s` given for api.advertiseAddress", api.AdvertiseAddress)
}
// set the master url using cfg.API.AdvertiseAddress + the cfg.API.BindPort
masterURL := &url.URL{
Scheme: "https",
Host: net.JoinHostPort(ip.String(), bindPort),
}
// if the controlplane endpoint is defined
if len(api.ControlPlaneEndpoint) > 0 {
// parse the controlplane endpoint
var host, port string
var err error
if host, port, err = ParseHostPort(api.ControlPlaneEndpoint); err != nil {
return "", fmt.Errorf("invalid value %q given for api.controlPlaneEndpoint: %s", api.ControlPlaneEndpoint, err)
}
// if a port is provided within the controlPlaneAddress warn the users we are using it, else use the bindport
if port != "" {
fmt.Println("[endpoint] WARNING: port specified in api.controlPlaneEndpoint overrides api.bindPort in the controlplane address")
} else {
port = bindPort
}
// overrides the master url using the controlPlaneAddress (and eventually the bindport)
masterURL = &url.URL{
Scheme: "https",
Host: net.JoinHostPort(host, port),
}
}
return masterURL.String(), nil
}
// GetMasterHostPort returns a properly formatted Master IP/port pair or error
// if the IP address can not be parsed or port is outside the valid TCP range.
func GetMasterHostPort(cfg *kubeadmapi.MasterConfiguration) (string, error) {
var masterIP string
if len(cfg.API.ControlPlaneEndpoint) > 0 {
errs := validation.IsDNS1123Subdomain(cfg.API.ControlPlaneEndpoint)
if len(errs) > 0 {
return "", fmt.Errorf("error parsing `ControlPlaneEndpoint` to valid dns subdomain with errors: %s", errs)
}
masterIP = cfg.API.ControlPlaneEndpoint
} else {
ip := net.ParseIP(cfg.API.AdvertiseAddress)
if ip == nil {
return "", fmt.Errorf("error parsing address %s", cfg.API.AdvertiseAddress)
}
masterIP = ip.String()
// ParseHostPort parses a network address of the form "host:port", "ipv4:port", "[ipv6]:port" into host and port;
// ":port" can be eventually omitted.
// If the string is not a valid representation of network address, ParseHostPort returns an error.
func ParseHostPort(hostport string) (string, string, error) {
var host, port string
var err error
// try to split host and port
if host, port, err = net.SplitHostPort(hostport); err != nil {
// if SplitHostPort returns an error, the entire hostport is considered as host
host = hostport
}
if cfg.API.BindPort < 0 || cfg.API.BindPort > 65535 {
return "", fmt.Errorf("api server port must be between 0 and 65535")
// if port is defined, parse and validate it
if port != "" {
if _, err := parsePort(port); err != nil {
return "", "", fmt.Errorf("port must be a valid number between 1 and 65535, inclusive")
}
}
hostPort := net.JoinHostPort(masterIP, strconv.Itoa(int(cfg.API.BindPort)))
return hostPort, nil
// if host is a valid IP, returns it
if ip := net.ParseIP(host); ip != nil {
return host, port, nil
}
// if host is a validate RFC-1123 subdomain, returns it
if errs := validation.IsDNS1123Subdomain(host); len(errs) == 0 {
return host, port, nil
}
return "", "", fmt.Errorf("host must be a valid IP address or a valid RFC-1123 DNS subdomain")
}
// ParsePort parses a string representing a TCP port.
// If the string is not a valid representation of a TCP port, ParsePort returns an error.
func parsePort(port string) (int, error) {
if portInt, err := strconv.Atoi(port); err == nil && (1 <= portInt && portInt <= 65535) {
return portInt, nil
}
return 0, fmt.Errorf("port must be a valid number between 1 and 65535, inclusive")
}

View File

@ -24,234 +24,322 @@ import (
func TestGetMasterEndpoint(t *testing.T) {
var tests = []struct {
name string
cfg *kubeadmapi.MasterConfiguration
endpoint string
expected bool
name string
api *kubeadmapi.API
expectedEndpoint string
expectedError bool
}{
{
name: "bad controlplane endpooint dns",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
ControlPlaneEndpoint: "bad!!cp.k8s.io",
BindPort: 1234,
},
name: "use ControlPlaneEndpoint (dns) if fully defined",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "cp.k8s.io:1234",
BindPort: 4567,
AdvertiseAddress: "4.5.6.7",
},
endpoint: "https://cp.k8s.io:1234",
expected: false,
expectedEndpoint: "https://cp.k8s.io:1234",
},
{
name: "both DNS and IP passed",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
AdvertiseAddress: "1.2.3.4",
ControlPlaneEndpoint: "cp.k8s.io",
BindPort: 1234,
},
name: "use ControlPlaneEndpoint (ipv4) if fully defined",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "1.2.3.4:1234",
BindPort: 4567,
AdvertiseAddress: "4.5.6.7",
},
endpoint: "https://cp.k8s.io:1234",
expected: true,
expectedEndpoint: "https://1.2.3.4:1234",
},
{
name: "valid DNS endpoint",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
ControlPlaneEndpoint: "cp.k8s.io",
BindPort: 1234,
},
name: "use ControlPlaneEndpoint (ipv6) if fully defined",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "[2001:db8::1]:1234",
BindPort: 4567,
AdvertiseAddress: "4.5.6.7",
},
endpoint: "https://cp.k8s.io:1234",
expected: true,
expectedEndpoint: "https://[2001:db8::1]:1234",
},
{
name: "valid IPv4 endpoint",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
AdvertiseAddress: "1.2.3.4",
BindPort: 1234,
},
name: "use ControlPlaneEndpoint (dns) + BindPort if ControlPlaneEndpoint defined without port",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "cp.k8s.io",
BindPort: 4567,
AdvertiseAddress: "4.5.6.7",
},
endpoint: "https://1.2.3.4:1234",
expected: true,
expectedEndpoint: "https://cp.k8s.io:4567",
},
{
name: "valid IPv6 endpoint",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
AdvertiseAddress: "2001:db8::1",
BindPort: 4321,
},
name: "use ControlPlaneEndpoint (ipv4) + BindPort if ControlPlaneEndpoint defined without port",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "1.2.3.4",
BindPort: 4567,
AdvertiseAddress: "4.5.6.7",
},
endpoint: "https://[2001:db8::1]:4321",
expected: true,
expectedEndpoint: "https://1.2.3.4:4567",
},
{
name: "invalid IPv4 endpoint",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
AdvertiseAddress: "1.2.3.4",
BindPort: 1234,
},
name: "use ControlPlaneEndpoint (ipv6) + BindPort if ControlPlaneEndpoint defined without port",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "2001:db8::1",
BindPort: 4567,
AdvertiseAddress: "4.5.6.7",
},
endpoint: "https://[1.2.3.4]:1234",
expected: false,
expectedEndpoint: "https://[2001:db8::1]:4567",
},
{
name: "invalid IPv6 endpoint",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
AdvertiseAddress: "2001:db8::1",
BindPort: 4321,
},
name: "use AdvertiseAddress (ipv4) + BindPort if ControlPlaneEndpoint is not defined",
api: &kubeadmapi.API{
BindPort: 4567,
AdvertiseAddress: "4.5.6.7",
},
endpoint: "https://2001:db8::1:4321",
expected: false,
expectedEndpoint: "https://4.5.6.7:4567",
},
{
name: "invalid IPv4 AdvertiseAddress",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
AdvertiseAddress: "1.2.34",
BindPort: 1234,
},
name: "use AdvertiseAddress (ipv6) + BindPort if ControlPlaneEndpoint is not defined",
api: &kubeadmapi.API{
BindPort: 4567,
AdvertiseAddress: "2001:db8::1",
},
endpoint: "https://1.2.3.4:1234",
expected: false,
expectedEndpoint: "https://[2001:db8::1]:4567",
},
{
name: "invalid IPv6 AdvertiseAddress",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
AdvertiseAddress: "2001::db8::1",
BindPort: 4321,
},
name: "fail if invalid BindPort",
api: &kubeadmapi.API{
BindPort: 0,
},
endpoint: "https://[2001:db8::1]:4321",
expected: false,
expectedError: true,
},
{
name: "fail if invalid ControlPlaneEndpoint (dns)",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "bad!!.cp.k8s.io",
BindPort: 4567,
},
expectedError: true,
},
{
name: "fail if invalid ControlPlaneEndpoint (ip4)",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "1..0",
BindPort: 4567,
},
expectedError: true,
},
{
name: "fail if invalid ControlPlaneEndpoint (ip6)",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "1200::AB00:1234::2552:7777:1313",
BindPort: 4567,
},
expectedError: true,
},
{
name: "fail if invalid ControlPlaneEndpoint (port)",
api: &kubeadmapi.API{
ControlPlaneEndpoint: "cp.k8s.io:0",
BindPort: 4567,
},
expectedError: true,
},
{
name: "fail if invalid AdvertiseAddress (ip4)",
api: &kubeadmapi.API{
AdvertiseAddress: "1..0",
BindPort: 4567,
},
expectedError: true,
},
{
name: "fail if invalid AdvertiseAddress (ip6)",
api: &kubeadmapi.API{
AdvertiseAddress: "1200::AB00:1234::2552:7777:1313",
BindPort: 4567,
},
expectedError: true,
},
}
for _, rt := range tests {
actual, err := GetMasterEndpoint(rt.cfg)
if err != nil && rt.expected {
t.Error(err)
actualEndpoint, actualError := GetMasterEndpoint(rt.api)
if (actualError != nil) && !rt.expectedError {
t.Errorf("%s unexpected failure: %v", rt.name, actualError)
continue
} else if (actualError == nil) && rt.expectedError {
t.Errorf("%s passed when expected to fail", rt.name)
continue
}
if actual != rt.endpoint && rt.expected {
t.Errorf(
"%s test case failed:\n\texpected: %s\n\t actual: %s",
rt.name,
rt.endpoint,
(actual),
)
if actualEndpoint != rt.expectedEndpoint {
t.Errorf("%s returned invalid endpoint %s, expected %s", rt.name, actualEndpoint, rt.expectedEndpoint)
}
}
}
func TestGetMasterHostPort(t *testing.T) {
func TestParseHostPort(t *testing.T) {
var tests = []struct {
name string
cfg *kubeadmapi.MasterConfiguration
hostPort string
expected bool
name string
hostport string
expectedHost string
expectedPort string
expectedError bool
}{
{
name: "valid IPv4 master host and port",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
AdvertiseAddress: "1.2.3.4",
BindPort: 1234,
},
},
hostPort: "1.2.3.4:1234",
expected: true,
name: "valid dns",
hostport: "cp.k8s.io",
expectedHost: "cp.k8s.io",
expectedPort: "",
},
{
name: "valid IPv6 master host port",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
AdvertiseAddress: "2001:db8::1",
BindPort: 4321,
},
},
hostPort: "[2001:db8::1]:4321",
expected: true,
name: "valid dns:port",
hostport: "cp.k8s.io:1234",
expectedHost: "cp.k8s.io",
expectedPort: "1234",
},
{
name: "invalid IPv4 address",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
AdvertiseAddress: "1.2.34",
BindPort: 1234,
},
},
hostPort: "1.2.3.4:1234",
expected: false,
name: "valid ip4",
hostport: "1.2.3.4",
expectedHost: "1.2.3.4",
expectedPort: "",
},
{
name: "invalid IPv6 address",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
AdvertiseAddress: "2001::db8::1",
BindPort: 4321,
},
},
hostPort: "[2001:db8::1]:4321",
expected: false,
name: "valid ipv4:port",
hostport: "1.2.3.4:1234",
expectedHost: "1.2.3.4",
expectedPort: "1234",
},
{
name: "invalid TCP port number",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
AdvertiseAddress: "1.2.3.4",
BindPort: 987654321,
},
},
hostPort: "1.2.3.4:987654321",
expected: false,
name: "valid ipv6",
hostport: "2001:db8::1",
expectedHost: "2001:db8::1",
expectedPort: "",
},
{
name: "invalid negative TCP port number",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
AdvertiseAddress: "1.2.3.4",
BindPort: -987654321,
},
},
hostPort: "1.2.3.4:-987654321",
expected: false,
name: "valid ipv6:port",
hostport: "[2001:db8::1]:1234",
expectedHost: "2001:db8::1",
expectedPort: "1234",
},
{
name: "unspecified IPv4 TCP port",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
AdvertiseAddress: "1.2.3.4",
},
},
hostPort: "1.2.3.4:0",
expected: true,
name: "invalid port(not a number)",
hostport: "cp.k8s.io:aaa",
expectedError: true,
},
{
name: "unspecified IPv6 TCP port",
cfg: &kubeadmapi.MasterConfiguration{
API: kubeadmapi.API{
AdvertiseAddress: "1:2:3::4",
},
},
hostPort: "[1:2:3::4]:0",
expected: true,
name: "invalid port(out of range, positive port number)",
hostport: "cp.k8s.io:987654321",
expectedError: true,
},
{
name: "invalid port(out of range, negative port number)",
hostport: "cp.k8s.io:-987654321",
expectedError: true,
},
{
name: "invalid port(out of range, negative port number)",
hostport: "cp.k8s.io:123:123",
expectedError: true,
},
{
name: "invalid dns",
hostport: "bad!!cp.k8s.io",
expectedError: true,
},
{
name: "invalid valid dns:port",
hostport: "bad!!cp.k8s.io:1234",
expectedError: true,
},
{
name: "invalid ip4, but valid DNS",
hostport: "259.2.3.4",
expectedHost: "259.2.3.4",
},
{
name: "invalid ip4",
hostport: "1..3.4",
expectedError: true,
},
{
name: "invalid ip4(2):port",
hostport: "1..3.4:1234",
expectedError: true,
},
{
name: "invalid ipv6",
hostport: "1200::AB00:1234::2552:7777:1313",
expectedError: true,
},
{
name: "invalid ipv6:port",
hostport: "[1200::AB00:1234::2552:7777:1313]:1234",
expectedError: true,
},
}
for _, rt := range tests {
actual, err := GetMasterHostPort(rt.cfg)
if err != nil && rt.expected {
t.Error(err)
actualHost, actualPort, actualError := ParseHostPort(rt.hostport)
if (actualError != nil) && !rt.expectedError {
t.Errorf("%s unexpected failure: %v", rt.name, actualError)
continue
} else if (actualError == nil) && rt.expectedError {
t.Errorf("%s passed when expected to fail", rt.name)
continue
}
if actual != rt.hostPort && rt.expected {
t.Errorf(
"%s test case failed:\n\texpected: %s\n\t actual: %s",
rt.name,
rt.hostPort,
(actual),
)
if actualHost != rt.expectedHost {
t.Errorf("%s returned invalid host %s, expected %s", rt.name, actualHost, rt.expectedHost)
continue
}
if actualPort != rt.expectedPort {
t.Errorf("%s returned invalid port %s, expected %s", rt.name, actualPort, rt.expectedPort)
}
}
}
func TestParsePort(t *testing.T) {
var tests = []struct {
name string
port string
expectedPort int
expectedError bool
}{
{
name: "valid port",
port: "1234",
expectedPort: 1234,
},
{
name: "invalid port (not a number)",
port: "a",
expectedError: true,
},
{
name: "invalid port (<1)",
port: "-10",
expectedError: true,
},
{
name: "invalid port (>65535)",
port: "66535",
expectedError: true,
},
}
for _, rt := range tests {
actualPort, actualError := parsePort(rt.port)
if (actualError != nil) && !rt.expectedError {
t.Errorf("%s unexpected failure: %v", rt.name, actualError)
continue
} else if (actualError == nil) && rt.expectedError {
t.Errorf("%s passed when expected to fail", rt.name)
continue
}
if actualPort != rt.expectedPort {
t.Errorf("%s returned invalid port %d, expected %d", rt.name, actualPort, rt.expectedPort)
}
}
}

View File

@ -22,7 +22,6 @@ import (
"strings"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
)
const (
@ -34,10 +33,6 @@ const (
ValidationExitCode = 3
)
type debugError interface {
DebugError() (msg string, args []interface{})
}
// fatal prints the message if set and then exits.
func fatal(msg string, code int) {
if len(msg) > 0 {
@ -57,16 +52,22 @@ func fatal(msg string, code int) {
// This method is generic to the command in use and may be used by non-Kubectl
// commands.
func CheckErr(err error) {
checkErr("", err, fatal)
checkErr(err, fatal)
}
// preflightError allows us to know if the error is a preflight error or not
// defining the interface here avoids an import cycle of pulling in preflight into the util package
type preflightError interface {
Preflight() bool
}
// checkErr formats a given error as a string and calls the passed handleErr
// func with that string and an kubectl exit code.
func checkErr(prefix string, err error, handleErr func(string, int)) {
// func with that string and an exit code.
func checkErr(err error, handleErr func(string, int)) {
switch err.(type) {
case nil:
return
case *preflight.Error:
case preflightError:
handleErr(err.Error(), PreFlightExitCode)
case utilerrors.Aggregate:
handleErr(err.Error(), ValidationExitCode)

View File

@ -19,10 +19,12 @@ package util
import (
"fmt"
"testing"
"k8s.io/kubernetes/cmd/kubeadm/app/preflight"
)
type pferror struct{}
func (p *pferror) Preflight() bool { return true }
func (p *pferror) Error() string { return "" }
func TestCheckErr(t *testing.T) {
var codeReturned int
errHandle := func(err string, code int) {
@ -35,12 +37,12 @@ func TestCheckErr(t *testing.T) {
}{
{nil, 0},
{fmt.Errorf(""), DefaultErrorExitCode},
{&preflight.Error{}, PreFlightExitCode},
{&pferror{}, PreFlightExitCode},
}
for _, rt := range tokenTest {
codeReturned = 0
checkErr("", rt.e, errHandle)
checkErr(rt.e, errHandle)
if codeReturned != rt.expected {
t.Errorf(
"failed checkErr:\n\texpected: %d\n\t actual: %d",

View File

@ -1,51 +0,0 @@
/*
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 util
import (
"context"
"github.com/coreos/etcd/clientv3"
"time"
)
// EtcdCluster is an interface to get etcd cluster related information
type EtcdCluster interface {
GetEtcdClusterStatus() (*clientv3.StatusResponse, error)
}
// LocalEtcdCluster represents an instance of a local etcd cluster
type LocalEtcdCluster struct{}
// GetEtcdClusterStatus returns nil for status Up or error for status Down
func (cluster LocalEtcdCluster) GetEtcdClusterStatus() (*clientv3.StatusResponse, error) {
ep := []string{"localhost:2379"}
cli, err := clientv3.New(clientv3.Config{
Endpoints: ep,
DialTimeout: 5 * time.Second,
})
if err != nil {
return nil, err
}
defer cli.Close()
resp, err := cli.Status(context.Background(), ep[0])
if err != nil {
return nil, err
}
return resp, nil
}

View File

@ -0,0 +1,39 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = ["etcd.go"],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/etcd",
visibility = ["//visibility:public"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/util/staticpod:go_default_library",
"//vendor/github.com/coreos/etcd/clientv3:go_default_library",
"//vendor/github.com/coreos/etcd/pkg/transport:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["etcd_test.go"],
embed = [":go_default_library"],
deps = [
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/test: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,224 @@
/*
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 etcd
import (
"context"
"crypto/tls"
"fmt"
"path/filepath"
"strings"
"time"
"github.com/coreos/etcd/clientv3"
"github.com/coreos/etcd/pkg/transport"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
"k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod"
)
// ClusterInterrogator is an interface to get etcd cluster related information
type ClusterInterrogator interface {
ClusterAvailable() (bool, error)
GetClusterStatus() (map[string]*clientv3.StatusResponse, error)
GetClusterVersions() (map[string]string, error)
GetVersion() (string, error)
HasTLS() bool
WaitForClusterAvailable(delay time.Duration, retries int, retryInterval time.Duration) (bool, error)
}
// Client provides connection parameters for an etcd cluster
type Client struct {
Endpoints []string
TLS *tls.Config
}
// HasTLS returns true if etcd is configured for TLS
func (c Client) HasTLS() bool {
return c.TLS != nil
}
// PodManifestsHaveTLS reads the etcd staticpod manifest from disk and returns false if the TLS flags
// are missing from the command list. If all the flags are present it returns true.
func PodManifestsHaveTLS(ManifestDir string) (bool, error) {
etcdPodPath := constants.GetStaticPodFilepath(constants.Etcd, ManifestDir)
etcdPod, err := staticpod.ReadStaticPodFromDisk(etcdPodPath)
if err != nil {
return false, fmt.Errorf("failed to check if etcd pod implements TLS: %v", err)
}
tlsFlags := []string{
"--cert-file=",
"--key-file=",
"--trusted-ca-file=",
"--client-cert-auth=",
"--peer-cert-file=",
"--peer-key-file=",
"--peer-trusted-ca-file=",
"--peer-client-cert-auth=",
}
FlagLoop:
for _, flag := range tlsFlags {
for _, container := range etcdPod.Spec.Containers {
for _, arg := range container.Command {
if strings.Contains(arg, flag) {
continue FlagLoop
}
}
}
// flag not found in any container
return false, nil
}
// all flags were found in container args; pod fully implements TLS
return true, nil
}
// New creates a new EtcdCluster client
func New(endpoints []string, ca, cert, key string) (*Client, error) {
client := Client{Endpoints: endpoints}
if ca != "" || cert != "" || key != "" {
tlsInfo := transport.TLSInfo{
CertFile: cert,
KeyFile: key,
TrustedCAFile: ca,
}
tlsConfig, err := tlsInfo.ClientConfig()
if err != nil {
return nil, err
}
client.TLS = tlsConfig
}
return &client, nil
}
// NewFromStaticPod creates a GenericClient from the given endpoints, manifestDir, and certificatesDir
func NewFromStaticPod(endpoints []string, manifestDir string, certificatesDir string) (*Client, error) {
hasTLS, err := PodManifestsHaveTLS(manifestDir)
if err != nil {
return nil, fmt.Errorf("could not read manifests from: %s, error: %v", manifestDir, err)
}
if hasTLS {
return New(
endpoints,
filepath.Join(certificatesDir, constants.EtcdCACertName),
filepath.Join(certificatesDir, constants.EtcdHealthcheckClientCertName),
filepath.Join(certificatesDir, constants.EtcdHealthcheckClientKeyName),
)
}
return New(endpoints, "", "", "")
}
// GetVersion returns the etcd version of the cluster.
// An error is returned if the version of all endpoints do not match
func (c Client) GetVersion() (string, error) {
var clusterVersion string
versions, err := c.GetClusterVersions()
if err != nil {
return "", err
}
for _, v := range versions {
if clusterVersion != "" && clusterVersion != v {
return "", fmt.Errorf("etcd cluster contains endpoints with mismatched versions: %v", versions)
}
clusterVersion = v
}
if clusterVersion == "" {
return "", fmt.Errorf("could not determine cluster etcd version")
}
return clusterVersion, nil
}
// GetClusterVersions returns a map of the endpoints and their associated versions
func (c Client) GetClusterVersions() (map[string]string, error) {
versions := make(map[string]string)
statuses, err := c.GetClusterStatus()
if err != nil {
return versions, err
}
for ep, status := range statuses {
versions[ep] = status.Version
}
return versions, nil
}
// ClusterAvailable returns true if the cluster status indicates the cluster is available.
func (c Client) ClusterAvailable() (bool, error) {
_, err := c.GetClusterStatus()
if err != nil {
return false, err
}
return true, nil
}
// GetClusterStatus returns nil for status Up or error for status Down
func (c Client) GetClusterStatus() (map[string]*clientv3.StatusResponse, error) {
cli, err := clientv3.New(clientv3.Config{
Endpoints: c.Endpoints,
DialTimeout: 5 * time.Second,
TLS: c.TLS,
})
if err != nil {
return nil, err
}
defer cli.Close()
clusterStatus := make(map[string]*clientv3.StatusResponse)
for _, ep := range c.Endpoints {
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
resp, err := cli.Status(ctx, ep)
cancel()
if err != nil {
return nil, err
}
clusterStatus[ep] = resp
}
return clusterStatus, nil
}
// WaitForClusterAvailable returns true if all endpoints in the cluster are available after an initial delay and retry attempts, an error is returned otherwise
func (c Client) WaitForClusterAvailable(delay time.Duration, retries int, retryInterval time.Duration) (bool, error) {
fmt.Printf("[util/etcd] Waiting %v for initial delay\n", delay)
time.Sleep(delay)
for i := 0; i < retries; i++ {
if i > 0 {
fmt.Printf("[util/etcd] Waiting %v until next retry\n", retryInterval)
time.Sleep(retryInterval)
}
fmt.Printf("[util/etcd] Attempting to see if all cluster endpoints are available %d/%d\n", i+1, retries)
resp, err := c.ClusterAvailable()
if err != nil {
switch err {
case context.DeadlineExceeded:
fmt.Println("[util/etcd] Attempt timed out")
default:
fmt.Printf("[util/etcd] Attempt failed with error: %v\n", err)
}
continue
}
return resp, nil
}
return false, fmt.Errorf("timeout waiting for etcd cluster to be available")
}
// CheckConfigurationIsHA returns true if the given MasterConfiguration etcd block appears to be an HA configuration.
func CheckConfigurationIsHA(cfg *kubeadmapi.Etcd) bool {
return cfg.External != nil && len(cfg.External.Endpoints) > 1
}

View File

@ -0,0 +1,310 @@
/*
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 etcd
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
)
const (
secureEtcdPod = `# generated by kubeadm v1.10.0
apiVersion: v1
kind: Pod
metadata:
annotations:
scheduler.alpha.kubernetes.io/critical-pod: ""
creationTimestamp: null
labels:
component: etcd
tier: control-plane
name: etcd
namespace: kube-system
spec:
containers:
- command:
- etcd
- --advertise-client-urls=https://127.0.0.1:2379
- --data-dir=/var/lib/etcd
- --peer-key-file=/etc/kubernetes/pki/etcd/peer.key
- --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
- --listen-client-urls=https://127.0.0.1:2379
- --peer-client-cert-auth=true
- --cert-file=/etc/kubernetes/pki/etcd/server.crt
- --key-file=/etc/kubernetes/pki/etcd/server.key
- --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
- --peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt
- --client-cert-auth=true
image: k8s.gcr.io/etcd-amd64:3.1.12
livenessProbe:
exec:
command:
- /bin/sh
- -ec
- ETCDCTL_API=3 etcdctl --endpoints=127.0.0.1:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt
--cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key
get foo
failureThreshold: 8
initialDelaySeconds: 15
timeoutSeconds: 15
name: etcd
resources: {}
volumeMounts:
- mountPath: /var/lib/etcd
name: etcd-data
- mountPath: /etc/kubernetes/pki/etcd
name: etcd-certs
hostNetwork: true
volumes:
- hostPath:
path: /var/lib/etcd
type: DirectoryOrCreate
name: etcd-data
- hostPath:
path: /etc/kubernetes/pki/etcd
type: DirectoryOrCreate
name: etcd-certs
status: {}
`
secureExposedEtcdPod = `
apiVersion: v1
kind: Pod
metadata:
annotations:
scheduler.alpha.kubernetes.io/critical-pod: ""
creationTimestamp: null
labels:
component: etcd
tier: control-plane
name: etcd
namespace: kube-system
spec:
containers:
- command:
- etcd
- --advertise-client-urls=https://10.0.5.5:2379
- --data-dir=/var/lib/etcd
- --peer-key-file=/etc/kubernetes/pki/etcd/peer.key
- --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
- --listen-client-urls=https://[::0:0]:2379
- --peer-client-cert-auth=true
- --cert-file=/etc/kubernetes/pki/etcd/server.crt
- --key-file=/etc/kubernetes/pki/etcd/server.key
- --trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
- --peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt
- --client-cert-auth=true
image: k8s.gcr.io/etcd-amd64:3.1.12
livenessProbe:
exec:
command:
- /bin/sh
- -ec
- ETCDCTL_API=3 etcdctl --endpoints=https://[::1]:2379 --cacert=/etc/kubernetes/pki/etcd/ca.crt
--cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt --key=/etc/kubernetes/pki/etcd/healthcheck-client.key
get foo
failureThreshold: 8
initialDelaySeconds: 15
timeoutSeconds: 15
name: etcd
resources: {}
volumeMounts:
- mountPath: /var/lib/etcd
name: etcd-data
- mountPath: /etc/kubernetes/pki/etcd
name: etcd-certs
hostNetwork: true
volumes:
- hostPath:
path: /var/lib/etcd
type: DirectoryOrCreate
name: etcd-data
- hostPath:
path: /etc/kubernetes/pki/etcd
type: DirectoryOrCreate
name: etcd-certs
status: {}
`
insecureEtcdPod = `# generated by kubeadm v1.9.6
apiVersion: v1
kind: Pod
metadata:
annotations:
scheduler.alpha.kubernetes.io/critical-pod: ""
creationTimestamp: null
labels:
component: etcd
tier: control-plane
name: etcd
namespace: kube-system
spec:
containers:
- command:
- etcd
- --listen-client-urls=http://127.0.0.1:2379
- --advertise-client-urls=http://127.0.0.1:2379
- --data-dir=/var/lib/etcd
image: gcr.io/google_containers/etcd-amd64:3.1.11
livenessProbe:
failureThreshold: 8
httpGet:
host: 127.0.0.1
path: /health
port: 2379
scheme: HTTP
initialDelaySeconds: 15
timeoutSeconds: 15
name: etcd
resources: {}
volumeMounts:
- mountPath: /var/lib/etcd
name: etcd
hostNetwork: true
volumes:
- hostPath:
path: /var/lib/etcd
type: DirectoryOrCreate
name: etcd
status: {}
`
invalidPod = `---{ broken yaml @@@`
)
func TestPodManifestHasTLS(t *testing.T) {
tests := []struct {
description string
podYaml string
hasTLS bool
expectErr bool
writeManifest bool
}{
{
description: "secure etcd returns true",
podYaml: secureEtcdPod,
hasTLS: true,
writeManifest: true,
expectErr: false,
},
{
description: "secure exposed etcd returns true",
podYaml: secureExposedEtcdPod,
hasTLS: true,
writeManifest: true,
expectErr: false,
},
{
description: "insecure etcd returns false",
podYaml: insecureEtcdPod,
hasTLS: false,
writeManifest: true,
expectErr: false,
},
{
description: "invalid pod fails to unmarshal",
podYaml: invalidPod,
hasTLS: false,
writeManifest: true,
expectErr: true,
},
{
description: "non-existent file returns error",
podYaml: ``,
hasTLS: false,
writeManifest: false,
expectErr: true,
},
}
for _, rt := range tests {
tmpdir := testutil.SetupTempDir(t)
defer os.RemoveAll(tmpdir)
manifestPath := filepath.Join(tmpdir, "etcd.yaml")
if rt.writeManifest {
err := ioutil.WriteFile(manifestPath, []byte(rt.podYaml), 0644)
if err != nil {
t.Fatalf("Failed to write pod manifest\n%s\n\tfatal error: %v", rt.description, err)
}
}
hasTLS, actualErr := PodManifestsHaveTLS(tmpdir)
if (actualErr != nil) != rt.expectErr {
t.Errorf(
"PodManifestHasTLS failed\n%s\n\texpected error: %t\n\tgot: %t\n\tactual error: %v",
rt.description,
rt.expectErr,
(actualErr != nil),
actualErr,
)
}
if hasTLS != rt.hasTLS {
t.Errorf("PodManifestHasTLS failed\n%s\n\texpected hasTLS: %t\n\tgot: %t", rt.description, rt.hasTLS, hasTLS)
}
}
}
func TestCheckConfigurationIsHA(t *testing.T) {
var tests = []struct {
name string
cfg *kubeadmapi.Etcd
expected bool
}{
{
name: "HA etcd",
cfg: &kubeadmapi.Etcd{
External: &kubeadmapi.ExternalEtcd{
Endpoints: []string{"10.100.0.1:2379", "10.100.0.2:2379", "10.100.0.3:2379"},
},
},
expected: true,
},
{
name: "single External etcd",
cfg: &kubeadmapi.Etcd{
External: &kubeadmapi.ExternalEtcd{
Endpoints: []string{"10.100.0.1:2379"},
},
},
expected: false,
},
{
name: "local etcd",
cfg: &kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{},
},
expected: false,
},
{
name: "empty etcd struct",
cfg: &kubeadmapi.Etcd{},
expected: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if isHA := CheckConfigurationIsHA(test.cfg); isHA != test.expected {
t.Errorf("expected isHA to be %v, got %v", test.expected, isHA)
}
})
}
}

View File

@ -17,12 +17,16 @@ limitations under the License.
package util
import (
"errors"
"fmt"
yaml "gopkg.in/yaml.v2"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/runtime/serializer"
clientsetscheme "k8s.io/client-go/kubernetes/scheme"
kubeadmapiv1alpha1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
)
// MarshalToYaml marshals an object into yaml.
@ -31,6 +35,8 @@ func MarshalToYaml(obj runtime.Object, gv schema.GroupVersion) ([]byte, error) {
}
// MarshalToYamlForCodecs marshals an object into yaml using the specified codec
// TODO: Is specifying the gv really needed here?
// TODO: Can we support json out of the box easily here?
func MarshalToYamlForCodecs(obj runtime.Object, gv schema.GroupVersion, codecs serializer.CodecFactory) ([]byte, error) {
mediaType := "application/yaml"
info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType)
@ -41,3 +47,88 @@ func MarshalToYamlForCodecs(obj runtime.Object, gv schema.GroupVersion, codecs s
encoder := codecs.EncoderForVersion(info.Serializer, gv)
return runtime.Encode(encoder, obj)
}
// UnmarshalFromYaml unmarshals yaml into an object.
func UnmarshalFromYaml(buffer []byte, gv schema.GroupVersion) (runtime.Object, error) {
return UnmarshalFromYamlForCodecs(buffer, gv, clientsetscheme.Codecs)
}
// UnmarshalFromYamlForCodecs unmarshals yaml into an object using the specified codec
// TODO: Is specifying the gv really needed here?
// TODO: Can we support json out of the box easily here?
func UnmarshalFromYamlForCodecs(buffer []byte, gv schema.GroupVersion, codecs serializer.CodecFactory) (runtime.Object, error) {
mediaType := "application/yaml"
info, ok := runtime.SerializerInfoForMediaType(codecs.SupportedMediaTypes(), mediaType)
if !ok {
return nil, fmt.Errorf("unsupported media type %q", mediaType)
}
decoder := codecs.DecoderToVersion(info.Serializer, gv)
return runtime.Decode(decoder, buffer)
}
// GroupVersionKindFromBytes parses the bytes and returns the gvk
func GroupVersionKindFromBytes(buffer []byte, codecs serializer.CodecFactory) (schema.GroupVersionKind, error) {
decoded, err := LoadYAML(buffer)
if err != nil {
return schema.EmptyObjectKind.GroupVersionKind(), fmt.Errorf("unable to decode config from bytes: %v", err)
}
kindStr, apiVersionStr := "", ""
// As there was a bug in kubeadm v1.10 and earlier that made the YAML uploaded to the cluster configmap NOT have metav1.TypeMeta information
// we need to populate this here manually. If kind or apiVersion is empty, we know the apiVersion is v1alpha1, as by the time kubeadm had this bug,
// it could only write
// TODO: Remove this "hack" in v1.12 when we know the ConfigMap always contains v1alpha2 content written by kubeadm v1.11. Also, we will drop support for
// v1alpha1 in v1.12
kind := decoded["kind"]
apiVersion := decoded["apiVersion"]
if kind == nil || len(kind.(string)) == 0 {
kindStr = "MasterConfiguration"
} else {
kindStr = kind.(string)
}
if apiVersion == nil || len(apiVersion.(string)) == 0 {
apiVersionStr = kubeadmapiv1alpha1.SchemeGroupVersion.String()
} else {
apiVersionStr = apiVersion.(string)
}
gv, err := schema.ParseGroupVersion(apiVersionStr)
if err != nil {
return schema.EmptyObjectKind.GroupVersionKind(), fmt.Errorf("unable to parse apiVersion: %v", err)
}
return gv.WithKind(kindStr), nil
}
// LoadYAML is a small wrapper around go-yaml that ensures all nested structs are map[string]interface{} instead of map[interface{}]interface{}.
func LoadYAML(bytes []byte) (map[string]interface{}, error) {
var decoded map[interface{}]interface{}
if err := yaml.Unmarshal(bytes, &decoded); err != nil {
return map[string]interface{}{}, fmt.Errorf("couldn't unmarshal YAML: %v", err)
}
converted, ok := convert(decoded).(map[string]interface{})
if !ok {
return map[string]interface{}{}, errors.New("yaml is not a map")
}
return converted, nil
}
// https://stackoverflow.com/questions/40737122/convert-yaml-to-json-without-struct-golang
func convert(i interface{}) interface{} {
switch x := i.(type) {
case map[interface{}]interface{}:
m2 := map[string]interface{}{}
for k, v := range x {
m2[k.(string)] = convert(v)
}
return m2
case []interface{}:
for i, v := range x {
x[i] = convert(v)
}
}
return i
}

View File

@ -0,0 +1,125 @@
/*
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 util
import (
"reflect"
"testing"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
)
func TestMarshalUnmarshalYaml(t *testing.T) {
pod := &corev1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: "someName",
Namespace: "testNamespace",
Labels: map[string]string{
"test": "yes",
},
},
Spec: corev1.PodSpec{
RestartPolicy: corev1.RestartPolicyAlways,
},
}
bytes, err := MarshalToYaml(pod, corev1.SchemeGroupVersion)
if err != nil {
t.Fatalf("unexpected error marshalling: %v", err)
}
t.Logf("\n%s", bytes)
obj2, err := UnmarshalFromYaml(bytes, corev1.SchemeGroupVersion)
if err != nil {
t.Fatalf("unexpected error marshalling: %v", err)
}
pod2, ok := obj2.(*corev1.Pod)
if !ok {
t.Fatal("did not get a Pod")
}
if pod2.Name != pod.Name {
t.Errorf("expected %q, got %q", pod.Name, pod2.Name)
}
if pod2.Namespace != pod.Namespace {
t.Errorf("expected %q, got %q", pod.Namespace, pod2.Namespace)
}
if !reflect.DeepEqual(pod2.Labels, pod.Labels) {
t.Errorf("expected %v, got %v", pod.Labels, pod2.Labels)
}
if pod2.Spec.RestartPolicy != pod.Spec.RestartPolicy {
t.Errorf("expected %q, got %q", pod.Spec.RestartPolicy, pod2.Spec.RestartPolicy)
}
}
func TestMarshalUnmarshalToYamlForCodecs(t *testing.T) {
cfg := &kubeadmapiext.MasterConfiguration{
API: kubeadmapiext.API{
AdvertiseAddress: "10.100.0.1",
BindPort: 4332,
},
NodeName: "testNode",
NoTaintMaster: true,
Networking: kubeadmapiext.Networking{
ServiceSubnet: "10.100.0.0/24",
PodSubnet: "10.100.1.0/24",
},
}
bytes, err := MarshalToYamlForCodecs(cfg, kubeadmapiext.SchemeGroupVersion, scheme.Codecs)
if err != nil {
t.Fatalf("unexpected error marshalling MasterConfiguration: %v", err)
}
t.Logf("\n%s", bytes)
obj, err := UnmarshalFromYamlForCodecs(bytes, kubeadmapiext.SchemeGroupVersion, scheme.Codecs)
if err != nil {
t.Fatalf("unexpected error unmarshalling MasterConfiguration: %v", err)
}
cfg2, ok := obj.(*kubeadmapiext.MasterConfiguration)
if !ok {
t.Fatal("did not get MasterConfiguration back")
}
if cfg2.API.AdvertiseAddress != cfg.API.AdvertiseAddress {
t.Errorf("expected %q, got %q", cfg.API.AdvertiseAddress, cfg2.API.AdvertiseAddress)
}
if cfg2.API.BindPort != cfg.API.BindPort {
t.Errorf("expected %d, got %d", cfg.API.BindPort, cfg2.API.BindPort)
}
if cfg2.NodeName != cfg.NodeName {
t.Errorf("expected %q, got %q", cfg.NodeName, cfg2.NodeName)
}
if cfg2.NoTaintMaster != cfg.NoTaintMaster {
t.Errorf("expected %v, got %v", cfg.NoTaintMaster, cfg2.NoTaintMaster)
}
if cfg2.Networking.ServiceSubnet != cfg.Networking.ServiceSubnet {
t.Errorf("expected %v, got %v", cfg.Networking.ServiceSubnet, cfg2.Networking.ServiceSubnet)
}
if cfg2.Networking.PodSubnet != cfg.Networking.PodSubnet {
t.Errorf("expected %v, got %v", cfg.Networking.PodSubnet, cfg2.Networking.PodSubnet)
}
}

View File

@ -14,6 +14,7 @@ go_test(
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
"//cmd/kubeadm/app/constants:go_default_library",
"//cmd/kubeadm/app/features:go_default_library",
"//cmd/kubeadm/test:go_default_library",
"//vendor/k8s.io/api/core/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",

View File

@ -64,9 +64,10 @@ func ComponentPod(container v1.Container, volumes map[string]v1.Volume) v1.Pod {
Labels: map[string]string{"component": container.Name, "tier": "control-plane"},
},
Spec: v1.PodSpec{
Containers: []v1.Container{container},
HostNetwork: true,
Volumes: VolumeMapToSlice(volumes),
Containers: []v1.Container{container},
PriorityClassName: "system-cluster-critical",
HostNetwork: true,
Volumes: VolumeMapToSlice(volumes),
},
}
}
@ -97,6 +98,24 @@ func ComponentProbe(cfg *kubeadmapi.MasterConfiguration, componentName string, p
}
}
// EtcdProbe is a helper function for building a shell-based, etcdctl v1.Probe object to healthcheck etcd
func EtcdProbe(cfg *kubeadmapi.MasterConfiguration, componentName string, port int, certsDir string, CACertName string, CertName string, KeyName string) *v1.Probe {
tlsFlags := fmt.Sprintf("--cacert=%[1]s/%[2]s --cert=%[1]s/%[3]s --key=%[1]s/%[4]s", certsDir, CACertName, CertName, KeyName)
// etcd pod is alive if a linearizable get succeeds.
cmd := fmt.Sprintf("ETCDCTL_API=3 etcdctl --endpoints=https://[%s]:%d %s get foo", GetProbeAddress(cfg, componentName), port, tlsFlags)
return &v1.Probe{
Handler: v1.Handler{
Exec: &v1.ExecAction{
Command: []string{"/bin/sh", "-ec", cmd},
},
},
InitialDelaySeconds: 15,
TimeoutSeconds: 15,
FailureThreshold: 8,
}
}
// NewVolume creates a v1.Volume with a hostPath mount to the specified location
func NewVolume(name, path string, pathType *v1.HostPathType) v1.Volume {
return v1.Volume{
@ -180,6 +199,23 @@ func WriteStaticPodToDisk(componentName, manifestDir string, pod v1.Pod) error {
return nil
}
// ReadStaticPodFromDisk reads a static pod file from disk
func ReadStaticPodFromDisk(manifestPath string) (*v1.Pod, error) {
buf, err := ioutil.ReadFile(manifestPath)
if err != nil {
return &v1.Pod{}, fmt.Errorf("failed to read manifest for %q: %v", manifestPath, err)
}
obj, err := util.UnmarshalFromYaml(buf, v1.SchemeGroupVersion)
if err != nil {
return &v1.Pod{}, fmt.Errorf("failed to unmarshal manifest for %q from YAML: %v", manifestPath, err)
}
pod := obj.(*v1.Pod)
return pod, nil
}
// GetProbeAddress returns an IP address or 127.0.0.1 to use for liveness probes
// in static pod manifests.
func GetProbeAddress(cfg *kubeadmapi.MasterConfiguration, componentName string) string {
@ -206,8 +242,8 @@ func GetProbeAddress(cfg *kubeadmapi.MasterConfiguration, componentName string)
return addr
}
case componentName == kubeadmconstants.Etcd:
if cfg.Etcd.ExtraArgs != nil {
if arg, exists := cfg.Etcd.ExtraArgs[etcdListenClientURLsArg]; exists {
if cfg.Etcd.Local != nil && cfg.Etcd.Local.ExtraArgs != nil {
if arg, exists := cfg.Etcd.Local.ExtraArgs[etcdListenClientURLsArg]; exists {
// Use the first url in the listen-client-urls if multiple url's are specified.
if strings.ContainsAny(arg, ",") {
arg = strings.Split(arg, ",")[0]
@ -218,6 +254,13 @@ func GetProbeAddress(cfg *kubeadmapi.MasterConfiguration, componentName string)
}
// Return the IP if the URL contains an address instead of a name.
if ip := net.ParseIP(parsedURL.Hostname()); ip != nil {
// etcdctl doesn't support auto-converting zero addresses into loopback addresses
if ip.Equal(net.IPv4zero) {
return "127.0.0.1"
}
if ip.Equal(net.IPv6zero) {
return net.IPv6loopback.String()
}
return ip.String()
}
// Use the local resolver to try resolving the name within the URL.

View File

@ -17,6 +17,9 @@ limitations under the License.
package staticpod
import (
"io/ioutil"
"os"
"path/filepath"
"reflect"
"sort"
"testing"
@ -28,6 +31,7 @@ import (
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
)
func TestComponentResources(t *testing.T) {
@ -161,48 +165,6 @@ func TestComponentProbe(t *testing.T) {
scheme: v1.URISchemeHTTP,
expected: "2001:db8::1",
},
{
name: "valid etcd probe using listen-client-urls IPv4 addresses",
cfg: &kubeadmapi.MasterConfiguration{
Etcd: kubeadmapi.Etcd{
ExtraArgs: map[string]string{
"listen-client-urls": "http://1.2.3.4:2379,http://4.3.2.1:2379"},
},
},
component: kubeadmconstants.Etcd,
port: 1,
path: "foo",
scheme: v1.URISchemeHTTP,
expected: "1.2.3.4",
},
{
name: "valid etcd probe using listen-client-urls IPv6 addresses",
cfg: &kubeadmapi.MasterConfiguration{
Etcd: kubeadmapi.Etcd{
ExtraArgs: map[string]string{
"listen-client-urls": "http://[2001:db8::1]:2379,http://[2001:db8::2]:2379"},
},
},
component: kubeadmconstants.Etcd,
port: 1,
path: "foo",
scheme: v1.URISchemeHTTP,
expected: "2001:db8::1",
},
{
name: "valid IPv4 etcd probe using hostname for listen-client-urls",
cfg: &kubeadmapi.MasterConfiguration{
Etcd: kubeadmapi.Etcd{
ExtraArgs: map[string]string{
"listen-client-urls": "http://localhost:2379"},
},
},
component: kubeadmconstants.Etcd,
port: 1,
path: "foo",
scheme: v1.URISchemeHTTP,
expected: "127.0.0.1",
},
}
for _, rt := range tests {
actual := ComponentProbe(rt.cfg, rt.component, rt.port, rt.path, rt.scheme)
@ -229,6 +191,155 @@ func TestComponentProbe(t *testing.T) {
}
}
func TestEtcdProbe(t *testing.T) {
var tests = []struct {
name string
cfg *kubeadmapi.MasterConfiguration
component string
port int
certsDir string
cacert string
cert string
key string
expected string
}{
{
name: "valid etcd probe using listen-client-urls IPv4 addresses",
cfg: &kubeadmapi.MasterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
"listen-client-urls": "http://1.2.3.4:2379,http://4.3.2.1:2379"},
},
},
},
component: kubeadmconstants.Etcd,
port: 1,
certsDir: "secretsA",
cacert: "ca1",
cert: "cert1",
key: "key1",
expected: "ETCDCTL_API=3 etcdctl --endpoints=https://[1.2.3.4]:1 --cacert=secretsA/ca1 --cert=secretsA/cert1 --key=secretsA/key1 get foo",
},
{
name: "valid etcd probe using listen-client-urls unspecified IPv6 address",
cfg: &kubeadmapi.MasterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
"listen-client-urls": "http://[0:0:0:0:0:0:0:0]:2379"},
},
},
},
component: kubeadmconstants.Etcd,
port: 1,
certsDir: "secretsB",
cacert: "ca2",
cert: "cert2",
key: "key2",
expected: "ETCDCTL_API=3 etcdctl --endpoints=https://[::1]:1 --cacert=secretsB/ca2 --cert=secretsB/cert2 --key=secretsB/key2 get foo",
},
{
name: "valid etcd probe using listen-client-urls unspecified IPv6 address 2",
cfg: &kubeadmapi.MasterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
"listen-client-urls": "http://[::0:0]:2379"},
},
},
},
component: kubeadmconstants.Etcd,
port: 1,
certsDir: "secretsB",
cacert: "ca2",
cert: "cert2",
key: "key2",
expected: "ETCDCTL_API=3 etcdctl --endpoints=https://[::1]:1 --cacert=secretsB/ca2 --cert=secretsB/cert2 --key=secretsB/key2 get foo",
},
{
name: "valid etcd probe using listen-client-urls unspecified IPv6 address 3",
cfg: &kubeadmapi.MasterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
"listen-client-urls": "http://[::]:2379"},
},
},
},
component: kubeadmconstants.Etcd,
port: 1,
certsDir: "secretsB",
cacert: "ca2",
cert: "cert2",
key: "key2",
expected: "ETCDCTL_API=3 etcdctl --endpoints=https://[::1]:1 --cacert=secretsB/ca2 --cert=secretsB/cert2 --key=secretsB/key2 get foo",
},
{
name: "valid etcd probe using listen-client-urls unspecified IPv4 address",
cfg: &kubeadmapi.MasterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
"listen-client-urls": "http://1.2.3.4:2379,http://4.3.2.1:2379"},
},
},
},
component: kubeadmconstants.Etcd,
port: 1,
certsDir: "secretsA",
cacert: "ca1",
cert: "cert1",
key: "key1",
expected: "ETCDCTL_API=3 etcdctl --endpoints=https://[1.2.3.4]:1 --cacert=secretsA/ca1 --cert=secretsA/cert1 --key=secretsA/key1 get foo",
},
{
name: "valid etcd probe using listen-client-urls IPv6 addresses",
cfg: &kubeadmapi.MasterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
"listen-client-urls": "http://[2001:db8::1]:2379,http://[2001:db8::2]:2379"},
},
},
},
component: kubeadmconstants.Etcd,
port: 1,
certsDir: "secretsB",
cacert: "ca2",
cert: "cert2",
key: "key2",
expected: "ETCDCTL_API=3 etcdctl --endpoints=https://[2001:db8::1]:1 --cacert=secretsB/ca2 --cert=secretsB/cert2 --key=secretsB/key2 get foo",
},
{
name: "valid IPv4 etcd probe using hostname for listen-client-urls",
cfg: &kubeadmapi.MasterConfiguration{
Etcd: kubeadmapi.Etcd{
Local: &kubeadmapi.LocalEtcd{
ExtraArgs: map[string]string{
"listen-client-urls": "http://localhost:2379"},
},
},
},
component: kubeadmconstants.Etcd,
port: 1,
certsDir: "secretsC",
cacert: "ca3",
cert: "cert3",
key: "key3",
expected: "ETCDCTL_API=3 etcdctl --endpoints=https://[127.0.0.1]:1 --cacert=secretsC/ca3 --cert=secretsC/cert3 --key=secretsC/key3 get foo",
},
}
for _, rt := range tests {
actual := EtcdProbe(rt.cfg, rt.component, rt.port, rt.certsDir, rt.cacert, rt.cert, rt.key)
if actual.Handler.Exec.Command[2] != rt.expected {
t.Errorf("%s test case failed:\n\texpected: %s\n\t actual: %s",
rt.name, rt.expected,
actual.Handler.Exec.Command[2])
}
}
}
func TestComponentPod(t *testing.T) {
var tests = []struct {
name string
@ -253,8 +364,9 @@ func TestComponentPod(t *testing.T) {
Name: "foo",
},
},
HostNetwork: true,
Volumes: []v1.Volume{},
PriorityClassName: "system-cluster-critical",
HostNetwork: true,
Volumes: []v1.Volume{},
},
},
},
@ -425,3 +537,73 @@ func TestGetExtraParameters(t *testing.T) {
}
}
}
const (
validPod = `
apiVersion: v1
kind: Pod
metadata:
labels:
component: etcd
tier: control-plane
name: etcd
namespace: kube-system
spec:
containers:
- image: gcr.io/google_containers/etcd-amd64:3.1.11
status: {}
`
invalidPod = `---{ broken yaml @@@`
)
func TestReadStaticPodFromDisk(t *testing.T) {
tests := []struct {
description string
podYaml string
expectErr bool
writeManifest bool
}{
{
description: "valid pod is marshaled",
podYaml: validPod,
writeManifest: true,
expectErr: false,
},
{
description: "invalid pod fails to unmarshal",
podYaml: invalidPod,
writeManifest: true,
expectErr: true,
},
{
description: "non-existent file returns error",
podYaml: ``,
writeManifest: false,
expectErr: true,
},
}
for _, rt := range tests {
tmpdir := testutil.SetupTempDir(t)
defer os.RemoveAll(tmpdir)
manifestPath := filepath.Join(tmpdir, "pod.yaml")
if rt.writeManifest {
err := ioutil.WriteFile(manifestPath, []byte(rt.podYaml), 0644)
if err != nil {
t.Fatalf("Failed to write pod manifest\n%s\n\tfatal error: %v", rt.description, err)
}
}
_, actualErr := ReadStaticPodFromDisk(manifestPath)
if (actualErr != nil) != rt.expectErr {
t.Errorf(
"ReadStaticPodFromDisk failed\n%s\n\texpected error: %t\n\tgot: %t\n\tactual error: %v",
rt.description,
rt.expectErr,
(actualErr != nil),
actualErr,
)
}
}
}

View File

@ -1,34 +0,0 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_test(
name = "go_default_test",
srcs = ["tokens_test.go"],
embed = [":go_default_library"],
deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = ["tokens.go"],
importpath = "k8s.io/kubernetes/cmd/kubeadm/app/util/token",
deps = ["//cmd/kubeadm/app/apis/kubeadm:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -1,125 +0,0 @@
/*
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 token
import (
"bufio"
"crypto/rand"
"fmt"
"regexp"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
const (
// TokenIDBytes defines a number of bytes used for a token id
TokenIDBytes = 6
// TokenSecretBytes defines a number of bytes used for a secret
TokenSecretBytes = 16
)
var (
// TokenIDRegexpString defines token's id regular expression pattern
TokenIDRegexpString = "^([a-z0-9]{6})$"
// TokenIDRegexp is a compiled regular expression of TokenIDRegexpString
TokenIDRegexp = regexp.MustCompile(TokenIDRegexpString)
// TokenRegexpString defines id.secret regular expression pattern
TokenRegexpString = "^([a-z0-9]{6})\\.([a-z0-9]{16})$"
// TokenRegexp is a compiled regular expression of TokenRegexpString
TokenRegexp = regexp.MustCompile(TokenRegexpString)
)
const validBootstrapTokenChars = "0123456789abcdefghijklmnopqrstuvwxyz"
func randBytes(length int) (string, error) {
// len("0123456789abcdefghijklmnopqrstuvwxyz") = 36 which doesn't evenly divide
// the possible values of a byte: 256 mod 36 = 4. Discard any random bytes we
// read that are >= 252 so the bytes we evenly divide the character set.
const maxByteValue = 252
var (
b byte
err error
token = make([]byte, length)
)
reader := bufio.NewReaderSize(rand.Reader, length*2)
for i := range token {
for {
if b, err = reader.ReadByte(); err != nil {
return "", err
}
if b < maxByteValue {
break
}
}
token[i] = validBootstrapTokenChars[int(b)%len(validBootstrapTokenChars)]
}
return string(token), nil
}
// GenerateToken generates a new token with a token ID that is valid as a
// Kubernetes DNS label.
// For more info, see kubernetes/pkg/util/validation/validation.go.
func GenerateToken() (string, error) {
tokenID, err := randBytes(TokenIDBytes)
if err != nil {
return "", err
}
tokenSecret, err := randBytes(TokenSecretBytes)
if err != nil {
return "", err
}
return fmt.Sprintf("%s.%s", tokenID, tokenSecret), nil
}
// ParseTokenID tries and parse a valid token ID from a string.
// An error is returned in case of failure.
func ParseTokenID(s string) error {
if !TokenIDRegexp.MatchString(s) {
return fmt.Errorf("token ID [%q] was not of form [%q]", s, TokenIDRegexpString)
}
return nil
}
// ParseToken tries and parse a valid token from a string.
// A token ID and token secret are returned in case of success, an error otherwise.
func ParseToken(s string) (string, string, error) {
split := TokenRegexp.FindStringSubmatch(s)
if len(split) != 3 {
return "", "", fmt.Errorf("token [%q] was not of form [%q]", s, TokenRegexpString)
}
return split[1], split[2], nil
}
// BearerToken returns a string representation of the passed token.
func BearerToken(d *kubeadmapi.TokenDiscovery) string {
return fmt.Sprintf("%s.%s", d.ID, d.Secret)
}
// ValidateToken validates whether a token is well-formed.
// In case it's not, the corresponding error is returned as well.
func ValidateToken(d *kubeadmapi.TokenDiscovery) (bool, error) {
if _, _, err := ParseToken(d.ID + "." + d.Secret); err != nil {
return false, err
}
return true, nil
}

View File

@ -1,173 +0,0 @@
/*
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 token
import (
"testing"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
)
func TestTokenParse(t *testing.T) {
var tests = []struct {
token string
expected bool
}{
{token: "1234567890123456789012", expected: false}, // invalid parcel size
{token: "12345.1234567890123456", expected: false}, // invalid parcel size
{token: ".1234567890123456", expected: false}, // invalid parcel size
{token: "123456:1234567890.123456", expected: false}, // invalid separation
{token: "abcdef:1234567890123456", expected: false}, // invalid separation
{token: "Abcdef.1234567890123456", expected: false}, // invalid token id
{token: "123456.AABBCCDDEEFFGGHH", expected: false}, // invalid token secret
{token: "abcdef.1234567890123456", expected: true},
{token: "123456.aabbccddeeffgghh", expected: true},
}
for _, rt := range tests {
_, _, actual := ParseToken(rt.token)
if (actual == nil) != rt.expected {
t.Errorf(
"failed ParseToken for this token: [%s]\n\texpected: %t\n\t actual: %t",
rt.token,
rt.expected,
(actual == nil),
)
}
}
}
func TestParseTokenID(t *testing.T) {
var tests = []struct {
tokenID string
expected bool
}{
{tokenID: "", expected: false},
{tokenID: "1234567890123456789012", expected: false},
{tokenID: "12345", expected: false},
{tokenID: "Abcdef", expected: false},
{tokenID: "abcdef", expected: true},
{tokenID: "123456", expected: true},
}
for _, rt := range tests {
actual := ParseTokenID(rt.tokenID)
if (actual == nil) != rt.expected {
t.Errorf(
"failed ParseTokenID for this token ID: [%s]\n\texpected: %t\n\t actual: %t",
rt.tokenID,
rt.expected,
(actual == nil),
)
}
}
}
func TestValidateToken(t *testing.T) {
var tests = []struct {
token *kubeadmapi.TokenDiscovery
expected bool
}{
{token: &kubeadmapi.TokenDiscovery{ID: "", Secret: ""}, expected: false},
{token: &kubeadmapi.TokenDiscovery{ID: "1234567890123456789012", Secret: ""}, expected: false},
{token: &kubeadmapi.TokenDiscovery{ID: "", Secret: "1234567890123456789012"}, expected: false},
{token: &kubeadmapi.TokenDiscovery{ID: "12345", Secret: "1234567890123456"}, expected: false},
{token: &kubeadmapi.TokenDiscovery{ID: "Abcdef", Secret: "1234567890123456"}, expected: false},
{token: &kubeadmapi.TokenDiscovery{ID: "123456", Secret: "AABBCCDDEEFFGGHH"}, expected: false},
{token: &kubeadmapi.TokenDiscovery{ID: "abc*ef", Secret: "1234567890123456"}, expected: false},
{token: &kubeadmapi.TokenDiscovery{ID: "abcdef", Secret: "123456789*123456"}, expected: false},
{token: &kubeadmapi.TokenDiscovery{ID: "abcdef", Secret: "1234567890123456"}, expected: true},
{token: &kubeadmapi.TokenDiscovery{ID: "123456", Secret: "aabbccddeeffgghh"}, expected: true},
{token: &kubeadmapi.TokenDiscovery{ID: "abc456", Secret: "1234567890123456"}, expected: true},
{token: &kubeadmapi.TokenDiscovery{ID: "abcdef", Secret: "123456ddeeffgghh"}, expected: true},
}
for _, rt := range tests {
valid, actual := ValidateToken(rt.token)
if (actual == nil) != rt.expected {
t.Errorf(
"failed ValidateToken for this token ID: [%s]\n\texpected: %t\n\t actual: %t",
rt.token,
rt.expected,
(actual == nil),
)
}
if (valid == true) != rt.expected {
t.Errorf(
"failed ValidateToken for this token ID: [%s]\n\texpected: %t\n\t actual: %t",
rt.token,
rt.expected,
(actual == nil),
)
}
}
}
func TestGenerateToken(t *testing.T) {
token, err := GenerateToken()
if err != nil {
t.Fatalf("GenerateToken returned an unexpected error: %+v", err)
}
tokenID, tokenSecret, err := ParseToken(token)
if err != nil {
t.Fatalf("GenerateToken returned an unexpected error: %+v", err)
}
if len(tokenID) != 6 {
t.Errorf("failed GenerateToken first part length:\n\texpected: 6\n\t actual: %d", len(tokenID))
}
if len(tokenSecret) != 16 {
t.Errorf("failed GenerateToken second part length:\n\texpected: 16\n\t actual: %d", len(tokenSecret))
}
}
func TestRandBytes(t *testing.T) {
var randTest = []int{
0,
1,
2,
3,
100,
}
for _, rt := range randTest {
actual, err := randBytes(rt)
if err != nil {
t.Errorf("failed randBytes: %v", err)
}
if len(actual) != rt {
t.Errorf("failed randBytes:\n\texpected: %d\n\t actual: %d\n", rt, len(actual))
}
}
}
func TestBearerToken(t *testing.T) {
var tests = []struct {
token *kubeadmapi.TokenDiscovery
expected string
}{
{token: &kubeadmapi.TokenDiscovery{ID: "foo", Secret: "bar"}, expected: "foo.bar"}, // should use default
}
for _, rt := range tests {
actual := BearerToken(rt.token)
if actual != rt.expected {
t.Errorf(
"failed BearerToken:\n\texpected: %s\n\t actual: %s",
rt.expected,
actual,
)
}
}
}

View File

@ -122,8 +122,8 @@ func splitVersion(version string) (string, string, error) {
switch {
case strings.HasPrefix(subs[0][2], "ci"):
// Special case. CI images populated only by ci-cross area
urlSuffix = "ci-cross"
// Just use whichever the user specified
urlSuffix = subs[0][2]
default:
urlSuffix = "release"
}

View File

@ -180,8 +180,10 @@ func TestSplitVersion(t *testing.T) {
{"release/v1.7.0", "https://dl.k8s.io/release", "v1.7.0", true},
{"release/latest-1.7", "https://dl.k8s.io/release", "latest-1.7", true},
// CI builds area, lookup actual builds at ci-cross/*.txt
{"ci/latest", "https://dl.k8s.io/ci", "latest", true},
{"ci-cross/latest", "https://dl.k8s.io/ci-cross", "latest", true},
{"ci/latest-1.7", "https://dl.k8s.io/ci-cross", "latest-1.7", true},
{"ci/latest-1.7", "https://dl.k8s.io/ci", "latest-1.7", true},
{"ci-cross/latest-1.7", "https://dl.k8s.io/ci-cross", "latest-1.7", true},
// unknown label in default (release) area: splitVersion validate only areas.
{"unknown-1", "https://dl.k8s.io/release", "unknown-1", true},
// unknown area, not valid input.