Skip to content

Commit 8291eff

Browse files
committed
Run PD CSI driver in an image on GCE VM in E2E
tests.
1 parent 96338ca commit 8291eff

File tree

7 files changed

+129
-61
lines changed

7 files changed

+129
-61
lines changed

Makefile

+7
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,13 @@ else
4747
$(warning gcp-pd-driver-windows only supports amd64.)
4848
endif
4949

50+
build-container-local: init-buildx
51+
$(DOCKER) buildx build --platform=linux --progress=plain \
52+
-t local:local \
53+
--build-arg BUILDPLATFORM=linux \
54+
--build-arg STAGINGVERSION=local \
55+
--load .
56+
5057
build-container: require-GCE_PD_CSI_STAGING_IMAGE require-GCE_PD_CSI_STAGING_VERSION init-buildx
5158
$(DOCKER) buildx build --platform=linux --progress=plain \
5259
-t $(STAGINGIMAGE):$(STAGINGVERSION) \

pkg/deviceutils/device-utils.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,8 @@ func filterAvailableNvmeDevFsPaths(devNvmePaths []string) []string {
324324
return diskNvmePaths
325325
}
326326

327-
func findAvailableDevFsPaths() ([]string, error) {
327+
// FindAvailableDevFsPaths returns a list of the paths of accessible devices.
328+
func FindAvailableDevFsPaths() ([]string, error) {
328329
diskSDPaths, err := filepath.Glob(diskSDGlob)
329330
if err != nil {
330331
return nil, fmt.Errorf("failed to filepath.Glob(\"%s\"): %w", diskSDGlob, err)
@@ -340,7 +341,7 @@ func findAvailableDevFsPaths() ([]string, error) {
340341

341342
func udevadmTriggerForDiskIfExists(deviceName string) error {
342343
devFsPathToSerial := map[string]string{}
343-
devFsPaths, err := findAvailableDevFsPaths()
344+
devFsPaths, err := FindAvailableDevFsPaths()
344345
if err != nil {
345346
return err
346347
}

test/e2e/tests/setup_e2e_test.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ var (
5555
computeAlphaService *computealpha.Service
5656
computeBetaService *computebeta.Service
5757
kmsClient *cloudkms.KeyManagementClient
58+
archivePath string
5859
)
5960

6061
func init() {
@@ -98,6 +99,9 @@ var _ = BeforeSuite(func() {
9899

99100
klog.Infof("Running in project %v with service account %v", *project, *serviceAccount)
100101

102+
archivePath, err = testutils.BuildLocalImage()
103+
Expect(err).To(BeNil())
104+
101105
for _, zone := range zones {
102106
go func(curZone string) {
103107
defer GinkgoRecover()
@@ -120,6 +124,9 @@ var _ = AfterSuite(func() {
120124
tc.Instance.DeleteInstance()
121125
}
122126
}
127+
128+
err := testutils.RemoveLocalImage()
129+
Expect(err).To(BeNil())
123130
})
124131

125132
func notEmpty(v string) bool {
@@ -128,8 +135,9 @@ func notEmpty(v string) bool {
128135

129136
func getDriverConfig() testutils.DriverConfig {
130137
return testutils.DriverConfig{
131-
ExtraFlags: slices.Filter(nil, strings.Split(*extraDriverFlags, ","), notEmpty),
132-
Zones: strings.Split(*zones, ","),
138+
ExtraFlags: slices.Filter(nil, strings.Split(*extraDriverFlags, ","), notEmpty),
139+
Zones: strings.Split(*zones, ","),
140+
ArchivePath: archivePath,
133141
}
134142
}
135143

test/e2e/utils/setup-remote.sh

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#!/bin/bash
2+
3+
## Install Kubectl: https://kubernetes.io/docs/tasks/tools/install-kubectl-linux/
4+
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
5+
install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
6+
7+
## Install Docker: https://docs.docker.com/engine/install/debian/
8+
sudo apt-get update
9+
sudo apt-get install ca-certificates curl
10+
sudo install -m 0755 -d /etc/apt/keyrings
11+
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
12+
sudo chmod a+r /etc/apt/keyrings/docker.asc
13+
14+
# Add the repository to Apt sources:
15+
echo \
16+
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian \
17+
bookworm stable" | \
18+
tee /etc/apt/sources.list.d/docker.list > /dev/null
19+
sudo apt-get update
20+
21+
sudo apt-get install -y uidmap docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin dbus-user-session slirp4netns docker-ce-rootless-extras
22+
23+
# systemctl disable --now docker.service docker.socket
24+
# rm /var/run/docker.sock
25+
26+
# touch /tmp/startup-configured.txt
27+

test/e2e/utils/utils.go

+43-7
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,17 @@ import (
1919
"fmt"
2020
"math/rand"
2121
"os"
22+
"os/exec"
2223
"path"
24+
"path/filepath"
2325
"regexp"
2426
"strconv"
2527
"strings"
2628
"time"
2729

2830
"golang.org/x/oauth2/google"
2931
cloudresourcemanager "google.golang.org/api/cloudresourcemanager/v1"
32+
"k8s.io/apimachinery/pkg/util/uuid"
3033
"k8s.io/klog/v2"
3134
boskosclient "sigs.k8s.io/boskos/client"
3235
"sigs.k8s.io/boskos/common"
@@ -41,22 +44,31 @@ const (
4144

4245
var (
4346
boskos, _ = boskosclient.NewClient(os.Getenv("JOB_NAME"), "http://boskos", "", "")
47+
48+
pkgPath string
49+
binPath string
50+
51+
archiveName = fmt.Sprintf("e2e_driver_binaries_%s.tar.gz", uuid.NewUUID())
4452
)
4553

4654
type DriverConfig struct {
4755
ComputeEndpoint string
4856
ExtraFlags []string
4957
Zones []string
58+
ArchivePath string
5059
}
5160

52-
func GCEClientAndDriverSetup(instance *remote.InstanceInfo, driverConfig DriverConfig) (*remote.TestContext, error) {
53-
port := fmt.Sprintf("%v", 1024+rand.Intn(10000))
61+
func init() {
5462
goPath, ok := os.LookupEnv("GOPATH")
5563
if !ok {
56-
return nil, fmt.Errorf("Could not find environment variable GOPATH")
64+
panic(fmt.Errorf("Could not find environment variable GOPATH"))
5765
}
58-
pkgPath := path.Join(goPath, "src/sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/")
59-
binPath := path.Join(pkgPath, "bin/gce-pd-csi-driver")
66+
pkgPath = path.Join(goPath, "src/sigs.k8s.io/gcp-compute-persistent-disk-csi-driver/")
67+
binPath = path.Join(pkgPath, "bin/gce-pd-csi-driver")
68+
}
69+
70+
func GCEClientAndDriverSetup(instance *remote.InstanceInfo, driverConfig DriverConfig) (*remote.TestContext, error) {
71+
port := fmt.Sprintf("%v", 1024+rand.Intn(10000))
6072

6173
endpoint := fmt.Sprintf("tcp://localhost:%s", port)
6274
extra_flags := []string{
@@ -76,14 +88,15 @@ func GCEClientAndDriverSetup(instance *remote.InstanceInfo, driverConfig DriverC
7688
workspace := remote.NewWorkspaceDir("gce-pd-e2e-")
7789
// Log at V(6) as the compute API calls are emitted at that level and it's
7890
// useful to see what's happening when debugging tests.
79-
driverRunCmd := fmt.Sprintf("sh -c '/usr/bin/nohup %s/gce-pd-csi-driver -v=6 --endpoint=%s %s 2> %s/prog.out < /dev/null > /dev/null &'",
80-
workspace, endpoint, strings.Join(extra_flags, " "), workspace)
91+
driverRunCmd := fmt.Sprintf("docker run --privileged -v /dev/:/dev/:shared -v /tmp/:/tmp/:shared -d --network=host local:local -v=6 --endpoint=%s %s 2> %s/prog.out < /dev/null",
92+
endpoint, strings.Join(extra_flags, " "), workspace)
8193
config := &remote.ClientConfig{
8294
PkgPath: pkgPath,
8395
BinPath: binPath,
8496
WorkspaceDir: workspace,
8597
RunDriverCmd: driverRunCmd,
8698
Port: port,
99+
ArchivePath: driverConfig.ArchivePath,
87100
}
88101

89102
err := os.Setenv("GCE_PD_CSI_STAGING_VERSION", "latest")
@@ -333,3 +346,26 @@ func ValidateLogicalLinkIsDisk(instance *remote.InstanceInfo, link, diskName str
333346
}
334347
return false, fmt.Errorf("symlinked disk %s for diskName %s does not match a supported /dev/sd* or /dev/nvme* path", devFsPath, diskName)
335348
}
349+
350+
func BuildLocalImage() (string, error) {
351+
klog.V(2).Infof("Building local image...")
352+
353+
archivePath := filepath.Join(pkgPath, archiveName)
354+
makeCmd := exec.Command("make", "build-container-local", "-C", pkgPath)
355+
if out, err := makeCmd.CombinedOutput(); err != nil {
356+
return "", fmt.Errorf("failed to build local image: %v, output: %s", err, out)
357+
}
358+
if out, err := exec.Command("docker", "save", "local:local", "-o", archivePath).CombinedOutput(); err != nil {
359+
return "", fmt.Errorf("failed to save local image: %v, output: %s", err, out)
360+
}
361+
362+
return archivePath, nil
363+
}
364+
365+
func RemoveLocalImage() error {
366+
klog.V(2).Infof("Removing local image archive...")
367+
if err := os.Remove(filepath.Join(pkgPath, archiveName)); err != nil {
368+
return fmt.Errorf("failed to remove local image archive: %v", err)
369+
}
370+
return nil
371+
}

test/remote/runner.go

+34-34
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,14 @@ package remote
1818

1919
import (
2020
"fmt"
21-
"path"
2221
"path/filepath"
2322
"strconv"
2423
"strings"
2524

2625
"k8s.io/klog/v2"
2726
)
2827

29-
func (i *InstanceInfo) UploadAndRun(archivePath, remoteWorkspace, driverRunCmd string) (int, error) {
28+
func (i *InstanceInfo) UploadAndRun(archiveName, archivePath, remoteWorkspace, driverRunCmd, pkgPath string) (int, error) {
3029

3130
// Create the temp staging directory
3231
klog.V(4).Infof("Staging test binaries on %q", i.cfg.Name)
@@ -37,53 +36,54 @@ func (i *InstanceInfo) UploadAndRun(archivePath, remoteWorkspace, driverRunCmd s
3736
return -1, fmt.Errorf("failed to create remoteWorkspace directory %q on instance %q: %v output: %q", remoteWorkspace, i.cfg.Name, err.Error(), output)
3837
}
3938

40-
// Copy the archive to the staging directory
41-
if output, err := runSSHCommand("scp", archivePath, fmt.Sprintf("%s:%s/", i.GetSSHTarget(), remoteWorkspace)); err != nil {
39+
// Copy the setup script to the staging directory
40+
if output, err := runSSHCommand("scp", fmt.Sprintf("%s/test/e2e/utils/setup-remote.sh", pkgPath), fmt.Sprintf("%s:%s/", i.GetSSHTarget(), remoteWorkspace)); err != nil {
4241
// Exit failure with the error
4342
return -1, fmt.Errorf("failed to copy test archive: %v, output: %q", err.Error(), output)
4443
}
4544

46-
// Extract the archive
47-
archiveName := path.Base(archivePath)
48-
cmd := getSSHCommand(" && ",
49-
fmt.Sprintf("cd %s", remoteWorkspace),
50-
fmt.Sprintf("tar -xzvf ./%s", archiveName),
51-
)
52-
klog.V(4).Infof("Extracting tar on %q", i.cfg.Name)
53-
// Do not use sudo here, because `sudo tar -x` will recover the file ownership inside the tar ball, but
54-
// we want the extracted files to be owned by the current user.
55-
if output, err := i.SSHNoSudo("sh", "-c", cmd); err != nil {
56-
// Exit failure with the error
57-
return -1, fmt.Errorf("failed to extract test archive: %v, output: %q", err.Error(), output)
45+
// Set up the VM env with docker and make
46+
if output, err := i.SSH("sh", "-c", fmt.Sprintf("%s/setup-remote.sh", remoteWorkspace)); err != nil {
47+
return -1, fmt.Errorf("failed to setup VM environment: %v, output: %q", err.Error(), output)
48+
}
49+
50+
// Upload local image to remote
51+
if output, err := runSSHCommand("scp", archivePath, fmt.Sprintf("%s:%s/", i.GetSSHTarget(), remoteWorkspace)); err != nil {
52+
return -1, fmt.Errorf("failed to copy image archive: %v, output: %q", err, output)
5853
}
5954

55+
// Run PD CSI driver as a container
6056
klog.V(4).Infof("Starting driver on %q", i.cfg.Name)
61-
// When the process is killed the driver should close the TCP endpoint, then we want to download the logs
62-
output, err := i.SSH(driverRunCmd)
57+
cmd := getSSHCommand(" && ",
58+
fmt.Sprintf("docker load -i %v/%v", remoteWorkspace, archiveName),
59+
driverRunCmd,
60+
)
61+
output, err := i.SSH("sh", "-c", cmd)
6362
if err != nil {
64-
// Exit failure with the error
65-
return -1, fmt.Errorf("failed start driver, got output: %v, error: %v", output, err.Error())
63+
return -1, fmt.Errorf("failed to load or run docker image: %v, output: %q", err, output)
6664
}
6765

68-
// Get the driver PID
69-
// ps -aux | grep /tmp/gce-pd-e2e-0180801T114407/gce-pd-csi-driver | awk '{print $2}'
70-
driverPIDCmd := getSSHCommand(" | ",
71-
"ps -aux",
72-
fmt.Sprintf("grep %s", remoteWorkspace),
73-
"grep -v grep",
74-
// All ye who try to deal with escaped/non-escaped quotes with exec beware.
75-
//`awk "{print \$2}"`,
76-
)
77-
driverPIDString, err := i.SSHNoSudo("sh", "-c", driverPIDCmd)
66+
// Grab the container ID from `docker run` output
67+
driverRunOutputs := strings.Split(output, "\n")
68+
numSplits := len(driverRunOutputs)
69+
if numSplits < 2 {
70+
return -1, fmt.Errorf("failed to get driver container ID from driver run outputs, outputs are: %v", output)
71+
}
72+
// Grabbing the second last split because it contains an empty string
73+
driverContainerID := driverRunOutputs[len(driverRunOutputs)-2]
74+
75+
// Grab driver PID from container ID
76+
driverPIDStr, err := i.SSH(fmt.Sprintf("docker inspect -f {{.State.Pid}} %v", driverContainerID))
7877
if err != nil {
7978
// Exit failure with the error
80-
return -1, fmt.Errorf("failed to get PID of driver, got output: %v, error: %v", output, err.Error())
79+
return -1, fmt.Errorf("failed to get PID of driver, got output: %v, error: %v", driverPIDStr, err.Error())
8180
}
82-
83-
driverPID, err := strconv.Atoi(strings.Fields(driverPIDString)[1])
81+
driverPIDStr = strings.TrimSpace(driverPIDStr)
82+
driverPID, err := strconv.Atoi(driverPIDStr)
8483
if err != nil {
85-
return -1, fmt.Errorf("failed to convert driver PID from string %s to int: %v", driverPIDString, err.Error())
84+
return -1, fmt.Errorf("failed to convert driver PID from string %s to int: %v", driverPIDStr, err.Error())
8685
}
86+
klog.V(4).Infof("Driver PID is: %v", driverPID)
8787

8888
return driverPID, nil
8989
}

test/remote/setup-teardown.go

+5-16
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,7 @@ package remote
1919
import (
2020
"fmt"
2121
"os"
22-
23-
"k8s.io/apimachinery/pkg/util/uuid"
24-
"k8s.io/klog/v2"
22+
"path"
2523
)
2624

2725
// TestContext holds the CSI Client handle to a remotely connected Driver
@@ -45,6 +43,8 @@ type ClientConfig struct {
4543
RunDriverCmd string
4644
// Port to use as SSH tunnel on both remote and local side.
4745
Port string
46+
// Absolute path of the archive of the built image
47+
ArchivePath string
4848
}
4949

5050
type processes struct {
@@ -70,20 +70,9 @@ func SetupInstance(cfg InstanceConfig) (*InstanceInfo, error) {
7070
// a CSI client to it through SHH tunnelling. It returns a TestContext with both a handle to the instance
7171
// that the driver is on and the CSI Client object to make CSI calls to the remote driver.
7272
func SetupNewDriverAndClient(instance *InstanceInfo, config *ClientConfig) (*TestContext, error) {
73-
archiveName := fmt.Sprintf("e2e_driver_binaries_%s.tar.gz", uuid.NewUUID())
74-
archivePath, err := CreateDriverArchive(archiveName, instance.cfg.Architecture, config.PkgPath, config.BinPath)
75-
if err != nil {
76-
return nil, err
77-
}
78-
defer func() {
79-
err = os.Remove(archivePath)
80-
if err != nil {
81-
klog.Warningf("Failed to remove archive file %s: %v", archivePath, err)
82-
}
83-
}()
84-
8573
// Upload archive to instance and run binaries
86-
driverPID, err := instance.UploadAndRun(archivePath, config.WorkspaceDir, config.RunDriverCmd)
74+
archiveName := path.Base(config.ArchivePath)
75+
driverPID, err := instance.UploadAndRun(archiveName, config.ArchivePath, config.WorkspaceDir, config.RunDriverCmd, config.PkgPath)
8776
if err != nil {
8877
return nil, err
8978
}

0 commit comments

Comments
 (0)