diff options
Diffstat (limited to 'Vland/config')
-rw-r--r-- | Vland/config/__init__.py | 23 | ||||
-rw-r--r-- | Vland/config/config.py | 270 | ||||
-rw-r--r-- | Vland/config/test-clashing-ports.cfg | 33 | ||||
-rw-r--r-- | Vland/config/test-invalid-DB.cfg | 5 | ||||
-rw-r--r-- | Vland/config/test-invalid-logging-level.cfg | 30 | ||||
-rw-r--r-- | Vland/config/test-invalid-vland.cfg | 11 | ||||
-rw-r--r-- | Vland/config/test-known-good.cfg | 35 | ||||
-rw-r--r-- | Vland/config/test-missing-db-username.cfg | 5 | ||||
-rw-r--r-- | Vland/config/test-missing-dbname.cfg | 6 | ||||
-rw-r--r-- | Vland/config/test-reused-switch-names.cfg | 26 | ||||
-rw-r--r-- | Vland/config/test-unknown-section.cfg | 29 | ||||
-rw-r--r-- | Vland/config/test.py | 101 |
12 files changed, 574 insertions, 0 deletions
diff --git a/Vland/config/__init__.py b/Vland/config/__init__.py new file mode 100644 index 0000000..89e40d5 --- /dev/null +++ b/Vland/config/__init__.py @@ -0,0 +1,23 @@ +#! /usr/bin/python + +# Copyright 2014-2015 Linaro Limited +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# VLANd configuration module +# +# Set the defaults here, over-ride later +# diff --git a/Vland/config/config.py b/Vland/config/config.py new file mode 100644 index 0000000..802f881 --- /dev/null +++ b/Vland/config/config.py @@ -0,0 +1,270 @@ +#! /usr/bin/python + +# Copyright 2014-2015 Linaro Limited +# Author: Steve McIntyre <steve.mcintyre@linaro.org> +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# 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, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, +# MA 02110-1301, USA. +# +# VLANd simple config parser +# + +import ConfigParser +import os, sys, re + +from Vland.errors import ConfigError + +def is_positive(text): + valid_true = ('1', 'y', 'yes', 't', 'true') + valid_false = ('0', 'n', 'no', 'f', 'false') + + if str(text) in valid_true or str(text).lower() in valid_true: + return True + elif str(text) in valid_false or str(text).lower() in valid_false: + return False + +def is_valid_logging_level(text): + valid = ('CRITICAL', 'ERROR', 'WARNING', 'INFO', 'DEBUG') + if text in valid: + return True + return False + +class DaemonConfigClass: + """ Simple container for stuff to make for nicer syntax """ + + def __repr__(self): + return "<DaemonConfig: port: %s>" % (self.port) + +class DBConfigClass: + """ Simple container for stuff to make for nicer syntax """ + + def __repr__(self): + return "<DBConfig: server: %s, port: %s, dbname: %s, username: %s, password: %s>" % (self.server, self.port, self.dbname, self.username, self.password) + +class LoggingConfigClass: + """ Simple container for stuff to make for nicer syntax """ + + def __repr__(self): + return "<LoggingConfig: level: %s, filename: %s>" % (self.level, self.filename) + +class VisualisationConfigClass: + """ Simple container for stuff to make for nicer syntax """ + def __repr__(self): + return "<VisualisationConfig: enabled: %s, port: %s>" % (self.enabled, self.port) + +class SwitchConfigClass: + """ Simple container for stuff to make for nicer syntax """ + def __repr__(self): + return "<SwitchConfig: name: %s, section: %s, driver: %s, username: %s, password: %s, enable_password: %s>" % (self.name, self.section, self.driver, self.username, self.password, self.enable_password) + +class VlanConfig: + """VLANd config class""" + def __init__(self, filenames): + + config = ConfigParser.RawConfigParser({ + # Set default values + 'dbname': None, + 'debug': False, + 'driver': None, + 'enable_password': None, + 'enabled': False, + 'name': None, + 'password': None, + 'port': None, + 'refresh': None, + 'server': None, + 'username': None, + }) + + config.read(filenames) + + # Parse out the config file + # Must have a [database] section + # May have a [vland] section + # May have a [logging] section + # May have multiple [switch 'foo'] sections + if not config.has_section('database'): + raise ConfigError('No database configuration section found') + + # No DB-specific defaults to set + self.database = DBConfigClass() + + # Set defaults logging details + self.logging = LoggingConfigClass() + self.logging.level = 'CRITICAL' + self.logging.filename = None + + # Set default port number and VLAN tag + self.vland = DaemonConfigClass() + self.vland.port = 3080 + self.vland.default_vlan_tag = 1 + + # Visualisation is disabled by default + self.visualisation = VisualisationConfigClass() + self.visualisation.port = 3081 + self.visualisation.enabled = False + + # No switch-specific defaults to set + self.switches = {} + + sw_regex = re.compile(r'(switch)\ (.*)', flags=re.I) + for section in config.sections(): + if section == 'database': + try: + self.database.server = config.get(section, 'server') + except ConfigParser.NoOptionError: + pass + except: + raise ConfigError('Invalid database configuration (server)') + + try: + port = config.get(section, 'port') + if port is not None: + self.database.port = config.getint(section, 'port') + except ConfigParser.NoOptionError: + pass + except: + raise ConfigError('Invalid database configuration (port)') + + try: + self.database.dbname = config.get(section, 'dbname') + except ConfigParser.NoOptionError: + pass + except: + raise ConfigError('Invalid database configuration (dbname)') + + try: + self.database.username = config.get(section, 'username') + except ConfigParser.NoOptionError: + pass + except: + raise ConfigError('Invalid database configuration (username)') + + try: + self.database.password = config.get(section, 'password') + except ConfigParser.NoOptionError: + pass + except: + raise ConfigError('Invalid database configuration (password)') + + # Other database config options are optional, but these are not + if self.database.dbname is None or self.database.username is None: + raise ConfigError('Database configuration section incomplete') + + elif section == 'logging': + try: + self.logging.level = config.get(section, 'level') + except ConfigParser.NoOptionError: + pass + except: + raise ConfigError('Invalid logging configuration (level)') + self.logging.level = self.logging.level.upper() + if not is_valid_logging_level(self.logging.level): + raise ConfigError('Invalid logging configuration (level)') + + try: + self.logging.filename = config.get(section, 'filename') + except ConfigParser.NoOptionError: + pass + except: + raise ConfigError('Invalid logging configuration (filename)') + + elif section == 'vland': + try: + self.vland.port = config.getint(section, 'port') + except ConfigParser.NoOptionError: + pass + except: + raise ConfigError('Invalid vland configuration (port)') + + try: + self.vland.default_vlan_tag = config.getint(section, 'default_vlan_tag') + except ConfigParser.NoOptionError: + pass + except: + raise ConfigError('Invalid vland configuration (default_vlan_tag)') + + elif section == 'visualisation': + try: + self.visualisation.port = config.getint(section, 'port') + except ConfigParser.NoOptionError: + pass + except: + raise ConfigError('Invalid visualisation configuration (port)') + + try: + self.visualisation.enabled = config.get(section, 'enabled') + if not is_positive(self.visualisation.enabled): + self.visualisation.enabled = False + elif is_positive(self.visualisation.enabled): + self.visualisation.enabled = True + except ConfigParser.NoOptionError: + pass + except: + raise ConfigError('Invalid visualisation configuration (enabled)') + + try: + self.visualisation.refresh = config.get(section, 'refresh') + if self.visualisation.refresh is not None: + if not is_positive(self.visualisation.refresh): + self.visualisation.refresh = None + else: + self.visualisation.refresh = int(self.visualisation.refresh) + except ConfigParser.NoOptionError: + pass + except: + raise ConfigError('Invalid visualisation configuration (refresh)') + + else: + match = sw_regex.match(section) + if match: + # Constraint: switch names must be unique! See if + # there's already a switch with this name + name = config.get(section, 'name') + for key in self.switches.keys(): + if name == key: + raise ConfigError('Found switches with the same name (%s)' % name) + self.switches[name] = SwitchConfigClass() + self.switches[name].name = name + self.switches[name].section = section + self.switches[name].driver = config.get(section, 'driver') + self.switches[name].username = config.get(section, 'username') + self.switches[name].password = config.get(section, 'password') + self.switches[name].enable_password = config.get(section, 'enable_password') + self.switches[name].debug = config.get(section, 'debug') + if not is_positive(self.switches[name].debug): + self.switches[name].debug = False + elif is_positive(self.switches[name].debug): + self.switches[name].debug = True + else: + raise ConfigError('Invalid vland configuration (switch "%s", debug "%s"' % (name, self.switches[name].debug)) + else: + raise ConfigError('Unrecognised config section %s' % section) + + # Generic checking for config values + if self.visualisation.enabled: + if self.visualisation.port == self.vland.port: + raise ConfigError('Invalid configuration: VLANd and the visualisation service must use distinct port numbers') + + def __del__(self): + pass + +if __name__ == '__main__': + c = VlanConfig(filenames=('./vland.cfg',)) + print c.database + print c.vland + for switch in c.switches: + print c.switches[switch] + diff --git a/Vland/config/test-clashing-ports.cfg b/Vland/config/test-clashing-ports.cfg new file mode 100644 index 0000000..46b2f3e --- /dev/null +++ b/Vland/config/test-clashing-ports.cfg @@ -0,0 +1,33 @@ +[database] +server = foo +port = 123 +dbname = vland +username = vland +password = vland + +[switch foo] +name = 10.172.2.51 +driver = CiscoSX300 +username = cisco +password = cisco +#enable_password = + +[switch bar] +name = 10.172.2.52 +driver = CiscoSX300 +username = cisco +password = cisco +#enable_password = + +[switch baz] +name = baz +driver = CiscoSX300 +username = cisco +password = cisco + +[vland] +port = 245 + +[visualisation] +enabled = yes +port = 245 diff --git a/Vland/config/test-invalid-DB.cfg b/Vland/config/test-invalid-DB.cfg new file mode 100644 index 0000000..a80fcbc --- /dev/null +++ b/Vland/config/test-invalid-DB.cfg @@ -0,0 +1,5 @@ +[database] +port = bar +dbname = vland +username = vland + diff --git a/Vland/config/test-invalid-logging-level.cfg b/Vland/config/test-invalid-logging-level.cfg new file mode 100644 index 0000000..d2de278 --- /dev/null +++ b/Vland/config/test-invalid-logging-level.cfg @@ -0,0 +1,30 @@ +[database] +server = foo +port = 123 +dbname = vland +username = vland +password = vland + +[switch foo] +name = 10.172.2.51 +driver = CiscoSX300 +username = cisco +password = cisco +#enable_password = + +[switch bar] +name = 10.172.2.52 +driver = CiscoSX300 +username = cisco +password = cisco +#enable_password = + +[switch baz] +name = baz +driver = CiscoSX300 +username = cisco +password = cisco + +[logging] +level = bibble + diff --git a/Vland/config/test-invalid-vland.cfg b/Vland/config/test-invalid-vland.cfg new file mode 100644 index 0000000..4478e7a --- /dev/null +++ b/Vland/config/test-invalid-vland.cfg @@ -0,0 +1,11 @@ +[database] +server = foo +port = 123 +dbname = vland +username = vland +password = vland + +[vland] +port = foo +default_vlan_tag = bar + diff --git a/Vland/config/test-known-good.cfg b/Vland/config/test-known-good.cfg new file mode 100644 index 0000000..bd060db --- /dev/null +++ b/Vland/config/test-known-good.cfg @@ -0,0 +1,35 @@ +# Example config for VLANd + +[database] +server=foo +port=123 +dbname = vland +username = user +password= pass + +[vland] +port = 9997 +default_vlan_tag = 42 + +[switch foo] +name = 10.172.2.51 +driver = CiscoSX300 +username = cisco_user +password = cisco_pass +enable_password = foobar + +[switch bar] +name = 10.172.2.52 +driver = CiscoSX300 +username = cisco +password = cisco +#enable_password = + +[switch baz] +name = baz +driver = CiscoSX300 +username = cisco +password = cisco + +[logging] + diff --git a/Vland/config/test-missing-db-username.cfg b/Vland/config/test-missing-db-username.cfg new file mode 100644 index 0000000..160934c --- /dev/null +++ b/Vland/config/test-missing-db-username.cfg @@ -0,0 +1,5 @@ +[database] +server = foo +port = 123 +dbname = vland +password = vland diff --git a/Vland/config/test-missing-dbname.cfg b/Vland/config/test-missing-dbname.cfg new file mode 100644 index 0000000..ddc7168 --- /dev/null +++ b/Vland/config/test-missing-dbname.cfg @@ -0,0 +1,6 @@ +[database] +server = foo +port = 123 +username = vland +password = vland + diff --git a/Vland/config/test-reused-switch-names.cfg b/Vland/config/test-reused-switch-names.cfg new file mode 100644 index 0000000..2d8485a --- /dev/null +++ b/Vland/config/test-reused-switch-names.cfg @@ -0,0 +1,26 @@ +[database] +server = foo +port = 123 +dbname = vland +username = vland +password = vland + +[switch foo] +name = 10.172.2.51 +driver = CiscoSX300 +username = cisco +password = cisco +#enable_password = + +[switch bar] +name = 10.172.2.51 +driver = CiscoSX300 +username = cisco +password = cisco +#enable_password = + +[switch baz] +name = baz +driver = CiscoSX300 +username = cisco +password = cisco diff --git a/Vland/config/test-unknown-section.cfg b/Vland/config/test-unknown-section.cfg new file mode 100644 index 0000000..446e0a5 --- /dev/null +++ b/Vland/config/test-unknown-section.cfg @@ -0,0 +1,29 @@ +[database] +server = foo +port = 123 +dbname = vland +username = vland +password = vland + +[switch foo] +name = 10.172.2.51 +driver = CiscoSX300 +username = cisco +password = cisco +#enable_password = + +[switch bar] +name = 10.172.2.52 +driver = CiscoSX300 +username = cisco +password = cisco +#enable_password = + +[switch baz] +name = baz +driver = CiscoSX300 +username = cisco +password = cisco + +[bibble] +foo = 1 diff --git a/Vland/config/test.py b/Vland/config/test.py new file mode 100644 index 0000000..046fbb2 --- /dev/null +++ b/Vland/config/test.py @@ -0,0 +1,101 @@ +import unittest, os, sys +vlandpath = os.path.abspath(os.path.normpath(os.path.dirname(sys.argv[0]))) +sys.path.insert(0, vlandpath) +sys.path.insert(0, "%s/../.." % vlandpath) + +os.chdir(vlandpath) + +from Vland.config.config import VlanConfig +from Vland.errors import ConfigError + +class MyTest(unittest.TestCase): + + # Check that we raise on missing database section + def test_missing_database_section(self): + with self.assertRaisesRegexp(ConfigError, 'No database'): + config = VlanConfig(filenames=("/dev/null",)) + del config + + # Check that we raise on broken database config values + def test_missing_database_config(self): + with self.assertRaisesRegexp(ConfigError, 'Invalid database'): + config = VlanConfig(filenames=("test-invalid-DB.cfg",)) + del config + + # Check that we raise on missing database config values + def test_missing_dbname(self): + with self.assertRaisesRegexp(ConfigError, 'Database.*incomplete'): + config = VlanConfig(filenames=("test-missing-dbname.cfg",)) + del config + + # Check that we raise on missing database config values + def test_missing_db_username(self): + with self.assertRaisesRegexp(ConfigError, 'Database.*incomplete'): + config = VlanConfig(filenames=("test-missing-db-username.cfg",)) + del config + + # Check that we raise on broken vland config values + def test_missing_vlan_config(self): + with self.assertRaisesRegexp(ConfigError, 'Invalid vland'): + config = VlanConfig(filenames=("test-invalid-vland.cfg",)) + del config + + # Check that we raise on broken logging level + def test_invalid_logging_level(self): + with self.assertRaisesRegexp(ConfigError, 'Invalid logging.*level'): + config = VlanConfig(filenames=("test-invalid-logging-level.cfg",)) + del config + + # Check that we raise when VLANd and visn are configured for the + # same port + def test_clashing_ports(self): + with self.assertRaisesRegexp(ConfigError, 'Invalid.*distinct port'): + config = VlanConfig(filenames=("test-clashing-ports.cfg",)) + del config + + # Check that we raise on repeated switch names + def test_missing_repeated_switch_names(self): + with self.assertRaisesRegexp(ConfigError, 'same name'): + config = VlanConfig(filenames=("test-reused-switch-names.cfg",)) + del config + + # Check that we raise on unknown config section + def test_unknown_config(self): + with self.assertRaisesRegexp(ConfigError, 'Unrecognised config'): + config = VlanConfig(filenames=("test-unknown-section.cfg",)) + del config + + # Check we get expected values on a known-good config + def test_known_good(self): + config = VlanConfig(filenames=("test-known-good.cfg",)) + self.assertEqual(config.database.server, 'foo') + self.assertEqual(config.database.port, 123) + self.assertEqual(config.database.dbname, 'vland') + self.assertEqual(config.database.username, 'user') + self.assertEqual(config.database.password, 'pass') + + self.assertEqual(config.vland.port, 9997) + self.assertEqual(config.vland.default_vlan_tag, 42) + + self.assertEqual(len(config.switches), 3) + self.assertEqual(config.switches["10.172.2.51"].name, '10.172.2.51') + self.assertEqual(config.switches["10.172.2.51"].driver, 'CiscoSX300') + self.assertEqual(config.switches["10.172.2.51"].username, 'cisco_user') + self.assertEqual(config.switches["10.172.2.51"].password, 'cisco_pass') + self.assertEqual(config.switches["10.172.2.51"].enable_password, 'foobar') + self.assertEqual(config.switches["10.172.2.51"].driver, 'CiscoSX300') + + self.assertEqual(config.switches["10.172.2.52"].name, '10.172.2.52') + self.assertEqual(config.switches["10.172.2.52"].driver, 'CiscoSX300') + self.assertEqual(config.switches["10.172.2.52"].username, 'cisco') + self.assertEqual(config.switches["10.172.2.52"].password, 'cisco') + self.assertEqual(config.switches["10.172.2.52"].enable_password, None) + self.assertEqual(config.switches["10.172.2.52"].driver, 'CiscoSX300') + + self.assertEqual(config.switches["baz"].name, 'baz') + self.assertEqual(config.switches["baz"].driver, 'CiscoSX300') + self.assertEqual(config.switches["baz"].username, 'cisco') + self.assertEqual(config.switches["baz"].password, 'cisco') + +if __name__ == '__main__': + unittest.main() |