commit:     2a6d218d97858342eae3266798085d95c3d31c83
Author:     Rabi Shanker Guha <guha.rabishankar <AT> gmail <DOT> com>
AuthorDate: Fri Jan  9 15:09:51 2015 +0000
Commit:     Robin H. Johnson <robbat2 <AT> gentoo <DOT> org>
CommitDate: Fri Jan  9 15:09:51 2015 +0000
URL:        
http://sources.gentoo.org/gitweb/?p=proj/netifrc.git;a=commit;h=2a6d218d

Test Suite

Run it using
sudo python3 test/src/netifrc.py test/specs/config.yaml
to generate the test output
and
sudo MODE=slave python3 test/src/netifrc.py test/specs/config.yaml
to match the output on a system-system against the output generated
on the previous openrc system

---
 .gitignore                 |   2 +
 test/conf.d/bond           |   9 ++
 test/conf.d/bridge         |   9 ++
 test/conf.d/eth_dhcp       |   1 +
 test/conf.d/eth_static     |   2 +
 test/conf.d/vlan           |   3 +
 test/config.ini            |  28 ++++++
 test/requirements.txt      |   2 +
 test/specs/bond.yaml       |  23 +++++
 test/specs/bridge.yaml     |  11 +++
 test/specs/eth_dhcp.yaml   |  14 +++
 test/specs/eth_static.yaml |  14 +++
 test/specs/vlan.yaml       |  11 +++
 test/src/file.py           |  21 +++++
 test/src/netifrc.py        | 227 +++++++++++++++++++++++++++++++++++++++++++++
 15 files changed, 377 insertions(+)

diff --git a/.gitignore b/.gitignore
index 56631ab..ad5b2de 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
 ChangeLog
+**/__pycache__
+*.swp

diff --git a/test/conf.d/bond b/test/conf.d/bond
new file mode 100644
index 0000000..49bbb24
--- /dev/null
+++ b/test/conf.d/bond
@@ -0,0 +1,9 @@
+# Bonded Interface Configuration
+#config_$$BOND_IFACE_1$$="null"
+#config_$$BOND_IFACE_2$$="null"
+
+slaves_$$BOND$$="$$BOND_IFACE_1$$ $$BOND_IFACE_2$$"
+config_$$BOND$$="null"
+# config_$$BOND$$="192.168.1.100"
+# routes_$$BOND$$="default via 192.168.1.1"
+rc_net_$$BOND$$_need="net.$$BOND_IFACE_1$$ net.$$BOND_IFACE_2$$"

diff --git a/test/conf.d/bridge b/test/conf.d/bridge
new file mode 100644
index 0000000..d49455b
--- /dev/null
+++ b/test/conf.d/bridge
@@ -0,0 +1,9 @@
+config_$$BRIDGE_IFACE_1$$="null"
+config_$$BRIDGE_IFACE_2$$="null"
+
+# bridge
+config_$$BRIDGE$$="dhcp"
+brctl_$$BRIDGE$$="setfd 0
+sethello 10
+stp off"
+bridge_$$BRIDGE$$="$$BRIDGE_IFACE_1$$ $$BRIDGE_IFACE_2$$"
\ No newline at end of file

diff --git a/test/conf.d/eth_dhcp b/test/conf.d/eth_dhcp
new file mode 100644
index 0000000..4d97c5f
--- /dev/null
+++ b/test/conf.d/eth_dhcp
@@ -0,0 +1 @@
+config_$$WIRED$$="dhcp"

diff --git a/test/conf.d/eth_static b/test/conf.d/eth_static
new file mode 100644
index 0000000..cc0fb23
--- /dev/null
+++ b/test/conf.d/eth_static
@@ -0,0 +1,2 @@
+config_$$WIRED$$="172.27.3.82/21"
+routes_$$WIRED$$="default via 172.27.7.254"

diff --git a/test/conf.d/vlan b/test/conf.d/vlan
new file mode 100644
index 0000000..8d6a936
--- /dev/null
+++ b/test/conf.d/vlan
@@ -0,0 +1,3 @@
+vlans_$$VLAN_IFACE_1$$="1"
+config_$$VLAN_IFACE_1$$="null"
+config_$$VLAN_IFACE_1$$_1="192.168.2.1/24"
\ No newline at end of file

