Create framework for varlink endpoint integration tests

add the ability to write integration tests similar to our e2e tests for
the varlink endpoints.

Signed-off-by: baude <bbaude@redhat.com>
This commit is contained in:
baude
2019-08-11 11:47:15 -05:00
parent 3f1657d729
commit 04f2f95bb4
12 changed files with 718 additions and 1 deletions

View File

@ -503,6 +503,28 @@ special_testing_cgroupv2_task:
always:
<<: *standardlogs
special_testing_endpoint_task:
depends_on:
- "gating"
- "varlink_api"
- "vendor"
only_if: $CIRRUS_CHANGE_MESSAGE !=~ '.*\*\*\*\s*CIRRUS:\s*TEST\s*IMAGES\s*\*\*\*.*'
env:
SPECIALMODE: 'endpoint' # See docs
timeout_in: 20m
setup_environment_script: '$SCRIPT_BASE/setup_environment.sh |& ${TIMESTAMP}'
integration_test_script: '$SCRIPT_BASE/integration_test.sh |& ${TIMESTAMP}'
on_failure:
failed_branch_script: '$CIRRUS_WORKING_DIR/$SCRIPT_BASE/notice_branch_failure.sh'
always:
<<: *standardlogs
# Test building of new cache-images for future PR testing, in this PR.
test_build_cache_images_task:
@ -608,6 +630,7 @@ success_task:
- "special_testing_in_podman"
- "special_testing_cgroupv2"
- "special_testing_cross"
- "special_testing_endpoint"
- "test_build_cache_images"
- "verify_test_built_images"
@ -648,6 +671,7 @@ release_task:
- "special_testing_in_podman"
- "special_testing_cgroupv2"
- "special_testing_cross"
- "special_testing_endpoint"
- "test_build_cache_images"
- "verify_test_built_images"
- "success"

1
.gitignore vendored
View File

@ -22,3 +22,4 @@ __pycache__
test/e2e/e2e.coverprofile
/podman*zip
podman*.tar.gz
.idea*

View File

@ -232,7 +232,7 @@ testunit: libpodimage ## Run unittest on the built image
localunit: test/goecho/goecho varlink_generate
ginkgo \
-r \
--skipPackage test/e2e,pkg/apparmor \
--skipPackage test/e2e,pkg/apparmor,test/endpoint \
--cover \
--covermode atomic \
--tags "$(BUILDTAGS)" \
@ -247,6 +247,9 @@ ginkgo:
ginkgo-remote:
ginkgo -v -tags "$(BUILDTAGS) remoteclient" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor test/e2e/.
endpoint:
ginkgo -v -tags "$(BUILDTAGS)" $(GINKGOTIMEOUT) -cover -flakeAttempts 3 -progress -trace -noColor -debug test/endpoint/.
localintegration: varlink_generate test-binaries ginkgo
remoteintegration: varlink_generate test-binaries ginkgo-remote

View File

@ -43,6 +43,12 @@ case "$SPECIALMODE" in
echo "WARNING: Integration tests not yet ready for cgroups V2"
#TODO: make local${TESTSUITE}
;;
endpoint)
make
make install PREFIX=/usr ETCDIR=/etc
make test-binaries
make endpoint
;;
none)
make
make install PREFIX=/usr ETCDIR=/etc

View File

@ -73,6 +73,9 @@ case "$SPECIALMODE" in
none)
remove_packaged_podman_files
;;
endpoint)
remove_packaged_podman_files
;;
rootless)
# Only do this once, even if ROOTLESS_USER (somehow) changes
if ! grep -q 'ROOTLESS_USER' /etc/environment

22
test/endpoint/config.go Normal file
View File

