Skip to content

Commit c8adbc0

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 c8adbc0

File tree

7 files changed

+387
-55
lines changed

7 files changed

+387
-55
lines changed

test/k8s-integration/cluster.go

+40-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,21 @@ 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+
if isVariableSet(gkeClusterVer) {
160+
cmdParams = append(cmdParams, "--cluster-version", *gkeClusterVer)
161+
} else {
162+
cmdParams = append(cmdParams, "--release-channel", *gkeReleaseChannel)
163+
}
164+
155165
if useManagedDriver {
156166
// 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)
167+
cmdParams = append([]string{"beta"}, cmdParams...)
168+
cmdParams = append(cmdParams, "--addons", "GcePersistentDiskCsiDriver")
164169
}
165170

171+
cmd = exec.Command("gcloud", cmdParams...)
166172
err = runCommand("Staring E2E Cluster on GKE", cmd)
167173
if err != nil {
168174
return fmt.Errorf("failed to bring up kubernetes e2e cluster on gke: %v", err)
@@ -300,3 +306,30 @@ func getNormalizedVersion(kubeVersion, gkeVersion string) (string, error) {
300306
return strings.Join(toks[:2], "."), nil
301307

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

test/k8s-integration/main.go

+34-42
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,10 @@ 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")
88+
ensureVariable(stagingImage, false, "'staging-image' must not be set when using GKE managed driver")
89+
ensureVariable(deployOverlayName, false, "'deploy-overlay-name' must not be set when using GKE managed driver")
8790
}
8891

8992
ensureVariable(saFile, true, "service-account-file is a required flag")
@@ -113,7 +116,8 @@ func main() {
113116
if *deploymentStrat == "gke" {
114117
ensureFlag(migrationTest, false, "Cannot set deployment strategy to 'gke' for migration tests.")
115118
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'.")
119+
ensureExactlyOneVariableSet([]*string{gkeClusterVer, gkeReleaseChannel},
120+
"For GKE cluster deployment, exactly one of 'gke-cluster-version' or 'gke-release-channel' must be set")
117121
ensureVariable(kubeFeatureGates, false, "Cannot set feature gates when using deployment strategy 'gke'.")
118122
if len(*localK8sDir) == 0 {
119123
ensureVariable(testVersion, true, "Must set either test-version or local k8s dir when using deployment strategy 'gke'.")
@@ -308,12 +312,11 @@ func handle() error {
308312
}
309313
}
310314

311-
normalizedVersion, err := getNormalizedVersion(*kubeVersion, *gkeClusterVer)
312-
if err != nil {
313-
return fmt.Errorf("failed to get cluster minor version: %v", err)
314-
}
315+
clusterVersion := mustGetKubClusterVersion()
316+
fmt.Printf("Kubernetes cluster version %q\n", clusterVersion)
317+
testSkip := generateTestSkip(clusterVersion, *useGKEManagedDriver)
318+
fmt.Printf("Test skip string %s", testSkip)
315319

316-
testSkip := generateTestSkip(normalizedVersion)
317320
// Run the tests using the testDir kubernetes
318321
if len(*storageClassFile) != 0 {
319322
err = runCSITests(pkgDir, testDir, *testFocus, testSkip, *storageClassFile, *snapshotClassFile, cloudProviderArgs, *deploymentStrat)
@@ -330,30 +333,19 @@ func handle() error {
330333
return nil
331334
}
332335

333-
func generateTestSkip(normalizedVersion string) string {
336+
// Helper function to generate skip string when using GKE managed PD CSI driver.
337+
func generateTestSkip(clusterVersion string, use_gke_managed_driver bool) string {
334338
skipString := "\\[Disruptive\\]|\\[Serial\\]"
335-
switch normalizedVersion {
336-
// Fall-through versioning since all test cases we want to skip in 1.15
337-
// should also be skipped in 1.14
338-
case "1.13":
339-
fallthrough
340-
case "1.14":
341-
fallthrough
342-
case "1.15":
343-
fallthrough
344-
case "1.16":
345-
// "volumeMode should not mount / map unused volumes in a pod" tests a
346-
// bug-fix introduced in 1.17
347-
// (https://github.com/kubernetes/kubernetes/pull/81163)
339+
curVer := MustParseVersion(clusterVersion)
340+
if (*curVer).IsLessThan(MustParseVersion("1.16.0")) {
348341
skipString = skipString + "|volumeMode\\sshould\\snot\\smount\\s/\\smap\\sunused\\svolumes\\sin\\sa\\spod"
349-
// Skip Snapshot tests pre 1.17
350-
skipString = skipString + "|snapshot"
351-
fallthrough
352-
case "1.17":
353-
case "latest":
354-
case "master":
355-
default:
356342
}
343+
344+
if (use_gke_managed_driver && (*curVer).IsLessThan(MustParseVersion("1.18.3-gke.0"))) ||
345+
(!use_gke_managed_driver && (*curVer).IsLessThan(MustParseVersion("1.17.0"))) {
346+
skipString = skipString + "|VolumeSnapshotDataSource"
347+
}
348+
357349
return skipString
358350
}
359351

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)