Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 7 additions & 50 deletions test/e2e/packaging/deb_install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,7 @@ import (
"path/filepath"
"runtime"
"strings"
"time"

"github.com/docker/docker/api/types/container"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
Expand Down Expand Up @@ -48,28 +46,8 @@ var _ = Describe("pf9-byohost deb", func() {
Expect(strings.TrimSpace(string(archOutput))).To(Equal(runtime.GOARCH))

By("starting a byoh/node:e2e container")
created, err := dockerClient.ContainerCreate(ctx,
&container.Config{Image: debTestImage},
&container.HostConfig{
Privileged: true,
Tmpfs: map[string]string{"/run": "", "/run/lock": "", "/tmp": ""},
},
nil, nil, "")
Expect(err).NotTo(HaveOccurred())
containerID := created.ID
defer func() {
_ = dockerClient.ContainerRemove(ctx, containerID, container.RemoveOptions{Force: true})
}()

Expect(dockerClient.ContainerStart(ctx, containerID, container.StartOptions{})).To(Succeed())

By("waiting for systemd to come up")
Eventually(func() (string, error) {
output, _, execErr := execInContainer(ctx, containerID, []string{"systemctl", "is-system-running"}, nil)
return output, execErr
}, 30*time.Second, time.Second).Should(SatisfyAny(
ContainSubstring("running"), ContainSubstring("degraded"),
))
containerID, cleanup := startPackagingContainer(ctx, debTestImage)
defer cleanup()

By("copying the built deb into the container")
Expect(copyFileToContainer(ctx, containerID, debPath, debContainerPath)).To(Succeed())
Expand All @@ -95,43 +73,22 @@ var _ = Describe("pf9-byohost deb", func() {
"/usr/bin/byohctl",
))

By("asserting byohctl was installed executable and runs")
byohctlOutput, exitCode, err := execInContainer(ctx, containerID, []string{"byohctl", "version"}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(exitCode).To(Equal(0), "byohctl version failed:\n%s", byohctlOutput)

By("asserting after-install.sh generated the EnvironmentFile the systemd unit reads BOOTSTRAP_KUBECONFIG from")
confOutput, exitCode, err := execInContainer(ctx, containerID,
[]string{"cat", "/etc/pf9-byohost-agent.service.d/pf9-byohost-agent.conf"}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(exitCode).To(Equal(0))
Expect(confOutput).To(ContainSubstring("BOOTSTRAP_KUBECONFIG="))
Expect(confOutput).To(ContainSubstring("NAMESPACE="))
Expect(confOutput).To(ContainSubstring("REGION="))

// Not asserting is-active: same reasoning as the RPM test - no real
// cluster for the agent to reach in this environment.
By("asserting the service is enabled")
enabledOutput, _, err := execInContainer(ctx, containerID, []string{"systemctl", "is-enabled", "pf9-byohost-agent"}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(strings.TrimSpace(enabledOutput)).To(Equal("enabled"))
assertByohctlRuns(ctx, containerID)
assertEnvironmentFile(ctx, containerID, "after-install.sh")
assertServiceEnabled(ctx, containerID)

By("uninstalling the deb")
uninstallOutput, exitCode, err := execInContainer(ctx, containerID, []string{"dpkg", "-r", "pf9-byohost-agent"}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(exitCode).To(Equal(0), "dpkg -r failed:\n%s", uninstallOutput)

By("asserting the binary, unit file, byohctl, and generated conf directory are gone")
for _, path := range []string{
assertPathsRemoved(ctx, containerID, "dpkg -r", []string{
"/binary/pf9-byoh-hostagent",
"/etc/systemd/system/pf9-byohost-agent.service",
"/etc/pf9-byohost-agent.service.d",
"/usr/bin/byohctl",
} {
_, pathExitCode, pathErr := execInContainer(ctx, containerID, []string{"test", "-e", path}, nil)
Expect(pathErr).NotTo(HaveOccurred())
Expect(pathExitCode).NotTo(Equal(0), "%s should have been removed by dpkg -r", path)
}
})

By("asserting before-remove.sh logged to the same directory every other pf9 log lives in")
uninstallLogOutput, exitCode, err := execInContainer(ctx, containerID,
Expand Down
137 changes: 137 additions & 0 deletions test/e2e/packaging/packaging_helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// Copyright 2026 Platform9, Inc. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

package packaging_test

import (
"archive/tar"
"bytes"
"context"
"io"
"os"
"strings"
"time"

"github.com/docker/docker/api/types/container"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

func copyFileToContainer(ctx context.Context, containerID, localPath, containerPath string) error {
content, err := os.ReadFile(localPath)
if err != nil {
return err
}

var buf bytes.Buffer
tw := tar.NewWriter(&buf)
if err := tw.WriteHeader(&tar.Header{
Name: strings.TrimPrefix(containerPath, "/"),
Mode: 0644,
Size: int64(len(content)),
}); err != nil {
return err
}
if _, err := tw.Write(content); err != nil {
return err
}
if err := tw.Close(); err != nil {
return err
}

return dockerClient.CopyToContainer(ctx, containerID, "/", &buf, container.CopyToContainerOptions{})
}

func execInContainer(ctx context.Context, containerID string, cmd, env []string) (output string, exitCode int, err error) {
created, err := dockerClient.ContainerExecCreate(ctx, containerID, container.ExecOptions{
Cmd: cmd,
Env: env,
AttachStdout: true,
AttachStderr: true,
Tty: true,
})
if err != nil {
return "", 0, err
}

// Tty must match the exec's own creation config: mismatched Tty here can
// leave the daemon multiplexing stdout/stderr with an 8-byte frame header
// per chunk instead of returning the raw stream, corrupting the output.
attached, err := dockerClient.ContainerExecAttach(ctx, created.ID, container.ExecAttachOptions{Tty: true})
if err != nil {
return "", 0, err
}
defer attached.Close()

outputBytes, err := io.ReadAll(attached.Reader)
if err != nil {
return "", 0, err
}

inspected, err := dockerClient.ContainerExecInspect(ctx, created.ID)
if err != nil {
return string(outputBytes), 0, err
}
return string(outputBytes), inspected.ExitCode, nil
}

func startPackagingContainer(ctx context.Context, image string) (containerID string, cleanup func()) {
created, err := dockerClient.ContainerCreate(ctx,
&container.Config{Image: image},
&container.HostConfig{
Privileged: true,
Tmpfs: map[string]string{"/run": "", "/run/lock": "", "/tmp": ""},
},
nil, nil, "")
Expect(err).NotTo(HaveOccurred())
containerID = created.ID
cleanup = func() {
_ = dockerClient.ContainerRemove(ctx, containerID, container.RemoveOptions{Force: true})
}

Expect(dockerClient.ContainerStart(ctx, containerID, container.StartOptions{})).To(Succeed())

By("waiting for systemd to come up")
Eventually(func() (string, error) {
output, _, execErr := execInContainer(ctx, containerID, []string{"systemctl", "is-system-running"}, nil)
return output, execErr
}, 30*time.Second, time.Second).Should(SatisfyAny(
ContainSubstring("running"), ContainSubstring("degraded"),
))

return containerID, cleanup
}

func assertByohctlRuns(ctx context.Context, containerID string) {
By("asserting byohctl was installed executable and runs")
byohctlOutput, exitCode, err := execInContainer(ctx, containerID, []string{"byohctl", "version"}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(exitCode).To(Equal(0), "byohctl version failed:\n%s", byohctlOutput)
}

func assertEnvironmentFile(ctx context.Context, containerID, generatedBy string) {
By("asserting " + generatedBy + " generated the EnvironmentFile the systemd unit reads BOOTSTRAP_KUBECONFIG from")
confOutput, exitCode, err := execInContainer(ctx, containerID,
[]string{"cat", "/etc/pf9-byohost-agent.service.d/pf9-byohost-agent.conf"}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(exitCode).To(Equal(0))
Expect(confOutput).To(ContainSubstring("BOOTSTRAP_KUBECONFIG="))
Expect(confOutput).To(ContainSubstring("NAMESPACE="))
Expect(confOutput).To(ContainSubstring("REGION="))
}

// Not asserting is-active: no real cluster for the agent to reach in this environment.
func assertServiceEnabled(ctx context.Context, containerID string) {
By("asserting the service is enabled")
enabledOutput, _, err := execInContainer(ctx, containerID, []string{"systemctl", "is-enabled", "pf9-byohost-agent"}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(strings.TrimSpace(enabledOutput)).To(Equal("enabled"))
}

func assertPathsRemoved(ctx context.Context, containerID, removedBy string, paths []string) {
for _, path := range paths {
_, exitCode, err := execInContainer(ctx, containerID, []string{"test", "-e", path}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(exitCode).NotTo(Equal(0), "%s should have been removed by %s", path, removedBy)
}
}
120 changes: 7 additions & 113 deletions test/e2e/packaging/rpm_install_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,82 +4,17 @@
package packaging_test

import (
"archive/tar"
"bytes"
"context"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"strings"
"time"

"github.com/docker/docker/api/types/container"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)

const rpmContainerPath = "/root/pf9-byohost.rpm"

func copyFileToContainer(ctx context.Context, containerID, localPath, containerPath string) error {
content, err := os.ReadFile(localPath)
if err != nil {
return err
}

var buf bytes.Buffer
tw := tar.NewWriter(&buf)
if err := tw.WriteHeader(&tar.Header{
Name: strings.TrimPrefix(containerPath, "/"),
Mode: 0644,
Size: int64(len(content)),
}); err != nil {
return err
}
if _, err := tw.Write(content); err != nil {
return err
}
if err := tw.Close(); err != nil {
return err
}

return dockerClient.CopyToContainer(ctx, containerID, "/", &buf, container.CopyToContainerOptions{})
}

func execInContainer(ctx context.Context, containerID string, cmd, env []string) (output string, exitCode int, err error) {
created, err := dockerClient.ContainerExecCreate(ctx, containerID, container.ExecOptions{
Cmd: cmd,
Env: env,
AttachStdout: true,
AttachStderr: true,
Tty: true,
})
if err != nil {
return "", 0, err
}

// Tty must match the exec's own creation config: mismatched Tty here can
// leave the daemon multiplexing stdout/stderr with an 8-byte frame header
// per chunk instead of returning the raw stream, corrupting the output.
attached, err := dockerClient.ContainerExecAttach(ctx, created.ID, container.ExecAttachOptions{Tty: true})
if err != nil {
return "", 0, err
}
defer attached.Close()

outputBytes, err := io.ReadAll(attached.Reader)
if err != nil {
return "", 0, err
}

inspected, err := dockerClient.ContainerExecInspect(ctx, created.ID)
if err != nil {
return string(outputBytes), 0, err
}
return string(outputBytes), inspected.ExitCode, nil
}

var _ = Describe("pf9-byohost RPM", func() {
It("installs cleanly and uninstalls cleanly", func() {
repoRootBytes, err := exec.Command("git", "rev-parse", "--show-toplevel").Output()
Expand All @@ -101,28 +36,8 @@ var _ = Describe("pf9-byohost RPM", func() {
rpmPath := matches[0]

By("starting a Rocky Linux container")
created, err := dockerClient.ContainerCreate(ctx,
&container.Config{Image: rpmTestImage},
&container.HostConfig{
Privileged: true,
Tmpfs: map[string]string{"/run": "", "/run/lock": "", "/tmp": ""},
},
nil, nil, "")
Expect(err).NotTo(HaveOccurred())
containerID := created.ID
defer func() {
_ = dockerClient.ContainerRemove(ctx, containerID, container.RemoveOptions{Force: true})
}()

Expect(dockerClient.ContainerStart(ctx, containerID, container.StartOptions{})).To(Succeed())

By("waiting for systemd to come up")
Eventually(func() (string, error) {
output, _, execErr := execInContainer(ctx, containerID, []string{"systemctl", "is-system-running"}, nil)
return output, execErr
}, 30*time.Second, time.Second).Should(SatisfyAny(
ContainSubstring("running"), ContainSubstring("degraded"),
))
containerID, cleanup := startPackagingContainer(ctx, rpmTestImage)
defer cleanup()

By("copying the built RPM into the container")
Expect(copyFileToContainer(ctx, containerID, rpmPath, rpmContainerPath)).To(Succeed())
Expand All @@ -144,41 +59,20 @@ var _ = Describe("pf9-byohost RPM", func() {
"/usr/bin/byohctl",
))

By("asserting byohctl was installed executable and runs")
byohctlOutput, exitCode, err := execInContainer(ctx, containerID, []string{"byohctl", "version"}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(exitCode).To(Equal(0), "byohctl version failed:\n%s", byohctlOutput)

By("asserting %post generated the EnvironmentFile the systemd unit reads BOOTSTRAP_KUBECONFIG from")
confOutput, exitCode, err := execInContainer(ctx, containerID,
[]string{"cat", "/etc/pf9-byohost-agent.service.d/pf9-byohost-agent.conf"}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(exitCode).To(Equal(0))
Expect(confOutput).To(ContainSubstring("BOOTSTRAP_KUBECONFIG="))
Expect(confOutput).To(ContainSubstring("NAMESPACE="))
Expect(confOutput).To(ContainSubstring("REGION="))

// Not asserting is-active: the agent gets a stub, empty kubeconfig
// with no real cluster to register with
By("asserting the service is enabled")
enabledOutput, _, err := execInContainer(ctx, containerID, []string{"systemctl", "is-enabled", "pf9-byohost-agent"}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(strings.TrimSpace(enabledOutput)).To(Equal("enabled"))
assertByohctlRuns(ctx, containerID)
assertEnvironmentFile(ctx, containerID, "%post")
assertServiceEnabled(ctx, containerID)

By("uninstalling the RPM")
uninstallOutput, exitCode, err := execInContainer(ctx, containerID, []string{"rpm", "-e", "pf9-byohost"}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(exitCode).To(Equal(0), "rpm -e failed:\n%s", uninstallOutput)

By("asserting the binary, unit file, and byohctl are gone")
for _, path := range []string{
assertPathsRemoved(ctx, containerID, "rpm -e", []string{
"/binary/pf9-byoh-hostagent",
"/etc/systemd/system/pf9-byohost-agent.service",
"/usr/bin/byohctl",
} {
_, exitCode, err := execInContainer(ctx, containerID, []string{"test", "-e", path}, nil)
Expect(err).NotTo(HaveOccurred())
Expect(exitCode).NotTo(Equal(0), fmt.Sprintf("%s should have been removed by rpm -e", path))
}
})
})
})
Loading