diff --git a/test/config.ini b/test/config.ini
new file mode 100644
index 0000000..286adb3
--- /dev/null
+++ b/test/config.ini
@@ -0,0 +1,28 @@
+[GLOBALS]
+MODE_MASTER = master
+MODE_SLAVE = slave
+CONFIG_FILE = /etc/conf.d/net
+CONFIG_FILE_BACKUP = /etc/conf.d/net.backup
+DELAY = 5
+TIMEOUT = 5
+
+[FILE]
+KEYSTORE = /tmp/netifrc_test.py
+
+[SPECS]
+DUMMY = dummy0
+WIRED = eth0
+WIRELESS = wlp3s0
+
+# [BONDING]
+BOND = bond0
+BOND_IFACE_1 = %(WIRED)s
+BOND_IFACE_2 = %(DUMMY)s
+
+# [VLan]
+VLAN_IFACE_1 = %(WIRED)s
+
+# [Bridge]
+BRIDGE = br0
+BRIDGE_IFACE_1 = %(WIRED)s
+BRIDGE_IFACE_2 = %(DUMMY)s

diff --git a/test/requirements.txt b/test/requirements.txt
new file mode 100644
index 0000000..f5a24b9
--- /dev/null
+++ b/test/requirements.txt
@@ -0,0 +1,2 @@
+pyyaml
+termcolor

diff --git a/test/specs/bond.yaml b/test/specs/bond.yaml
new file mode 100644
index 0000000..46bc312
--- /dev/null
+++ b/test/specs/bond.yaml
@@ -0,0 +1,23 @@
+name: Bond Test Suite
+description: Test to check the functioning of net.bond0
+net_config: conf.d/bond
+interface: $$BOND$$
+tests:
+  - name: Check Bond Interface
+    command: ifconfig $$BOND$$
+    keys:
+    - name: status
+      type: boolean
+      value: grep -q UP
+    - name: IP
+      value: "grep 'inet ' |  sed -e 's/^[ \t]*//' | cut -d ' ' -f2"
+      match: "r'\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b'"
+  - name: "Check /proc/net file"
+    command: "cat /proc/net/bonding/$$BOND$$"
+    keys:
+    - name: Slave $$BOND_IFACE_1$$ status
+      type: boolean
+      value: "grep -A 1 'Slave Interface: $$BOND_IFACE_1$$' | grep Status | 
grep -q -i up"
+    - name: Slave $$BOND_IFACE_2$$ status
+      type: boolean
+      value: "grep -A 1 'Slave Interface: $$BOND_IFACE_2$$' | grep Status | 
grep -q -i up"
\ No newline at end of file

diff --git a/test/specs/bridge.yaml b/test/specs/bridge.yaml
new file mode 100644
index 0000000..681bbda
--- /dev/null
+++ b/test/specs/bridge.yaml
@@ -0,0 +1,11 @@
+name: Bridge Test Suite
+description: Test to check the functioning of Bridge
+net_config: conf.d/bridge
+interface: $$BRIDGE$$
+tests:
+  - name: Check Bridged Interfaces
+    command: brctl show
+    keys:
+    - name: Bridge $$BRIDGE$$ status
+      type: boolean
+      value: grep -q $$BRIDGE$$
\ No newline at end of file

diff --git a/test/specs/eth_dhcp.yaml b/test/specs/eth_dhcp.yaml
new file mode 100644
index 0000000..ed67cc7
--- /dev/null
+++ b/test/specs/eth_dhcp.yaml
@@ -0,0 +1,14 @@
+name: DHCP
+description: A battery of tests to check the correction functioning of static
+net_config: conf.d/eth_dhcp
+interface: $$WIRED$$
+tests:
+  - name: Check DHCP
+    command: ifconfig $$WIRED$$
+    keys:
+    - name: status
+      type: boolean
+      value: grep -q UP
+    - name: IP
+      value: "grep 'inet ' |  sed -e 's/^[ \t]*//' | cut -d ' ' -f2"
+      match: "r'\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b'"

diff --git a/test/specs/eth_static.yaml b/test/specs/eth_static.yaml
new file mode 100644
index 0000000..5b6ea25
--- /dev/null
+++ b/test/specs/eth_static.yaml
@@ -0,0 +1,14 @@
+name: Static Ethernet Test Suite
+description: A battery of tests to check the correction functioning of static 
net.eth0
+net_config: conf.d/eth_static
+interface: $$WIRED$$
+tests:
+  - name: Check DHCP
+    command: ifconfig $$WIRED$$
+    keys:
+    - name: status
+      type: boolean
+      value: grep -q UP
+    - name: IP
+      value: "grep 'inet ' |  sed -e 's/^[ \t]*//' | cut -d ' ' -f2"
+      match: "r'\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\b'"