@ -0,0 +1,22 @@
package endpoint
import "encoding/json"
var (
STORAGE_FS = "vfs"
STORAGE_OPTIONS = "--storage-driver vfs"
ROOTLESS_STORAGE_FS = "vfs"
ROOTLESS_STORAGE_OPTIONS = "--storage-driver vfs"
CACHE_IMAGES = []string{ALPINE, BB, fedoraMinimal, nginx, redis, registry, infra, labels}
nginx = "quay.io/libpod/alpine_nginx:latest"
BB_GLIBC = "docker.io/library/busybox:glibc"
registry = "docker.io/library/registry:2"
labels = "quay.io/libpod/alpine_labels:latest"
)
func makeNameMessage(name string) string {
n := make(map[string]string)
n["name"] = name
b, _ := json.Marshal(n)
return string(b)
}

218
test/endpoint/endpoint.go Normal file
View File

@ -0,0 +1,218 @@
package endpoint
import (
"bytes"
"encoding/json"
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"syscall"
"time"
iopodman "github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/pkg/rootless"
. "github.com/onsi/ginkgo"
"github.com/onsi/gomega/gexec"
)
var (
ARTIFACT_DIR = "/tmp/.artifacts"
CGROUP_MANAGER = "systemd"
defaultWaitTimeout = 90
//RESTORE_IMAGES = []string{ALPINE, BB}
INTEGRATION_ROOT string
ImageCacheDir = "/tmp/podman/imagecachedir"
VarlinkBinary = "/usr/bin/varlink"
ALPINE = "docker.io/library/alpine:latest"
infra = "k8s.gcr.io/pause:3.1"
BB = "docker.io/library/busybox:latest"
redis = "docker.io/library/redis:alpine"
fedoraMinimal = "registry.fedoraproject.org/fedora-minimal:latest"
)
type EndpointTestIntegration struct {
ArtifactPath string
CNIConfigDir string
CgroupManager string
ConmonBinary string
CrioRoot string
//Host HostOS
ImageCacheDir string
ImageCacheFS string
OCIRuntime string
PodmanBinary string
RemoteTest bool
RunRoot string
SignaturePolicyPath string
StorageOptions string
TmpDir string
Timings []string
VarlinkBinary string
VarlinkCommand *exec.Cmd
VarlinkEndpoint string
VarlinkSession *os.Process
}
func (p *EndpointTestIntegration) StartVarlink() {
p.startVarlink(false)
}
func (p *EndpointTestIntegration) StartVarlinkWithCache() {
p.startVarlink(true)
}
func (p *EndpointTestIntegration) startVarlink(useImageCache bool) {
var (
counter int
)
if os.Geteuid() == 0 {
os.MkdirAll("/run/podman", 0755)
}
varlinkEndpoint := p.VarlinkEndpoint
//p.SetVarlinkAddress(p.VarlinkEndpoint)
args := []string{"varlink", "--timeout", "0", varlinkEndpoint}
podmanOptions := getVarlinkOptions(p, args)
if useImageCache {
cacheOptions := []string{"--storage-opt", fmt.Sprintf("%s.imagestore=%s", p.ImageCacheFS, p.ImageCacheDir)}
podmanOptions = append(cacheOptions, podmanOptions...)
}
command := exec.Command(p.PodmanBinary, podmanOptions...)
fmt.Printf("Running: %s %s\n", p.PodmanBinary, strings.Join(podmanOptions, " "))
command.Start()
command.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
p.VarlinkCommand = command
p.VarlinkSession = command.Process
for {
if result := p.endpointReady(); result == 0 {
break
}
fmt.Println("Waiting for varlink connection to become active", counter)
time.Sleep(250 * time.Millisecond)
counter++
if counter > 40 {
Fail("varlink endpoint never became ready")
}
}
}
func (p *EndpointTestIntegration) endpointReady() int {
session := p.Varlink("GetVersion", "", false)
return session.ExitCode()
}
func (p *EndpointTestIntegration) StopVarlink() {
var out bytes.Buffer
var pids []int
varlinkSession := p.VarlinkSession
if !rootless.IsRootless() {
if err := varlinkSession.Kill(); err != nil {
fmt.Fprintf(os.Stderr, "error on varlink stop-kill %q", err)
}
if _, err := varlinkSession.Wait(); err != nil {
fmt.Fprintf(os.Stderr, "error on varlink stop-wait %q", err)
}
} else {
//p.ResetVarlinkAddress()
parentPid := fmt.Sprintf("%d", p.VarlinkSession.Pid)
pgrep := exec.Command("pgrep", "-P", parentPid)
fmt.Printf("running: pgrep %s\n", parentPid)
pgrep.Stdout = &out
err := pgrep.Run()
if err != nil {
fmt.Fprint(os.Stderr, "unable to find varlink pid")
}
for _, s := range strings.Split(out.String(), "\n") {
if len(s) == 0 {
continue
}
p, err := strconv.Atoi(s)
if err != nil {
fmt.Fprintf(os.Stderr, "unable to convert %s to int", s)
}
if p != 0 {
pids = append(pids, p)
}
}
pids = append(pids, p.VarlinkSession.Pid)
for _, pid := range pids {
syscall.Kill(pid, syscall.SIGKILL)
}
}
socket := strings.Split(p.VarlinkEndpoint, ":")[1]
if err := os.Remove(socket); err != nil {
fmt.Println(err)
}
}
type EndpointSession struct {
*gexec.Session
}
func getVarlinkOptions(p *EndpointTestIntegration, args []string) []string {
podmanOptions := strings.Split(fmt.Sprintf("--root %s --runroot %s --runtime %s --conmon %s --cni-config-dir %s --cgroup-manager %s",
p.CrioRoot, p.RunRoot, p.OCIRuntime, p.ConmonBinary, p.CNIConfigDir, p.CgroupManager), " ")
if os.Getenv("HOOK_OPTION") != "" {
podmanOptions = append(podmanOptions, os.Getenv("HOOK_OPTION"))
}
podmanOptions = append(podmanOptions, strings.Split(p.StorageOptions, " ")...)
podmanOptions = append(podmanOptions, args...)
return podmanOptions
}
func (p *EndpointTestIntegration) Varlink(endpoint, message string, more bool) *EndpointSession {
//call unix:/run/user/1000/podman/io.podman/io.podman.GetContainerStats '{"name": "foobar" }'
var (
command *exec.Cmd
)
args := []string{"call"}
if more {
args = append(args, "-m")
}
args = append(args, []string{fmt.Sprintf("%s/io.podman.%s", p.VarlinkEndpoint, endpoint)}...)
if len(message) > 0 {
args = append(args, message)
}
command = exec.Command(p.VarlinkBinary, args...)
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
if err != nil {
Fail(fmt.Sprintf("unable to run varlink command: %s\n%v", strings.Join(args, " "), err))
}
session.Wait(defaultWaitTimeout)
return &EndpointSession{session}
}
func (s *EndpointSession) OutputToString() string {
fields := strings.Fields(fmt.Sprintf("%s", s.Out.Contents()))
return strings.Join(fields, " ")
}
func (s *EndpointSession) OutputToBytes() []byte {
out := s.OutputToString()
return []byte(out)
}
func (s *EndpointSession) OutputToStringMap() map[string]string {
var out map[string]string
json.Unmarshal(s.OutputToBytes(), &out)
return out
}
func (s *EndpointSession) OutputToMapToInt() map[string]int {
var out map[string]int
json.Unmarshal(s.OutputToBytes(), &out)
return out
}
func (s *EndpointSession) OutputToMoreResponse() iopodman.MoreResponse {
out := make(map[string]iopodman.MoreResponse)
json.Unmarshal(s.OutputToBytes(), &out)
return out["reply"]
}

