diff --git a/gns3server/config.py b/gns3server/config.py index 1729f9aa..ef36dee4 100644 --- a/gns3server/config.py +++ b/gns3server/config.py @@ -22,6 +22,7 @@ Reads the configuration file and store the settings for the server & modules. import sys import os import configparser +import asyncio import logging log = logging.getLogger(__name__) @@ -33,13 +34,19 @@ class Config(object): """ Configuration file management using configparser. + + :params files: Array of configuration files (optionnal) """ - def __init__(self): + def __init__(self, files=None): + + self._files = files + self._watched_files = {} - appname = "GNS3" if sys.platform.startswith("win"): + appname = "GNS3" + # On windows, the configuration file location can be one of the following: # 1: %APPDATA%/GNS3/server.ini # 2: %APPDATA%/GNS3.ini @@ -51,12 +58,13 @@ class Config(object): common_appdata = os.path.expandvars("%COMMON_APPDATA%") self._cloud_file = os.path.join(appdata, appname, "cloud.ini") filename = "server.ini" - self._files = [os.path.join(appdata, appname, filename), - os.path.join(appdata, appname + ".ini"), - os.path.join(common_appdata, appname, filename), - os.path.join(common_appdata, appname + ".ini"), - filename, - self._cloud_file] + if self._files is None: + self._files = [os.path.join(appdata, appname, filename), + os.path.join(appdata, appname + ".ini"), + os.path.join(common_appdata, appname, filename), + os.path.join(common_appdata, appname + ".ini"), + filename, + self._cloud_file] else: # On UNIX-like platforms, the configuration file location can be one of the following: @@ -70,17 +78,36 @@ class Config(object): home = os.path.expanduser("~") self._cloud_file = os.path.join(home, ".config", appname, "cloud.conf") filename = "server.conf" - self._files = [os.path.join(home, ".config", appname, filename), - os.path.join(home, ".config", appname + ".conf"), - os.path.join("/etc/xdg", appname, filename), - os.path.join("/etc/xdg", appname + ".conf"), - filename, - self._cloud_file] + if self._files is None: + self._files = [os.path.join(home, ".config", appname, filename), + os.path.join(home, ".config", appname + ".conf"), + os.path.join("/etc/xdg", appname, filename), + os.path.join("/etc/xdg", appname + ".conf"), + filename, + self._cloud_file] self._config = configparser.ConfigParser() self.read_config() self._cloud_config = configparser.ConfigParser() self.read_cloud_config() + self._watch_config_file() + + def _watch_config_file(self): + asyncio.get_event_loop().call_later(1, self._check_config_file_change) + + def _check_config_file_change(self): + """ + Check if configuration file has changed on the disk + """ + + changed = False + for file in self._watched_files: + if os.stat(file).st_mtime != self._watched_files[file]: + changed = True + if changed: + self.read_config() + # TODO: Support command line override + self._watch_config_file() def list_cloud_config_file(self): return self._cloud_file @@ -101,6 +128,10 @@ class Config(object): parsed_files = self._config.read(self._files) if not parsed_files: log.warning("No configuration file could be found or read") + else: + for file in parsed_files: + log.info("Load configuration file {}".format(file)) + self._watched_files[file] = os.stat(file).st_mtime def get_default_section(self): """ @@ -132,7 +163,10 @@ class Config(object): :param content: A dictionary with section content """ - self._config[section] = content + if not self._config.has_section(section): + self._config.add_section(section) + for key in content: + self._config.set(section, key, content[key]) @staticmethod def instance(): diff --git a/gns3server/main.py b/gns3server/main.py index f94c8e40..bba7cd3c 100644 --- a/gns3server/main.py +++ b/gns3server/main.py @@ -105,16 +105,18 @@ def main(): Entry point for GNS3 server """ + # We init the logger with info level during config file parsing + user_log = init_logger(logging.INFO) + user_log.info("GNS3 server version {}".format(__version__)) current_year = datetime.date.today().year - args = parse_arguments() + user_log.info("Copyright (c) 2007-{} GNS3 Technologies Inc.".format(current_year)) + level = logging.INFO + args = parse_arguments() if args.debug: level = logging.DEBUG user_log = init_logger(level, quiet=args.quiet) - user_log.info("GNS3 server version {}".format(__version__)) - user_log.info("Copyright (c) 2007-{} GNS3 Technologies Inc.".format(current_year)) - server_config = Config.instance().get_section_config("Server") if server_config.getboolean("local"): log.warning("Local mode is enabled. Beware, clients will have full control on your filesystem") diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 00000000..4b7e5063 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,97 @@ +# -*- coding: utf-8 -*- +# +# Copyright (C) 2015 GNS3 Technologies Inc. +# +# 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 3 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, see . + + +import configparser +import time +import os + +from gns3server.config import Config + + +def load_config(tmpdir, settings): + """ + Create a configuration file for + the test. + + :params tmpdir: Temporary directory + :params settings: Configuration settings + :returns: Configuration instance + """ + + path = write_config(tmpdir, settings) + return Config(files=[path]) + + +def write_config(tmpdir, settings): + """ + Write a configuration file for the test. + + :params tmpdir: Temporary directory + :params settings: Configuration settings + :returns: File path + """ + + path = str(tmpdir / "server.conf") + + config = configparser.ConfigParser() + config.read_dict(settings) + with open(path, "w+") as f: + config.write(f) + return path + + +def test_get_section_config(tmpdir): + + config = load_config(tmpdir, { + "Server": { + "host": "127.0.0.1" + } + }) + assert dict(config.get_section_config("Server")) == {"host": "127.0.0.1"} + + +def test_set_section_config(tmpdir): + + config = load_config(tmpdir, { + "Server": { + "host": "127.0.0.1" + } + }) + assert dict(config.get_section_config("Server")) == {"host": "127.0.0.1"} + config.set_section_config("Server", {"host": "192.168.1.1"}) + assert dict(config.get_section_config("Server")) == {"host": "192.168.1.1"} + + +def test_check_config_file_change(tmpdir): + + config = load_config(tmpdir, { + "Server": { + "host": "127.0.0.1" + } + }) + assert dict(config.get_section_config("Server")) == {"host": "127.0.0.1"} + + path = write_config(tmpdir, { + "Server": { + "host": "192.168.1.1" + } + }) + os.utime(path, (time.time() + 1, time.time() + 1)) + + config._check_config_file_change() + assert dict(config.get_section_config("Server")) == {"host": "192.168.1.1"}