From ff2f35cb8a849e1a7ee3ddf60760c825bc88ef10 Mon Sep 17 00:00:00 2001 From: Till Maas Date: Fri, 18 May 2018 19:38:23 +0200 Subject: [PATCH 1/6] Add helper tasks for integration tests --- tests/run-tasks.yml | 6 ++++ tests/tasks/assert-device_absent.yml | 7 ++++ tests/tasks/assert-device_present.yml | 8 +++++ tests/tasks/create-and-remove-interface.yml | 20 +++++++++++ tests/tasks/make-profile-absent.yml | 10 ++++++ tests/tasks/make-profile-down.yml | 10 ++++++ tests/tasks/manage-test-interface.yml | 39 +++++++++++++++++++++ tests/tasks/show-interfaces.yml | 4 +++ tests/tests_helpers-and-asserts.yml | 24 +++++++++++++ 9 files changed, 128 insertions(+) create mode 100644 tests/run-tasks.yml create mode 100644 tests/tasks/assert-device_absent.yml create mode 100644 tests/tasks/assert-device_present.yml create mode 100644 tests/tasks/create-and-remove-interface.yml create mode 100644 tests/tasks/make-profile-absent.yml create mode 100644 tests/tasks/make-profile-down.yml create mode 100644 tests/tasks/manage-test-interface.yml create mode 100644 tests/tasks/show-interfaces.yml create mode 100644 tests/tests_helpers-and-asserts.yml diff --git a/tests/run-tasks.yml b/tests/run-tasks.yml new file mode 100644 index 0000000..ea56720 --- /dev/null +++ b/tests/run-tasks.yml @@ -0,0 +1,6 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- name: Run the tasklist {{ task }} + hosts: all + tasks: + - include_tasks: "{{ task }}" diff --git a/tests/tasks/assert-device_absent.yml b/tests/tasks/assert-device_absent.yml new file mode 100644 index 0000000..06be2b6 --- /dev/null +++ b/tests/tasks/assert-device_absent.yml @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- setup: +- assert: + name: "assert that {{ interface }} is absent" + that: "{{ not interface in ansible_interfaces }}" + msg: "{{ interface }} is in ansible_interfaces: {{ ansible_interfaces }}" diff --git a/tests/tasks/assert-device_present.yml b/tests/tasks/assert-device_present.yml new file mode 100644 index 0000000..5c09bd3 --- /dev/null +++ b/tests/tasks/assert-device_present.yml @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +# need to run setup again when this is just a task to make sure ansible_interfaces is correct +- setup: +- assert: + name: "assert that {{ interface }} is present" + that: "{{ interface in ansible_interfaces }}" + msg: "{{ interface }} is not in ansible_interfaces: {{ ansible_interfaces }}" diff --git a/tests/tasks/create-and-remove-interface.yml b/tests/tasks/create-and-remove-interface.yml new file mode 100644 index 0000000..9bebf6e --- /dev/null +++ b/tests/tasks/create-and-remove-interface.yml @@ -0,0 +1,20 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- include_tasks: show-interfaces.yml +- include_tasks: manage-test-interface.yml + vars: + state: absent +- include_tasks: show-interfaces.yml +- include_tasks: assert-device_absent.yml + +- include_tasks: manage-test-interface.yml + vars: + state: present +- include_tasks: show-interfaces.yml +- include_tasks: assert-device_present.yml + +- include_tasks: manage-test-interface.yml + vars: + state: absent +- include_tasks: show-interfaces.yml +- include_tasks: assert-device_absent.yml diff --git a/tests/tasks/make-profile-absent.yml b/tests/tasks/make-profile-absent.yml new file mode 100644 index 0000000..95496aa --- /dev/null +++ b/tests/tasks/make-profile-absent.yml @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- name: Remove {{ profile }} + hosts: all + vars: + network_connections: + - name: "{{ profile }}" + state: absent + roles: + - linux-system-roles.network diff --git a/tests/tasks/make-profile-down.yml b/tests/tasks/make-profile-down.yml new file mode 100644 index 0000000..5087240 --- /dev/null +++ b/tests/tasks/make-profile-down.yml @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- name: Set {{ profile }} down + hosts: all + vars: + network_connections: + - name: "{{ profile }}" + state: down + roles: + - linux-system-roles.network diff --git a/tests/tasks/manage-test-interface.yml b/tests/tasks/manage-test-interface.yml new file mode 100644 index 0000000..6d6add8 --- /dev/null +++ b/tests/tasks/manage-test-interface.yml @@ -0,0 +1,39 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- fail: + msg: "state needs to be present or absent, not '{{ state }}'" + when: state not in ["present", "absent"] + +- fail: + msg: "type needs to be dummy, tap or veth, not '{{ type }}'" + when: type not in ["dummy", "tap", "veth"] + +# run setup again to make sure ansible_interfaces is accurate +- setup: + + # veth +- name: Create veth interface {{ interface }} + shell: ip link add {{ interface }} type veth peer name peer{{ interface }} + when: "type == 'veth' and state == 'present' and interface not in ansible_interfaces" + +- name: Delete veth interface {{ interface }} + shell: ip link del {{ interface }} type veth + when: "type == 'veth' and state == 'absent' and interface in ansible_interfaces" + + # dummy +- name: Create dummy interface {{ interface }} + shell: ip link add "{{ interface }}" type dummy + when: "type == 'dummy' and state == 'present' and interface not in ansible_interfaces" + +- name: Delete dummy interface {{ interface }} + shell: ip link del "{{ interface }}" type dummy + when: "type == 'dummy' and state == 'absent' and interface in ansible_interfaces" + + # tap +- name: Create tap interface {{ interface }} + shell: ip tuntap add dev {{ interface }} mode tap + when: "type == 'tap' and state == 'present' and interface not in ansible_interfaces" + +- name: Delete tap interface {{ interface }} + shell: ip tuntap del dev {{ interface }} mode tap + when: "type == 'tap' and state == 'absent' and interface in ansible_interfaces" diff --git a/tests/tasks/show-interfaces.yml b/tests/tasks/show-interfaces.yml new file mode 100644 index 0000000..a4b8c44 --- /dev/null +++ b/tests/tasks/show-interfaces.yml @@ -0,0 +1,4 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- debug: + msg: "ansible_interfaces: {{ ansible_interfaces }}" diff --git a/tests/tests_helpers-and-asserts.yml b/tests/tests_helpers-and-asserts.yml new file mode 100644 index 0000000..33f3e84 --- /dev/null +++ b/tests/tests_helpers-and-asserts.yml @@ -0,0 +1,24 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- name: Check that creating and removing test devices and assertions work + hosts: all + tasks: + - name: test veth interface management + include_tasks: tasks/create-and-remove-interface.yml + vars: + type: veth + interface: veth1298 + + - name: test veth interface management + include_tasks: tasks/create-and-remove-interface.yml + vars: + type: dummy + interface: dummy1298 + +# FIXME: when: does not seem to work with include_tasks, therefore this cannot be safely tested for now +# - name: test tap interfaces +# include_tasks: tasks/create-and-remove-interface.yml type=tap interface=tap1298 +# when: ansible_distribution_major_version > 6 +# # ip tuntap does not exist on RHEL6 +# # FIXME: Maybe use some other tool to manage devices, openvpn can do this, +# # but it is in EPEL From 640e11001ec4c31f36cbd4075f1082b9e2cf6b25 Mon Sep 17 00:00:00 2001 From: Till Maas Date: Fri, 18 May 2018 22:46:33 +0200 Subject: [PATCH 2/6] Add examples to remove or take down a profile --- examples/down-profile.yml | 10 ++++++++++ examples/remove-profile.yml | 10 ++++++++++ 2 files changed, 20 insertions(+) create mode 100644 examples/down-profile.yml create mode 100644 examples/remove-profile.yml diff --git a/examples/down-profile.yml b/examples/down-profile.yml new file mode 100644 index 0000000..5087240 --- /dev/null +++ b/examples/down-profile.yml @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- name: Set {{ profile }} down + hosts: all + vars: + network_connections: + - name: "{{ profile }}" + state: down + roles: + - linux-system-roles.network diff --git a/examples/remove-profile.yml b/examples/remove-profile.yml new file mode 100644 index 0000000..95496aa --- /dev/null +++ b/examples/remove-profile.yml @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- name: Remove {{ profile }} + hosts: all + vars: + network_connections: + - name: "{{ profile }}" + state: absent + roles: + - linux-system-roles.network From 910f39966525848e4f26a65e2bb4db51452dd96c Mon Sep 17 00:00:00 2001 From: Till Maas Date: Fri, 18 May 2018 23:00:59 +0200 Subject: [PATCH 3/6] Add helper scripts to get coverage data --- .gitignore | 7 ++- tests/ansible_module_network_connections.py | 1 + tests/covstats | 16 +++++ tests/get-coverage.sh | 68 +++++++++++++++++++++ tests/get-coverage.yml | 66 ++++++++++++++++++++ tests/get-total-coverage.sh | 34 +++++++++++ tests/merge-coverage.sh | 35 +++++++++++ 7 files changed, 225 insertions(+), 2 deletions(-) create mode 120000 tests/ansible_module_network_connections.py create mode 100755 tests/covstats create mode 100755 tests/get-coverage.sh create mode 100644 tests/get-coverage.yml create mode 100755 tests/get-total-coverage.sh create mode 100755 tests/merge-coverage.sh diff --git a/.gitignore b/.gitignore index b751a3d..086289b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,10 @@ -*.pyc /.image-cache/ +*.pyc /.pytest_cache /tests/.coverage -/tests/htmlcov-py*/ +/tests/htmlcov* /tests/__pycache__/ +/tests/remote-coveragedata-* +/tests/tmp_merge_coveragerc +/tests/total-*coveragedata /.tox diff --git a/tests/ansible_module_network_connections.py b/tests/ansible_module_network_connections.py new file mode 120000 index 0000000..b30a744 --- /dev/null +++ b/tests/ansible_module_network_connections.py @@ -0,0 +1 @@ +roles/linux-system-roles.network/library/network_connections.py \ No newline at end of file diff --git a/tests/covstats b/tests/covstats new file mode 100755 index 0000000..310327d --- /dev/null +++ b/tests/covstats @@ -0,0 +1,16 @@ +#! /bin/bash +# SPDX-License-Identifier: BSD-3-Clause + +if [ "$#" -lt 1 ] +then + echo "USAGE: ${0} coverage_data_file..." + echo "Show Statistics for each coverage data file" + exit 1 +fi + +for coverage_file in "${@}" +do + echo "coverage statistics for: ${coverage_file}:" + COVERAGE_FILE="${coverage_file}" coverage report + echo +done diff --git a/tests/get-coverage.sh b/tests/get-coverage.sh new file mode 100755 index 0000000..7b6cd21 --- /dev/null +++ b/tests/get-coverage.sh @@ -0,0 +1,68 @@ +#! /bin/bash +# SPDX-License-Identifier: BSD-3-Clause + +if [ -n "${DEBUG}" ] +then + set -x +fi +set -e + +if [ "$#" -lt 2 ] +then + echo "USAGE: ${0} host playbook" + echo "Get coverage info from host for playbook" + exit 1 +fi + +host="${1}" +shift +playbook="${1}" + +coverage_data="remote-coveragedata-${host}-${playbook%.yml}" +coverage="/root/.local/bin/coverage" + +echo "Getting coverage for ${playbook} on ${host}" >&2 + +call_ansible() { + local module="${1}" + shift + local args="${1}" + shift + ansible -m "${module}" -i "${host}", -a "${args}" all "${@}" +} + +remote_coverage_dir="$(mktemp -d /tmp/remote_coverage-XXXXXX)" +trap "rm -rf '${remote_coverage_dir}'" EXIT +ansible-playbook -i "${host}", get-coverage.yml -e "test_playbook=${playbook} destdir=${remote_coverage_dir}" + +#COVERAGE_FILE=remote-coverage coverage combine remote-coverage/tests_*/*/root/.coverage +./merge-coverage.sh coverage "${coverage_data}"-tmp $(find "${remote_coverage_dir}" -type f | tr , _) + +# When https://github.com/nedbat/coveragepy/pull/49 is merged, this can be simplified: +if false +then +cat > tmp_merge_coveragerc < tmp_merge_coveragerc <> tmp_merge_coveragerc +done +fi + +COVERAGE_FILE="${coverage_data}" coverage combine --rcfile tmp_merge_coveragerc "${coverage_data}"-tmp +rm tmp_merge_coveragerc + +COVERAGE_FILE="${coverage_data}" coverage report ||: +COVERAGE_FILE="${coverage_data}" coverage html --directory "htmlcov-${coverage_data}" ||: + +echo "Coverage collected in: ${coverage_data}" diff --git a/tests/get-coverage.yml b/tests/get-coverage.yml new file mode 100644 index 0000000..4845c62 --- /dev/null +++ b/tests/get-coverage.yml @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +# This expects the variable test_playbook to be set from the outside +- name: Prepare for coverage extraction + hosts: all + tasks: + # Use set_fact to set variables to make them available in all plays + # 'vars:' Would only set variables for the current play + - name: set facts + set_fact: + coverage_module: network_connections + coverage: /root/.local/bin/coverage + destdir: "remote_coverage/{{ test_playbook }}" + + # This uses variables from the other set_fact task, therefore it needs to + # be its own task + - name: set more facts + set_fact: + coverage_file: ansible-coverage-{{ coverage_module }}-{{ test_playbook|replace('.yml', '') }} + + - name: debug info + debug: + msg: Getting coverage for '{{ coverage_module }}' with '{{ test_playbook }}' + + # combine data in case old data is left there + - command: "{{ coverage }} combine" + environment: + COVERAGE_FILE: "{{ coverage_file }}" + ignore_errors: yes + + - name: remove old data + file: + state: absent + path: "{{ coverage_file }}" + + - name: remove old data + shell: rm -f .coverage.* + + - name: copy coveragerc + copy: + content: "[run]\ndisable_warnings = no-data-collected\n" + dest: .coveragerc + + - name: install latest pip + pip: + name: coverage + extra_args: --user --upgrade + +- import_playbook: "{{ test_playbook }}" + vars: + ansible_python_interpreter: "{{ coverage }} run -p --include *ansible_module_{{ coverage_module }}.py" + +- name: Gather coverage data + hosts: all + tasks: + - shell: "{{ coverage }} combine .coverage.*" + environment: + COVERAGE_FILE: "{{ coverage_file }}" + +- name: Get coverage data + hosts: all + tasks: + - fetch: + src: "{{ coverage_file }}" + dest: "{{ destdir }}" + flat: no diff --git a/tests/get-total-coverage.sh b/tests/get-total-coverage.sh new file mode 100755 index 0000000..c3dacfe --- /dev/null +++ b/tests/get-total-coverage.sh @@ -0,0 +1,34 @@ +#! /bin/bash +# SPDX-License-Identifier: BSD-3-Clause + +set -e +coverage_data=total-coveragedata +testhost="${1}" + +if [ "$#" -lt 1 ] +then + echo "USAGE: ${0} host" + echo "Get local and all remote coverage data for host" + exit 1 +fi + +rm -f remote-coveragedata* "${coveragedata}" + + +# collect pytest coverage +tox -e py26,py27,py36,py37 -- --cov-append + +for test_playbook in tests_*.yml +do + ./get-coverage.sh "${testhost}" "${test_playbook}" +done + +./merge-coverage.sh coverage "total-remote-coveragedata" remote-coveragedata-* +./covstats .coverage remote-coveragedata-* "total-remote-coveragedata" + +./merge-coverage.sh coverage "${coverage_data}" .coverage remote-coveragedata-* +echo "Total coverage:" +COVERAGE_FILE="${coverage_data}" coverage report ||: +COVERAGE_FILE="${coverage_data}" coverage html --directory "htmlcov-${coverage_data}" ||: +echo "Open HTML report with:" +echo "xdg-open htmlcov-${coverage_data}/index.html" diff --git a/tests/merge-coverage.sh b/tests/merge-coverage.sh new file mode 100755 index 0000000..a33e94d --- /dev/null +++ b/tests/merge-coverage.sh @@ -0,0 +1,35 @@ +#! /bin/bash +# SPDX-License-Identifier: BSD-3-Clause + +if [ -n "${DEBUG}" ] +then + set -x +fi +set -e + +if [ "$#" -lt 3 ] +then + echo "USAGE: ${0} path_to_coverage_binary output_file input_files..." + echo "Merges all input_files into output file without removing input_files" + exit 1 +fi + +# path to coverage binary +coverage="${1}" +shift + +# read by coverage binary +export COVERAGE_FILE="${1}" +shift + +tempdir="$(mktemp -d /tmp/coverage_merge-XXXXXX)" +trap "rm -rf '${tempdir}'" EXIT + +cp --backup=numbered -- "${@}" "${tempdir}" +# FIXME: Would not work if coverage files are not hidden but they are by +# default +shopt -s dotglob +"${coverage}" combine "${tempdir}/"* + +echo "Merged data into ${COVERAGE_FILE}" +./covstats "${COVERAGE_FILE}" From b01ab7fd505648d608e8d1c947e89e5e49338bb4 Mon Sep 17 00:00:00 2001 From: Till Maas Date: Fri, 18 May 2018 23:28:41 +0200 Subject: [PATCH 4/6] Add integration test for Ethernet device --- tests/tests_ethernet.yml | 46 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 tests/tests_ethernet.yml diff --git a/tests/tests_ethernet.yml b/tests/tests_ethernet.yml new file mode 100644 index 0000000..9461435 --- /dev/null +++ b/tests/tests_ethernet.yml @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: BSD-3-Clause +--- +- name: Test configuring ethernet devices + hosts: all + vars: + type: veth + interface: lsr27 + + tasks: + - name: "set type={{ type }} and interface={{ interface }}" + set_fact: + type: "{{ type }}" + interface: "{{ interface }}" + - include_tasks: tasks/show-interfaces.yml + - include_tasks: tasks/manage-test-interface.yml state=present + - include_tasks: tasks/assert-device_present.yml + +- name: Test static interface up + hosts: all + vars: + network_connections: + - name: "{{ interface }}" + interface_name: "{{ interface }}" + state: up + type: ethernet + autoconnect: yes + ip: + address: 192.0.27.1/24 + roles: + - linux-system-roles.network +# FIXME: assert profile present +# FIXME: assert profile/device up + IP address +# FIXME: state down does not work currently +#- import_playbook: down-profile.yml +# vars: +# profile: "{{ interface }}" +## FIXME: assert profile/device down +#- import_playbook: remove-profile.yml +# vars: +# profile: "{{ interface }}" +# FIXME: assert profile away +- name: Remove interfaces + hosts: all + tasks: + - include_tasks: tasks/manage-test-interface.yml state=absent + - include_tasks: tasks/assert-device_absent.yml From 75784ea5eddc3f4429564873b3b21dc204c6c643 Mon Sep 17 00:00:00 2001 From: Till Maas Date: Wed, 23 May 2018 19:28:55 +0200 Subject: [PATCH 5/6] Install python{,3}-gobject-base if required --- defaults/main.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/defaults/main.yml b/defaults/main.yml index a2930e1..d09c7b0 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -2,8 +2,13 @@ network_provider: "{{ network_provider_default }}" network_connections: [] +# The python-gobject-base packag depends on the python version and +# distribution: +# - python-gobject-base on RHEL7 (no python2-gobject-base :-/) +# - python-gobject-base or python2-gobject-base on Fedora 27 +# - python3-gobject-base on Fedora 28+ network_service_name_default_nm: NetworkManager -network_packages_default_nm: [ ethtool, NetworkManager ] +network_packages_default_nm: [ ethtool, NetworkManager, "python{{ ansible_python['version']['major'] | replace('2', '') }}-gobject-base"] network_service_name_default_initscripts: network network_packages_default_initscripts: [ ethtool ] From 05ae9bbc1ed6aaf268d1f517abeef285f837b1a7 Mon Sep 17 00:00:00 2001 From: Till Maas Date: Thu, 24 May 2018 15:15:38 +0200 Subject: [PATCH 6/6] Remove .image-cache from .gitignore It was used by an old version of the test harness --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 086289b..47d6cac 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -/.image-cache/ *.pyc /.pytest_cache /tests/.coverage