Skip to content

Commit 7d5e541

Browse files
authored
Merge pull request #2264 from jackfrancis/helm-general
generalize helm install during E2E testing
2 parents f277598 + 23666ad commit 7d5e541

File tree

2 files changed

+124
-106
lines changed

2 files changed

+124
-106
lines changed

test/e2e/cloud-provider-azure.go

Lines changed: 31 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -23,122 +23,47 @@ import (
2323
"context"
2424
"fmt"
2525

26-
"github.com/Azure/go-autorest/autorest/to"
2726
. "github.com/onsi/ginkgo"
2827
. "github.com/onsi/gomega"
29-
helmAction "helm.sh/helm/v3/pkg/action"
30-
helmLoader "helm.sh/helm/v3/pkg/chart/loader"
31-
helmCli "helm.sh/helm/v3/pkg/cli"
32-
helmVals "helm.sh/helm/v3/pkg/cli/values"
33-
helmGetter "helm.sh/helm/v3/pkg/getter"
34-
corev1 "k8s.io/api/core/v1"
35-
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28+
"k8s.io/apimachinery/pkg/labels"
3629
"sigs.k8s.io/cluster-api/test/framework"
3730
"sigs.k8s.io/cluster-api/test/framework/clusterctl"
38-
crclient "sigs.k8s.io/controller-runtime/pkg/client"
31+
"sigs.k8s.io/controller-runtime/pkg/client"
32+
)
33+
34+
const (
35+
cloudProviderAzureHelmRepoURL = "https://raw.githubusercontent.com/kubernetes-sigs/cloud-provider-azure/master/helm/repo"
36+
cloudProviderAzureChartName = "cloud-provider-azure"
37+
cloudProvierAzureHelmReleaseName = "cloud-provider-azure-oot"
3938
)
4039