diff --git a/test/specs/vlan.yaml b/test/specs/vlan.yaml
new file mode 100644
index 0000000..83eb66d
--- /dev/null
+++ b/test/specs/vlan.yaml
@@ -0,0 +1,11 @@
+name: Vlan Test Suite
+description: Test to check the functioning of Virtual LAN
+net_config: conf.d/vlan
+interface: $$VLAN_IFACE_1$$
+tests:
+  - name: Check Bond Interface
+    command: ip -d link show $$VLAN_IFACE_1$$.1
+    keys:
+    - name: vlan status
+      type: boolean
+      value: grep -q vlan
\ No newline at end of file

diff --git a/test/src/file.py b/test/src/file.py
new file mode 100644
index 0000000..e1e6ed8
--- /dev/null
+++ b/test/src/file.py
@@ -0,0 +1,21 @@
+import configparser
+import os
+
+BASEDIR = os.path.normpath(os.path.join(os.path.dirname(__file__), os.pardir))
+
+_config = configparser.ConfigParser()
+_config.read(os.path.join(BASEDIR, 'config.ini'))
+config = _config['FILE']
+
+
+def save(key, value):
+    with open(config['KEYSTORE'], "a") as keystore:
+        keystore.write("{} = {}\n".format(key, value))
+
+
+def fetch(key):
+    with open(config['KEYSTORE'], "r") as keystore:
+        for line in keystore:
+            if(line.startswith(key + " = ")):
+                val = line[line.index("=")+2:]
+    return val.strip()