View File

@ -0,0 +1,70 @@
package endpoint
import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"testing"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
func TestEndpoint(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "Endpoint Suite")
}
var LockTmpDir string
var _ = SynchronizedBeforeSuite(func() []byte {
// Cache images
cwd, _ := os.Getwd()
INTEGRATION_ROOT = filepath.Join(cwd, "../../")
podman := Setup("/tmp")
podman.ArtifactPath = ARTIFACT_DIR
if _, err := os.Stat(ARTIFACT_DIR); os.IsNotExist(err) {
if err = os.Mkdir(ARTIFACT_DIR, 0777); err != nil {
fmt.Printf("%q\n", err)
os.Exit(1)
}
}
// make cache dir
if err := os.MkdirAll(ImageCacheDir, 0777); err != nil {
fmt.Printf("%q\n", err)
os.Exit(1)
}
podman.StartVarlink()
for _, image := range CACHE_IMAGES {
podman.createArtifact(image)
}
podman.StopVarlink()
// If running localized tests, the cache dir is created and populated. if the
// tests are remote, this is a no-op
populateCache(podman)
path, err := ioutil.TempDir("", "libpodlock")
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return []byte(path)
}, func(data []byte) {
LockTmpDir = string(data)
})
var _ = SynchronizedAfterSuite(func() {},
func() {
podman := Setup("/tmp")
if err := os.RemoveAll(podman.CrioRoot); err != nil {
fmt.Printf("%q\n", err)
os.Exit(1)
}
if err := os.RemoveAll(podman.ImageCacheDir); err != nil {
fmt.Printf("%q\n", err)
os.Exit(1)
}
})

