aboutsummaryrefslogtreecommitdiff
path: root/Vland/config
diff options
context:
space:
mode:
Diffstat (limited to 'Vland/config')
-rw-r--r--Vland/config/__init__.py23
-rw-r--r--Vland/config/config.py270
-rw-r--r--Vland/config/test-clashing-ports.cfg33
-rw-r--r--Vland/config/test-invalid-DB.cfg5
-rw-r--r--Vland/config/test-invalid-logging-level.cfg30
-rw-r--r--Vland/config/test-invalid-vland.cfg11
-rw-r--r--Vland/config/test-known-good.cfg35
-rw-r--r--Vland/config/test-missing-db-username.cfg5
-rw-r--r--Vland/config/test-missing-dbname.cfg6
-rw-r--r--Vland/config/test-reused-switch-names.cfg26
-rw-r--r--Vland/config/test-unknown-section.cfg29
-rw-r--r--Vland/config/test.py101
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()