diff --git a/test/src/netifrc.py b/test/src/netifrc.py
new file mode 100755
index 0000000..8840583
--- /dev/null
+++ b/test/src/netifrc.py
@@ -0,0 +1,227 @@
+#!/usr/bin/python3
+
+import configparser
+import os
+import re
+import sys
+import subprocess
+from termcolor import colored
+import time
+from yaml import load as yamlLoad
+
+try:
+    from yaml import CLoader as Loader, CDumper as Dumper
+except ImportError:
+    from yaml import Loader, Dumper
+
+
+BASEDIR = os.path.normpath(os.path.join(os.path.dirname(__file__), os.pardir))
+
+config = configparser.ConfigParser()
+config.read(os.path.join(BASEDIR, 'config.ini'))
+defaults = config['GLOBALS']
+
+from file import save as backend_save, fetch as backend_retrieve
+
+
+def get_mode():
+    if(os.path.exists("/var/run/openrc")):
+        return defaults['MODE_MASTER']
+    return defaults['MODE_SLAVE']
+
+
+def normalize(*args):
+    return '_'.join(''.join(c for c in arg if c.isalnum()) for arg in args)
+
+
+def _service_call(iface, command):
+    if(os.path.exists("/var/run/openrc")):
+        subprocess.check_call(["rc-service", "net."+iface, command])
+    elif(os.path.exists("/run/systemd")):
+        subprocess.check_call(["systemctl", command, "net@"+iface])
+
+
+def stop_iface(iface):
+    _service_call(iface, "stop")
+
+
+def start_iface(iface):
+    _service_call(iface, "start")
+
+
+def restart_iface(iface):
+    _service_call(iface, "restart")
+
+
+def init(data):
+    print(colored("Backing up {}".format(defaults['CONFIG_FILE']),
+                  "yellow"))
+    try:
+        if(os.path.isfile(defaults['CONFIG_FILE'])):
+            subprocess.check_call(["mv", defaults['CONFIG_FILE'],
+                                   defaults['CONFIG_FILE_BACKUP']])
+
+    except subprocess.CalledProcessError:
+        print("Could not backup Config File")
+        sys.exit(1)
+
+    with open(BASEDIR + "/" + data['net_config'], 'r') as current_config_file:
+        current_config = current_config_file.read()
+        # Replace with the variables defined in SPECS
+        for var in config['SPECS']:
+            current_config = current_config.replace(
+                "$${}$$".format(var.upper()),
+                config['SPECS'][var])
+
+    with open(defaults['CONFIG_FILE'], 'w') as config_file:
+        config_file.write(current_config)
+
+    try:
+        restart_iface(data['interface'])
+    except subprocess.CalledProcessError:
+        print("Could not effectively start interface "+data['interface'])
+        sys.exit(1)
+    else:
+        time.sleep(float(defaults['DELAY']))
+
+def finalize(data):
+    print(colored("Restoring {}".format(defaults['CONFIG_FILE']),
+                  'yellow'))
+    try:
+        subprocess.check_call(["mv",
+                               defaults['CONFIG_FILE_BACKUP'],
+                               defaults['CONFIG_FILE']])
+    except subprocess.CalledProcessError:
+        print("Could not Restore Config File")
+        sys.exit(1)
+
+    try:
+        stop_iface(data['interface'])
+    except subprocess.CalledProcessError:
+        print("Could not effectively stop interface " + data['interface'])
+        sys.exit(1)
+
+
+def failure(value, backend_value):
+    print(colored("[ FAIL ]", 'red'))
+    err = "    Backend value {} does not match {}"
+    print(colored(err.format(backend_value, value),
+                  'red'))
+    sys.exit(1)
+
+
+def success(value, backend_value):
+    print(colored("[ PASS ]", 'green'))
+
+
+def test(data, mode):
+    print(colored(data['name'], 'green'))
+    for test in data['tests']:
+        command = subprocess.Popen(test['command'], stdout=subprocess.PIPE,
+                                   shell=True)
+        try:
+            command.wait(float(defaults['TIMEOUT']))
+            if(command.returncode != 0):
+                raise subprocess.CalledProcessError(
+                    command.returncode, test['command'])
+            (out, err) = command.communicate(timeout=int(defaults['TIMEOUT']))
+
+        except subprocess.TimeoutExpired:
+            print(colored("Command {} Expired".format(test['command']), 'red'))
+            command.kill()
+            command.communicate()
+        except subprocess.CalledProcessError as err:
+            print(colored("  {}: {}".format(test['name'], test['command']),
+                          'red'))
+
+        else:
+            print(colored("  {}: {}".format(test['name'], test['command']),
+                          'green'))
+            for key in test['keys']:
+                print(colored("    Extracting {}:".format(key['name']),
+                              'green'), end=" ", flush=True)
+                key_command = subprocess.Popen(key['value'],
+                                               stdin=subprocess.PIPE,
+                                               stdout=subprocess.PIPE,
+                                               shell=True)
+                try:
+                    (stdout, stderr) = key_command.communicate(
+                        input=out,
+                        timeout=int(defaults['TIMEOUT']))
+
+                except subprocess.TimeoutExpired:
+                    print(colored("Trying to find the value of {} timed out"
+                                  .format(key['name']), 'red'))
+                    key_command.kill()
+                    key_command.communicate()
+                except subprocess.CalledProcessError as err:
+                    print(colored("Command failed: " + err.cmd, 'red'))
+
+                else:
+                    if('type' in key and key['type'] == "boolean"):
+                        value = str(key_command.returncode)
+                    else:
+                        value = stdout.decode("utf-8").strip()
+
+                    if('match' in key):
+                        if(key['match'][0] == 'r'):
+                            match = re.match(key['match'][2:-1], value)
+                            if(match is None):
+                                failure(value, key['match'])
+                        else:
+                            try:
+                                assert value == key['match']
+                            except AssertionError:
+                                failure(value, key['match'])
+
+                    print(colored(value, 'green'), end=" ", flush=True)
+
+                    if(mode == defaults['MODE_MASTER']):
+                        backend_save(normalize(data['name'],
+                                               test['name'],
+                                               key['name']), value)
+                        print()
+                    else:
+                        backend_value = backend_retrieve(
+                            normalize(data['name'], test['name'], key['name']))
+
+                        if(backend_value[0] == 'r'):
+                            match = re.match(backend_value[2:-1], value)
+                            if(match is None):
+                                failure(value, backend_value)
+                            else:
+                                success(value, backend_value)
+                        else:
+                            try:
+                                assert value == backend_value
+                            except AssertionError:
+                                failure(value, backend_value)
+                            else:
+                                success(value, backend_value)
+
+
+for file in sys.argv[1:]:
+    with open(file, 'r') as f:
+        document = f.read()
+
+    # Replace with the variables defined in SPECS
+    for var in config['SPECS']:
+        document = document.replace("$${}$$".format(var.upper()),
+                                    config['SPECS'][var])
+
+    # Parse the yaml file
+    data = yamlLoad(document, Loader=Loader)
+
+    try:
+        mode = os.environ['MODE']
+    except KeyError:
+        mode = ""
+
+    if(mode != defaults['MODE_MASTER'] and mode != defaults['MODE_SLAVE']):
+        mode = get_mode()
+
+    try:
+        init(data)
+        test(data, mode=mode)
+    finally:
+        finalize(data)

Reply via email to