View File

@ -0,0 +1,66 @@
package endpoint
import (
"os"
. "github.com/containers/libpod/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Podman pull", func() {
var (
tempdir string
err error
endpointTest *EndpointTestIntegration
)
BeforeEach(func() {
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
}
endpointTest = Setup(tempdir)
endpointTest.StartVarlinkWithCache()
})
AfterEach(func() {
endpointTest.Cleanup()
//f := CurrentGinkgoTestDescription()
//processTestResult(f)
})
It("image exists in local storage", func() {
result := endpointTest.Varlink("ImageExists", makeNameMessage(ALPINE), false)
Expect(result.ExitCode()).To(BeZero())
output := result.OutputToMapToInt()
Expect(output["exists"]).To(BeZero())
})
It("image exists in local storage by shortname", func() {
result := endpointTest.Varlink("ImageExists", makeNameMessage("alpine"), false)
Expect(result.ExitCode()).To(BeZero())
output := result.OutputToMapToInt()
Expect(output["exists"]).To(BeZero())
})
It("image does not exist in local storage", func() {
result := endpointTest.Varlink("ImageExists", makeNameMessage("alpineforest"), false)
Expect(result.ExitCode()).To(BeZero())
output := result.OutputToMapToInt()
Expect(output["exists"]).To(Equal(1))
})
It("container exists in local storage by name", func() {
_ = endpointTest.startTopContainer("top")
result := endpointTest.Varlink("ContainerExists", makeNameMessage("top"), false)
Expect(result.ExitCode()).To(BeZero())
output := result.OutputToMapToInt()
Expect(output["exists"]).To(BeZero())
})
})

View File

@ -0,0 +1,44 @@
package endpoint
import (
"os"
. "github.com/containers/libpod/test/utils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Podman pull", func() {
var (
tempdir string
err error
endpointTest *EndpointTestIntegration
)
BeforeEach(func() {
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
}
endpointTest = Setup(tempdir)
endpointTest.StartVarlink()
})
AfterEach(func() {
endpointTest.Cleanup()
//f := CurrentGinkgoTestDescription()
//processTestResult(f)
})
It("podman pull", func() {
session := endpointTest.Varlink("PullImage", makeNameMessage(ALPINE), false)
Expect(session.ExitCode()).To(BeZero())
result := endpointTest.Varlink("ImageExists", makeNameMessage(ALPINE), false)
Expect(result.ExitCode()).To(BeZero())
output := result.OutputToMapToInt()
Expect(output["exists"]).To(BeZero())
})
})

219
test/endpoint/setup.go Normal file
View File

