mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-05 02:09:56 +00:00
Merge pull request #129620 from neolit123/1.33-update-all-cp-components-check
kubeadm: graduate WaitForAllControlPlaneComponents to Beta
This commit is contained in:
commit
569d1896e6
@ -25,14 +25,17 @@ import (
|
||||
"github.com/lithammer/dedent"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
kubeletconfig "k8s.io/kubelet/config/v1beta1"
|
||||
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/componentconfigs"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||
dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun"
|
||||
staticpodutil "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -122,10 +125,15 @@ func runWaitControlPlanePhase(c workflow.RunData) error {
|
||||
return handleError(err)
|
||||
}
|
||||
|
||||
var podMap map[string]*v1.Pod
|
||||
waiter.SetTimeout(data.Cfg().Timeouts.ControlPlaneComponentHealthCheck.Duration)
|
||||
if features.Enabled(data.Cfg().ClusterConfiguration.FeatureGates, features.WaitForAllControlPlaneComponents) {
|
||||
err = waiter.WaitForControlPlaneComponents(&data.Cfg().ClusterConfiguration,
|
||||
podMap, err = staticpodutil.ReadMultipleStaticPodsFromDisk(data.ManifestDir(),
|
||||
constants.ControlPlaneComponents...)
|
||||
if err == nil {
|
||||
err = waiter.WaitForControlPlaneComponents(podMap,
|
||||
data.Cfg().LocalAPIEndpoint.AdvertiseAddress)
|
||||
}
|
||||
} else {
|
||||
err = waiter.WaitForAPI()
|
||||
}
|
||||
|
@ -26,9 +26,11 @@ import (
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/phases/workflow"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/features"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||
dryrunutil "k8s.io/kubernetes/cmd/kubeadm/app/util/dryrun"
|
||||
staticpodutil "k8s.io/kubernetes/cmd/kubeadm/app/util/staticpod"
|
||||
)
|
||||
|
||||
// NewWaitControlPlanePhase is a hidden phase that runs after the control-plane and etcd phases
|
||||
@ -71,7 +73,12 @@ func runWaitControlPlanePhase(c workflow.RunData) error {
|
||||
}
|
||||
|
||||
waiter.SetTimeout(data.Cfg().Timeouts.ControlPlaneComponentHealthCheck.Duration)
|
||||
if err := waiter.WaitForControlPlaneComponents(&initCfg.ClusterConfiguration,
|
||||
pods, err := staticpodutil.ReadMultipleStaticPodsFromDisk(data.ManifestDir(),
|
||||
constants.ControlPlaneComponents...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = waiter.WaitForControlPlaneComponents(pods,
|
||||
data.Cfg().ControlPlane.LocalAPIEndpoint.AdvertiseAddress); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ var InitFeatureGates = FeatureList{
|
||||
DeprecationMessage: "Deprecated in favor of the core kubelet feature UserNamespacesSupport which is beta since 1.30." +
|
||||
" Once UserNamespacesSupport graduates to GA, kubeadm will start using it and RootlessControlPlane will be removed.",
|
||||
},
|
||||
WaitForAllControlPlaneComponents: {FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Alpha}},
|
||||
WaitForAllControlPlaneComponents: {FeatureSpec: featuregate.FeatureSpec{Default: true, PreRelease: featuregate.Beta}},
|
||||
ControlPlaneKubeletLocalMode: {FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Alpha}},
|
||||
NodeLocalCRISocket: {FeatureSpec: featuregate.FeatureSpec{Default: false, PreRelease: featuregate.Alpha}},
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
"go.etcd.io/etcd/client/pkg/v3/transport"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
certutil "k8s.io/client-go/util/cert"
|
||||
|
||||
@ -99,7 +100,7 @@ func NewFakeStaticPodWaiter(errsToReturn map[string]error) apiclient.Waiter {
|
||||
}
|
||||
|
||||
// WaitForControlPlaneComponents just returns a dummy nil, to indicate that the program should just proceed
|
||||
func (w *fakeWaiter) WaitForControlPlaneComponents(cfg *kubeadmapi.ClusterConfiguration, apiServerAddress string) error {
|
||||
func (w *fakeWaiter) WaitForControlPlaneComponents(podsMap map[string]*v1.Pod, apiServerAddress string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@ import (
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
@ -34,14 +35,27 @@ import (
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
)
|
||||
|
||||
const (
|
||||
// TODO: switch to /livez once all components support it
|
||||
// and delete the endpointHealthz constant.
|
||||
// https://github.com/kubernetes/kubernetes/issues/118158
|
||||
endpointHealthz = "healthz"
|
||||
endpointLivez = "livez"
|
||||
|
||||
argPort = "secure-port"
|
||||
argBindAddress = "bind-address"
|
||||
// By default, for kube-api-server, kubeadm does not apply a --bind-address flag.
|
||||
// Check --advertise-address instead.
|
||||
argAdvertiseAddress = "advertise-address"
|
||||
)
|
||||
|
||||
// Waiter is an interface for waiting for criteria in Kubernetes to happen
|
||||
type Waiter interface {
|
||||
// WaitForControlPlaneComponents waits for all control plane components to be ready.
|
||||
WaitForControlPlaneComponents(cfg *kubeadmapi.ClusterConfiguration, apiServerAddress string) error
|
||||
WaitForControlPlaneComponents(podMap map[string]*v1.Pod, apiServerAddress string) error
|
||||
// WaitForAPI waits for the API Server's /healthz endpoint to become "ok"
|
||||
// TODO: remove WaitForAPI once WaitForAllControlPlaneComponents goes GA:
|
||||
// https://github.com/kubernetes/kubeadm/issues/2907
|
||||
@ -77,80 +91,147 @@ func NewKubeWaiter(client clientset.Interface, timeout time.Duration, writer io.
|
||||
}
|
||||
}
|
||||
|
||||
// controlPlaneComponent holds a component name and an URL
|
||||
// on which to perform health checks.
|
||||
type controlPlaneComponent struct {
|
||||
name string
|
||||
url string
|
||||
}
|
||||
|
||||
const (
|
||||
// TODO: switch to /livez once all components support it
|
||||
// and delete the endpointHealthz constant.
|
||||
// https://github.com/kubernetes/kubernetes/issues/118158
|
||||
endpointHealthz = "healthz"
|
||||
endpointLivez = "livez"
|
||||
)
|
||||
// getControlPlaneComponentAddressAndPort parses the command in a static Pod
|
||||
// container and extracts the values of the given args.
|
||||
func getControlPlaneComponentAddressAndPort(pod *v1.Pod, name string, args []string) ([]string, error) {
|
||||
var (
|
||||
values = make([]string, len(args))
|
||||
container *v1.Container
|
||||
)
|
||||
|
||||
// getControlPlaneComponents takes a ClusterConfiguration and returns a slice of
|
||||
// control plane components and their health check URLs.
|
||||
func getControlPlaneComponents(cfg *kubeadmapi.ClusterConfiguration, defaultAddressAPIServer string) []controlPlaneComponent {
|
||||
const (
|
||||
portArg = "secure-port"
|
||||
bindAddressArg = "bind-address"
|
||||
// By default, for kube-api-server, kubeadm does not apply a --bind-address flag.
|
||||
// Check --advertise-address instead, which can override the defaultAddressAPIServer value.
|
||||
advertiseAddressArg = "advertise-address"
|
||||
if pod == nil {
|
||||
return values, errors.Errorf("got nil Pod for component %q", name)
|
||||
}
|
||||
|
||||
for i, c := range pod.Spec.Containers {
|
||||
if len(c.Command) == 0 {
|
||||
continue
|
||||
}
|
||||
if c.Command[0] == name {
|
||||
container = &pod.Spec.Containers[i]
|
||||
break
|
||||
}
|
||||
}
|
||||
if container == nil {
|
||||
return values, errors.Errorf("the Pod has no container command starting with %q", name)
|
||||
}
|
||||
|
||||
for _, line := range container.Command {
|
||||
for i, arg := range args {
|
||||
line = strings.TrimSpace(line)
|
||||
if !strings.HasPrefix(line, "--"+arg) && !strings.HasPrefix(line, "-"+arg) {
|
||||
continue
|
||||
}
|
||||
_, value, found := strings.Cut(line, "=")
|
||||
if !found {
|
||||
_, value, _ = strings.Cut(line, " ")
|
||||
}
|
||||
values[i] = value
|
||||
}
|
||||
}
|
||||
return values, nil
|
||||
}
|
||||
|
||||
// getControlPlaneComponents reads the static Pods of control plane components
|
||||
// and returns a slice of 'controlPlaneComponent'.
|
||||
func getControlPlaneComponents(podMap map[string]*v1.Pod, addressAPIServer string) ([]controlPlaneComponent, error) {
|
||||
var (
|
||||
// By default kubeadm deploys the kube-controller-manager and kube-scheduler
|
||||
// with --bind-address=127.0.0.1. This should match get{Scheduler|ControllerManager}Command().
|
||||
defaultAddressKCM = "127.0.0.1"
|
||||
defaultAddressScheduler = "127.0.0.1"
|
||||
)
|
||||
addressKCM = "127.0.0.1"
|
||||
addressScheduler = "127.0.0.1"
|
||||
|
||||
portAPIServer, idx := kubeadmapi.GetArgValue(cfg.APIServer.ExtraArgs, portArg, -1)
|
||||
if idx == -1 {
|
||||
portAPIServer = fmt.Sprintf("%d", constants.KubeAPIServerPort)
|
||||
}
|
||||
portKCM, idx := kubeadmapi.GetArgValue(cfg.ControllerManager.ExtraArgs, portArg, -1)
|
||||
if idx == -1 {
|
||||
portKCM = fmt.Sprintf("%d", constants.KubeControllerManagerPort)
|
||||
}
|
||||
portScheduler, idx := kubeadmapi.GetArgValue(cfg.Scheduler.ExtraArgs, portArg, -1)
|
||||
if idx == -1 {
|
||||
portScheduler = fmt.Sprintf("%d", constants.KubeSchedulerPort)
|
||||
}
|
||||
|
||||
addressAPIServer, idx := kubeadmapi.GetArgValue(cfg.APIServer.ExtraArgs, advertiseAddressArg, -1)
|
||||
if idx == -1 {
|
||||
addressAPIServer = defaultAddressAPIServer
|
||||
}
|
||||
addressKCM, idx := kubeadmapi.GetArgValue(cfg.ControllerManager.ExtraArgs, bindAddressArg, -1)
|
||||
if idx == -1 {
|
||||
addressKCM = defaultAddressKCM
|
||||
}
|
||||
addressScheduler, idx := kubeadmapi.GetArgValue(cfg.Scheduler.ExtraArgs, bindAddressArg, -1)
|
||||
if idx == -1 {
|
||||
addressScheduler = defaultAddressScheduler
|
||||
}
|
||||
|
||||
getURL := func(address, port, endpoint string) string {
|
||||
return fmt.Sprintf(
|
||||
"https://%s/%s",
|
||||
net.JoinHostPort(address, port),
|
||||
endpoint,
|
||||
errs []error
|
||||
result []controlPlaneComponent
|
||||
)
|
||||
|
||||
type componentConfig struct {
|
||||
name string
|
||||
podKey string
|
||||
args []string
|
||||
defaultAddr string
|
||||
defaultPort string
|
||||
endpoint string
|
||||
}
|
||||
return []controlPlaneComponent{
|
||||
{name: "kube-apiserver", url: getURL(addressAPIServer, portAPIServer, endpointLivez)},
|
||||
{name: "kube-controller-manager", url: getURL(addressKCM, portKCM, endpointHealthz)},
|
||||
{name: "kube-scheduler", url: getURL(addressScheduler, portScheduler, endpointLivez)},
|
||||
|
||||
components := []componentConfig{
|
||||
{
|
||||
name: "kube-apiserver",
|
||||
podKey: constants.KubeAPIServer,
|
||||
args: []string{argAdvertiseAddress, argPort},
|
||||
defaultAddr: addressAPIServer,
|
||||
defaultPort: portAPIServer,
|
||||
endpoint: endpointLivez,
|
||||
},
|
||||
{
|
||||
name: "kube-controller-manager",
|
||||
podKey: constants.KubeControllerManager,
|
||||
args: []string{argBindAddress, argPort},
|
||||
defaultAddr: addressKCM,
|
||||
defaultPort: portKCM,
|
||||
endpoint: endpointHealthz,
|
||||
},
|
||||
{
|
||||
name: "kube-scheduler",
|
||||
podKey: constants.KubeScheduler,
|
||||
args: []string{argBindAddress, argPort},
|
||||
defaultAddr: addressScheduler,
|
||||
defaultPort: portScheduler,
|
||||
endpoint: endpointLivez,
|
||||
},
|
||||
}
|
||||
|
||||
for _, component := range components {
|
||||
address, port := component.defaultAddr, component.defaultPort
|
||||
|
||||
values, err := getControlPlaneComponentAddressAndPort(
|
||||
podMap[component.podKey],
|
||||
component.podKey,
|
||||
component.args,
|
||||
)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
}
|
||||
|
||||
if len(values[0]) != 0 {
|
||||
address = values[0]
|
||||
}
|
||||
if len(values[1]) != 0 {
|
||||
port = values[1]
|
||||
}
|
||||
|
||||
result = append(result, controlPlaneComponent{
|
||||
name: component.name,
|
||||
url: fmt.Sprintf("https://%s/%s", net.JoinHostPort(address, port), component.endpoint),
|
||||
})
|
||||
}
|
||||
|
||||
if len(errs) > 0 {
|
||||
return nil, utilerrors.NewAggregate(errs)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// WaitForControlPlaneComponents waits for all control plane components to report "ok".
|
||||
func (w *KubeWaiter) WaitForControlPlaneComponents(cfg *kubeadmapi.ClusterConfiguration, apiSeverAddress string) error {
|
||||
func (w *KubeWaiter) WaitForControlPlaneComponents(podMap map[string]*v1.Pod, apiSeverAddress string) error {
|
||||
fmt.Printf("[control-plane-check] Waiting for healthy control plane components."+
|
||||
" This can take up to %v\n", w.timeout)
|
||||
|
||||
components := getControlPlaneComponents(cfg, apiSeverAddress)
|
||||
components, err := getControlPlaneComponents(podMap, apiSeverAddress)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "could not parse the address and port of all control plane components")
|
||||
}
|
||||
|
||||
var errs []error
|
||||
errChan := make(chan error, len(components))
|
||||
|
@ -21,38 +21,56 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
v1 "k8s.io/api/core/v1"
|
||||
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
)
|
||||
|
||||
func TestGetControlPlaneComponents(t *testing.T) {
|
||||
testcases := []struct {
|
||||
getTestPod := func(command []string) *v1.Pod {
|
||||
pod := &v1.Pod{
|
||||
Spec: v1.PodSpec{},
|
||||
}
|
||||
if command != nil {
|
||||
pod.Spec.Containers = []v1.Container{{}}
|
||||
if len(command) > 0 {
|
||||
pod.Spec.Containers[0].Command = command
|
||||
}
|
||||
}
|
||||
return pod
|
||||
}
|
||||
testCases := []struct {
|
||||
name string
|
||||
cfg *kubeadmapi.ClusterConfiguration
|
||||
setup func() map[string]*v1.Pod
|
||||
expected []controlPlaneComponent
|
||||
expectedError string
|
||||
}{
|
||||
{
|
||||
name: "port and addresses from config",
|
||||
cfg: &kubeadmapi.ClusterConfiguration{
|
||||
APIServer: kubeadmapi.APIServer{
|
||||
ControlPlaneComponent: kubeadmapi.ControlPlaneComponent{
|
||||
ExtraArgs: []kubeadmapi.Arg{
|
||||
{Name: "secure-port", Value: "1111"},
|
||||
{Name: "advertise-address", Value: "fd00:1::"},
|
||||
},
|
||||
},
|
||||
},
|
||||
ControllerManager: kubeadmapi.ControlPlaneComponent{
|
||||
ExtraArgs: []kubeadmapi.Arg{
|
||||
{Name: "secure-port", Value: "2222"},
|
||||
{Name: "bind-address", Value: "127.0.0.1"},
|
||||
},
|
||||
},
|
||||
Scheduler: kubeadmapi.ControlPlaneComponent{
|
||||
ExtraArgs: []kubeadmapi.Arg{
|
||||
{Name: "secure-port", Value: "3333"},
|
||||
{Name: "bind-address", Value: "127.0.0.1"},
|
||||
},
|
||||
},
|
||||
name: "valid: all port and addresses from config",
|
||||
setup: func() map[string]*v1.Pod {
|
||||
var (
|
||||
pod *v1.Pod
|
||||
podMap = map[string]*v1.Pod{}
|
||||
)
|
||||
pod = getTestPod([]string{
|
||||
constants.KubeAPIServer,
|
||||
fmt.Sprintf("--%s=%s", argAdvertiseAddress, "fd00:1::"),
|
||||
fmt.Sprintf("--%s=%s", argPort, "1111"),
|
||||
})
|
||||
podMap[constants.KubeAPIServer] = pod
|
||||
pod = getTestPod([]string{
|
||||
constants.KubeControllerManager,
|
||||
fmt.Sprintf("--%s=%s", argBindAddress, "127.0.0.1"),
|
||||
fmt.Sprintf("--%s=%s", argPort, "2222"),
|
||||
})
|
||||
podMap[constants.KubeControllerManager] = pod
|
||||
pod = getTestPod([]string{
|
||||
constants.KubeScheduler,
|
||||
fmt.Sprintf("--%s=%s", argBindAddress, "127.0.0.1"),
|
||||
fmt.Sprintf("--%s=%s", argPort, "3333"),
|
||||
})
|
||||
podMap[constants.KubeScheduler] = pod
|
||||
return podMap
|
||||
},
|
||||
expected: []controlPlaneComponent{
|
||||
{name: "kube-apiserver", url: fmt.Sprintf("https://[fd00:1::]:1111/%s", endpointLivez)},
|
||||
@ -61,19 +79,115 @@ func TestGetControlPlaneComponents(t *testing.T) {
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "default ports and addresses",
|
||||
cfg: &kubeadmapi.ClusterConfiguration{},
|
||||
name: "valid: all port and addresses from config (alt. formatting)",
|
||||
setup: func() map[string]*v1.Pod {
|
||||
var (
|
||||
pod *v1.Pod
|
||||
podMap = map[string]*v1.Pod{}
|
||||
)
|
||||
pod = getTestPod([]string{
|
||||
constants.KubeAPIServer,
|
||||
fmt.Sprintf("-%s=%s", argAdvertiseAddress, "fd00:1::"),
|
||||
fmt.Sprintf("-%s=%s", argPort, "1111"),
|
||||
})
|
||||
podMap[constants.KubeAPIServer] = pod
|
||||
pod = getTestPod([]string{
|
||||
constants.KubeControllerManager,
|
||||
fmt.Sprintf("-%s %s", argBindAddress, "127.0.0.1"),
|
||||
fmt.Sprintf("-%s %s", argPort, "2222"),
|
||||
})
|
||||
podMap[constants.KubeControllerManager] = pod
|
||||
pod = getTestPod([]string{
|
||||
constants.KubeScheduler,
|
||||
fmt.Sprintf("-%s %s", argBindAddress, "127.0.0.1"),
|
||||
fmt.Sprintf("-%s %s", argPort, "3333"),
|
||||
})
|
||||
podMap[constants.KubeScheduler] = pod
|
||||
return podMap
|
||||
},
|
||||
expected: []controlPlaneComponent{
|
||||
{name: "kube-apiserver", url: fmt.Sprintf("https://[fd00:1::]:1111/%s", endpointLivez)},
|
||||
{name: "kube-controller-manager", url: fmt.Sprintf("https://127.0.0.1:2222/%s", endpointHealthz)},
|
||||
{name: "kube-scheduler", url: fmt.Sprintf("https://127.0.0.1:3333/%s", endpointLivez)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "valid: default ports and addresses",
|
||||
setup: func() map[string]*v1.Pod {
|
||||
var (
|
||||
pod *v1.Pod
|
||||
podMap = map[string]*v1.Pod{}
|
||||
)
|
||||
pod = getTestPod([]string{
|
||||
constants.KubeAPIServer,
|
||||
})
|
||||
podMap[constants.KubeAPIServer] = pod
|
||||
pod = getTestPod([]string{
|
||||
constants.KubeControllerManager,
|
||||
})
|
||||
podMap[constants.KubeControllerManager] = pod
|
||||
pod = getTestPod([]string{
|
||||
constants.KubeScheduler,
|
||||
})
|
||||
podMap[constants.KubeScheduler] = pod
|
||||
return podMap
|
||||
},
|
||||
expected: []controlPlaneComponent{
|
||||
{name: "kube-apiserver", url: fmt.Sprintf("https://192.168.0.1:6443/%s", endpointLivez)},
|
||||
{name: "kube-controller-manager", url: fmt.Sprintf("https://127.0.0.1:10257/%s", endpointHealthz)},
|
||||
{name: "kube-scheduler", url: fmt.Sprintf("https://127.0.0.1:10259/%s", endpointLivez)},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid: nil Pods in map",
|
||||
setup: func() map[string]*v1.Pod {
|
||||
return map[string]*v1.Pod{}
|
||||
},
|
||||
expectedError: `[got nil Pod for component "kube-apiserver", ` +
|
||||
`got nil Pod for component "kube-controller-manager", ` +
|
||||
`got nil Pod for component "kube-scheduler"]`,
|
||||
},
|
||||
{
|
||||
name: "invalid: empty commands in containers",
|
||||
setup: func() map[string]*v1.Pod {
|
||||
podMap := map[string]*v1.Pod{}
|
||||
podMap[constants.KubeAPIServer] = getTestPod([]string{})
|
||||
podMap[constants.KubeControllerManager] = getTestPod([]string{})
|
||||
podMap[constants.KubeScheduler] = getTestPod([]string{})
|
||||
return podMap
|
||||
},
|
||||
expectedError: `[the Pod has no container command starting with "kube-apiserver", ` +
|
||||
`the Pod has no container command starting with "kube-controller-manager", ` +
|
||||
`the Pod has no container command starting with "kube-scheduler"]`,
|
||||
},
|
||||
{
|
||||
name: "invalid: missing commands in containers",
|
||||
setup: func() map[string]*v1.Pod {
|
||||
var (
|
||||
pod = getTestPod([]string{""})
|
||||
podMap = map[string]*v1.Pod{}
|
||||
)
|
||||
podMap[constants.KubeAPIServer] = pod
|
||||
podMap[constants.KubeControllerManager] = pod
|
||||
podMap[constants.KubeScheduler] = pod
|
||||
return podMap
|
||||
},
|
||||
expectedError: `[the Pod has no container command starting with "kube-apiserver", ` +
|
||||
`the Pod has no container command starting with "kube-controller-manager", ` +
|
||||
`the Pod has no container command starting with "kube-scheduler"]`,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
actual := getControlPlaneComponents(tc.cfg, "192.168.0.1")
|
||||
m := tc.setup()
|
||||
actual, err := getControlPlaneComponents(m, "192.168.0.1")
|
||||
if err != nil {
|
||||
if err.Error() != tc.expectedError {
|
||||
t.Fatalf("expected error:\n%v\ngot:\n%v",
|
||||
tc.expectedError, err)
|
||||
}
|
||||
}
|
||||
if !reflect.DeepEqual(tc.expected, actual) {
|
||||
t.Fatalf("expected result: %+v, got: %+v", tc.expected, actual)
|
||||
}
|
||||
|
@ -23,10 +23,10 @@ import (
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
errorsutil "k8s.io/apimachinery/pkg/util/errors"
|
||||
|
||||
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
|
||||
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
|
||||
"k8s.io/kubernetes/cmd/kubeadm/app/util/apiclient"
|
||||
)
|
||||
@ -90,7 +90,7 @@ func NewWaiter() apiclient.Waiter {
|
||||
}
|
||||
|
||||
// WaitForControlPlaneComponents just returns a dummy nil, to indicate that the program should just proceed
|
||||
func (w *Waiter) WaitForControlPlaneComponents(cfg *kubeadmapi.ClusterConfiguration, apiServerAddress string) error {
|
||||
func (w *Waiter) WaitForControlPlaneComponents(podsMap map[string]*v1.Pod, apiServerAddress string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -31,6 +31,7 @@ import (
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/pmezard/go-difflib/difflib"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/resource"
|
||||
@ -229,6 +230,28 @@ func ReadStaticPodFromDisk(manifestPath string) (*v1.Pod, error) {
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
// ReadMultipleStaticPodsFromDisk reads multiple known component static Pods from manifestDir
|
||||
// and returns a list of Pods objects.
|
||||
func ReadMultipleStaticPodsFromDisk(manifestDir string, components ...string) (map[string]*v1.Pod, error) {
|
||||
var (
|
||||
podMap = map[string]*v1.Pod{}
|
||||
errs []error
|
||||
)
|
||||
for _, c := range components {
|
||||
path := kubeadmconstants.GetStaticPodFilepath(c, manifestDir)
|
||||
pod, err := ReadStaticPodFromDisk(path)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
continue
|
||||
}
|
||||
podMap[c] = pod
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return nil, utilerrors.NewAggregate(errs)
|
||||
}
|
||||
return podMap, nil
|
||||
}
|
||||
|
||||
// LivenessProbe creates a Probe object with a HTTPGet handler
|
||||
func LivenessProbe(host, path string, port int32, scheme v1.URIScheme) *v1.Probe {
|
||||
// sets initialDelaySeconds same as periodSeconds to skip one period before running a check
|
||||
|
@ -21,10 +21,13 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
@ -692,6 +695,88 @@ func TestReadStaticPodFromDisk(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestReadMultipleStaticPodsFromDisk(t *testing.T) {
|
||||
getTestPod := func(name string) *v1.Pod {
|
||||
return &v1.Pod{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Pod",
|
||||
APIVersion: "v1",
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
setup func(dir string)
|
||||
components []string
|
||||
expected []*v1.Pod
|
||||
expectedErrorContains []string
|
||||
}{
|
||||
{
|
||||
name: "valid: all pods are written and read",
|
||||
setup: func(dir string) {
|
||||
var pod *v1.Pod
|
||||
pod = getTestPod("a")
|
||||
_ = WriteStaticPodToDisk(kubeadmconstants.KubeAPIServer, dir, *pod)
|
||||
pod = getTestPod("b")
|
||||
_ = WriteStaticPodToDisk(kubeadmconstants.KubeControllerManager, dir, *pod)
|
||||
pod = getTestPod("c")
|
||||
_ = WriteStaticPodToDisk(kubeadmconstants.KubeScheduler, dir, *pod)
|
||||
},
|
||||
components: kubeadmconstants.ControlPlaneComponents,
|
||||
expected: []*v1.Pod{
|
||||
getTestPod("a"),
|
||||
getTestPod("b"),
|
||||
getTestPod("c"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid: all pods returned errors",
|
||||
setup: func(dir string) {},
|
||||
components: kubeadmconstants.ControlPlaneComponents,
|
||||
expectedErrorContains: []string{
|
||||
"kube-apiserver.yaml: no such file or directory",
|
||||
"kube-controller-manager.yaml: no such file or directory",
|
||||
"kube-scheduler.yaml: no such file or directory",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
tc.setup(dir)
|
||||
m, err := ReadMultipleStaticPodsFromDisk(dir, tc.components...)
|
||||
if err != nil {
|
||||
for _, ec := range tc.expectedErrorContains {
|
||||
if !strings.Contains(err.Error(), ec) {
|
||||
t.Fatalf("expected error to contain string: %s\nerror:\n%v", ec, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compare sorted result to expected result.
|
||||
var actual []*v1.Pod
|
||||
for _, v := range m {
|
||||
actual = append(actual, v)
|
||||
}
|
||||
sort.Slice(actual, func(a, b int) bool {
|
||||
return actual[a].Name < actual[b].Name
|
||||
})
|
||||
sort.Slice(tc.expected, func(a, b int) bool {
|
||||
return actual[a].Name < actual[b].Name
|
||||
})
|
||||
|
||||
if diff := cmp.Diff(tc.expected, actual); diff != "" {
|
||||
t.Fatalf("unexpected difference (-want,+got):\n%s", diff)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestManifestFilesAreEqual(t *testing.T) {
|
||||
var tests = []struct {
|
||||
description string
|
||||
|
Loading…
Reference in New Issue
Block a user