4140
// InstallCloudProviderAzureHelmChart installs the official cloud-provider-azure helm chart
41+
// Fulfills the clusterctl.Waiter type so that it can be used as ApplyClusterTemplateAndWaitInput data
42+
// in the flow of a clusterctl.ApplyClusterTemplateAndWait E2E test scenario
4243
func InstallCloudProviderAzureHelmChart(ctx context.Context, input clusterctl.ApplyClusterTemplateAndWaitInput, result *clusterctl.ApplyClusterTemplateAndWaitResult) {
43-
By("Waiting for workload cluster kubeconfig secret")
44-
Eventually(func() error {
45-
client := input.ClusterProxy.GetClient()
46-
secret := &corev1.Secret{}
47-
key := crclient.ObjectKey{
48-
Name: fmt.Sprintf("%s-kubeconfig", input.ConfigCluster.ClusterName),
49-
Namespace: input.ConfigCluster.Namespace,
50-
}
51-
return client.Get(ctx, key, secret)
52-
}, input.WaitForControlPlaneIntervals...).Should(Succeed())
53-
clusterProxy := input.ClusterProxy.GetWorkloadCluster(ctx, input.ConfigCluster.Namespace, input.ConfigCluster.ClusterName)
54-
By("Waiting for nodes to come online indicating that the cluster is ready to accept work")
55-
Eventually(func() error {
56-
clientSet := clusterProxy.GetClientSet()
57-
var runningNodes int
58-
list, err := clientSet.CoreV1().Nodes().List(ctx, v1.ListOptions{})
59-
if err != nil {
60-
return err
61-
}
62-
for _, n := range list.Items {
63-
if n.Status.Phase == corev1.NodeRunning {
64-
runningNodes++
65-
}
66-
}
67-
if runningNodes > 0 {
68-
return nil
69-
}
70-
return err
71-
}, input.WaitForControlPlaneIntervals...).Should(Succeed())
44+
By(fmt.Sprintf("Ensuring the kubeconfig secret for cluster %s/%s exists before installing cloud-provider-azure components", input.ConfigCluster.Namespace, input.ConfigCluster.ClusterName))
45+
WaitForWorkloadClusterKubeconfigSecret(ctx, input)
7246
By("Installing the correct version of cloud-provider-azure components via helm")
73-
kubeConfigPath := clusterProxy.GetKubeconfigPath()
74-
clusterName := input.ClusterProxy.GetName()
75-
settings := helmCli.New()
76-
settings.KubeConfig = kubeConfigPath
77-
actionConfig := new(helmAction.Configuration)
78-
err := actionConfig.Init(settings.RESTClientGetter(), "default", "secret", Logf)
79-
Expect(err).To(BeNil())
80-
i := helmAction.NewInstall(actionConfig)
81-
i.RepoURL = "https://raw.githubusercontent.com/kubernetes-sigs/cloud-provider-azure/master/helm/repo"
82-
i.ReleaseName = "cloud-provider-azure-oot"
83-
Eventually(func() error {
84-
cp, err := i.ChartPathOptions.LocateChart("cloud-provider-azure", helmCli.New())
85-
if err != nil {
86-
return err
87-
}
88-
p := helmGetter.All(settings)
89-
valueOpts := &helmVals.Options{}
90-
valueOpts.Values = []string{fmt.Sprintf("infra.clusterName=%s", clusterName)}
91-
vals, err := valueOpts.MergeValues(p)
92-
if err != nil {
93-
return err
94-
}
95-
chartRequested, err := helmLoader.Load(cp)
96-
if err != nil {
97-
return err
98-
}
99-
release, err := i.RunWithContext(ctx, chartRequested, vals)
100-
if err != nil {
101-
return err
102-
}
103-
Logf(release.Info.Description)
104-
return nil
105-
}, input.WaitForControlPlaneIntervals...).Should(Succeed())
47+
values := []string{fmt.Sprintf("infra.clusterName=%s", input.ConfigCluster.ClusterName)}
48+
InstallHelmChart(ctx, input, cloudProviderAzureHelmRepoURL, cloudProviderAzureChartName, cloudProvierAzureHelmReleaseName, values)
10649
By("Waiting for a Running cloud-controller-manager pod")
107-
Eventually(func() bool {
108-
clusterProxy := input.ClusterProxy.GetWorkloadCluster(ctx, input.ConfigCluster.Namespace, input.ConfigCluster.ClusterName)
109-
clientSet := clusterProxy.GetClientSet()
110-
var runningPods int
111-
list, err := clientSet.CoreV1().Pods("kube-system").List(ctx, v1.ListOptions{
112-
LabelSelector: "component=cloud-controller-manager",
113-
})
114-
if err != nil {
115-
return false
116-
}
117-
for _, p := range list.Items {
118-
if p.Status.Phase == corev1.PodRunning {
119-
runningPods++
120-
}
121-
}
122-
return runningPods > 0
123-
}, input.WaitForControlPlaneIntervals...).Should(BeTrue())
124-
By("Waiting for Running cloud-node-manager pods")
125-
Eventually(func() bool {
126-
clusterProxy := input.ClusterProxy.GetWorkloadCluster(ctx, input.ConfigCluster.Namespace, input.ConfigCluster.ClusterName)
127-
clientSet := clusterProxy.GetClientSet()
128-
var runningPods int64
129-
list, err := clientSet.CoreV1().Pods("kube-system").List(ctx, v1.ListOptions{
130-
LabelSelector: "k8s-app=cloud-node-manager",
131-
})
132-
if err != nil {
133-
return false
134-
}
135-
for _, p := range list.Items {
136-
if p.Status.Phase == corev1.PodRunning {
137-
runningPods++
138-
}
139-
}
140-
return runningPods >= to.Int64(input.ConfigCluster.ControlPlaneMachineCount)
141-
}, input.WaitForControlPlaneIntervals...).Should(BeTrue())
50+
clusterProxy := input.ClusterProxy.GetWorkloadCluster(ctx, input.ConfigCluster.Namespace, input.ConfigCluster.ClusterName)
51+
workloadClusterClient := clusterProxy.GetClient()
52+
cloudControllerManagerPodLabel, err := labels.Parse("component=cloud-controller-manager")
53+
Expect(err).ToNot(HaveOccurred())
54+
framework.WaitForPodListCondition(ctx, framework.WaitForPodListConditionInput{
55+
Lister: workloadClusterClient,
56+
ListOptions: &client.ListOptions{
57+
LabelSelector: cloudControllerManagerPodLabel,
58+
Namespace: "kube-system",
59+
},
60+
Condition: podListHasNumPods(1),
61+
}, input.WaitForControlPlaneIntervals...)
62+
Expect(err).ToNot(HaveOccurred())
63+
By(fmt.Sprintf("Waiting for Ready cloud-node-manager daemonset pods"))
64+
for _, ds := range []string{"cloud-node-manager", "cloud-node-manager-windows"} {
65+
WaitForDaemonset(ctx, input, workloadClusterClient, ds, "kube-system")
66+
}
14267
By("Done installing cloud-provider-azure components, ensuring control plane is initialized")
14368
result.ControlPlane = framework.DiscoveryAndWaitForControlPlaneInitialized(ctx, framework.DiscoveryAndWaitForControlPlaneInitializedInput{
14469
Lister: input.ClusterProxy.GetClient(),

test/e2e/helpers.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ import (
6666
"sigs.k8s.io/cluster-api/test/framework/kubernetesversions"
6767
"sigs.k8s.io/cluster-api/util"
6868
"sigs.k8s.io/controller-runtime/pkg/client"
69+
70+
helmAction "helm.sh/helm/v3/pkg/action"
71+
helmLoader "helm.sh/helm/v3/pkg/chart/loader"
72+
helmCli "helm.sh/helm/v3/pkg/cli"
73+
helmVals "helm.sh/helm/v3/pkg/cli/values"
74+
helmGetter "helm.sh/helm/v3/pkg/getter"
6975
)
7076

7177
const (
@@ -823,3 +829,90 @@ func getPodLogs(ctx context.Context, clientset *kubernetes.Clientset, pod corev1
823829
}
824830
return b.String()
825831
}
832+
833+
// InstallHelmChart takes a helm repo URL, a chart name, and release name, and installs a helm release onto the E2E workload cluster
834+
func InstallHelmChart(ctx context.Context, input clusterctl.ApplyClusterTemplateAndWaitInput, repoURL, chartName, releaseName string, values []string) {
835+
clusterProxy := input.ClusterProxy.GetWorkloadCluster(ctx, input.ConfigCluster.Namespace, input.ConfigCluster.ClusterName)
836+
kubeConfigPath := clusterProxy.GetKubeconfigPath()
837+
settings := helmCli.New()
838+
settings.KubeConfig = kubeConfigPath
839+
actionConfig := new(helmAction.Configuration)
840+
err := actionConfig.Init(settings.RESTClientGetter(), "default", "secret", Logf)
841+
Expect(err).To(BeNil())
842+
i := helmAction.NewInstall(actionConfig)
843+
i.RepoURL = repoURL
844+
i.ReleaseName = releaseName
845+
Eventually(func() error {
846+
cp, err := i.ChartPathOptions.LocateChart(chartName, helmCli.New())
847+
if err != nil {
848+
return err
849+
}
850+
p := helmGetter.All(settings)
851+
valueOpts := &helmVals.Options{}
852+
valueOpts.Values = values
853+
vals, err := valueOpts.MergeValues(p)
854+
if err != nil {
855+
return err
856+
}
857+
chartRequested, err := helmLoader.Load(cp)
858+
if err != nil {
859+
return err
860+
}
861+
release, err := i.RunWithContext(ctx, chartRequested, vals)
862+
if err != nil {
863+
return err
864+
}
865+
Logf(release.Info.Description)
866+
return nil
867+
}, input.WaitForControlPlaneIntervals...).Should(Succeed())
868+
}
869+
870+
// WaitForWorkloadClusterKubeconfigSecret retries during E2E until the workload cluster kubeconfig secret exists
871+
func WaitForWorkloadClusterKubeconfigSecret(ctx context.Context, input clusterctl.ApplyClusterTemplateAndWaitInput) {
872+
// Ensure the workload cluster kubeconfig secret exists before getting the workload cluster clusterProxy object
873+
Eventually(func() error {
874+
cl := input.ClusterProxy.GetClient()
875+
secret := &corev1.Secret{}
876+
key := client.ObjectKey{
877+
Name: fmt.Sprintf("%s-kubeconfig", input.ConfigCluster.ClusterName),
878+
Namespace: input.ConfigCluster.Namespace,
879+
}
880+
err := cl.Get(ctx, key, secret)
881+
if err != nil {
882+
return err
883+
}
884+
return nil
885+
}, input.WaitForControlPlaneIntervals...).Should(Succeed())
886+
}
887+
888+
// WaitForDaemonset retries during E2E until a daemonset's pods are all Running
889+
func WaitForDaemonset(ctx context.Context, input clusterctl.ApplyClusterTemplateAndWaitInput, cl client.Client, name, namespace string) {
890+
// Ensure the workload cluster kubeconfig secret exists before getting the workload cluster clusterProxy object
891+
Eventually(func() bool {
892+
ds := &appsv1.DaemonSet{}
893+
if err := cl.Get(ctx, client.ObjectKey{Name: name, Namespace: namespace}, ds); err != nil {
894+
return false
895+
}
896+
if ds.Status.DesiredNumberScheduled == ds.Status.NumberReady {
897+
return true
898+
}
899+
return false
900+
}, input.WaitForControlPlaneIntervals...).Should(Equal(true))
901+
}
902+
903+
// podListHasNumPods fulfills the cluster-api PodListCondition type spec
904+
// given a list of pods, we validate for an exact number of those pods in a Running state
905+
func podListHasNumPods(numPods int) func(pl *corev1.PodList) error {
906+
return func(pl *corev1.PodList) error {
907+
var runningPods int
908+
for _, p := range pl.Items {
909+
if p.Status.Phase == corev1.PodRunning {
910+
runningPods++
911+
}
912+
}
913+
if runningPods != numPods {
914+
return errors.Errorf("expected %d Running pods, got %d", numPods, runningPods)
915+
}
916+
return nil
917+
}
918+
}

0 commit comments

Comments
 (0)