@ -0,0 +1,219 @@
package endpoint
import (
"encoding/json"
"fmt"
"os"
"os/exec"
"path/filepath"
"strings"
iopodman "github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/pkg/rootless"
"github.com/containers/storage/pkg/stringid"
"github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
"github.com/sirupsen/logrus"
)
func Setup(tempDir string) *EndpointTestIntegration {
var (
endpoint string
)
cwd, _ := os.Getwd()
INTEGRATION_ROOT = filepath.Join(cwd, "../../")
podmanBinary := filepath.Join(cwd, "../../bin/podman")
if os.Getenv("PODMAN_BINARY") != "" {
podmanBinary = os.Getenv("PODMAN_BINARY")
}
conmonBinary := filepath.Join("/usr/libexec/podman/conmon")
altConmonBinary := "/usr/bin/conmon"
if _, err := os.Stat(conmonBinary); os.IsNotExist(err) {
conmonBinary = altConmonBinary
}
if os.Getenv("CONMON_BINARY") != "" {
conmonBinary = os.Getenv("CONMON_BINARY")
}
storageOptions := STORAGE_OPTIONS
if os.Getenv("STORAGE_OPTIONS") != "" {
storageOptions = os.Getenv("STORAGE_OPTIONS")
}
cgroupManager := CGROUP_MANAGER
if rootless.IsRootless() {
cgroupManager = "cgroupfs"
}
if os.Getenv("CGROUP_MANAGER") != "" {
cgroupManager = os.Getenv("CGROUP_MANAGER")
}
ociRuntime := os.Getenv("OCI_RUNTIME")
if ociRuntime == "" {
var err error
ociRuntime, err = exec.LookPath("runc")
// If we cannot find the runc binary, setting to something static as we have no way
// to return an error. The tests will fail and point out that the runc binary could
// not be found nicely.
if err != nil {
ociRuntime = "/usr/bin/runc"
}
}
os.Setenv("DISABLE_HC_SYSTEMD", "true")
CNIConfigDir := "/etc/cni/net.d"
storageFs := STORAGE_FS
if rootless.IsRootless() {
storageFs = ROOTLESS_STORAGE_FS
}
uuid := stringid.GenerateNonCryptoID()
if !rootless.IsRootless() {
endpoint = fmt.Sprintf("unix:/run/podman/io.podman-%s", uuid)
} else {
runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
socket := fmt.Sprintf("io.podman-%s", uuid)
fqpath := filepath.Join(runtimeDir, socket)
endpoint = fmt.Sprintf("unix:%s", fqpath)
}
eti := EndpointTestIntegration{
ArtifactPath: ARTIFACT_DIR,
CNIConfigDir: CNIConfigDir,
CgroupManager: cgroupManager,
ConmonBinary: conmonBinary,
CrioRoot: filepath.Join(tempDir, "crio"),
ImageCacheDir: ImageCacheDir,
ImageCacheFS: storageFs,
OCIRuntime: ociRuntime,
PodmanBinary: podmanBinary,
RunRoot: filepath.Join(tempDir, "crio-run"),
SignaturePolicyPath: filepath.Join(INTEGRATION_ROOT, "test/policy.json"),
StorageOptions: storageOptions,
TmpDir: tempDir,
//Timings: nil,
VarlinkBinary: VarlinkBinary,
VarlinkCommand: nil,
VarlinkEndpoint: endpoint,
VarlinkSession: nil,
}
return &eti
}
func (p *EndpointTestIntegration) Cleanup() {
// Remove all containers
// TODO Make methods to do all this?
p.stopAllContainers()
//TODO need to make stop all pods
p.StopVarlink()
// Nuke tempdir
if err := os.RemoveAll(p.TmpDir); err != nil {
fmt.Printf("%q\n", err)
}
// Clean up the registries configuration file ENV variable set in Create
resetRegistriesConfigEnv()
}
func (p *EndpointTestIntegration) listContainers() []iopodman.Container {
containers := p.Varlink("ListContainers", "", false)
var varlinkContainers map[string][]iopodman.Container
if err := json.Unmarshal(containers.OutputToBytes(), &varlinkContainers); err != nil {
logrus.Error("failed to unmarshal containers")
}
return varlinkContainers["containers"]
}
func (p *EndpointTestIntegration) stopAllContainers() {
containers := p.listContainers()
for _, container := range containers {
p.stopContainer(container.Id)
}
}
func (p *EndpointTestIntegration) stopContainer(cid string) {
p.Varlink("StopContainer", fmt.Sprintf("{\"name\":\"%s\", \"timeout\":0}", cid), false)
}
func resetRegistriesConfigEnv() {
os.Setenv("REGISTRIES_CONFIG_PATH", "")
}
func (p *EndpointTestIntegration) createArtifact(image string) {
if os.Getenv("NO_TEST_CACHE") != "" {
return
}
dest := strings.Split(image, "/")
destName := fmt.Sprintf("/tmp/%s.tar", strings.Replace(strings.Join(strings.Split(dest[len(dest)-1], "/"), ""), ":", "-", -1))
fmt.Printf("Caching %s at %s...", image, destName)
if _, err := os.Stat(destName); os.IsNotExist(err) {
pull := p.Varlink("PullImage", fmt.Sprintf("{\"name\":\"%s\"}", image), false)
Expect(pull.ExitCode()).To(Equal(0))
imageSave := iopodman.ImageSaveOptions{
//Name:image,
//Output: destName,
//Format: "oci-archive",
}
imageSave.Name = image
imageSave.Output = destName
imageSave.Format = "oci-archive"
foo := make(map[string]iopodman.ImageSaveOptions)
foo["options"] = imageSave
f, _ := json.Marshal(foo)
save := p.Varlink("ImageSave", string(f), false)
result := save.OutputToMoreResponse()
Expect(save.ExitCode()).To(Equal(0))
Expect(os.Rename(result.Id, destName)).To(BeNil())
fmt.Printf("\n")
} else {
fmt.Printf(" already exists.\n")
}
}
func populateCache(p *EndpointTestIntegration) {
p.CrioRoot = p.ImageCacheDir
p.StartVarlink()
for _, image := range CACHE_IMAGES {
p.RestoreArtifactToCache(image)
}
p.StopVarlink()
}
func (p *EndpointTestIntegration) RestoreArtifactToCache(image string) error {
fmt.Printf("Restoring %s...\n", image)
dest := strings.Split(image, "/")
destName := fmt.Sprintf("/tmp/%s.tar", strings.Replace(strings.Join(strings.Split(dest[len(dest)-1], "/"), ""), ":", "-", -1))
//fmt.Println(destName, p.ImageCacheDir)
load := p.Varlink("LoadImage", fmt.Sprintf("{\"name\": \"%s\", \"inputFile\": \"%s\"}", image, destName), false)
Expect(load.ExitCode()).To(BeZero())
return nil
}
func (p *EndpointTestIntegration) startTopContainer(name string) string {
t := true
args := iopodman.Create{
Args: []string{"docker.io/library/alpine:latest", "top"},
Tty: &t,
Detach: &t,
}
if len(name) > 0 {
args.Name = &name
}
b, err := json.Marshal(args)
if err != nil {
ginkgo.Fail("failed to marshal data for top container")
}
input := fmt.Sprintf("{\"create\":%s}", string(b))
top := p.Varlink("CreateContainer", input, false)
if top.ExitCode() != 0 {
ginkgo.Fail("failed to start top container")
}
start := p.Varlink("StartContainer", fmt.Sprintf("{\"name\":\"%s\"}", name), false)
if start.ExitCode() != 0 {
ginkgo.Fail("failed to start top container")
}
return start.OutputToString()
}

View File

@ -0,0 +1,41 @@
package endpoint
import (
"os"
. "github.com/containers/libpod/test/utils"
"github.com/containers/libpod/version"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Podman version", func() {
var (
tempdir string
err error
endpointTest *EndpointTestIntegration
)
BeforeEach(func() {
tempdir, err = CreateTempDirInTempDir()
if err != nil {
os.Exit(1)
}
endpointTest = Setup(tempdir)
endpointTest.StartVarlink()
})
AfterEach(func() {
endpointTest.Cleanup()
//f := CurrentGinkgoTestDescription()
//processTestResult(f)
})
It("podman version", func() {
session := endpointTest.Varlink("GetVersion", "", false)
result := session.OutputToStringMap()
Expect(result["version"]).To(Equal(version.Version))
Expect(session.ExitCode()).To(Equal(0))
})
})