Skip to content

Commit a1b38b5

Browse files
committed
Add option to deploy GKE managed PD CSI driver for integration tests
Summary of changes: 1. Add a new hook to pass release-channel for GKE cluster deployment. 2. Add a new hook to deploy GKE managed PD CSI driver. 3. Added a version comparision util with supporting UTs.
1 parent 0b4cb88 commit a1b38b5

File tree

6 files changed

+385
-29
lines changed

6 files changed

+385
-29
lines changed

test/k8s-integration/cluster.go

+43-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package main
22

33
import (
4+
"encoding/json"
45
"errors"
56
"fmt"
67
"os"
@@ -9,6 +10,7 @@ import (
910
"strconv"
1011
"strings"
1112

13+
apimachineryversion "k8s.io/apimachinery/pkg/version"
1214
"k8s.io/klog"
1315
)
1416

@@ -152,17 +154,24 @@ func clusterUpGKE(gceZone, gceRegion string, numNodes int, imageType string, use
152154
}
153155

154156
var cmd *exec.Cmd
157+
cmdParams := []string{"container", "clusters", "create", gkeTestClusterName,
158+
locationArg, locationVal, "--num-nodes", strconv.Itoa(numNodes)}
159+
160+
ensureExactlyOneVariableSet([]*string{gkeClusterVer, gkeReleaseChannel},
161+
"For GKE cluster deployment, exactly one of 'gke-cluster-version' or 'gke-release-channel' must be set")
162+
if isVariableSet(gkeClusterVer) {
163+
cmdParams = append(cmdParams, "--cluster-version", *gkeClusterVer)
164+
} else {
165+
cmdParams = append(cmdParams, "--release-channel", *gkeReleaseChannel)
166+
}
167+
155168
if useManagedDriver {
156169
// PD CSI Driver add on is enabled only in gcloud beta.
157-
cmd = exec.Command("gcloud", "beta", "container", "clusters", "create", gkeTestClusterName,
158-
locationArg, locationVal, "--cluster-version", *gkeClusterVer, "--num-nodes", strconv.Itoa(numNodes),
159-
"--quiet", "--machine-type", "n1-standard-2", "--image-type", imageType, "--addons", "GcePersistentDiskCsiDriver")
160-
} else {
161-
cmd = exec.Command("gcloud", "container", "clusters", "create", gkeTestClusterName,
162-
locationArg, locationVal, "--cluster-version", *gkeClusterVer, "--num-nodes", strconv.Itoa(numNodes),
163-
"--quiet", "--machine-type", "n1-standard-2", "--image-type", imageType)
170+
cmdParams = append([]string{"beta"}, cmdParams...)
171+
cmdParams = append(cmdParams, "--addons", "GcePersistentDiskCsiDriver")
164172
}
165173

174+
cmd = exec.Command("gcloud", cmdParams...)
166175
err = runCommand("Staring E2E Cluster on GKE", cmd)
167176
if err != nil {
168177
return fmt.Errorf("failed to bring up kubernetes e2e cluster on gke: %v", err)
@@ -300,3 +309,30 @@ func getNormalizedVersion(kubeVersion, gkeVersion string) (string, error) {
300309
return strings.Join(toks[:2], "."), nil
301310

302311
}
312+
313+
func getKubClusterVersion() (string, error) {
314+
out, err := exec.Command("kubectl", "version", "-o=json").CombinedOutput()
315+
if err != nil {
316+
return "", fmt.Errorf("failed to obtain cluster version, error: %v", err)
317+
}
318+
type version struct {
319+
ClientVersion *apimachineryversion.Info `json:"clientVersion,omitempty" yaml:"clientVersion,omitempty"`
320+
ServerVersion *apimachineryversion.Info `json:"serverVersion,omitempty" yaml:"serverVersion,omitempty"`
321+
}
322+
323+
var v version
324+
err = json.Unmarshal(out, &v)
325+
if err != nil {
326+
return "", fmt.Errorf("Failed to parse kubectl version output, error: %v", err)
327+
}
328+
329+
return v.ServerVersion.GitVersion, nil
330+
}
331+
332+
func MustGetKubClusterVersion() string {
333+
ver, err := getKubClusterVersion()
334+
if err != nil {
335+
klog.Fatalf("Error: %v", err)
336+
}
337+
return ver
338+
}

test/k8s-integration/main.go

+44-20
Original file line numberDiff line numberDiff line change
@@ -30,19 +30,20 @@ import (
3030

3131
var (
3232
// Kubernetes cluster flags
33-
teardownCluster = flag.Bool("teardown-cluster", true, "teardown the cluster after the e2e test")
34-
teardownDriver = flag.Bool("teardown-driver", true, "teardown the driver after the e2e test")
35-
bringupCluster = flag.Bool("bringup-cluster", true, "build kubernetes and bringup a cluster")
36-
gceZone = flag.String("gce-zone", "", "zone that the gce k8s cluster is created/found in")
37-
gceRegion = flag.String("gce-region", "", "region that gke regional cluster should be created in")
38-
kubeVersion = flag.String("kube-version", "", "version of Kubernetes to download and use for the cluster")
39-
testVersion = flag.String("test-version", "", "version of Kubernetes to download and use for tests")
40-
kubeFeatureGates = flag.String("kube-feature-gates", "", "feature gates to set on new kubernetes cluster")
41-
localK8sDir = flag.String("local-k8s-dir", "", "local prebuilt kubernetes/kubernetes directory to use for cluster and test binaries")
42-
deploymentStrat = flag.String("deployment-strategy", "gce", "choose between deploying on gce or gke")
43-
gkeClusterVer = flag.String("gke-cluster-version", "", "version of Kubernetes master and node for gke")
44-
numNodes = flag.Int("num-nodes", -1, "the number of nodes in the test cluster")
45-
imageType = flag.String("image-type", "cos", "the image type to use for the cluster")
33+
teardownCluster = flag.Bool("teardown-cluster", true, "teardown the cluster after the e2e test")
34+
teardownDriver = flag.Bool("teardown-driver", true, "teardown the driver after the e2e test")
35+
bringupCluster = flag.Bool("bringup-cluster", true, "build kubernetes and bringup a cluster")
36+
gceZone = flag.String("gce-zone", "", "zone that the gce k8s cluster is created/found in")
37+
gceRegion = flag.String("gce-region", "", "region that gke regional cluster should be created in")
38+
kubeVersion = flag.String("kube-version", "", "version of Kubernetes to download and use for the cluster")
39+
testVersion = flag.String("test-version", "", "version of Kubernetes to download and use for tests")
40+
kubeFeatureGates = flag.String("kube-feature-gates", "", "feature gates to set on new kubernetes cluster")
41+
localK8sDir = flag.String("local-k8s-dir", "", "local prebuilt kubernetes/kubernetes directory to use for cluster and test binaries")
42+
deploymentStrat = flag.String("deployment-strategy", "gce", "choose between deploying on gce or gke")
43+
gkeClusterVer = flag.String("gke-cluster-version", "", "version of Kubernetes master and node for gke")
44+
numNodes = flag.Int("num-nodes", -1, "the number of nodes in the test cluster")
45+
imageType = flag.String("image-type", "cos", "the image type to use for the cluster")
46+
gkeReleaseChannel = flag.String("gke-release-channel", "", "GKE release channel to be used for cluster deploy. One of 'rapid', 'stable' or 'regular'")
4647

4748
// Test infrastructure flags
4849
boskosResourceType = flag.String("boskos-resource-type", "gce-project", "name of the boskos resource type to reserve")
@@ -82,8 +83,8 @@ func main() {
8283

8384
if *useGKEManagedDriver {
8485
ensureVariableVal(deploymentStrat, "gke", "deployment strategy must be GKE for using managed driver")
85-
ensureFlag(doDriverBuild, false, "driver build flag will be ignored when using GKE managed driver")
86-
ensureFlag(teardownDriver, false, "driver teardown flag will be ignored when using GKE managed driver")
86+
ensureFlag(doDriverBuild, false, "'do-driver-build' must be false when using GKE managed driver")
87+
ensureFlag(teardownDriver, false, "'teardown-driver' must be false when using GKE managed driver")
8788
}
8889

8990
ensureVariable(saFile, true, "service-account-file is a required flag")
@@ -113,7 +114,8 @@ func main() {
113114
if *deploymentStrat == "gke" {
114115
ensureFlag(migrationTest, false, "Cannot set deployment strategy to 'gke' for migration tests.")
115116
ensureVariable(kubeVersion, false, "Cannot set kube-version when using deployment strategy 'gke'. Use gke-cluster-version.")
116-
ensureVariable(gkeClusterVer, true, "Must set gke-cluster-version when using deployment strategy 'gke'.")
117+
ensureExactlyOneVariableSet([]*string{gkeClusterVer, gkeReleaseChannel},
118+
"For GKE cluster deployment, exactly one of 'gke-cluster-version' or 'gke-release-channel' must be set")
117119
ensureVariable(kubeFeatureGates, false, "Cannot set feature gates when using deployment strategy 'gke'.")
118120
if len(*localK8sDir) == 0 {
119121
ensureVariable(testVersion, true, "Must set either test-version or local k8s dir when using deployment strategy 'gke'.")
@@ -308,12 +310,19 @@ func handle() error {
308310
}
309311
}
310312

311-
normalizedVersion, err := getNormalizedVersion(*kubeVersion, *gkeClusterVer)
312-
if err != nil {
313-
return fmt.Errorf("failed to get cluster minor version: %v", err)
313+
var testSkip string
314+
if *useGKEManagedDriver {
315+
clusterVersion := MustGetKubClusterVersion()
316+
fmt.Printf("GKE cluster version %q\n", clusterVersion)
317+
testSkip = generateGKEBasedTestSkip(clusterVersion)
318+
} else {
319+
normalizedVersion, err := getNormalizedVersion(*kubeVersion, *gkeClusterVer)
320+
if err != nil {
321+
return fmt.Errorf("failed to get cluster minor version: %v", err)
322+
}
323+
testSkip = generateTestSkip(normalizedVersion)
314324
}
315325

316-
testSkip := generateTestSkip(normalizedVersion)
317326
// Run the tests using the testDir kubernetes
318327
if len(*storageClassFile) != 0 {
319328
err = runCSITests(pkgDir, testDir, *testFocus, testSkip, *storageClassFile, *snapshotClassFile, cloudProviderArgs, *deploymentStrat)
@@ -357,6 +366,21 @@ func generateTestSkip(normalizedVersion string) string {
357366
return skipString
358367
}
359368

369+
// Helper function to generate skip string when using GKE managed PD CSI driver.
370+
func generateGKEBasedTestSkip(clusterVersion string) string {
371+
skipString := "\\[Disruptive\\]|\\[Serial\\]"
372+
curVer := MustParseVersion(clusterVersion)
373+
if (*curVer).IsLessThan(MustParseVersion("1.17.0")) {
374+
skipString = skipString + "|volumeMode\\sshould\\snot\\smount\\s/\\smap\\sunused\\svolumes\\sin\\sa\\spod"
375+
}
376+
377+
if (*curVer).IsLessThan(MustParseVersion("1.18.3-gke.0")) {
378+
skipString = skipString + "|snapshot"
379+
}
380+
381+
return skipString
382+
}
383+
360384
func setEnvProject(project string) error {
361385
out, err := exec.Command("gcloud", "config", "set", "project", project).CombinedOutput()
362386
if err != nil {

test/k8s-integration/utils.go

+17
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,19 @@ func ensureFlag(v *bool, setTo bool, msgOnError string) {
5858
}
5959
}
6060

61+
func ensureExactlyOneVariableSet(vars []*string, msgOnError string) {
62+
var count int
63+
for _, v := range vars {
64+
if len(*v) != 0 {
65+
count++
66+
}
67+
}
68+
69+
if count != 1 {
70+
klog.Fatal(msgOnError)
71+
}
72+
}
73+
6174
func shredFile(filePath string) {
6275
if _, err := os.Stat(filePath); os.IsNotExist(err) {
6376
klog.V(4).Infof("File %v was not found, skipping shredding", filePath)
@@ -85,3 +98,7 @@ func ensureVariableVal(v *string, val string, msgOnError string) {
8598
klog.Fatal(msgOnError)
8699
}
87100
}
101+
102+
func isVariableSet(v *string) bool {
103+
return len(*v) != 0
104+
}

test/k8s-integration/version.go

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"regexp"
6+
"strconv"
7+
"strings"
8+
9+
"k8s.io/klog"
10+
)
11+
12+
var (
13+
versionNum = `(0|[1-9][0-9]*)`
14+
internalPatchVersion = `(\-[a-zA-Z0-9_.+-]+)`
15+
16+
versionRegex = regexp.MustCompile(`^` + versionNum + `\.` + versionNum + `\.` + versionNum + internalPatchVersion + "?$")
17+
gkeExtraVersionRegex = regexp.MustCompile(`^(?:gke)\.(0|[1-9][0-9]*)$`)
18+
)
19+
20+
type Version struct {
21+
version [4]int
22+
extra string
23+
}
24+
25+
func (v Version) String() string {
26+
return fmt.Sprintf("%d.%d.%d-gke.%d", v.version[0], v.version[1], v.version[2], v.version[3])
27+
}
28+
29+
func (v Version) isGKEExtraVersion() bool {
30+
return gkeExtraVersionRegex.MatchString(v.extra)
31+
}
32+
33+
func extractGKEExtraVersion(extra string) (int, error) {
34+
m := gkeExtraVersionRegex.FindStringSubmatch(extra)
35+
if len(m) != 2 {
36+
return -1, fmt.Errorf("Invalid GKE Patch version %q", extra)
37+
}
38+
39+
v, err := strconv.Atoi(m[1])
40+
if err != nil {
41+
return -1, fmt.Errorf("GKE extra version atoi failed %q", extra)
42+
}
43+
44+
if v < 0 {
45+
return -1, fmt.Errorf("GKE extra version check failed %q", extra)
46+
}
47+
return v, nil
48+
}
49+
50+
func ParseVersion(version string) (*Version, error) {
51+
// If version has a prefix 'v', remove it before parsing.
52+
if strings.HasPrefix(version, "v") {
53+
version = version[1:]
54+
}
55+
56+
submatches := versionRegex.FindStringSubmatch(version)
57+
if submatches == nil {
58+
return nil, fmt.Errorf("version %q is invalid", version)
59+
}
60+
61+
var v Version
62+
// submatches[0] is the whole match, [1]..[3] are the version bits, [4] is the extra
63+
for i, sm := range submatches[1:4] {
64+
var err error
65+
if v.version[i], err = strconv.Atoi(sm); err != nil {
66+
return nil, fmt.Errorf("submatch %q failed atoi conversion", sm)
67+
}
68+
}
69+
if submatches[4] != "" {
70+
v.extra = submatches[4][1:]
71+
}
72+
73+
// Ensure 1.X.Y < 1.X.Y-gke.0
74+
v.version[3] = -1
75+
if v.isGKEExtraVersion() {
76+
ver, err := extractGKEExtraVersion(v.extra)
77+
if err != nil {
78+
return nil, err
79+
}
80+
v.version[3] = ver
81+
}
82+
return &v, nil
83+
}
84+
85+
func MustParseVersion(version string) *Version {
86+
v, err := ParseVersion(version)
87+
if err != nil {
88+
klog.Fatalf("Failed to parse GKE version: %q", version)
89+
}
90+
return v
91+
}
92+
93+
// Helper function to compare versions.
94+
// -1 -- if left < right
95+
// 0 -- if left == right
96+
// 1 -- if left > right
97+
func (v Version) compare(right *Version) int {
98+
for i, b := range v.version {
99+
if b > right.version[i] {
100+
return 1
101+
}
102+
if b < right.version[i] {
103+
return -1
104+
}
105+
}
106+
107+
return 0
108+
}
109+
110+
// Compare versions if left is strictly less than right.
111+
func (v Version) IsLessThan(right *Version) bool {
112+
return v.compare(right) < 0
113+
}

0 commit comments

Comments
 (0)