mirror of https://github.com/k3s-io/k3s
Merge pull request #50302 from fabriziopandini/kubeadm-phases-controlplane2
Automatic merge from submit-queue kubeadm: Implementing the controlplane phase **What this PR does / why we need it:** This contains implementation of controlplane phases in kubeadm, which is part of the wider effort of implementing phases in kubeadm, previously in alpha stage. The original proposal for this activity can be found [here](https://github.com/kubernetes/kubeadm/pull/156/files) and related comments. Kubeadm phase implementation checklist is defined [here](https://github.com/kubernetes/kubeadm/issues/267) Common implementation guidelines and principles for all phases are defined [here](https://docs.google.com/document/d/1VQMyFIVMfRGQPP3oCUpfjiWtOr3pLxp4g7cP-hXQFXc/edit?usp=sharing) This PR implements: - [x] kubeadm phase controlplane: wip by @fabriziopandini - [x] kubeadm phase controlplane all - [x] kubeadm phase controlplane etcd - [x] kubeadm phase controlplane apiserver - [x] kubeadm phase controlplane scheduler - [x] kubeadm phase controlplane controller-manager **Which issue this PR fixes:** https://github.com/kubernetes/kubeadm/issues/349 **Special notes for your reviewer:** This PR implements the same approach of #49419, thus minimising rework/impacts on existing codebase.pull/6/head
commit
c85aea35cb
|
@ -74,7 +74,6 @@ package_group(
|
|||
packages = [
|
||||
"//cmd/kubeadm/app",
|
||||
"//cmd/kubeadm/app/cmd",
|
||||
"//cmd/kubeadm/app/phases/controlplane",
|
||||
],
|
||||
)
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ filegroup(
|
|||
"//cmd/kubeadm/app/phases/bootstraptoken/node:all-srcs",
|
||||
"//cmd/kubeadm/app/phases/certs:all-srcs",
|
||||
"//cmd/kubeadm/app/phases/controlplane:all-srcs",
|
||||
"//cmd/kubeadm/app/phases/etcd:all-srcs",
|
||||
"//cmd/kubeadm/app/phases/kubeconfig:all-srcs",
|
||||
"//cmd/kubeadm/app/phases/markmaster:all-srcs",
|
||||
"//cmd/kubeadm/app/phases/selfhosting:all-srcs",
|
||||
|
|
|
@ -32,6 +32,7 @@ go_library(
|
|||
"//cmd/kubeadm/app/phases/bootstraptoken/node:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/controlplane:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/etcd:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/kubeconfig:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/markmaster:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/selfhosting:go_default_library",
|
||||
|
|
|
@ -20,7 +20,6 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"text/template"
|
||||
|
||||
|
@ -41,6 +40,7 @@ import (
|
|||
nodebootstraptokenphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/bootstraptoken/node"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/phases/certs/pkiutil"
|
||||
controlplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane"
|
||||
etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd"
|
||||
kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
|
||||
markmasterphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/markmaster"
|
||||
selfhostingphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/selfhosting"
|
||||
|
@ -242,9 +242,16 @@ func (i *Init) Run(out io.Writer) error {
|
|||
}
|
||||
|
||||
// PHASE 3: Bootstrap the control plane
|
||||
if err := controlplanephase.WriteStaticPodManifests(i.cfg, k8sVersion, kubeadmconstants.GetStaticPodDirectory()); err != nil {
|
||||
manifestPath := kubeadmconstants.GetStaticPodDirectory()
|
||||
if err := controlplanephase.CreateInitStaticPodManifestFiles(manifestPath, i.cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
// Add etcd static pod spec only if external etcd is not configured
|
||||
if len(i.cfg.Etcd.Endpoints) == 0 {
|
||||
if err := etcdphase.CreateLocalEtcdStaticPodManifestFile(manifestPath, i.cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
client, err := kubeadmutil.CreateClientAndWaitForAPI(kubeadmconstants.GetAdminKubeConfigPath())
|
||||
if err != nil {
|
||||
|
@ -319,7 +326,7 @@ func (i *Init) Run(out io.Writer) error {
|
|||
}
|
||||
|
||||
ctx := map[string]string{
|
||||
"KubeConfigPath": filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.AdminKubeConfigFileName),
|
||||
"KubeConfigPath": kubeadmconstants.GetAdminKubeConfigPath(),
|
||||
"KubeConfigName": kubeadmconstants.AdminKubeConfigFileName,
|
||||
"Token": i.cfg.Token,
|
||||
"CAPubKeyPin": pubkeypin.Hash(caCert),
|
||||
|
|
|
@ -11,21 +11,27 @@ go_library(
|
|||
srcs = [
|
||||
"bootstraptoken.go",
|
||||
"certs.go",
|
||||
"controlplane.go",
|
||||
"etcd.go",
|
||||
"kubeconfig.go",
|
||||
"markmaster.go",
|
||||
"phase.go",
|
||||
"preflight.go",
|
||||
"selfhosting.go",
|
||||
"uploadconfig.go",
|
||||
"util.go",
|
||||
],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
|
||||
"//cmd/kubeadm/app/apis/kubeadm/validation:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/bootstraptoken/clusterinfo:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/bootstraptoken/node:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/certs:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/certs/pkiutil:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/controlplane:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/etcd:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/kubeconfig:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/markmaster:go_default_library",
|
||||
"//cmd/kubeadm/app/phases/selfhosting:go_default_library",
|
||||
|
@ -45,6 +51,8 @@ go_test(
|
|||
name = "go_default_test",
|
||||
srcs = [
|
||||
"certs_test.go",
|
||||
"controlplane_test.go",
|
||||
"etcd_test.go",
|
||||
"kubeconfig_test.go",
|
||||
"phase_test.go",
|
||||
],
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
/*
|
||||
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 phases
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
controlplanephase "k8s.io/kubernetes/cmd/kubeadm/app/phases/controlplane"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
// NewCmdControlplane return main command for Controlplane phase
|
||||
func NewCmdControlplane() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "controlplane",
|
||||
Short: "Generate all static pod manifest files necessary to establish the control plane.",
|
||||
RunE: subCmdRunE("controlplane"),
|
||||
}
|
||||
|
||||
manifestPath := kubeadmconstants.GetStaticPodDirectory()
|
||||
cmd.AddCommand(getControlPlaneSubCommands(manifestPath)...)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// getControlPlaneSubCommands returns sub commands for Controlplane phase
|
||||
func getControlPlaneSubCommands(outDir string) []*cobra.Command {
|
||||
|
||||
cfg := &kubeadmapiext.MasterConfiguration{}
|
||||
// Default values for the cobra help text
|
||||
api.Scheme.Default(cfg)
|
||||
|
||||
var cfgPath string
|
||||
var subCmds []*cobra.Command
|
||||
|
||||
subCmdProperties := []struct {
|
||||
use string
|
||||
short string
|
||||
cmdFunc func(outDir string, cfg *kubeadmapi.MasterConfiguration) error
|
||||
}{
|
||||
{
|
||||
use: "all",
|
||||
short: "Generate all static pod manifest files necessary to establish the control plane.",
|
||||
cmdFunc: controlplanephase.CreateInitStaticPodManifestFiles,
|
||||
},
|
||||
{
|
||||
use: "apiserver",
|
||||
short: "Generate apiserver static pod manifest.",
|
||||
cmdFunc: controlplanephase.CreateAPIServerStaticPodManifestFile,
|
||||
},
|
||||
{
|
||||
use: "controller-manager",
|
||||
short: "Generate controller-manager static pod manifest.",
|
||||
cmdFunc: controlplanephase.CreateControllerManagerStaticPodManifestFile,
|
||||
},
|
||||
{
|
||||
use: "scheduler",
|
||||
short: "Generate scheduler static pod manifest.",
|
||||
cmdFunc: controlplanephase.CreateSchedulerStaticPodManifestFile,
|
||||
},
|
||||
}
|
||||
|
||||
for _, properties := range subCmdProperties {
|
||||
// Creates the UX Command
|
||||
cmd := &cobra.Command{
|
||||
Use: properties.use,
|
||||
Short: properties.short,
|
||||
Run: runCmdPhase(properties.cmdFunc, &outDir, &cfgPath, cfg),
|
||||
}
|
||||
|
||||
// Add flags to the command
|
||||
cmd.Flags().StringVar(&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, `The path where certificates are stored`)
|
||||
cmd.Flags().StringVar(&cfg.KubernetesVersion, "kubernetes-version", cfg.KubernetesVersion, `Choose a specific Kubernetes version for the control plane`)
|
||||
|
||||
if properties.use == "all" || properties.use == "apiserver" {
|
||||
cmd.Flags().StringVar(&cfg.API.AdvertiseAddress, "apiserver-advertise-address", cfg.API.AdvertiseAddress, "The IP address the API Server will advertise it's listening on. 0.0.0.0 means the default network interface's address.")
|
||||
cmd.Flags().Int32Var(&cfg.API.BindPort, "apiserver-bind-port", cfg.API.BindPort, "Port for the API Server to bind to")
|
||||
cmd.Flags().StringVar(&cfg.Networking.ServiceSubnet, "service-cidr", cfg.Networking.ServiceSubnet, "Use alternative range of IP address for service VIPs")
|
||||
}
|
||||
|
||||
if properties.use == "all" || properties.use == "controller-manager" {
|
||||
cmd.Flags().StringVar(&cfg.Networking.PodSubnet, "pod-network-cidr", cfg.Networking.PodSubnet, "Specify range of IP addresses for the pod network; if set, the control plane will automatically allocate CIDRs for every node")
|
||||
}
|
||||
|
||||
cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)")
|
||||
|
||||
subCmds = append(subCmds, cmd)
|
||||
}
|
||||
|
||||
return subCmds
|
||||
}
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
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 phases
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
// required for triggering api machinery startup when running unit tests
|
||||
_ "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/install"
|
||||
|
||||
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
|
||||
cmdtestutil "k8s.io/kubernetes/cmd/kubeadm/test/cmd"
|
||||
)
|
||||
|
||||
func TestControlPlaneSubCommandsHasFlags(t *testing.T) {
|
||||
|
||||
subCmds := getControlPlaneSubCommands("")
|
||||
|
||||
commonFlags := []string{
|
||||
"cert-dir",
|
||||
"config",
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
command string
|
||||
additionalFlags []string
|
||||
}{
|
||||
{
|
||||
command: "all",
|
||||
additionalFlags: []string{
|
||||
"kubernetes-version",
|
||||
"apiserver-advertise-address",
|
||||
"apiserver-bind-port",
|
||||
"service-cidr",
|
||||
"pod-network-cidr",
|
||||
},
|
||||
},
|
||||
{
|
||||
command: "apiserver",
|
||||
additionalFlags: []string{
|
||||
"kubernetes-version",
|
||||
"apiserver-advertise-address",
|
||||
"apiserver-bind-port",
|
||||
"service-cidr",
|
||||
},
|
||||
},
|
||||
{
|
||||
command: "controller-manager",
|
||||
additionalFlags: []string{
|
||||
"kubernetes-version",
|
||||
"pod-network-cidr",
|
||||
},
|
||||
},
|
||||
{
|
||||
command: "scheduler",
|
||||
additionalFlags: []string{
|
||||
"kubernetes-version",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
expectedFlags := append(commonFlags, test.additionalFlags...)
|
||||
cmdtestutil.AssertSubCommandHasFlags(t, subCmds, test.command, expectedFlags...)
|
||||
}
|
||||
}
|
||||
|
||||
func TestControlPlaneCreateFilesWithFlags(t *testing.T) {
|
||||
|
||||
var tests = []struct {
|
||||
command string
|
||||
additionalFlags []string
|
||||
expectedFiles []string
|
||||
}{
|
||||
{
|
||||
command: "all",
|
||||
additionalFlags: []string{
|
||||
"--kubernetes-version=v1.7.0",
|
||||
"--apiserver-advertise-address=1.2.3.4",
|
||||
"--apiserver-bind-port=6443",
|
||||
"--service-cidr=1.2.3.4/16",
|
||||
"--pod-network-cidr=1.2.3.4/16",
|
||||
},
|
||||
expectedFiles: []string{
|
||||
"kube-apiserver.yaml",
|
||||
"kube-controller-manager.yaml",
|
||||
"kube-scheduler.yaml",
|
||||
},
|
||||
},
|
||||
{
|
||||
command: "apiserver",
|
||||
additionalFlags: []string{
|
||||
"--kubernetes-version=v1.7.0",
|
||||
"--apiserver-advertise-address=1.2.3.4",
|
||||
"--apiserver-bind-port=6443",
|
||||
"--service-cidr=1.2.3.4/16",
|
||||
},
|
||||
expectedFiles: []string{"kube-apiserver.yaml"},
|
||||
},
|
||||
{
|
||||
command: "controller-manager",
|
||||
additionalFlags: []string{
|
||||
"--kubernetes-version=v1.7.0",
|
||||
"--pod-network-cidr=1.2.3.4/16",
|
||||
},
|
||||
expectedFiles: []string{"kube-controller-manager.yaml"},
|
||||
},
|
||||
{
|
||||
command: "scheduler",
|
||||
additionalFlags: []string{
|
||||
"--kubernetes-version=v1.7.0",
|
||||
},
|
||||
expectedFiles: []string{"kube-scheduler.yaml"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
// Create temp folder for the test case
|
||||
tmpdir := testutil.SetupTempDir(t)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
// Get subcommands working in the temporary directory
|
||||
subCmds := getControlPlaneSubCommands(tmpdir)
|
||||
|
||||
// Execute the subcommand
|
||||
certDirFlag := fmt.Sprintf("--cert-dir=%s", tmpdir)
|
||||
allFlags := append(test.additionalFlags, certDirFlag)
|
||||
cmdtestutil.RunSubCommand(t, subCmds, test.command, allFlags...)
|
||||
|
||||
// Checks that requested files are there
|
||||
testutil.AssertFileExists(t, tmpdir, test.expectedFiles...)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
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 phases
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
etcdphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/etcd"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
// NewCmdEtcd return main command for Etcd phase
|
||||
func NewCmdEtcd() *cobra.Command {
|
||||
cmd := &cobra.Command{
|
||||
Use: "etcd",
|
||||
Short: "Generate static pod manifest file for etcd.",
|
||||
RunE: subCmdRunE("etcd"),
|
||||
}
|
||||
|
||||
manifestPath := kubeadmconstants.GetStaticPodDirectory()
|
||||
cmd.AddCommand(getEtcdSubCommands(manifestPath)...)
|
||||
return cmd
|
||||
}
|
||||
|
||||
// getEtcdSubCommands returns sub commands for etcd phase
|
||||
func getEtcdSubCommands(outDir string) []*cobra.Command {
|
||||
|
||||
cfg := &kubeadmapiext.MasterConfiguration{}
|
||||
// Default values for the cobra help text
|
||||
api.Scheme.Default(cfg)
|
||||
|
||||
var cfgPath string
|
||||
var subCmds []*cobra.Command
|
||||
|
||||
properties := struct {
|
||||
use string
|
||||
short string
|
||||
cmdFunc func(outDir string, cfg *kubeadmapi.MasterConfiguration) error
|
||||
}{
|
||||
use: "local",
|
||||
short: "Generate static pod manifest file for a local, single-node etcd instance.",
|
||||
cmdFunc: etcdphase.CreateLocalEtcdStaticPodManifestFile,
|
||||
}
|
||||
|
||||
// Creates the UX Command
|
||||
cmd := &cobra.Command{
|
||||
Use: properties.use,
|
||||
Short: properties.short,
|
||||
Run: runCmdPhase(properties.cmdFunc, &outDir, &cfgPath, cfg),
|
||||
}
|
||||
|
||||
// Add flags to the command
|
||||
cmd.Flags().StringVar(&cfg.CertificatesDir, "cert-dir", cfg.CertificatesDir, `The path where certificates are stored`)
|
||||
cmd.Flags().StringVar(&cfgPath, "config", cfgPath, "Path to kubeadm config file (WARNING: Usage of a configuration file is experimental)")
|
||||
|
||||
subCmds = append(subCmds, cmd)
|
||||
|
||||
return subCmds
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
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 phases
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
// required for triggering api machinery startup when running unit tests
|
||||
_ "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/install"
|
||||
|
||||
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
|
||||
cmdtestutil "k8s.io/kubernetes/cmd/kubeadm/test/cmd"
|
||||
)
|
||||
|
||||
func TestEtcdSubCommandsHasFlags(t *testing.T) {
|
||||
|
||||
subCmds := getEtcdSubCommands("")
|
||||
|
||||
commonFlags := []string{
|
||||
"cert-dir",
|
||||
"config",
|
||||
}
|
||||
|
||||
var tests = []struct {
|
||||
command string
|
||||
additionalFlags []string
|
||||
}{
|
||||
{
|
||||
command: "local",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
expectedFlags := append(commonFlags, test.additionalFlags...)
|
||||
cmdtestutil.AssertSubCommandHasFlags(t, subCmds, test.command, expectedFlags...)
|
||||
}
|
||||
}
|
||||
|
||||
func TestEtcdCreateFilesWithFlags(t *testing.T) {
|
||||
|
||||
var tests = []struct {
|
||||
command string
|
||||
additionalFlags []string
|
||||
expectedFiles []string
|
||||
}{
|
||||
{
|
||||
command: "local",
|
||||
expectedFiles: []string{"etcd.yaml"},
|
||||
additionalFlags: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
|
||||
// Create temp folder for the test case
|
||||
tmpdir := testutil.SetupTempDir(t)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
// Get subcommands working in the temporary directory
|
||||
subCmds := getEtcdSubCommands(tmpdir)
|
||||
|
||||
// Execute the subcommand
|
||||
certDirFlag := fmt.Sprintf("--cert-dir=%s", tmpdir)
|
||||
allFlags := append(test.additionalFlags, certDirFlag)
|
||||
cmdtestutil.RunSubCommand(t, subCmds, test.command, allFlags...)
|
||||
|
||||
// Checks that requested files are there
|
||||
testutil.AssertFileExists(t, tmpdir, test.expectedFiles...)
|
||||
}
|
||||
}
|
|
@ -31,13 +31,15 @@ func NewCmdPhase(out io.Writer) *cobra.Command {
|
|||
RunE: subCmdRunE("phase"),
|
||||
}
|
||||
|
||||
cmd.AddCommand(NewCmdKubeConfig(out))
|
||||
cmd.AddCommand(NewCmdBootstrapToken())
|
||||
cmd.AddCommand(NewCmdCerts())
|
||||
cmd.AddCommand(NewCmdControlplane())
|
||||
cmd.AddCommand(NewCmdEtcd())
|
||||
cmd.AddCommand(NewCmdKubeConfig(out))
|
||||
cmd.AddCommand(NewCmdMarkMaster())
|
||||
cmd.AddCommand(NewCmdPreFlight())
|
||||
cmd.AddCommand(NewCmdSelfhosting())
|
||||
cmd.AddCommand(NewCmdMarkMaster())
|
||||
cmd.AddCommand(NewCmdUploadConfig())
|
||||
cmd.AddCommand(NewCmdBootstrapToken())
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
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 phases
|
||||
|
||||
import (
|
||||
"github.com/spf13/cobra"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/validation"
|
||||
kubeadmutil "k8s.io/kubernetes/cmd/kubeadm/app/util"
|
||||
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
|
||||
)
|
||||
|
||||
// runCmdPhase creates a cobra.Command Run function, by composing the call to the given cmdFunc with necessary additional steps (e.g preparation of input parameters)
|
||||
func runCmdPhase(cmdFunc func(outDir string, cfg *kubeadmapi.MasterConfiguration) error, outDir, cfgPath *string, cfg *kubeadmapiext.MasterConfiguration) func(cmd *cobra.Command, args []string) {
|
||||
|
||||
// the following statement build a clousure that wraps a call to a cmdFunc, binding
|
||||
// the function itself with the specific parameters of each sub command.
|
||||
// Please note that specific parameter should be passed as value, while other parameters - passed as reference -
|
||||
// are shared between sub commands and gets access to current value e.g. flags value.
|
||||
|
||||
return func(cmd *cobra.Command, args []string) {
|
||||
if err := validation.ValidateMixedArguments(cmd.Flags()); err != nil {
|
||||
kubeadmutil.CheckErr(err)
|
||||
}
|
||||
|
||||
// This call returns the ready-to-use configuration based on the configuration file that might or might not exist and the default cfg populated by flags
|
||||
internalcfg, err := configutil.ConfigFileAndDefaultsToInternalConfig(*cfgPath, cfg)
|
||||
kubeadmutil.CheckErr(err)
|
||||
|
||||
// Execute the cmdFunc
|
||||
err = cmdFunc(*outDir, internalcfg)
|
||||
kubeadmutil.CheckErr(err)
|
||||
}
|
||||
}
|
|
@ -16,11 +16,9 @@ go_test(
|
|||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/test:go_default_library",
|
||||
"//pkg/util/version: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",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
@ -35,15 +33,10 @@ go_library(
|
|||
"//cmd/kubeadm/app/apis/kubeadm/v1alpha1:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/images:go_default_library",
|
||||
"//cmd/kubeadm/app/util/staticpod:go_default_library",
|
||||
"//pkg/kubeapiserver/authorizer/modes:go_default_library",
|
||||
"//pkg/kubectl/cmd/util:go_default_library",
|
||||
"//pkg/kubelet/types:go_default_library",
|
||||
"//pkg/util/version:go_default_library",
|
||||
"//vendor/github.com/ghodss/yaml:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
],
|
||||
)
|
||||
|
|
|
@ -17,26 +17,19 @@ limitations under the License.
|
|||
package controlplane
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmapiext "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1alpha1"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/images"
|
||||
staticpodutil "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod"
|
||||
authzmodes "k8s.io/kubernetes/pkg/kubeapiserver/authorizer/modes"
|
||||
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
"k8s.io/kubernetes/pkg/util/version"
|
||||
)
|
||||
|
||||
|
@ -47,125 +40,96 @@ const (
|
|||
defaultv17AdmissionControl = "Initializers,NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,DefaultTolerationSeconds,NodeRestriction,ResourceQuota"
|
||||
)
|
||||
|
||||
// WriteStaticPodManifests builds manifest objects based on user provided configuration and then dumps it to disk
|
||||
// where kubelet will pick and schedule them.
|
||||
func WriteStaticPodManifests(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version.Version, manifestsDir string) error {
|
||||
// CreateInitStaticPodManifestFiles will write all static pod manifest files needed to bring up the control plane.
|
||||
func CreateInitStaticPodManifestFiles(manifestDir string, cfg *kubeadmapi.MasterConfiguration) error {
|
||||
return createStaticPodFiles(manifestDir, cfg, kubeadmconstants.KubeAPIServer, kubeadmconstants.KubeControllerManager, kubeadmconstants.KubeScheduler)
|
||||
}
|
||||
|
||||
// CreateAPIServerStaticPodManifestFile will write APIserver static pod manifest file.
|
||||
func CreateAPIServerStaticPodManifestFile(manifestDir string, cfg *kubeadmapi.MasterConfiguration) error {
|
||||
return createStaticPodFiles(manifestDir, cfg, kubeadmconstants.KubeAPIServer)
|
||||
}
|
||||
|
||||
// CreateControllerManagerStaticPodManifestFile will write controller manager static pod manifest file.
|
||||
func CreateControllerManagerStaticPodManifestFile(manifestDir string, cfg *kubeadmapi.MasterConfiguration) error {
|
||||
return createStaticPodFiles(manifestDir, cfg, kubeadmconstants.KubeControllerManager)
|
||||
}
|
||||
|
||||
// CreateSchedulerStaticPodManifestFile will write scheduler static pod manifest file.
|
||||
func CreateSchedulerStaticPodManifestFile(manifestDir string, cfg *kubeadmapi.MasterConfiguration) error {
|
||||
return createStaticPodFiles(manifestDir, cfg, kubeadmconstants.KubeScheduler)
|
||||
}
|
||||
|
||||
// GetStaticPodSpecs returns all staticPodSpecs actualized to the context of the current MasterConfiguration
|
||||
// NB. this methods holds the information about how kubeadm creates static pod mainfests.
|
||||
func GetStaticPodSpecs(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version.Version) map[string]v1.Pod {
|
||||
|
||||
// Get the required hostpath mounts
|
||||
mounts := getHostPathVolumesForTheControlPlane(cfg)
|
||||
|
||||
// Prepare static pod specs
|
||||
staticPodSpecs := map[string]v1.Pod{
|
||||
kubeadmconstants.KubeAPIServer: componentPod(v1.Container{
|
||||
kubeadmconstants.KubeAPIServer: staticpodutil.ComponentPod(v1.Container{
|
||||
Name: kubeadmconstants.KubeAPIServer,
|
||||
Image: images.GetCoreImage(kubeadmconstants.KubeAPIServer, cfg.ImageRepository, cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage),
|
||||
Command: getAPIServerCommand(cfg, k8sVersion),
|
||||
VolumeMounts: mounts.GetVolumeMounts(kubeadmconstants.KubeAPIServer),
|
||||
LivenessProbe: componentProbe(int(cfg.API.BindPort), "/healthz", v1.URISchemeHTTPS),
|
||||
Resources: componentResources("250m"),
|
||||
LivenessProbe: staticpodutil.ComponentProbe(int(cfg.API.BindPort), "/healthz", v1.URISchemeHTTPS),
|
||||
Resources: staticpodutil.ComponentResources("250m"),
|
||||
Env: getProxyEnvVars(),
|
||||
}, mounts.GetVolumes(kubeadmconstants.KubeAPIServer)),
|
||||
kubeadmconstants.KubeControllerManager: componentPod(v1.Container{
|
||||
kubeadmconstants.KubeControllerManager: staticpodutil.ComponentPod(v1.Container{
|
||||
Name: kubeadmconstants.KubeControllerManager,
|
||||
Image: images.GetCoreImage(kubeadmconstants.KubeControllerManager, cfg.ImageRepository, cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage),
|
||||
Command: getControllerManagerCommand(cfg, k8sVersion),
|
||||
VolumeMounts: mounts.GetVolumeMounts(kubeadmconstants.KubeControllerManager),
|
||||
LivenessProbe: componentProbe(10252, "/healthz", v1.URISchemeHTTP),
|
||||
Resources: componentResources("200m"),
|
||||
LivenessProbe: staticpodutil.ComponentProbe(10252, "/healthz", v1.URISchemeHTTP),
|
||||
Resources: staticpodutil.ComponentResources("200m"),
|
||||
Env: getProxyEnvVars(),
|
||||
}, mounts.GetVolumes(kubeadmconstants.KubeControllerManager)),
|
||||
kubeadmconstants.KubeScheduler: componentPod(v1.Container{
|
||||
kubeadmconstants.KubeScheduler: staticpodutil.ComponentPod(v1.Container{
|
||||
Name: kubeadmconstants.KubeScheduler,
|
||||
Image: images.GetCoreImage(kubeadmconstants.KubeScheduler, cfg.ImageRepository, cfg.KubernetesVersion, cfg.UnifiedControlPlaneImage),
|
||||
Command: getSchedulerCommand(cfg),
|
||||
VolumeMounts: mounts.GetVolumeMounts(kubeadmconstants.KubeScheduler),
|
||||
LivenessProbe: componentProbe(10251, "/healthz", v1.URISchemeHTTP),
|
||||
Resources: componentResources("100m"),
|
||||
LivenessProbe: staticpodutil.ComponentProbe(10251, "/healthz", v1.URISchemeHTTP),
|
||||
Resources: staticpodutil.ComponentResources("100m"),
|
||||
Env: getProxyEnvVars(),
|
||||
}, mounts.GetVolumes(kubeadmconstants.KubeScheduler)),
|
||||
}
|
||||
|
||||
// Add etcd static pod spec only if external etcd is not configured
|
||||
if len(cfg.Etcd.Endpoints) == 0 {
|
||||
return staticPodSpecs
|
||||
}
|
||||
|
||||
etcdPod := componentPod(v1.Container{
|
||||
Name: kubeadmconstants.Etcd,
|
||||
Command: getEtcdCommand(cfg),
|
||||
Image: images.GetCoreImage(kubeadmconstants.Etcd, cfg.ImageRepository, "", cfg.Etcd.Image),
|
||||
// Mount the etcd datadir path read-write so etcd can store data in a more persistent manner
|
||||
VolumeMounts: []v1.VolumeMount{newVolumeMount(etcdVolumeName, cfg.Etcd.DataDir, false)},
|
||||
LivenessProbe: componentProbe(2379, "/health", v1.URISchemeHTTP),
|
||||
}, []v1.Volume{newVolume(etcdVolumeName, cfg.Etcd.DataDir)})
|
||||
// createStaticPodFiles creates all the requested static pod files.
|
||||
func createStaticPodFiles(manifestDir string, cfg *kubeadmapi.MasterConfiguration, componentNames ...string) error {
|
||||
|
||||
staticPodSpecs[kubeadmconstants.Etcd] = etcdPod
|
||||
// TODO: Move the "pkg/util/version".Version object into the internal API instead of always parsing the string
|
||||
k8sVersion, err := version.ParseSemantic(cfg.KubernetesVersion)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := os.MkdirAll(manifestsDir, 0700); err != nil {
|
||||
return fmt.Errorf("failed to create directory %q [%v]", manifestsDir, err)
|
||||
}
|
||||
for name, spec := range staticPodSpecs {
|
||||
filename := kubeadmconstants.GetStaticPodFilepath(name, manifestsDir)
|
||||
serialized, err := yaml.Marshal(spec)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal manifest for %q to YAML [%v]", name, err)
|
||||
// gets the StaticPodSpecs, actualized for the current MasterConfiguration
|
||||
specs := GetStaticPodSpecs(cfg, k8sVersion)
|
||||
|
||||
// creates required static pod specs
|
||||
for _, componentName := range componentNames {
|
||||
// retrives the StaticPodSpec for given component
|
||||
spec, exists := specs[componentName]
|
||||
if !exists {
|
||||
return fmt.Errorf("couldn't retrive StaticPodSpec for %s", componentName)
|
||||
}
|
||||
if err := cmdutil.DumpReaderToFile(bytes.NewReader(serialized), filename); err != nil {
|
||||
return fmt.Errorf("failed to create static pod manifest file for %q (%q) [%v]", name, filename, err)
|
||||
|
||||
// writes the StaticPodSpec to disk
|
||||
if err := staticpodutil.WriteStaticPodToDisk(componentName, manifestDir, spec); err != nil {
|
||||
return fmt.Errorf("failed to create static pod manifest file for %q: %v", componentName, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// componentResources returns the v1.ResourceRequirements object needed for allocating a specified amount of the CPU
|
||||
func componentResources(cpu string) v1.ResourceRequirements {
|
||||
return v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceCPU): resource.MustParse(cpu),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// componentProbe is a helper function building a ready v1.Probe object from some simple parameters
|
||||
func componentProbe(port int, path string, scheme v1.URIScheme) *v1.Probe {
|
||||
return &v1.Probe{
|
||||
Handler: v1.Handler{
|
||||
HTTPGet: &v1.HTTPGetAction{
|
||||
// Host has to be set to "127.0.0.1" here due to that our static Pods are on the host's network
|
||||
Host: "127.0.0.1",
|
||||
Path: path,
|
||||
Port: intstr.FromInt(port),
|
||||
Scheme: scheme,
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: 15,
|
||||
TimeoutSeconds: 15,
|
||||
FailureThreshold: 8,
|
||||
}
|
||||
}
|
||||
|
||||
// componentPod returns a Pod object from the container and volume specifications
|
||||
func componentPod(container v1.Container, volumes []v1.Volume) v1.Pod {
|
||||
return v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Pod",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: container.Name,
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
Annotations: map[string]string{kubetypes.CriticalPodAnnotationKey: ""},
|
||||
// The component and tier labels are useful for quickly identifying the control plane Pods when doing a .List()
|
||||
// against Pods in the kube-system namespace. Can for example be used together with the WaitForPodsWithLabel function
|
||||
Labels: map[string]string{"component": container.Name, "tier": "control-plane"},
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{container},
|
||||
HostNetwork: true,
|
||||
Volumes: volumes,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// getAPIServerCommand builds the right API server command from the given config object and version
|
||||
func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version.Version) []string {
|
||||
defaultArguments := map[string]string{
|
||||
|
@ -195,7 +159,7 @@ func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration, k8sVersion *versio
|
|||
}
|
||||
|
||||
command := []string{"kube-apiserver"}
|
||||
command = append(command, getExtraParameters(cfg.APIServerExtraArgs, defaultArguments)...)
|
||||
command = append(command, staticpodutil.GetExtraParameters(cfg.APIServerExtraArgs, defaultArguments)...)
|
||||
command = append(command, getAuthzParameters(cfg.AuthorizationModes)...)
|
||||
|
||||
// Check if the user decided to use an external etcd cluster
|
||||
|
@ -227,19 +191,6 @@ func getAPIServerCommand(cfg *kubeadmapi.MasterConfiguration, k8sVersion *versio
|
|||
return command
|
||||
}
|
||||
|
||||
// getEtcdCommand builds the right etcd command from the given config object
|
||||
func getEtcdCommand(cfg *kubeadmapi.MasterConfiguration) []string {
|
||||
defaultArguments := map[string]string{
|
||||
"listen-client-urls": "http://127.0.0.1:2379",
|
||||
"advertise-client-urls": "http://127.0.0.1:2379",
|
||||
"data-dir": cfg.Etcd.DataDir,
|
||||
}
|
||||
|
||||
command := []string{"etcd"}
|
||||
command = append(command, getExtraParameters(cfg.Etcd.ExtraArgs, defaultArguments)...)
|
||||
return command
|
||||
}
|
||||
|
||||
// getControllerManagerCommand builds the right controller manager command from the given config object and version
|
||||
func getControllerManagerCommand(cfg *kubeadmapi.MasterConfiguration, k8sVersion *version.Version) []string {
|
||||
defaultArguments := map[string]string{
|
||||
|
@ -255,7 +206,7 @@ func getControllerManagerCommand(cfg *kubeadmapi.MasterConfiguration, k8sVersion
|
|||
}
|
||||
|
||||
command := []string{"kube-controller-manager"}
|
||||
command = append(command, getExtraParameters(cfg.ControllerManagerExtraArgs, defaultArguments)...)
|
||||
command = append(command, staticpodutil.GetExtraParameters(cfg.ControllerManagerExtraArgs, defaultArguments)...)
|
||||
|
||||
if cfg.CloudProvider != "" {
|
||||
command = append(command, "--cloud-provider="+cfg.CloudProvider)
|
||||
|
@ -283,7 +234,7 @@ func getSchedulerCommand(cfg *kubeadmapi.MasterConfiguration) []string {
|
|||
}
|
||||
|
||||
command := []string{"kube-scheduler"}
|
||||
command = append(command, getExtraParameters(cfg.SchedulerExtraArgs, defaultArguments)...)
|
||||
command = append(command, staticpodutil.GetExtraParameters(cfg.SchedulerExtraArgs, defaultArguments)...)
|
||||
return command
|
||||
}
|
||||
|
||||
|
@ -327,19 +278,3 @@ func getAuthzParameters(modes []string) []string {
|
|||
command = append(command, "--authorization-mode="+strings.Join(modes, ","))
|
||||
return command
|
||||
}
|
||||
|
||||
// getExtraParameters builds a list of flag arguments two string-string maps, one with default, base commands and one with overrides
|
||||
func getExtraParameters(overrides map[string]string, defaults map[string]string) []string {
|
||||
var command []string
|
||||
for k, v := range overrides {
|
||||
if len(v) > 0 {
|
||||
command = append(command, fmt.Sprintf("--%s=%s", k, v))
|
||||
}
|
||||
}
|
||||
for k, v := range defaults {
|
||||
if _, overrideExists := overrides[k]; !overrideExists {
|
||||
command = append(command, fmt.Sprintf("--%s=%s", k, v))
|
||||
}
|
||||
}
|
||||
return command
|
||||
}
|
||||
|
|
|
@ -18,20 +18,17 @@ package controlplane
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/apimachinery/pkg/util/yaml"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/pkg/util/version"
|
||||
|
||||
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -39,189 +36,98 @@ const (
|
|||
etcdDataDir = "/var/lib/etcd"
|
||||
)
|
||||
|
||||
func TestWriteStaticPodManifests(t *testing.T) {
|
||||
tmpdir, err := ioutil.TempDir("", "")
|
||||
if err != nil {
|
||||
t.Fatalf("Couldn't create tmpdir")
|
||||
func TestGetStaticPodSpecs(t *testing.T) {
|
||||
|
||||
// Creates a Master Configuration
|
||||
cfg := &kubeadmapi.MasterConfiguration{
|
||||
KubernetesVersion: "v1.7.0",
|
||||
}
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
// set up tmp KubernetesDir for testing
|
||||
kubeadmconstants.KubernetesDir = fmt.Sprintf("%s/etc/kubernetes", tmpdir)
|
||||
defer func() { kubeadmconstants.KubernetesDir = "/etc/kubernetes" }()
|
||||
// Executes GetStaticPodSpecs
|
||||
|
||||
var tests = []struct {
|
||||
cfg *kubeadmapi.MasterConfiguration
|
||||
expectErr bool
|
||||
expectedAPIProbePort int32
|
||||
// TODO: Move the "pkg/util/version".Version object into the internal API instead of always parsing the string
|
||||
k8sVersion, _ := version.ParseSemantic(cfg.KubernetesVersion)
|
||||
|
||||
specs := GetStaticPodSpecs(cfg, k8sVersion)
|
||||
|
||||
var assertions = []struct {
|
||||
staticPodName string
|
||||
}{
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
KubernetesVersion: "v1.7.0",
|
||||
},
|
||||
expectErr: false,
|
||||
staticPodName: kubeadmconstants.KubeAPIServer,
|
||||
},
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
API: kubeadmapi.API{
|
||||
BindPort: 443,
|
||||
},
|
||||
KubernetesVersion: "v1.7.0",
|
||||
},
|
||||
expectErr: false,
|
||||
expectedAPIProbePort: 443,
|
||||
staticPodName: kubeadmconstants.KubeControllerManager,
|
||||
},
|
||||
{
|
||||
staticPodName: kubeadmconstants.KubeScheduler,
|
||||
},
|
||||
}
|
||||
for _, rt := range tests {
|
||||
|
||||
actual := WriteStaticPodManifests(rt.cfg, version.MustParseSemantic(rt.cfg.KubernetesVersion), fmt.Sprintf("%s/etc/kubernetes/manifests", tmpdir))
|
||||
if (actual == nil) && rt.expectErr {
|
||||
t.Error("expected an error from WriteStaticPodManifests but got none")
|
||||
continue
|
||||
}
|
||||
if (actual != nil) && !rt.expectErr {
|
||||
t.Errorf("didn't expect an error from WriteStaticPodManifests but got: %v", err)
|
||||
continue
|
||||
}
|
||||
if rt.expectErr {
|
||||
continue
|
||||
}
|
||||
for _, assertion := range assertions {
|
||||
|
||||
// Below is dead code.
|
||||
if rt.expectedAPIProbePort != 0 {
|
||||
manifest, err := os.Open(filepath.Join(kubeadmconstants.KubernetesDir, kubeadmconstants.ManifestsSubDirName, "kube-apiserver.yaml"))
|
||||
if err != nil {
|
||||
t.Errorf("WriteStaticPodManifests: %v", err)
|
||||
continue
|
||||
}
|
||||
defer manifest.Close()
|
||||
// assert the spec for the staticPodName exists
|
||||
if spec, ok := specs[assertion.staticPodName]; ok {
|
||||
|
||||
var pod v1.Pod
|
||||
d := yaml.NewYAMLOrJSONDecoder(manifest, 4096)
|
||||
if err := d.Decode(&pod); err != nil {
|
||||
t.Error("WriteStaticPodManifests: error decoding manifests/kube-apiserver.yaml into Pod")
|
||||
continue
|
||||
// Assert each specs refers to the right pod
|
||||
if spec.Spec.Containers[0].Name != assertion.staticPodName {
|
||||
t.Errorf("getKubeConfigSpecs spec for %s contains pod %s, expectes %s", assertion.staticPodName, spec.Spec.Containers[0].Name, assertion.staticPodName)
|
||||
}
|
||||
|
||||
// Lots of individual checks as we traverse pointers so we don't panic dereferencing a nil on failure
|
||||
containers := pod.Spec.Containers
|
||||
if containers == nil || len(containers) == 0 {
|
||||
t.Error("WriteStaticPodManifests: wrote an apiserver manifest without any containers")
|
||||
continue
|
||||
}
|
||||
|
||||
probe := containers[0].LivenessProbe
|
||||
if probe == nil {
|
||||
t.Error("WriteStaticPodManifests: wrote an apiserver manifest without a liveness probe")
|
||||
continue
|
||||
}
|
||||
|
||||
httpGET := probe.Handler.HTTPGet
|
||||
if httpGET == nil {
|
||||
t.Error("WriteStaticPodManifests: wrote an apiserver manifest without an HTTP liveness probe")
|
||||
continue
|
||||
}
|
||||
|
||||
port := httpGET.Port.IntVal
|
||||
if rt.expectedAPIProbePort != port {
|
||||
t.Errorf("WriteStaticPodManifests: apiserver pod liveness probe port was: %v, wanted %v", port, rt.expectedAPIProbePort)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("getStaticPodSpecs didn't create spec for %s ", assertion.staticPodName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestComponentResources(t *testing.T) {
|
||||
a := componentResources("250m")
|
||||
if a.Requests == nil {
|
||||
t.Errorf(
|
||||
"failed componentResources, return value was nil",
|
||||
)
|
||||
}
|
||||
}
|
||||
func TestCreateStaticPodFilesAndWrappers(t *testing.T) {
|
||||
|
||||
func TestComponentProbe(t *testing.T) {
|
||||
var tests = []struct {
|
||||
port int
|
||||
path string
|
||||
scheme v1.URIScheme
|
||||
createStaticPodFunction func(outDir string, cfg *kubeadmapi.MasterConfiguration) error
|
||||
expectedFiles []string
|
||||
}{
|
||||
{
|
||||
port: 1,
|
||||
path: "foo",
|
||||
scheme: v1.URISchemeHTTP,
|
||||
{ // CreateInitStaticPodManifestFiles
|
||||
createStaticPodFunction: CreateInitStaticPodManifestFiles,
|
||||
expectedFiles: []string{kubeadmconstants.KubeAPIServer, kubeadmconstants.KubeControllerManager, kubeadmconstants.KubeScheduler},
|
||||
},
|
||||
{
|
||||
port: 2,
|
||||
path: "bar",
|
||||
scheme: v1.URISchemeHTTPS,
|
||||
{ // CreateAPIServerStaticPodManifestFile
|
||||
createStaticPodFunction: CreateAPIServerStaticPodManifestFile,
|
||||
expectedFiles: []string{kubeadmconstants.KubeAPIServer},
|
||||
},
|
||||
}
|
||||
for _, rt := range tests {
|
||||
actual := componentProbe(rt.port, rt.path, rt.scheme)
|
||||
if actual.Handler.HTTPGet.Port != intstr.FromInt(rt.port) {
|
||||
t.Errorf(
|
||||
"failed componentProbe:\n\texpected: %v\n\t actual: %v",
|
||||
rt.port,
|
||||
actual.Handler.HTTPGet.Port,
|
||||
)
|
||||
}
|
||||
if actual.Handler.HTTPGet.Path != rt.path {
|
||||
t.Errorf(
|
||||
"failed componentProbe:\n\texpected: %s\n\t actual: %s",
|
||||
rt.path,
|
||||
actual.Handler.HTTPGet.Path,
|
||||
)
|
||||
}
|
||||
if actual.Handler.HTTPGet.Scheme != rt.scheme {
|
||||
t.Errorf(
|
||||
"failed componentProbe:\n\texpected: %v\n\t actual: %v",
|
||||
rt.scheme,
|
||||
actual.Handler.HTTPGet.Scheme,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestComponentPod(t *testing.T) {
|
||||
var tests = []struct {
|
||||
name string
|
||||
expected v1.Pod
|
||||
}{
|
||||
{
|
||||
name: "foo",
|
||||
expected: v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Pod",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
Namespace: "kube-system",
|
||||
Annotations: map[string]string{"scheduler.alpha.kubernetes.io/critical-pod": ""},
|
||||
Labels: map[string]string{"component": "foo", "tier": "control-plane"},
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{
|
||||
{
|
||||
Name: "foo",
|
||||
},
|
||||
},
|
||||
HostNetwork: true,
|
||||
Volumes: []v1.Volume{},
|
||||
},
|
||||
},
|
||||
{ // CreateControllerManagerStaticPodManifestFile
|
||||
createStaticPodFunction: CreateControllerManagerStaticPodManifestFile,
|
||||
expectedFiles: []string{kubeadmconstants.KubeControllerManager},
|
||||
},
|
||||
{ // CreateSchedulerStaticPodManifestFile
|
||||
createStaticPodFunction: CreateSchedulerStaticPodManifestFile,
|
||||
expectedFiles: []string{kubeadmconstants.KubeScheduler},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
c := v1.Container{Name: rt.name}
|
||||
actual := componentPod(c, []v1.Volume{})
|
||||
if !reflect.DeepEqual(rt.expected, actual) {
|
||||
t.Errorf(
|
||||
"failed componentPod:\n\texpected: %v\n\t actual: %v",
|
||||
rt.expected,
|
||||
actual,
|
||||
)
|
||||
for _, test := range tests {
|
||||
|
||||
// Create temp folder for the test case
|
||||
tmpdir := testutil.SetupTempDir(t)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
// Creates a Master Configuration
|
||||
cfg := &kubeadmapi.MasterConfiguration{
|
||||
KubernetesVersion: "v1.7.0",
|
||||
}
|
||||
|
||||
// Execute createStaticPodFunction
|
||||
manifestPath := filepath.Join(tmpdir, kubeadmconstants.ManifestsSubDirName)
|
||||
err := test.createStaticPodFunction(manifestPath, cfg)
|
||||
if err != nil {
|
||||
t.Errorf("Error executing createStaticPodFunction: %v", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Assert expected files are there
|
||||
testutil.AssertFilesCount(t, manifestPath, len(test.expectedFiles))
|
||||
|
||||
for _, fileName := range test.expectedFiles {
|
||||
testutil.AssertFileExists(t, manifestPath, fileName+".yaml")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -498,62 +404,6 @@ func TestGetControllerManagerCommand(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestGetEtcdCommand(t *testing.T) {
|
||||
var tests = []struct {
|
||||
cfg *kubeadmapi.MasterConfiguration
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
Etcd: kubeadmapi.Etcd{DataDir: "/var/lib/etcd"},
|
||||
},
|
||||
expected: []string{
|
||||
"etcd",
|
||||
"--listen-client-urls=http://127.0.0.1:2379",
|
||||
"--advertise-client-urls=http://127.0.0.1:2379",
|
||||
"--data-dir=/var/lib/etcd",
|
||||
},
|
||||
},
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
Etcd: kubeadmapi.Etcd{
|
||||
DataDir: "/var/lib/etcd",
|
||||
ExtraArgs: map[string]string{
|
||||
"listen-client-urls": "http://10.0.1.10:2379",
|
||||
"advertise-client-urls": "http://10.0.1.10:2379",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{
|
||||
"etcd",
|
||||
"--listen-client-urls=http://10.0.1.10:2379",
|
||||
"--advertise-client-urls=http://10.0.1.10:2379",
|
||||
"--data-dir=/var/lib/etcd",
|
||||
},
|
||||
},
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
Etcd: kubeadmapi.Etcd{DataDir: "/etc/foo"},
|
||||
},
|
||||
expected: []string{
|
||||
"etcd",
|
||||
"--listen-client-urls=http://127.0.0.1:2379",
|
||||
"--advertise-client-urls=http://127.0.0.1:2379",
|
||||
"--data-dir=/etc/foo",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
actual := getEtcdCommand(rt.cfg)
|
||||
sort.Strings(actual)
|
||||
sort.Strings(rt.expected)
|
||||
if !reflect.DeepEqual(actual, rt.expected) {
|
||||
t.Errorf("failed getEtcdCommand:\nexpected:\n%v\nsaw:\n%v", rt.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSchedulerCommand(t *testing.T) {
|
||||
var tests = []struct {
|
||||
cfg *kubeadmapi.MasterConfiguration
|
||||
|
@ -651,50 +501,3 @@ func TestGetAuthzParameters(t *testing.T) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetExtraParameters(t *testing.T) {
|
||||
var tests = []struct {
|
||||
overrides map[string]string
|
||||
defaults map[string]string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
overrides: map[string]string{
|
||||
"admission-control": "NamespaceLifecycle,LimitRanger",
|
||||
},
|
||||
defaults: map[string]string{
|
||||
"admission-control": "NamespaceLifecycle",
|
||||
"insecure-bind-address": "127.0.0.1",
|
||||
"allow-privileged": "true",
|
||||
},
|
||||
expected: []string{
|
||||
"--admission-control=NamespaceLifecycle,LimitRanger",
|
||||
"--insecure-bind-address=127.0.0.1",
|
||||
"--allow-privileged=true",
|
||||
},
|
||||
},
|
||||
{
|
||||
overrides: map[string]string{
|
||||
"admission-control": "NamespaceLifecycle,LimitRanger",
|
||||
},
|
||||
defaults: map[string]string{
|
||||
"insecure-bind-address": "127.0.0.1",
|
||||
"allow-privileged": "true",
|
||||
},
|
||||
expected: []string{
|
||||
"--admission-control=NamespaceLifecycle,LimitRanger",
|
||||
"--insecure-bind-address=127.0.0.1",
|
||||
"--allow-privileged=true",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
actual := getExtraParameters(rt.overrides, rt.defaults)
|
||||
sort.Strings(actual)
|
||||
sort.Strings(rt.expected)
|
||||
if !reflect.DeepEqual(actual, rt.expected) {
|
||||
t.Errorf("failed getExtraParameters:\nexpected:\n%v\nsaw:\n%v", rt.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,11 +26,11 @@ import (
|
|||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
staticpodutil "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod"
|
||||
)
|
||||
|
||||
const (
|
||||
k8sCertsVolumeName = "k8s-certs"
|
||||
etcdVolumeName = "etcd"
|
||||
caCertsVolumeName = "ca-certs"
|
||||
caCertsVolumePath = "/etc/ssl/certs"
|
||||
caCertsPkiVolumeName = "ca-certs-etc-pki"
|
||||
|
@ -98,8 +98,8 @@ func newControlPlaneHostPathMounts() controlPlaneHostPathMounts {
|
|||
}
|
||||
|
||||
func (c *controlPlaneHostPathMounts) NewHostPathMount(component, mountName, hostPath, containerPath string, readOnly bool) {
|
||||
c.volumes[component] = append(c.volumes[component], newVolume(mountName, hostPath))
|
||||
c.volumeMounts[component] = append(c.volumeMounts[component], newVolumeMount(mountName, containerPath, readOnly))
|
||||
c.volumes[component] = append(c.volumes[component], staticpodutil.NewVolume(mountName, hostPath))
|
||||
c.volumeMounts[component] = append(c.volumeMounts[component], staticpodutil.NewVolumeMount(mountName, containerPath, readOnly))
|
||||
}
|
||||
|
||||
func (c *controlPlaneHostPathMounts) AddHostPathMounts(component string, vols []v1.Volume, volMounts []v1.VolumeMount) {
|
||||
|
@ -115,25 +115,6 @@ func (c *controlPlaneHostPathMounts) GetVolumeMounts(component string) []v1.Volu
|
|||
return c.volumeMounts[component]
|
||||
}
|
||||
|
||||
// newVolume creates a v1.Volume with a hostPath mount to the specified location
|
||||
func newVolume(name, path string) v1.Volume {
|
||||
return v1.Volume{
|
||||
Name: name,
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{Path: path},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// newVolumeMount creates a v1.VolumeMount to the specified location
|
||||
func newVolumeMount(name, path string, readOnly bool) v1.VolumeMount {
|
||||
return v1.VolumeMount{
|
||||
Name: name,
|
||||
MountPath: path,
|
||||
ReadOnly: readOnly,
|
||||
}
|
||||
}
|
||||
|
||||
// getEtcdCertVolumes returns the volumes/volumemounts needed for talking to an external etcd cluster
|
||||
func getEtcdCertVolumes(etcdCfg kubeadmapi.Etcd) ([]v1.Volume, []v1.VolumeMount) {
|
||||
certPaths := []string{etcdCfg.CAFile, etcdCfg.CertFile, etcdCfg.KeyFile}
|
||||
|
@ -166,8 +147,8 @@ func getEtcdCertVolumes(etcdCfg kubeadmapi.Etcd) ([]v1.Volume, []v1.VolumeMount)
|
|||
volumeMounts := []v1.VolumeMount{}
|
||||
for i, certDir := range certDirs.List() {
|
||||
name := fmt.Sprintf("etcd-certs-%d", i)
|
||||
volumes = append(volumes, newVolume(name, certDir))
|
||||
volumeMounts = append(volumeMounts, newVolumeMount(name, certDir, true))
|
||||
volumes = append(volumes, staticpodutil.NewVolume(name, certDir))
|
||||
volumeMounts = append(volumeMounts, staticpodutil.NewVolumeMount(name, certDir, true))
|
||||
}
|
||||
return volumes, volumeMounts
|
||||
}
|
||||
|
|
|
@ -28,77 +28,6 @@ import (
|
|||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
)
|
||||
|
||||
func TestNewVolume(t *testing.T) {
|
||||
var tests = []struct {
|
||||
name string
|
||||
path string
|
||||
expected v1.Volume
|
||||
}{
|
||||
{
|
||||
name: "foo",
|
||||
path: "/etc/foo",
|
||||
expected: v1.Volume{
|
||||
Name: "foo",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{Path: "/etc/foo"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
actual := newVolume(rt.name, rt.path)
|
||||
if !reflect.DeepEqual(actual, rt.expected) {
|
||||
t.Errorf(
|
||||
"failed newVolume:\n\texpected: %v\n\t actual: %v",
|
||||
rt.expected,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewVolumeMount(t *testing.T) {
|
||||
var tests = []struct {
|
||||
name string
|
||||
path string
|
||||
ro bool
|
||||
expected v1.VolumeMount
|
||||
}{
|
||||
{
|
||||
name: "foo",
|
||||
path: "/etc/foo",
|
||||
ro: false,
|
||||
expected: v1.VolumeMount{
|
||||
Name: "foo",
|
||||
MountPath: "/etc/foo",
|
||||
ReadOnly: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bar",
|
||||
path: "/etc/foo/bar",
|
||||
ro: true,
|
||||
expected: v1.VolumeMount{
|
||||
Name: "bar",
|
||||
MountPath: "/etc/foo/bar",
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
actual := newVolumeMount(rt.name, rt.path, rt.ro)
|
||||
if !reflect.DeepEqual(actual, rt.expected) {
|
||||
t.Errorf(
|
||||
"failed newVolumeMount:\n\texpected: %v\n\t actual: %v",
|
||||
rt.expected,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetEtcdCertVolumes(t *testing.T) {
|
||||
var tests = []struct {
|
||||
ca, cert, key string
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["local_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/test:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["local.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/apis/kubeadm:go_default_library",
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//cmd/kubeadm/app/images:go_default_library",
|
||||
"//cmd/kubeadm/app/util/staticpod:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
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 etcd
|
||||
|
||||
import (
|
||||
"k8s.io/api/core/v1"
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/images"
|
||||
staticpodutil "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod"
|
||||
)
|
||||
|
||||
const (
|
||||
etcdVolumeName = "etcd"
|
||||
)
|
||||
|
||||
// CreateLocalEtcdStaticPodManifestFile will write local etcd static pod manifest file.
|
||||
func CreateLocalEtcdStaticPodManifestFile(manifestDir string, cfg *kubeadmapi.MasterConfiguration) error {
|
||||
|
||||
// gets etcd StaticPodSpec, actualized for the current MasterConfiguration
|
||||
spec := GetEtcdPodSpec(cfg)
|
||||
|
||||
// writes etcd StaticPod to disk
|
||||
return staticpodutil.WriteStaticPodToDisk(kubeadmconstants.Etcd, manifestDir, spec)
|
||||
}
|
||||
|
||||
// GetEtcdPodSpec returns the etcd static Pod actualized to the context of the current MasterConfiguration
|
||||
// NB. GetEtcdPodSpec methods holds the information about how kubeadm creates etcd static pod mainfests.
|
||||
func GetEtcdPodSpec(cfg *kubeadmapi.MasterConfiguration) v1.Pod {
|
||||
return staticpodutil.ComponentPod(v1.Container{
|
||||
Name: kubeadmconstants.Etcd,
|
||||
Command: getEtcdCommand(cfg),
|
||||
Image: images.GetCoreImage(kubeadmconstants.Etcd, cfg.ImageRepository, "", cfg.Etcd.Image),
|
||||
// Mount the etcd datadir path read-write so etcd can store data in a more persistent manner
|
||||
VolumeMounts: []v1.VolumeMount{staticpodutil.NewVolumeMount(etcdVolumeName, cfg.Etcd.DataDir, false)},
|
||||
LivenessProbe: staticpodutil.ComponentProbe(2379, "/health", v1.URISchemeHTTP),
|
||||
}, []v1.Volume{staticpodutil.NewVolume(etcdVolumeName, cfg.Etcd.DataDir)})
|
||||
}
|
||||
|
||||
// getEtcdCommand builds the right etcd command from the given config object
|
||||
func getEtcdCommand(cfg *kubeadmapi.MasterConfiguration) []string {
|
||||
defaultArguments := map[string]string{
|
||||
"listen-client-urls": "http://127.0.0.1:2379",
|
||||
"advertise-client-urls": "http://127.0.0.1:2379",
|
||||
"data-dir": cfg.Etcd.DataDir,
|
||||
}
|
||||
|
||||
command := []string{"etcd"}
|
||||
command = append(command, staticpodutil.GetExtraParameters(cfg.Etcd.ExtraArgs, defaultArguments)...)
|
||||
return command
|
||||
}
|
|
@ -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 etcd
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
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 TestGetEtcdPodSpec(t *testing.T) {
|
||||
|
||||
// Creates a Master Configuration
|
||||
cfg := &kubeadmapi.MasterConfiguration{
|
||||
KubernetesVersion: "v1.7.0",
|
||||
}
|
||||
|
||||
// Executes GetEtcdPodSpec
|
||||
spec := GetEtcdPodSpec(cfg)
|
||||
|
||||
// Assert each specs refers to the right pod
|
||||
if spec.Spec.Containers[0].Name != kubeadmconstants.Etcd {
|
||||
t.Errorf("getKubeConfigSpecs spec for etcd contains pod %s, expectes %s", spec.Spec.Containers[0].Name, kubeadmconstants.Etcd)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateLocalEtcdStaticPodManifestFile(t *testing.T) {
|
||||
|
||||
// Create temp folder for the test case
|
||||
tmpdir := testutil.SetupTempDir(t)
|
||||
defer os.RemoveAll(tmpdir)
|
||||
|
||||
// Creates a Master Configuration
|
||||
cfg := &kubeadmapi.MasterConfiguration{
|
||||
KubernetesVersion: "v1.7.0",
|
||||
}
|
||||
|
||||
// Execute createStaticPodFunction
|
||||
manifestPath := filepath.Join(tmpdir, kubeadmconstants.ManifestsSubDirName)
|
||||
err := CreateLocalEtcdStaticPodManifestFile(manifestPath, cfg)
|
||||
if err != nil {
|
||||
t.Errorf("Error executing CreateEtcdStaticPodManifestFile: %v", err)
|
||||
}
|
||||
|
||||
// Assert expected files are there
|
||||
testutil.AssertFilesCount(t, manifestPath, 1)
|
||||
testutil.AssertFileExists(t, manifestPath, kubeadmconstants.Etcd+".yaml")
|
||||
}
|
||||
|
||||
func TestGetEtcdCommand(t *testing.T) {
|
||||
var tests = []struct {
|
||||
cfg *kubeadmapi.MasterConfiguration
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
Etcd: kubeadmapi.Etcd{DataDir: "/var/lib/etcd"},
|
||||
},
|
||||
expected: []string{
|
||||
"etcd",
|
||||
"--listen-client-urls=http://127.0.0.1:2379",
|
||||
"--advertise-client-urls=http://127.0.0.1:2379",
|
||||
"--data-dir=/var/lib/etcd",
|
||||
},
|
||||
},
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
Etcd: kubeadmapi.Etcd{
|
||||
DataDir: "/var/lib/etcd",
|
||||
ExtraArgs: map[string]string{
|
||||
"listen-client-urls": "http://10.0.1.10:2379",
|
||||
"advertise-client-urls": "http://10.0.1.10:2379",
|
||||
},
|
||||
},
|
||||
},
|
||||
expected: []string{
|
||||
"etcd",
|
||||
"--listen-client-urls=http://10.0.1.10:2379",
|
||||
"--advertise-client-urls=http://10.0.1.10:2379",
|
||||
"--data-dir=/var/lib/etcd",
|
||||
},
|
||||
},
|
||||
{
|
||||
cfg: &kubeadmapi.MasterConfiguration{
|
||||
Etcd: kubeadmapi.Etcd{DataDir: "/etc/foo"},
|
||||
},
|
||||
expected: []string{
|
||||
"etcd",
|
||||
"--listen-client-urls=http://127.0.0.1:2379",
|
||||
"--advertise-client-urls=http://127.0.0.1:2379",
|
||||
"--data-dir=/etc/foo",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
actual := getEtcdCommand(rt.cfg)
|
||||
sort.Strings(actual)
|
||||
sort.Strings(rt.expected)
|
||||
if !reflect.DeepEqual(actual, rt.expected) {
|
||||
t.Errorf("failed getEtcdCommand:\nexpected:\n%v\nsaw:\n%v", rt.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -52,6 +52,7 @@ filegroup(
|
|||
"//cmd/kubeadm/app/util/config: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"],
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["utils_test.go"],
|
||||
library = ":go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["utils.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//cmd/kubeadm/app/constants:go_default_library",
|
||||
"//pkg/kubelet/types:go_default_library",
|
||||
"//vendor/github.com/ghodss/yaml:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/resource:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/intstr:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
|
@ -0,0 +1,137 @@
|
|||
/*
|
||||
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 staticpod
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
|
||||
"github.com/ghodss/yaml"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
kubetypes "k8s.io/kubernetes/pkg/kubelet/types"
|
||||
)
|
||||
|
||||
// ComponentPod returns a Pod object from the container and volume specifications
|
||||
func ComponentPod(container v1.Container, volumes []v1.Volume) v1.Pod {
|
||||
return v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
APIVersion: "v1",
|
||||
Kind: "Pod",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: container.Name,
|
||||
Namespace: metav1.NamespaceSystem,
|
||||
Annotations: map[string]string{kubetypes.CriticalPodAnnotationKey: ""},
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
Containers: []v1.Container{container},
|
||||
HostNetwork: true,
|
||||
Volumes: volumes,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ComponentResources returns the v1.ResourceRequirements object needed for allocating a specified amount of the CPU
|
||||
func ComponentResources(cpu string) v1.ResourceRequirements {
|
||||
return v1.ResourceRequirements{
|
||||
Requests: v1.ResourceList{
|
||||
v1.ResourceName(v1.ResourceCPU): resource.MustParse(cpu),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ComponentProbe is a helper function building a ready v1.Probe object from some simple parameters
|
||||
func ComponentProbe(port int, path string, scheme v1.URIScheme) *v1.Probe {
|
||||
return &v1.Probe{
|
||||
Handler: v1.Handler{
|
||||
HTTPGet: &v1.HTTPGetAction{
|
||||
// Host has to be set to "127.0.0.1" here due to that our static Pods are on the host's network
|
||||
Host: "127.0.0.1",
|
||||
Path: path,
|
||||
Port: intstr.FromInt(port),
|
||||
Scheme: scheme,
|
||||
},
|
||||
},
|
||||
InitialDelaySeconds: 15,
|
||||
TimeoutSeconds: 15,
|
||||
FailureThreshold: 8,
|
||||
}
|
||||
}
|
||||
|
||||
// NewVolume creates a v1.Volume with a hostPath mount to the specified location
|
||||
func NewVolume(name, path string) v1.Volume {
|
||||
return v1.Volume{
|
||||
Name: name,
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{Path: path},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// NewVolumeMount creates a v1.VolumeMount to the specified location
|
||||
func NewVolumeMount(name, path string, readOnly bool) v1.VolumeMount {
|
||||
return v1.VolumeMount{
|
||||
Name: name,
|
||||
MountPath: path,
|
||||
ReadOnly: readOnly,
|
||||
}
|
||||
}
|
||||
|
||||
// GetExtraParameters builds a list of flag arguments two string-string maps, one with default, base commands and one with overrides
|
||||
func GetExtraParameters(overrides map[string]string, defaults map[string]string) []string {
|
||||
var command []string
|
||||
for k, v := range overrides {
|
||||
if len(v) > 0 {
|
||||
command = append(command, fmt.Sprintf("--%s=%s", k, v))
|
||||
}
|
||||
}
|
||||
for k, v := range defaults {
|
||||
if _, overrideExists := overrides[k]; !overrideExists {
|
||||
command = append(command, fmt.Sprintf("--%s=%s", k, v))
|
||||
}
|
||||
}
|
||||
return command
|
||||
}
|
||||
|
||||
// WriteStaticPodToDisk writes a static pod file to disk
|
||||
func WriteStaticPodToDisk(componentName, manifestDir string, pod v1.Pod) error {
|
||||
|
||||
// creates target folder if not already exists
|
||||
if err := os.MkdirAll(manifestDir, 0700); err != nil {
|
||||
return fmt.Errorf("failed to create directory %q: %v", manifestDir, err)
|
||||
}
|
||||
|
||||
// writes the pod to disk
|
||||
serialized, err := yaml.Marshal(pod)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal manifest for %q to YAML: %v", componentName, err)
|
||||
}
|
||||
|
||||
filename := kubeadmconstants.GetStaticPodFilepath(componentName, manifestDir)
|
||||
|
||||
if err := ioutil.WriteFile(filename, serialized, 0700); err != nil {
|
||||
return fmt.Errorf("failed to write static pod manifest file for %q (%q): %v", componentName, filename, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,219 @@
|
|||
/*
|
||||
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 staticpod
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
)
|
||||
|
||||
func TestComponentResources(t *testing.T) {
|
||||
a := ComponentResources("250m")
|
||||
if a.Requests == nil {
|
||||
t.Errorf(
|
||||
"failed componentResources, return value was nil",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
func TestComponentProbe(t *testing.T) {
|
||||
var tests = []struct {
|
||||
port int
|
||||
path string
|
||||
scheme v1.URIScheme
|
||||
}{
|
||||
{
|
||||
port: 1,
|
||||
path: "foo",
|
||||
scheme: v1.URISchemeHTTP,
|
||||
},
|
||||
{
|
||||
port: 2,
|
||||
path: "bar",
|
||||
scheme: v1.URISchemeHTTPS,
|
||||
},
|
||||
}
|
||||
for _, rt := range tests {
|
||||
actual := ComponentProbe(rt.port, rt.path, rt.scheme)
|
||||
if actual.Handler.HTTPGet.Port != intstr.FromInt(rt.port) {
|
||||
t.Errorf(
|
||||
"failed componentProbe:\n\texpected: %v\n\t actual: %v",
|
||||
rt.port,
|
||||
actual.Handler.HTTPGet.Port,
|
||||
)
|
||||
}
|
||||
if actual.Handler.HTTPGet.Path != rt.path {
|
||||
t.Errorf(
|
||||
"failed componentProbe:\n\texpected: %s\n\t actual: %s",
|
||||
rt.path,
|
||||
actual.Handler.HTTPGet.Path,
|
||||
)
|
||||
}
|
||||
if actual.Handler.HTTPGet.Scheme != rt.scheme {
|
||||
t.Errorf(
|
||||
"failed componentProbe:\n\texpected: %v\n\t actual: %v",
|
||||
rt.scheme,
|
||||
actual.Handler.HTTPGet.Scheme,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestComponentPod(t *testing.T) {
|
||||
var tests = []struct {
|
||||
n string
|
||||
}{
|
||||
{
|
||||
n: "foo",
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
c := v1.Container{Name: rt.n}
|
||||
v := []v1.Volume{}
|
||||
actual := ComponentPod(c, v)
|
||||
if actual.ObjectMeta.Name != rt.n {
|
||||
t.Errorf(
|
||||
"failed componentPod:\n\texpected: %s\n\t actual: %s",
|
||||
rt.n,
|
||||
actual.ObjectMeta.Name,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewVolume(t *testing.T) {
|
||||
var tests = []struct {
|
||||
name string
|
||||
path string
|
||||
expected v1.Volume
|
||||
}{
|
||||
{
|
||||
name: "foo",
|
||||
path: "/etc/foo",
|
||||
expected: v1.Volume{
|
||||
Name: "foo",
|
||||
VolumeSource: v1.VolumeSource{
|
||||
HostPath: &v1.HostPathVolumeSource{Path: "/etc/foo"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
actual := NewVolume(rt.name, rt.path)
|
||||
if !reflect.DeepEqual(actual, rt.expected) {
|
||||
t.Errorf(
|
||||
"failed newVolume:\n\texpected: %v\n\t actual: %v",
|
||||
rt.expected,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewVolumeMount(t *testing.T) {
|
||||
var tests = []struct {
|
||||
name string
|
||||
path string
|
||||
ro bool
|
||||
expected v1.VolumeMount
|
||||
}{
|
||||
{
|
||||
name: "foo",
|
||||
path: "/etc/foo",
|
||||
ro: false,
|
||||
expected: v1.VolumeMount{
|
||||
Name: "foo",
|
||||
MountPath: "/etc/foo",
|
||||
ReadOnly: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bar",
|
||||
path: "/etc/foo/bar",
|
||||
ro: true,
|
||||
expected: v1.VolumeMount{
|
||||
Name: "bar",
|
||||
MountPath: "/etc/foo/bar",
|
||||
ReadOnly: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
actual := NewVolumeMount(rt.name, rt.path, rt.ro)
|
||||
if !reflect.DeepEqual(actual, rt.expected) {
|
||||
t.Errorf(
|
||||
"failed newVolumeMount:\n\texpected: %v\n\t actual: %v",
|
||||
rt.expected,
|
||||
actual,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetExtraParameters(t *testing.T) {
|
||||
var tests = []struct {
|
||||
overrides map[string]string
|
||||
defaults map[string]string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
overrides: map[string]string{
|
||||
"admission-control": "NamespaceLifecycle,LimitRanger",
|
||||
},
|
||||
defaults: map[string]string{
|
||||
"admission-control": "NamespaceLifecycle",
|
||||
"insecure-bind-address": "127.0.0.1",
|
||||
"allow-privileged": "true",
|
||||
},
|
||||
expected: []string{
|
||||
"--admission-control=NamespaceLifecycle,LimitRanger",
|
||||
"--insecure-bind-address=127.0.0.1",
|
||||
"--allow-privileged=true",
|
||||
},
|
||||
},
|
||||
{
|
||||
overrides: map[string]string{
|
||||
"admission-control": "NamespaceLifecycle,LimitRanger",
|
||||
},
|
||||
defaults: map[string]string{
|
||||
"insecure-bind-address": "127.0.0.1",
|
||||
"allow-privileged": "true",
|
||||
},
|
||||
expected: []string{
|
||||
"--admission-control=NamespaceLifecycle,LimitRanger",
|
||||
"--insecure-bind-address=127.0.0.1",
|
||||
"--allow-privileged=true",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, rt := range tests {
|
||||
actual := GetExtraParameters(rt.overrides, rt.defaults)
|
||||
sort.Strings(actual)
|
||||
sort.Strings(rt.expected)
|
||||
if !reflect.DeepEqual(actual, rt.expected) {
|
||||
t.Errorf("failed getExtraParameters:\nexpected:\n%v\nsaw:\n%v", rt.expected, actual)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue