Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add first crictl e2e test framework and suite #529

Merged
merged 1 commit into from
Sep 10, 2019
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
22 changes: 22 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ before_install:
fi
)


install:
- make install.tools

Expand Down Expand Up @@ -64,6 +65,27 @@ jobs:
- powershell -c "Set-ExecutionPolicy Bypass -Scope CURRENTUSER -Force"
- travis_wait powershell hack/install-kubelet.ps1
# Skip hack/run-critest.sh temporarily.
- stage: Test
name: crictl e2e
os: linux
script:
- |
sudo apt-get update &&\
sudo apt-get install -y libseccomp-dev
- |
VERSION=v0.8.2 &&\
sudo mkdir -p /opt/cni/bin &&\
sudo wget -qO- https://github.com/containernetworking/plugins/releases/download/$VERSION/cni-plugins-linux-amd64-$VERSION.tgz \
| sudo tar xfz - -C /opt/cni/bin &&\
ls -lah /opt/cni/bin
- |
VERSION=v1.0.0-rc8 &&\
sudo wget -q -O \
/usr/bin/runc \
https://github.com/opencontainers/runc/releases/download/$VERSION/runc.amd64 &&\
sudo chmod +x /usr/bin/runc &&\
runc --version
- sudo -E env "PATH=$PATH" make all install test-e2e TESTFLAGS=-v

stages:
- Static check
Expand Down
10 changes: 10 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ $(GINKGO):
release:
hack/release.sh

# needs to run as root to work
test-e2e: $(GINKGO)
$(GINKGO) $(TESTFLAGS) \
-r -p \
--randomizeAllSpecs \
--randomizeSuites \
--succinct \
test

vendor:
export GO111MODULE=on \
$(GO) mod tidy && \
Expand All @@ -116,4 +125,5 @@ vendor:
lint \
install.tools \
release \
test-e2e \
vendor
44 changes: 44 additions & 0 deletions test/e2e/help_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package e2e

import (
. "github.com/onsi/ginkgo"
)

// The actual test suite
var _ = t.Describe("help", func() {

const helpMessageIdentifier = "crictl - client for CRI"

It("should succeed with `help` subcommand", func() {
t.CrictlExpectSuccess("help", helpMessageIdentifier)
})

It("should succeed with `--help` flag", func() {
t.CrictlExpectSuccess("--help", helpMessageIdentifier)
})

It("should succeed with `-h` flag", func() {
t.CrictlExpectSuccess("-h", helpMessageIdentifier)
})

It("should show help on invalid flag", func() {
t.CrictlExpectFailure("--invalid", helpMessageIdentifier,
"flag provided but not defined")
})
})
47 changes: 47 additions & 0 deletions test/e2e/suite_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package e2e

import (
"testing"

. "github.com/kubernetes-sigs/cri-tools/test/framework"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)

// TestE2E runs the created specs
func TestE2E(t *testing.T) {
RegisterFailHandler(Fail)
RunSpecs(t, "e2e")
}

var t *TestFramework

var _ = SynchronizedBeforeSuite(func() []byte {
// Setup only once
dir := SetupCrio()
return []byte(dir)

}, func(dir []byte) {
t = NewTestFramework()
t.Setup(string(dir))
})

var _ = AfterSuite(func() {
t.Teardown()
})
35 changes: 35 additions & 0 deletions test/e2e/version_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
Copyright 2017 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package e2e

import (
. "github.com/onsi/ginkgo"
)

