diff options
10 files changed, 232 insertions, 0 deletions
diff --git a/test-requirements.txt b/test-requirements.txt index 585cca0b9..06883390b 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -12,3 +12,4 @@ coverage==4.3.4 mock==2.0.0 pytest==3.0.7 pytest-cov==2.4.0 +docker-py==1.10.6 diff --git a/test/integration/README.md b/test/integration/README.md new file mode 100644 index 000000000..0edbccd74 --- /dev/null +++ b/test/integration/README.md @@ -0,0 +1,12 @@ +# Integration tests + +Integration tests exercise the OpenShift Ansible playbooks by performing +simulated installations in Docker containers. + +## Running the tests + +From the repository root, run with: + +``` +tox -e integration +``` diff --git a/test/integration/openshift_health_checker/common.go b/test/integration/openshift_health_checker/common.go new file mode 100644 index 000000000..aea85342f --- /dev/null +++ b/test/integration/openshift_health_checker/common.go @@ -0,0 +1,99 @@ +package test + +import ( + "bytes" + "os" + "os/exec" + "path/filepath" + "strings" + "syscall" + "testing" +) + +// A PlaybookTest executes a given Ansible playbook and checks the exit code and +// output contents. +type PlaybookTest struct { + // inputs + Path string + // expected outputs + ExitCode int + Output []string // zero or more strings that should be in the output +} + +// Run runs the PlaybookTest. +func (p PlaybookTest) Run(t *testing.T) { + // A PlaybookTest is intended to be run in parallel with other tests. + t.Parallel() + + cmd := exec.Command("ansible-playbook", p.Path) + cmd.Env = append(os.Environ(), "ANSIBLE_FORCE_COLOR=1") + b, err := cmd.CombinedOutput() + + // Check exit code. + if (err == nil) && (p.ExitCode != 0) { + p.checkExitCode(t, 0, p.ExitCode, cmd, b) + } + if (err != nil) && (p.ExitCode == 0) { + got, ok := getExitCode(err) + if !ok { + t.Logf("unexpected error (%T): %[1]v", err) + p.logCmdAndOutput(t, cmd, b) + t.FailNow() + } + p.checkExitCode(t, got, p.ExitCode, cmd, b) + } + + // Check output contents. + var missing []string + for _, s := range p.Output { + if !bytes.Contains(b, []byte(s)) { + missing = append(missing, s) + } + } + if len(missing) > 0 { + t.Logf("missing in output: %q", missing) + p.logCmdAndOutput(t, cmd, b) + t.FailNow() + } +} + +// getExitCode returns an exit code and true if the exit code could be taken +// from err, false otherwise. +// The implementation is GOOS-specific, and currently only supports Linux. +func getExitCode(err error) (int, bool) { + exitErr, ok := err.(*exec.ExitError) + if !ok { + return -1, false + } + waitStatus, ok := exitErr.Sys().(syscall.WaitStatus) + if !ok { + return -1, false + } + return waitStatus.ExitStatus(), true +} + +// checkExitCode marks the test as failed when got is different than want. +func (p PlaybookTest) checkExitCode(t *testing.T, got, want int, cmd *exec.Cmd, output []byte) { + if got == want { + return + } + t.Logf("got exit code %v, want %v", got, want) + p.logCmdAndOutput(t, cmd, output) + t.FailNow() +} + +// logCmdAndOutput logs how to re-run a command and a summary of the output of +// its last execution for debugging. +func (p PlaybookTest) logCmdAndOutput(t *testing.T, cmd *exec.Cmd, output []byte) { + const maxLines = 10 + lines := bytes.Split(bytes.TrimRight(output, "\n"), []byte("\n")) + if len(lines) > maxLines { + lines = append([][]byte{[]byte("...")}, lines[len(lines)-maxLines:len(lines)]...) + } + output = bytes.Join(lines, []byte("\n")) + dir, err := filepath.Abs(cmd.Dir) + if err != nil { + panic(err) + } + t.Logf("\n$ (cd %s && %s)\n%s", dir, strings.Join(cmd.Args, " "), output) +} diff --git a/test/integration/openshift_health_checker/example/example_test.go b/test/integration/openshift_health_checker/example/example_test.go new file mode 100644 index 000000000..f59c21291 --- /dev/null +++ b/test/integration/openshift_health_checker/example/example_test.go @@ -0,0 +1,26 @@ +package example + +import ( + "testing" + + . ".." +) + +// TestPing and TestFail below are just examples of tests that involve running +// 'ansible-playbook' with a given playbook and verifying the outcome. Real +// tests look similar, but call more interesting playbooks. + +func TestPing(t *testing.T) { + PlaybookTest{ + Path: "playbooks/test_ping.yml", + Output: []string{"[test ping]"}, + }.Run(t) +} + +func TestFail(t *testing.T) { + PlaybookTest{ + Path: "playbooks/test_fail.yml", + ExitCode: 2, + Output: []string{"[test fail]", `"msg": "Failed as requested from task"`}, + }.Run(t) +} diff --git a/test/integration/openshift_health_checker/example/playbooks/test_fail.yml b/test/integration/openshift_health_checker/example/playbooks/test_fail.yml new file mode 100644 index 000000000..318f1c507 --- /dev/null +++ b/test/integration/openshift_health_checker/example/playbooks/test_fail.yml @@ -0,0 +1,14 @@ +--- +# This is just a placeholder playbook. Our aim is to make it: +# 1. Build one or more Docker images with a certain interesting state; +# 2. Ensure one or more containers (with random names) are running with the +# latest build of the image; +# 3. Run the byo OpenShift installation playbook targeting the container. +- hosts: localhost + gather_facts: no + tasks: + - name: waste some time + pause: + seconds: 1 + - name: test fail + fail: diff --git a/test/integration/openshift_health_checker/example/playbooks/test_ping.yml b/test/integration/openshift_health_checker/example/playbooks/test_ping.yml new file mode 100644 index 000000000..da31b3d85 --- /dev/null +++ b/test/integration/openshift_health_checker/example/playbooks/test_ping.yml @@ -0,0 +1,14 @@ +--- +# This is just a placeholder playbook. Our aim is to make it: +# 1. Build one or more Docker images with a certain interesting state; +# 2. Ensure one or more containers (with random names) are running with the +# latest build of the image; +# 3. Run the byo OpenShift installation playbook targeting the container. +- hosts: localhost + gather_facts: no + tasks: + - name: waste some time + pause: + seconds: 1 + - name: test ping + ping: diff --git a/test/integration/openshift_health_checker/preflight/playbooks/preflight_fail_all.yml b/test/integration/openshift_health_checker/preflight/playbooks/preflight_fail_all.yml new file mode 100644 index 000000000..e7790a0d4 --- /dev/null +++ b/test/integration/openshift_health_checker/preflight/playbooks/preflight_fail_all.yml @@ -0,0 +1,11 @@ +--- +- include: setup_container.yml + vars: + name: preflight_fail_all + +- name: Run preflight checks + include: ../../../../../playbooks/byo/openshift-preflight/check.yml + +# - include: tasks/teardown_container.yml +# vars: +# name: preflight_fail_all diff --git a/test/integration/openshift_health_checker/preflight/playbooks/setup_container.yml b/test/integration/openshift_health_checker/preflight/playbooks/setup_container.yml new file mode 100644 index 000000000..fff797c27 --- /dev/null +++ b/test/integration/openshift_health_checker/preflight/playbooks/setup_container.yml @@ -0,0 +1,23 @@ +--- +# Required vars: +# * name = name of the container to be started + +- name: Start CentOS 7 container + gather_facts: no + hosts: localhost + connection: local + vars: + container_name: openshift_ansible_test_{{ name }} + tasks: + - name: start container + docker_container: + name: "{{ container_name }}" + image: centos:7 + command: sleep infinity + recreate: yes + - name: add host + add_host: + name: "{{ container_name }}" + ansible_connection: docker + groups: OSEv3,masters,nodes + deployment_type: origin diff --git a/test/integration/openshift_health_checker/preflight/preflight_test.go b/test/integration/openshift_health_checker/preflight/preflight_test.go new file mode 100644 index 000000000..a1b98bf0f --- /dev/null +++ b/test/integration/openshift_health_checker/preflight/preflight_test.go @@ -0,0 +1,24 @@ +package preflight + +import ( + "testing" + + . ".." +) + +func TestPreflightFailAll(t *testing.T) { + PlaybookTest{ + Path: "playbooks/preflight_fail_all.yml", + ExitCode: 2, + Output: []string{ + "Failure summary", + "Cannot install all of the necessary packages", + "origin-clients", + "origin-master", + "origin-node", + "origin-sdn-ovs", + "python-httplib2", + "failed=1", + }, + }.Run(t) +} @@ -3,6 +3,7 @@ minversion=2.3.1 envlist = py{27,35}-{flake8,pylint,unit} py27-{yamllint,ansible_syntax,generate_validation} + integration skipsdist=True skip_missing_interpreters=True @@ -13,6 +14,8 @@ deps = -rtest-requirements.txt py35-flake8: flake8-bugbear==17.3.0 +whitelist_externals = env + commands = unit: pip install -e utils unit: pytest {posargs} @@ -22,3 +25,8 @@ commands = generate_validation: python setup.py generate_validation # TODO(rhcarvalho): check syntax of other important entrypoint playbooks ansible_syntax: python setup.py ansible_syntax + + # Unset GOPATH because tests use relative imports. This should be removed if + # we require openshift-ansible to live in a Go work space and use absolute + # imports in tests (desirable). + integration: env -u GOPATH go test -v ./test/integration/... |