From 93929b09a3456b85dde64cf018e95dbc838faa36 Mon Sep 17 00:00:00 2001 From: Andreas Scheuring Date: Mon, 20 Feb 2017 13:00:12 +0100 Subject: [PATCH] Tests for guest image tools Adding Testcases for the functions used by guest-image-tools. The tests are executed by python and integrated into the py27 and py35 test jobs. Sometimes the guest image tools call external commands (like ip, zneconf,...). for this case dummy versions of those executeables are placed in a faked root directory. The test takes care of exectuing those commands in this directory, therefore the dummy (bash) executables are chosen over the real ones. However those dummy executeables cannot replace mocks where a lot of assertions can be done. As workaround the dummy executeable just check if it was called with a certain list of arguments. The test needs to exactly pass those arguments in order to succeed. Ideally those tests would be functional tests running on a real system z envrionement. Doing so we could verify if the real operations are working as well. Change-Id: I35d0840c2e4bb9d216d57a5927f20b3f0ff182c0 --- .../usr/bin/dpm_guest_image_tools_common | 30 +++- .../tests/unit/guest_image_tools/__init__.py | 0 .../root_dir_default/proc/cmdline | 1 + .../root_dir_default/sbin/ip | 19 ++ .../ccwgroup/devices/0.0.0001/.placeholder | 0 .../root_dir_default/znetconf | 6 + .../test_dpm_guest_image_tools.py | 162 ++++++++++++++++++ .../unit/guest_image_tools/test_wrapper.sh | 39 +++++ 8 files changed, 252 insertions(+), 5 deletions(-) create mode 100644 nova_dpm/tests/unit/guest_image_tools/__init__.py create mode 100644 nova_dpm/tests/unit/guest_image_tools/root_dir_default/proc/cmdline create mode 100755 nova_dpm/tests/unit/guest_image_tools/root_dir_default/sbin/ip create mode 100644 nova_dpm/tests/unit/guest_image_tools/root_dir_default/sys/bus/ccwgroup/devices/0.0.0001/.placeholder create mode 100755 nova_dpm/tests/unit/guest_image_tools/root_dir_default/znetconf create mode 100644 nova_dpm/tests/unit/guest_image_tools/test_dpm_guest_image_tools.py create mode 100755 nova_dpm/tests/unit/guest_image_tools/test_wrapper.sh diff --git a/guest_image_tools/usr/bin/dpm_guest_image_tools_common b/guest_image_tools/usr/bin/dpm_guest_image_tools_common index 83e41f1..febc8ec 100644 --- a/guest_image_tools/usr/bin/dpm_guest_image_tools_common +++ b/guest_image_tools/usr/bin/dpm_guest_image_tools_common @@ -41,6 +41,25 @@ function log { fi } +function _change_root { + # $1 = input path or command + # returns new path + local path=$1 + + if [ -z "$ROOT_DIR" ]; then + # Changing root dir not set + echo "$path" + return 0 + fi + + if [[ "$path" == /* ]]; then + echo "$ROOT_DIR$path" + else + # command called without a path + echo "$ROOT_DIR/$path" + fi +} + function extract_devno { # Extracts the device number out of a device path # $1 = the device path, e.g. "/devices/qeth/0.0.0001/net/enc1" @@ -133,8 +152,8 @@ function get_ip_cmd { local paths=("/usr/sbin/ip" "/sbin/ip") for path in "${paths[@]}"; do - local full_path=$ROOT_DIR$path - if [[ -x $full_path ]]; then + local full_path=$(_change_root "$path") + if [[ -x "$full_path" ]]; then echo "$full_path" return 0 fi @@ -180,7 +199,7 @@ function device_exists { local dev_bus_id="$1" # Check if device is already configured - path="/sys/bus/ccwgroup/devices/$dev_bus_id" + local path=$(_change_root "/sys/bus/ccwgroup/devices/$dev_bus_id") if ! [ -d "$path" ]; then return 1 fi @@ -195,11 +214,12 @@ function configure_device { # TODO(andreas_s): Do not depend on znetconf # Errors of the following command are written to stderr, and therefore # show up in the systemd units journal - znetconf -a $dev_bus_id -o portno=$port_no,layer2=1 + local cmd=$(_change_root "znetconf -a $dev_bus_id -o portno=$port_no,layer2=1") + eval "$cmd" return "$?" } function get_cmdline { #Example: "some stuff nics=0001,0,aabbccddeeff;abcd,1,001122334455;" - echo $(cat /proc/cmdline) + echo $(cat $(_change_root "/proc/cmdline")) } \ No newline at end of file diff --git a/nova_dpm/tests/unit/guest_image_tools/__init__.py b/nova_dpm/tests/unit/guest_image_tools/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/nova_dpm/tests/unit/guest_image_tools/root_dir_default/proc/cmdline b/nova_dpm/tests/unit/guest_image_tools/root_dir_default/proc/cmdline new file mode 100644 index 0000000..9091506 --- /dev/null +++ b/nova_dpm/tests/unit/guest_image_tools/root_dir_default/proc/cmdline @@ -0,0 +1 @@ +this-is-the-cmd-line \ No newline at end of file diff --git a/nova_dpm/tests/unit/guest_image_tools/root_dir_default/sbin/ip b/nova_dpm/tests/unit/guest_image_tools/root_dir_default/sbin/ip new file mode 100755 index 0000000..b160473 --- /dev/null +++ b/nova_dpm/tests/unit/guest_image_tools/root_dir_default/sbin/ip @@ -0,0 +1,19 @@ +#! /bin/bash + +# $ip_cmd link set $if_name address $mac +echo "foo" +if ! [[ "$1" == "link" ]]; then + exit 1 +fi + +if ! [[ "$2" == "set" ]]; then + exit 1 +fi + +# TODO: Check if_name + +if ! [[ "$4" == "address" ]]; then + exit 1 +fi + +# TODO: check_mac diff --git a/nova_dpm/tests/unit/guest_image_tools/root_dir_default/sys/bus/ccwgroup/devices/0.0.0001/.placeholder b/nova_dpm/tests/unit/guest_image_tools/root_dir_default/sys/bus/ccwgroup/devices/0.0.0001/.placeholder new file mode 100644 index 0000000..e69de29 diff --git a/nova_dpm/tests/unit/guest_image_tools/root_dir_default/znetconf b/nova_dpm/tests/unit/guest_image_tools/root_dir_default/znetconf new file mode 100755 index 0000000..f2e5c58 --- /dev/null +++ b/nova_dpm/tests/unit/guest_image_tools/root_dir_default/znetconf @@ -0,0 +1,6 @@ +#! /bin/bash + +# znetconf -a $dev_bus_id -o portno=$port,layer2=1 +if ! [[ "$@" == "-a 0.0.0001 -o portno=1,layer2=1" ]]; then + exit 1 +fi diff --git a/nova_dpm/tests/unit/guest_image_tools/test_dpm_guest_image_tools.py b/nova_dpm/tests/unit/guest_image_tools/test_dpm_guest_image_tools.py new file mode 100644 index 0000000..eb1925c --- /dev/null +++ b/nova_dpm/tests/unit/guest_image_tools/test_dpm_guest_image_tools.py @@ -0,0 +1,162 @@ +# Copyright 2017 IBM Corp. +# +# All Rights Reserved. +# +# 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. + +import os +from subprocess import PIPE +from subprocess import Popen + +from oslotest import base + +base_path = os.path.dirname(os.path.realpath(__file__)) +test_root_dir = base_path + "/root_dir_default" + + +class TestDPMGuestImageTools(base.BaseTestCase): + + def setUp(self): + super(TestDPMGuestImageTools, self).setUp() + # Change the root directory to the default root dir that contains the + # fake commands + self.env = dict(os.environ, ROOT_DIR=test_root_dir) + + def _execute_command(self, cmd): + proc = Popen(cmd, stdout=PIPE, env=self.env) + stdout, stderr = proc.communicate() + # convert stdout from byte to unicode. Required for python 3 + stdout = stdout.decode("utf-8") + self.stdout = stdout.strip("\n") + self.stderr = stderr + self.rc = proc.returncode + print(self.stdout) + + def _test_function(self, func_name, args): + """Calling a guest image tool bash function for test + + :param func_name: The bash function name to be called + :param args: List of arguments to be passed into the function + """ + tools_path =\ + "nova_dpm/tests/unit/guest_image_tools/test_wrapper.sh" + cmd = [tools_path, func_name] + args + self._execute_command(cmd) + + def _assert(self, rc, stdout="", stderr=None): + self.assertEqual(rc, self.rc) + self.assertEqual(stdout, self.stdout) + self.assertEqual(stderr, self.stderr) + + def test_extract_interface_name(self): + args = ["/devices/qeth/0.0.0001/net/enc1"] + self._test_function("extract_interface_name", args) + self._assert(0, stdout="enc1") + + def test_extract_interface_name_invalid_input(self): + args = ["foobar"] + self._test_function("extract_interface_name", args) + self._assert(1) + + def test_extract_devno(self): + args = ["/devices/qeth/0.0.0001/net/enc1"] + self._test_function("extract_devno", args) + self._assert(0, stdout="0001") + + def test_extract_devno_invalid_input(self): + args = ["foobar"] + self._test_function("extract_devno", args) + self._assert(1) + + def test_extract_mac(self): + args = ["0001", "foo 0001,0,aabbccddeeff;0004,1,112233445566;"] + self._test_function("extract_mac", args) + self._assert(0, stdout="aa:bb:cc:dd:ee:ff") + + def test_extract_mac_invalid_mac(self): + args = ["0001", "foo 0001,0,aabbccddeeffaa;"] + self._test_function("extract_mac", args) + self._assert(1) + + def test_extract_mac_not_found(self): + args = ["aaaa", "foo 0001,0,aabbccddeeff;0004,1,112233445566;"] + self._test_function("extract_mac", args) + self._assert(1) + + def test_is_locally_administered_mac_yes(self): + local_macs = ["0a0000000000", "020000000000", "060000000000", + "0e0000000000"] + for mac in local_macs: + self._test_function("is_locally_administered_mac", [mac]) + self._assert(0) + + def test_is_locally_administered_mac_no(self): + local_macs = ["010000000000", "030000000000", "040000000000", + "050000000000", "070000000000", "080000000000", + "090000000000", "0b0000000000", "0c0000000000", + "0d0000000000"] + for mac in local_macs: + self._test_function("is_locally_administered_mac", [mac]) + self._assert(1) + + def test_get_ip_cmd(self): + self._test_function("get_ip_cmd", []) + self._assert(0, test_root_dir + "/sbin/ip") + + def test_set_mac(self): + self._test_function("set_mac", ["eth0", "0a0000000000"]) + self._assert(0) + + def test_device_exists(self): + self._test_function("device_exists", ["0.0.0001"]) + self._assert(0) + + def test_device_exists_not(self): + self._test_function("device_exists", ["0.0.0002"]) + self._assert(1) + + def test_get_cmdline(self): + self._test_function("get_cmdline", []) + self._assert(0, "this-is-the-cmd-line") + + def test_configure_device(self): + self._test_function("configure_device", ["0.0.0001", "1"]) + self._assert(0) + + def test_configure_device_fail(self): + self._test_function("configure_device", ["foo", "1"]) + self._assert(1) + + def test_get_device_bus_id(self): + self._test_function("get_device_bus_id", ["0001"]) + self._assert(0, "0.0.0001") + + def test__change_root_cmd(self): + self._test_function("_change_root", ["cmd"]) + self._assert(0, test_root_dir + "/cmd") + + def test__change_root_cmd_no_root_dir_set(self): + # Unset the default root dir variable + self.env = dict() + self._test_function("_change_root", ["cmd"]) + self._assert(0, "cmd") + + def test__change_root_path(self): + self._test_function("_change_root", ["/foo/bar"]) + self._assert(0, test_root_dir + "/foo/bar") + + def test__change_root_path_no_root_dir_set(self): + # Unset the default root dir variable + self.env = dict() + self._test_function("_change_root", ["/foo/bar"]) + self._assert(0, "/foo/bar") diff --git a/nova_dpm/tests/unit/guest_image_tools/test_wrapper.sh b/nova_dpm/tests/unit/guest_image_tools/test_wrapper.sh new file mode 100755 index 0000000..e986410 --- /dev/null +++ b/nova_dpm/tests/unit/guest_image_tools/test_wrapper.sh @@ -0,0 +1,39 @@ +#! /bin/bash +# Copyright 2017 IBM Corp. All Rights Reserved. +# +# 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. + +# This is a test wrapper. It's only job is to help executing certain functions +# of other shell scripts. + +# This script is called from the projects root directory. Therefore all paths +# must be specified relative to it. +# Manual execution +# nova_dpm/tests/unit/guest_image_tools/test_wrapper.sh + +source guest_image_tools/usr/bin/dpm_guest_image_tools_common + + +# $1 = The function to be called +# $2,3... = the parameters for the function +func="$1" +# Remove the first argument from $@ +shift 1 +# Make sure only defined function are called +if [[ $(type -t $func) == "function" ]]; then + # Call function with arguments passed in as $2,3,... + $func "$@" + exit $? +else + exit 1 +fi