// The actual test suite
var _ = t.Describe("version", func() {
It("should succeed", func() {
// Given
endpoint, testDir, crio := t.StartCrio()

// When
t.CrictlExpectSuccessWithEndpoint(endpoint, "version", "RuntimeName: cri-o")

// Then
t.StopCrio(testDir, crio)
})
})
199 changes: 199 additions & 0 deletions test/framework/framework.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,199 @@
/*
Copyright 2017 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package framework

import (
"fmt"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"time"

. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
. "github.com/onsi/gomega/gbytes"
. "github.com/onsi/gomega/gexec"
"github.com/sirupsen/logrus"
)

// TestFramework is used to support commonly used test features
type TestFramework struct {
crioDir string
}

// NewTestFramework creates a new test framework instance
func NewTestFramework() *TestFramework {
return &TestFramework{""}
}

// Setup is the global initialization function which runs before each test
// suite
func (t *TestFramework) Setup(dir string) {
// Global initialization for the whole framework goes in here
logrus.SetLevel(logrus.DebugLevel)
logrus.SetOutput(GinkgoWriter)
t.crioDir = dir
}

// Teardown is the global deinitialization function which runs after each test
// suite
func (t *TestFramework) Teardown() {
}

// Describe is a convenience wrapper around the `ginkgo.Describe` function
func (t *TestFramework) Describe(text string, body func()) bool {
return Describe("crictl: "+text, body)
}

// Convenience method for command creation
func cmd(workDir, format string, args ...interface{}) *Session {
c := strings.Split(fmt.Sprintf(format, args...), " ")
command := exec.Command(c[0], c[1:]...)
if workDir != "" {
command.Dir = workDir
}

session, err := Start(command, GinkgoWriter, GinkgoWriter)
Expect(err).To(BeNil())

return session
}

// Convenience method for command creation in the current working directory
func lcmd(format string, args ...interface{}) *Session {
return cmd("", format, args...)
}

// Run crictl and return the resulting session
func (t *TestFramework) Crictl(args string) *Session {
return lcmd("crictl %s", args).Wait()
}

// Run crictl on the specified endpoint and return the resulting session
func (t *TestFramework) CrictlWithEndpoint(endpoint, args string) *Session {
return lcmd("crictl --runtime-endpoint=%s %s", endpoint, args).Wait()
}

// Run crictl and expect success containing the specified output
func (t *TestFramework) CrictlExpectSuccess(args, expectedOut string) {
t.CrictlExpectSuccessWithEndpoint("", args, expectedOut)
}

// Run crictl and expect success containing the specified output
func (t *TestFramework) CrictlExpectSuccessWithEndpoint(endpoint, args, expectedOut string) {
// When
res := t.CrictlWithEndpoint(endpoint, args)

// Then
Expect(res).To(Exit(0))
Expect(res.Out).To(Say(expectedOut))
Expect(res.Err.Contents()).To(BeEmpty())
}

// Run crictl and expect error containing the specified outputs
func (t *TestFramework) CrictlExpectFailure(
args string, expectedOut, expectedErr string,
) {
// When
res := t.Crictl(args)

// Then
Expect(res).To(Exit(1))
Expect(res.Out).To(Say(expectedOut))
Expect(res.Err).To(Say(expectedErr))
}

func SetupCrio() string {
const (
crioURL = "https://github.com/cri-o/cri-o"
timeout = 10 * time.Minute
)
tmpDir := filepath.Join(os.TempDir(), "crio-tmp")

if _, err := os.Stat(tmpDir); os.IsNotExist(err) {
logrus.Info("cloning and building CRI-O")
lcmd("git clone --depth=1 %s %s", crioURL, tmpDir).Wait(timeout)
cmd(tmpDir, "make").Wait(timeout)
}

return tmpDir
}

// Start the container runtime process
func (t *TestFramework) StartCrio() (string, string, *Session) {
// Create a new sandbox directory
tmpDir, err := ioutil.TempDir("", "crictl-e2e-")
Expect(err).To(BeNil())

// Copy everything together
lcmd("cp -R %s %s", filepath.Join(t.crioDir, "bin"), tmpDir).Wait()

lcmd("cp %s %s", filepath.Join(t.crioDir, "test", "policy.json"),
tmpDir).Wait()

lcmd("cp %s %s", filepath.Join(t.crioDir, "contrib", "cni",
"10-crio-bridge.conf"), tmpDir).Wait()

for _, d := range []string{
"cni-config", "root", "runroot", "log", "exits", "attach",
} {
Expect(os.MkdirAll(filepath.Join(tmpDir, d), 0755)).To(BeNil())
}

endpoint := filepath.Join(tmpDir, "crio.sock")

session := cmd(tmpDir, "%s"+
" --listen=%s"+
" --conmon=%s"+
" --container-exits-dir=%s"+
" --container-attach-socket-dir=%s"+
" --log-dir=%s"+
" --signature-policy=%s"+
" --cni-config-dir=%s"+
" --root=%s"+
" --runroot=%s"+
" --storage-driver=vfs",
filepath.Join(tmpDir, "bin", "crio"),
endpoint,
filepath.Join(tmpDir, "bin", "conmon"),
filepath.Join(tmpDir, "exits"),
filepath.Join(tmpDir, "attach"),
filepath.Join(tmpDir, "log"),
filepath.Join(tmpDir, "policy.json"),
filepath.Join(tmpDir, "cni-config"),
filepath.Join(tmpDir, "root"),
filepath.Join(tmpDir, "runroot"),
)

// Wait for the connection to be available
for i := 0; i < 100; i++ {
res := t.CrictlWithEndpoint(endpoint, "--timeout=200ms info")
if res.ExitCode() == 0 {
break
}
logrus.Info("waiting for CRI-O to become ready")
}
return endpoint, tmpDir, session
}

// Stop the container runtime process
func (t *TestFramework) StopCrio(testDir string, session *Session) {
Expect(session.Interrupt().Wait()).To(Exit(0))
Expect(os.RemoveAll(testDir)).To(BeNil())
}
Loading