From 09d4f3d93fe272b12b5fae5de36e2c5c47206133 Mon Sep 17 00:00:00 2001 From: Yuya Kusakabe Date: Thu, 17 May 2018 00:22:02 +0900 Subject: Add tests for hostname --- Makefile | 2 +- sonar-project.properties | 2 +- src/conf_mode/host_name.py | 27 +++++-- src/tests/test_host_name.py | 136 ++++++++++++++++++++++++++++++++++ src/tests/test_task_scheduler.py | 130 ++++++++++++++++++++++++++++++++ src/tests/test_vyos_update_crontab.py | 130 -------------------------------- 6 files changed, 289 insertions(+), 138 deletions(-) create mode 100644 src/tests/test_host_name.py create mode 100644 src/tests/test_task_scheduler.py delete mode 100644 src/tests/test_vyos_update_crontab.py diff --git a/Makefile b/Makefile index e90e3b640..d1ac73c2f 100644 --- a/Makefile +++ b/Makefile @@ -38,7 +38,7 @@ clean: .PHONY: test test: - python3 -m "nose" --with-xunit src --with-coverage --cover-erase --cover-xml --cover-package src/conf_mode,src/op_mode --verbose + python3 -m "nose" --with-xunit src --with-coverage --cover-erase --cover-xml --cover-package src/conf_mode,src/op_mode,src/completion,src/helpers,src/validators --verbose .PHONY: sonar sonar: diff --git a/sonar-project.properties b/sonar-project.properties index 476285275..1258da817 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -3,7 +3,7 @@ sonar.projectName=vyos-1x sonar.projectVersion=1.2.0 sonar.organization=vyos -sonar.sources=src/conf_mode,src/op_mode +sonar.sources=src/conf_mode,src/op_mode,src/completion,src/helpers,src/validators sonar.language=py sonar.sourceEncoding=UTF-8 diff --git a/src/conf_mode/host_name.py b/src/conf_mode/host_name.py index 2a245b211..f6d091ac2 100755 --- a/src/conf_mode/host_name.py +++ b/src/conf_mode/host_name.py @@ -16,17 +16,26 @@ # # +""" +conf-mode script for 'system host-name' and 'system domain-name'. +""" + import os import re import sys import subprocess from vyos.config import Config -from vyos.util import ConfigError +from vyos import ConfigError + +hosts_file = '/etc/hosts' hostname_regex = re.compile("^[A-Za-z0-9][-.A-Za-z0-9]*[A-Za-z0-9]$") +local_addr = '127.0.1.1' # NOSONAR + def get_config(): + """Get configuration""" conf = Config() hostname = conf.return_value("system host-name") @@ -44,9 +53,11 @@ def get_config(): return {"hostname": hostname, "domain": domain, "fqdn": fqdn} + def verify(config): + """Verify configuration""" # check for invalid host - + # pattern $VAR(@) "^[[:alnum:]][-.[:alnum:]]*[[:alnum:]]$" ; "invalid host name $VAR(@)" if not hostname_regex.match(config["hostname"]): raise ConfigError('Invalid host name ' + config["hostname"]) @@ -54,29 +65,33 @@ def verify(config): # pattern $VAR(@) "^.{1,63}$" ; "invalid host-name length" length = len(config["hostname"]) if length < 1 or length > 63: - raise ConfigError('Invalid host-name length, must be less than 63 characters') + raise ConfigError( + 'Invalid host-name length, must be less than 63 characters') return None def generate(config): + """Generate configuration files""" # read the hosts file - with open('/etc/hosts', 'r') as f: + with open(hosts_file, 'r') as f: hosts = f.read() # get the current hostname old_hostname = subprocess.check_output(['hostname']).decode().strip() # replace the local host line - hosts = re.sub(r"(127.0.1.1\s+{0}.*)".format(old_hostname), r"127.0.1.1\t{0} # VyOS entry\n".format(config["fqdn"]), hosts) + hosts = re.sub(r"({}\s+{}.*)".format(local_addr, old_hostname), + r"{}\t{} # VyOS entry\n".format(local_addr, config["fqdn"]), hosts) - with open('/etc/hosts', 'w') as f: + with open(hosts_file, 'w') as f: f.write(hosts) return None def apply(config): + """Apply configuration""" os.system("hostnamectl set-hostname {0}".format(config["fqdn"])) # restart services that use the hostname diff --git a/src/tests/test_host_name.py b/src/tests/test_host_name.py new file mode 100644 index 000000000..3df1f975c --- /dev/null +++ b/src/tests/test_host_name.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# + +import os +import tempfile +import unittest +from unittest import TestCase, mock + +from vyos import ConfigError +try: + from src.conf_mode import host_name +except ModuleNotFoundError: # for unittest.main() + import sys + sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) + from src.conf_mode import host_name + + +class TestHostName(TestCase): + + def test_get_config(self): + tests = [ + {'name': 'empty_hostname_and_domain', + 'host-name': '', + 'domain-name': '', + 'expected': {"hostname": 'vyos', "domain": '', "fqdn": 'vyos'}}, + {'name': 'empty_hostname', + 'host-name': '', + 'domain-name': 'localdomain', + 'expected': {"hostname": 'vyos', "domain": 'localdomain', "fqdn": 'vyos.localdomain'}}, + {'name': 'has_hostname', + 'host-name': 'router', + 'domain-name': '', + 'expected': {"hostname": 'router', "domain": '', "fqdn": 'router'}}, + {'name': 'has_hostname_and_domain', + 'host-name': 'router', + 'domain-name': 'localdomain', + 'expected': {"hostname": 'router', "domain": 'localdomain', "fqdn": 'router.localdomain'}}, + ] + for t in tests: + def mocked_return_value(path, default=None): + return t[path.split()[1]] + + with self.subTest(msg=t['name'], hostname=t['host-name'], domain=t['domain-name'], expected=t['expected']): + with mock.patch('vyos.config.Config.return_value', side_effect=mocked_return_value): + actual = host_name.get_config() + self.assertEqual(actual, t['expected']) + + + def test_verify(self): + tests = [ + {'name': 'valid_hostname', + 'config': {"hostname": 'vyos', "domain": 'localdomain', "fqdn": 'vyos.localdomain'}, + 'expected': None}, + {'name': 'invalid_hostname', + 'config': {"hostname": 'vyos..', "domain": '', "fqdn": ''}, + 'expected': ConfigError}, + {'name': 'invalid_hostname_length', + 'config': {"hostname": 'a'*64, "domain": '', "fqdn": ''}, + 'expected': ConfigError} + ] + for t in tests: + with self.subTest(msg=t['name'], config=t['config'], expected=t['expected']): + if t['expected'] is not None: + with self.assertRaises(t['expected']): + host_name.verify(t['config']) + else: + host_name.verify(t['config']) + + def test_generate(self): + tests = [ + {'name': 'valid_hostname', + 'config': {"hostname": 'router', "domain": 'localdomain', "fqdn": 'vyos.localdomain'}, + 'expected': ['127.0.0.1 localhost debian', + '::1 localhost ip6-localhost ip6-loopback', + 'fe00::0 ip6-localnet', + 'ff00::0 ip6-mcastprefix', + 'ff02::1 ip6-allnodes', + 'ff02::2 ip6-allrouters', + '127.0.1.1 vyos #vyatta entry']} + ] + for t in tests: + with self.subTest(msg=t['name'], tasks=t['config'], expected=t['expected']): + m = mock.MagicMock(return_value=b'localhost') + with mock.patch('subprocess.check_output', m): + host_name.hosts_file = tempfile.mkstemp()[1] + with open(host_name.hosts_file, 'w') as f: + f.writelines(['127.0.0.1 localhost debian\n', + '::1 localhost ip6-localhost ip6-loopback\n', + 'fe00::0 ip6-localnet\n', + 'ff00::0 ip6-mcastprefix\n', + 'ff02::1 ip6-allnodes\n', + 'ff02::2 ip6-allrouters\n', + '127.0.1.1 vyos #vyatta entry']) + host_name.generate(t['config']) + if len(t['expected']) > 0: + self.assertTrue(os.path.isfile(host_name.hosts_file)) + with open(host_name.hosts_file) as f: + actual = f.read() + self.assertEqual( + t['expected'], actual.splitlines()) + os.remove(host_name.hosts_file) + else: + self.assertFalse(os.path.isfile(host_name.hosts_file)) + + + def test_apply(self): + tests = [ + {'name': 'valid_hostname', + 'config': {"hostname": 'router', "domain": 'localdomain', "fqdn": 'vyos.localdomain'}, + 'expected': [mock.call('hostnamectl set-hostname vyos.localdomain'), + mock.call('systemctl restart rsyslog.service')]} + ] + for t in tests: + with self.subTest(msg=t['name'], c=t['config'], expected=t['expected']): + with mock.patch('os.system') as os_system: + host_name.apply(t['config']) + os_system.assert_has_calls(t['expected']) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/tests/test_task_scheduler.py b/src/tests/test_task_scheduler.py new file mode 100644 index 000000000..7acbbddc5 --- /dev/null +++ b/src/tests/test_task_scheduler.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +# +# Copyright (C) 2018 VyOS maintainers and contributors +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 or later as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# + +import os +import tempfile +import unittest + +from vyos import ConfigError +try: + from src.conf_mode import task_scheduler +except ModuleNotFoundError: # for unittest.main() + import sys + sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) + from src.conf_mode import task_scheduler + + +class TestUpdateCrontab(unittest.TestCase): + + def test_verify(self): + tests = [ + {'name': 'one_task', + 'tasks': [{'name': 'aaa', 'interval': '60m', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], + 'expected': None + }, + {'name': 'has_interval_and_spec', + 'tasks': [{'name': 'aaa', 'interval': '60m', 'spec': '0 * * * *', 'executable': '/bin/ls', 'args': '-l'}], + 'expected': ConfigError + }, + {'name': 'has_no_interval_and_spec', + 'tasks': [{'name': 'aaa', 'interval': '', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], + 'expected': ConfigError + }, + {'name': 'invalid_interval', + 'tasks': [{'name': 'aaa', 'interval': '1y', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], + 'expected': ConfigError + }, + {'name': 'invalid_interval_min', + 'tasks': [{'name': 'aaa', 'interval': '61m', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], + 'expected': ConfigError + }, + {'name': 'invalid_interval_hour', + 'tasks': [{'name': 'aaa', 'interval': '25h', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], + 'expected': ConfigError + }, + {'name': 'invalid_interval_day', + 'tasks': [{'name': 'aaa', 'interval': '32d', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], + 'expected': ConfigError + }, + {'name': 'no_executable', + 'tasks': [{'name': 'aaa', 'interval': '60m', 'spec': '', 'executable': '', 'args': ''}], + 'expected': ConfigError + }, + {'name': 'invalid_executable', + 'tasks': [{'name': 'aaa', 'interval': '60m', 'spec': '', 'executable': '/bin/aaa', 'args': ''}], + 'expected': ConfigError + } + ] + for t in tests: + with self.subTest(msg=t['name'], tasks=t['tasks'], expected=t['expected']): + if t['expected'] is not None: + with self.assertRaises(t['expected']): + task_scheduler.verify(t['tasks']) + else: + task_scheduler.verify(t['tasks']) + + def test_generate(self): + tests = [ + {'name': 'zero_task', + 'tasks': [], + 'expected': [] + }, + {'name': 'one_task', + 'tasks': [{'name': 'aaa', 'interval': '60m', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], + 'expected': [ + '### Generated by vyos-update-crontab.py ###', + '*/60 * * * * root /bin/ls -l'] + }, + {'name': 'one_task_with_hour', + 'tasks': [{'name': 'aaa', 'interval': '10h', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], + 'expected': [ + '### Generated by vyos-update-crontab.py ###', + '0 */10 * * * root /bin/ls -l'] + }, + {'name': 'one_task_with_day', + 'tasks': [{'name': 'aaa', 'interval': '10d', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], + 'expected': [ + '### Generated by vyos-update-crontab.py ###', + '0 0 */10 * * root /bin/ls -l'] + }, + {'name': 'multiple_tasks', + 'tasks': [{'name': 'aaa', 'interval': '60m', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}, + {'name': 'bbb', 'interval': '', 'spec': '0 0 * * *', 'executable': '/bin/ls', 'args': '-ltr'} + ], + 'expected': [ + '### Generated by vyos-update-crontab.py ###', + '*/60 * * * * root /bin/ls -l', + '0 0 * * * root /bin/ls -ltr'] + } + ] + for t in tests: + with self.subTest(msg=t['name'], tasks=t['tasks'], expected=t['expected']): + task_scheduler.crontab_file = tempfile.mkstemp()[1] + task_scheduler.generate(t['tasks']) + if len(t['expected']) > 0: + self.assertTrue(os.path.isfile(task_scheduler.crontab_file)) + with open(task_scheduler.crontab_file) as f: + actual = f.read() + self.assertEqual(t['expected'], actual.splitlines()) + os.remove(task_scheduler.crontab_file) + else: + self.assertFalse(os.path.isfile(task_scheduler.crontab_file)) + + +if __name__ == "__main__": + unittest.main() diff --git a/src/tests/test_vyos_update_crontab.py b/src/tests/test_vyos_update_crontab.py deleted file mode 100644 index 7acbbddc5..000000000 --- a/src/tests/test_vyos_update_crontab.py +++ /dev/null @@ -1,130 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright (C) 2018 VyOS maintainers and contributors -# -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 or later as -# published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program. If not, see . -# -# - -import os -import tempfile -import unittest - -from vyos import ConfigError -try: - from src.conf_mode import task_scheduler -except ModuleNotFoundError: # for unittest.main() - import sys - sys.path.append(os.path.join(os.path.dirname(__file__), '../..')) - from src.conf_mode import task_scheduler - - -class TestUpdateCrontab(unittest.TestCase): - - def test_verify(self): - tests = [ - {'name': 'one_task', - 'tasks': [{'name': 'aaa', 'interval': '60m', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], - 'expected': None - }, - {'name': 'has_interval_and_spec', - 'tasks': [{'name': 'aaa', 'interval': '60m', 'spec': '0 * * * *', 'executable': '/bin/ls', 'args': '-l'}], - 'expected': ConfigError - }, - {'name': 'has_no_interval_and_spec', - 'tasks': [{'name': 'aaa', 'interval': '', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], - 'expected': ConfigError - }, - {'name': 'invalid_interval', - 'tasks': [{'name': 'aaa', 'interval': '1y', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], - 'expected': ConfigError - }, - {'name': 'invalid_interval_min', - 'tasks': [{'name': 'aaa', 'interval': '61m', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], - 'expected': ConfigError - }, - {'name': 'invalid_interval_hour', - 'tasks': [{'name': 'aaa', 'interval': '25h', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], - 'expected': ConfigError - }, - {'name': 'invalid_interval_day', - 'tasks': [{'name': 'aaa', 'interval': '32d', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], - 'expected': ConfigError - }, - {'name': 'no_executable', - 'tasks': [{'name': 'aaa', 'interval': '60m', 'spec': '', 'executable': '', 'args': ''}], - 'expected': ConfigError - }, - {'name': 'invalid_executable', - 'tasks': [{'name': 'aaa', 'interval': '60m', 'spec': '', 'executable': '/bin/aaa', 'args': ''}], - 'expected': ConfigError - } - ] - for t in tests: - with self.subTest(msg=t['name'], tasks=t['tasks'], expected=t['expected']): - if t['expected'] is not None: - with self.assertRaises(t['expected']): - task_scheduler.verify(t['tasks']) - else: - task_scheduler.verify(t['tasks']) - - def test_generate(self): - tests = [ - {'name': 'zero_task', - 'tasks': [], - 'expected': [] - }, - {'name': 'one_task', - 'tasks': [{'name': 'aaa', 'interval': '60m', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], - 'expected': [ - '### Generated by vyos-update-crontab.py ###', - '*/60 * * * * root /bin/ls -l'] - }, - {'name': 'one_task_with_hour', - 'tasks': [{'name': 'aaa', 'interval': '10h', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], - 'expected': [ - '### Generated by vyos-update-crontab.py ###', - '0 */10 * * * root /bin/ls -l'] - }, - {'name': 'one_task_with_day', - 'tasks': [{'name': 'aaa', 'interval': '10d', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}], - 'expected': [ - '### Generated by vyos-update-crontab.py ###', - '0 0 */10 * * root /bin/ls -l'] - }, - {'name': 'multiple_tasks', - 'tasks': [{'name': 'aaa', 'interval': '60m', 'spec': '', 'executable': '/bin/ls', 'args': '-l'}, - {'name': 'bbb', 'interval': '', 'spec': '0 0 * * *', 'executable': '/bin/ls', 'args': '-ltr'} - ], - 'expected': [ - '### Generated by vyos-update-crontab.py ###', - '*/60 * * * * root /bin/ls -l', - '0 0 * * * root /bin/ls -ltr'] - } - ] - for t in tests: - with self.subTest(msg=t['name'], tasks=t['tasks'], expected=t['expected']): - task_scheduler.crontab_file = tempfile.mkstemp()[1] - task_scheduler.generate(t['tasks']) - if len(t['expected']) > 0: - self.assertTrue(os.path.isfile(task_scheduler.crontab_file)) - with open(task_scheduler.crontab_file) as f: - actual = f.read() - self.assertEqual(t['expected'], actual.splitlines()) - os.remove(task_scheduler.crontab_file) - else: - self.assertFalse(os.path.isfile(task_scheduler.crontab_file)) - - -if __name__ == "__main__": - unittest.main() -- cgit v1.2.3