[lnst] nettestctl: create user config file at first launch
by Jiří Pírko
commit cca79f00a475bb4c73f9e48790a3c4f761dc458b
Author: Ondrej Lichtner <olichtne(a)redhat.com>
Date: Fri Nov 9 14:48:39 2012 +0100
nettestctl: create user config file at first launch
The controller detets its first launch by searching for a configuration
file in the users home directory. If it doesn't exist it creates it and
fills it with values that are currently loaded. Using this configuration
file is not yet fully functional as it requires the correct values to be
loaded from the system-wide configuration file /etc/lnst-ctl.conf which
will be added at a later date.
Signed-off-by: Ondrej Lichtner <olichtne(a)redhat.com>
nettestctl.py | 10 +++++++++-
1 files changed, 9 insertions(+), 1 deletions(-)
---
diff --git a/nettestctl.py b/nettestctl.py
index e97733c..416e0ab 100755
--- a/nettestctl.py
+++ b/nettestctl.py
@@ -106,7 +106,15 @@ def main():
config.load_config(gitcfg)
else:
config.load_config('/etc/lnst-ctl.conf')
- config.load_config('~/.lnst/lnst-ctl.conf')
+ usr_cfg = os.path.expanduser('~/.lnst/lnst-ctl.conf')
+ if os.path.isfile(usr_cfg):
+ config.load_config(usr_cfg)
+ else:
+ if not os.path.isdir(os.path.dirname(usr_cfg)):
+ os.makedirs(os.path.dirname(usr_cfg))
+ with open(usr_cfg, 'w') as f:
+ f.write(config.dump_config())
+
debug = 0
recipe_path = None
11 years, 5 months
[lnst] Config: add method dump_config
by Jiří Pírko
commit 0d4ef9a3f573595d08f7d3d4a50d6a41af711485
Author: Ondrej Lichtner <olichtne(a)redhat.com>
Date: Fri Nov 9 14:48:38 2012 +0100
Config: add method dump_config
This patch adds a method that returns the currently loaded config values
in a .ini file formatted string. In the future it will be used to create
the config file in users home directory.
I decided to dump all config values and use the '=' operator. Using the
'+=' where it can be used would make more sense but it would be more
complicated to implement and I currently don't see any pretty way to do
it. But I might change this in the future.
Signed-off-by: Ondrej Lichtner <olichtne(a)redhat.com>
Common/Config.py | 22 ++++++++++++++++++++++
1 files changed, 22 insertions(+), 0 deletions(-)
---
diff --git a/Common/Config.py b/Common/Config.py
index 4a4905b..56269b8 100644
--- a/Common/Config.py
+++ b/Common/Config.py
@@ -218,3 +218,25 @@ class Config():
raise ConfigError(msg)
return timeval
+
+ def dump_config(self):
+ string = ""
+ for section in self.options:
+ string += "[%s]\n" % section
+ for option in self.options[section]:
+ val = self.value_to_string(section, option)
+ opt_name = self.options[section][option]["name"]
+ string += "%s = %s\n" % (opt_name, val)
+
+ return string
+
+ def value_to_string(self, section, option):
+ string = ""
+ value = self.options[section][option]["value"]
+
+ if type(value) == list:
+ string = " ".join(value)
+ else:
+ string = str(value)
+
+ return string
11 years, 5 months
[lnst] Config: reimplementation parsing hierarchy
by Jiří Pírko
commit 1cc5d2a94099f18386bdcca1e57ffd49a3158bb7
Author: Ondrej Lichtner <olichtne(a)redhat.com>
Date: Fri Nov 9 14:48:37 2012 +0100
Config: reimplementation parsing hierarchy
This commit changes how we parse the config files. We still use the
ConfigParser for the direct parsing of the files, but the way we
transform its output is changed.
First of all the initialization chooses which scheme should be used.
This means that the options atribute is initialized in a particular way.
The main change is in the deepest point in the hierarchy of this
attribute. Previously we only stored the value of an option, but now
after this change the specific option is a dictionary consisting of
value, additive flag, method used for value handling, and the name used
in the configuration file.
This change allows us to replace the long switch statements for
something more compact. This means we no longer used separate methods
for different sections.
These changes however require all the option handling functions to
accept the same number of arguments. That means I had to add the
options cfg_path to some of these functions.
The reasoning behind this change is that if we are to be adding more and
more options the previous switch scheme would be getting more and more
unreadable. Another point is that the name of the option will be needed
to create the file itself at the first launch.
Signed-off-by: Ondrej Lichtner <olichtne(a)redhat.com>
Common/Config.py | 172 ++++++++++++++++++++++++++----------------------------
1 files changed, 82 insertions(+), 90 deletions(-)
---
diff --git a/Common/Config.py b/Common/Config.py
index 9cd18b0..4a4905b 100644
--- a/Common/Config.py
+++ b/Common/Config.py
@@ -30,33 +30,64 @@ class Config():
self._scheme = scheme
if self._scheme == "controller":
- self.init_controller()
+ self.controller_scheme()
elif self._scheme == "slave":
- self.init_slave()
+ self.slave_scheme()
else:
msg = "Unknow scheme: '%s', can't set up configuration"\
% self._scheme
raise ConfigError(msg)
- def init_controller(self):
+ def controller_scheme(self):
self.options['log'] = dict()
- self.options['log']['path'] = os.path.abspath(os.path.join(
- os.path.dirname(sys.argv[0]), './Logs'))
+ self.options['log']['path'] = {\
+ "value" : os.path.abspath(os.path.join(
+ os.path.dirname(sys.argv[0]), './Logs')),
+ "additive" : False,
+ "action" : self.optionPath,
+ "name" : "path"}
self.options['environment'] = dict()
- self.options['environment']['mac_pool_range'] = \
- ['52:54:01:00:00:01', '52:54:01:FF:FF:FF']
- self.options['environment']['rpcport'] = DefaultRPCPort
- self.options['environment']['pool_dirs'] = []
- self.options['environment']['tool_dirs'] = []
- self.options['environment']['module_dirs'] = []
-
- def init_slave(self):
+ self.options['environment']['mac_pool_range'] = {\
+ "value" : ['52:54:01:00:00:01', '52:54:01:FF:FF:FF'],
+ "additive" : False,
+ "action" : self.optionMacRange,
+ "name" : "mac_pool_range"}
+ self.options['environment']['rpcport'] = {\
+ "value" : DefaultRPCPort,
+ "additive" : False,
+ "action" : self.optionPort,
+ "name" : "rpcport"}
+ self.options['environment']['pool_dirs'] = {\
+ "value" : [],
+ "additive" : True,
+ "action" : self.optionDirList,
+ "name" : "machine_pool_dirs"}
+ self.options['environment']['tool_dirs'] = {\
+ "value" : [],
+ "additive" : True,
+ "action" : self.optionDirList,
+ "name" : "test_tool_dirs"}
+ self.options['environment']['module_dirs'] = {\
+ "value" : [],
+ "additive" : True,
+ "action" : self.optionDirList,
+ "name" : "test_module_dirs"}
+
+ def slave_scheme(self):
self.options['cache'] = dict()
- self.options['cache']['dir'] = os.path.abspath(os.path.join(
- os.path.dirname(sys.argv[0]), './cache'))
-
- self.options['cache']['expiration_period'] = 7*24*60*60 # 1 week
+ self.options['cache']['dir'] = {\
+ "value" : os.path.abspath(os.path.join(
+ os.path.dirname(sys.argv[0]), './cache')),
+ "additive" : False,
+ "action" : self.optionPath,
+ "name" : "cache_dir"}
+
+ self.options['cache']['expiration_period'] = {\
+ "value" : 7*24*60*60, # 1 week
+ "additive" : False,
+ "action" : self.optionTimeval,
+ "name" : "expiration_period"}
def get_config(self):
return self.options
@@ -72,7 +103,7 @@ class Config():
if option not in sect:
msg = 'Unknown option: %s in section: %s' % (option, section)
raise ConfigError(msg)
- return sect[option]
+ return sect[option]["value"]
def load_config(self, path):
'''Parse and load the config file'''
@@ -83,90 +114,51 @@ class Config():
sections = parser._sections
- if self._scheme == "controller":
- self.sectionsCntl(sections, abs_path)
- elif self._scheme == "slave":
- self.sectionsSlave(sections, abs_path)
- else:
- msg = "Unknow scheme: '%s', can't parse sections." \
- % self._scheme
- raise ConfigError(msg)
+ self.handleSections(sections, abs_path)
- def sectionsCntl(self, sections, path):
+ def handleSections(self, sections, path):
for section in sections:
- if section == "log":
- self.sectionLogs(sections[section], path)
- elif section == "environment":
- self.sectionEnvironment(sections[section], path)
+ if section in self.options:
+ self.handleOptions(section, sections[section], path)
else:
msg = "Unknown section: %s" % section
raise ConfigError(msg)
- def sectionsSlave(self, sections, path):
- for section in sections:
- if section == "cache":
- self.sectionCache(sections[section], path)
- else:
- msg = "Unknown section: %s" % section
- raise ConfigError(msg)
-
- def sectionLogs(self, config, cfg_path):
- section = self.options['log']
+ def handleOptions(self, section_name, config, cfg_path):
+ section = self.options[section_name]
config.pop('__name__', None)
- for option in config:
- if option == 'path':
- section['path'] = self.optionPath(config[option], cfg_path)
+ for opt in config:
+ option = self._find_option_by_name(section, opt)
+ if option != None:
+ if option[1]: #additive?
+ option[0]["value"] +=\
+ option[0]["action"](config[opt], cfg_path)
+ else:
+ option[0]["value"] =\
+ option[0]["action"](config[opt], cfg_path)
else:
- msg = "Unknown option: %s in section log" % option
+ msg = "Unknown option: %s in section %s" % (opt, section_name)
raise ConfigError(msg)
- def sectionCache(self, config, cfg_path):
- section = self.options['cache']
-
- config.pop('__name__', None)
- for option in config:
- if option == 'cache_dir':
- section['dir'] = self.optionPath(config[option], cfg_path)
- elif option == 'expiration_period':
- value = self.optionTimeval(config[option])
- section['expiration_period'] = value
- else:
- msg = "Unknown option: %s in section cache" % option
- raise ConfigError(msg)
+ def _find_option_by_name(self, section, opt_name):
+ match = re.match(r'^(\w*)(\s+\+)$', opt_name)
+ if match != None:
+ additive = True
+ opt_name = match.groups()[0]
+ else:
+ additive = False
- def sectionEnvironment(self, config, cfg_path):
- section = self.options['environment']
+ for option in section.itervalues():
+ if option["name"] == opt_name:
+ if (not option["additive"]) and additive:
+ msg = "Operator += cannot be used in option %s" % opt_name
+ raise ConfigError(msg)
+ return (option, additive)
- config.pop('__name__', None)
- for option in config:
- if option == 'mac_pool_range':
- section['mac_pool_range'] = self.optionMacRange(config[option])
- elif option == 'rpcport':
- section['rpcport'] = self.optionPort(config[option])
- elif option == 'machine_pool_dirs':
- section['pool_dirs'] = self.optionDirList(config[option],
- cfg_path)
- elif re.match(r'^machine_pool_dirs\s+\+$', option):
- section['pool_dirs'] += self.optionDirList(config[option],
- cfg_path)
- elif option == 'test_module_dirs':
- section['module_dirs'] = self.optionDirList(config[option],
- cfg_path)
- elif re.match(r'^test_module_dirs\s+\+$', option):
- section['module_dirs'] += self.optionDirList(config[option],
- cfg_path)
- elif option == 'test_tool_dirs':
- section['tool_dirs'] = self.optionDirList(config[option],
- cfg_path)
- elif re.match(r'^test_tool_dirs\s+\+$', option):
- section['tool_dirs'] += self.optionDirList(config[option],
- cfg_path)
- else:
- msg = "Unknown option: %s in section environment" % option
- raise ConfigError(msg)
+ return None
- def optionPort(self, option):
+ def optionPort(self, option, cfg_path):
try:
int(option)
except ValueError:
@@ -180,7 +172,7 @@ class Config():
norm_path = os.path.normpath(abs_path)
return norm_path
- def optionMacRange(self, option):
+ def optionMacRange(self, option, cfg_path):
vals = option.split()
if len(vals) != 2:
msg = "Option mac_pool_range expects 2"\
@@ -206,7 +198,7 @@ class Config():
return dirs
- def optionTimeval(self, option):
+ def optionTimeval(self, option, cfg_path):
timeval_re = "^(([0-9]+)days?)?\s*(([0-9]+)hours?)?\s*" \
"(([0-9]+)minutes?)?\s*(([0-9]+)seconds?)?$"
timeval_match = re.match(timeval_re, option)
11 years, 5 months
[PATCH 1/3] Config: reimplementation parsing hierarchy
by Ondrej Lichtner
From: Ondrej Lichtner <olichtne(a)redhat.com>
This commit changes how we parse the config files. We still use the
ConfigParser for the direct parsing of the files, but the way we
transform its output is changed.
First of all the initialization chooses which scheme should be used.
This means that the options atribute is initialized in a particular way.
The main change is in the deepest point in the hierarchy of this
attribute. Previously we only stored the value of an option, but now
after this change the specific option is a dictionary consisting of
value, additive flag, method used for value handling, and the name used
in the configuration file.
This change allows us to replace the long switch statements for
something more compact. This means we no longer used separate methods
for different sections.
These changes however require all the option handling functions to
accept the same number of arguments. That means I had to add the
options cfg_path to some of these functions.
The reasoning behind this change is that if we are to be adding more and
more options the previous switch scheme would be getting more and more
unreadable. Another point is that the name of the option will be needed
to create the file itself at the first launch.
Signed-off-by: Ondrej Lichtner <olichtne(a)redhat.com>
---
Common/Config.py | 172 ++++++++++++++++++++++++++-----------------------------
1 file changed, 82 insertions(+), 90 deletions(-)
diff --git a/Common/Config.py b/Common/Config.py
index 9cd18b0..4a4905b 100644
--- a/Common/Config.py
+++ b/Common/Config.py
@@ -30,33 +30,64 @@ class Config():
self._scheme = scheme
if self._scheme == "controller":
- self.init_controller()
+ self.controller_scheme()
elif self._scheme == "slave":
- self.init_slave()
+ self.slave_scheme()
else:
msg = "Unknow scheme: '%s', can't set up configuration"\
% self._scheme
raise ConfigError(msg)
- def init_controller(self):
+ def controller_scheme(self):
self.options['log'] = dict()
- self.options['log']['path'] = os.path.abspath(os.path.join(
- os.path.dirname(sys.argv[0]), './Logs'))
+ self.options['log']['path'] = {\
+ "value" : os.path.abspath(os.path.join(
+ os.path.dirname(sys.argv[0]), './Logs')),
+ "additive" : False,
+ "action" : self.optionPath,
+ "name" : "path"}
self.options['environment'] = dict()
- self.options['environment']['mac_pool_range'] = \
- ['52:54:01:00:00:01', '52:54:01:FF:FF:FF']
- self.options['environment']['rpcport'] = DefaultRPCPort
- self.options['environment']['pool_dirs'] = []
- self.options['environment']['tool_dirs'] = []
- self.options['environment']['module_dirs'] = []
-
- def init_slave(self):
+ self.options['environment']['mac_pool_range'] = {\
+ "value" : ['52:54:01:00:00:01', '52:54:01:FF:FF:FF'],
+ "additive" : False,
+ "action" : self.optionMacRange,
+ "name" : "mac_pool_range"}
+ self.options['environment']['rpcport'] = {\
+ "value" : DefaultRPCPort,
+ "additive" : False,
+ "action" : self.optionPort,
+ "name" : "rpcport"}
+ self.options['environment']['pool_dirs'] = {\
+ "value" : [],
+ "additive" : True,
+ "action" : self.optionDirList,
+ "name" : "machine_pool_dirs"}
+ self.options['environment']['tool_dirs'] = {\
+ "value" : [],
+ "additive" : True,
+ "action" : self.optionDirList,
+ "name" : "test_tool_dirs"}
+ self.options['environment']['module_dirs'] = {\
+ "value" : [],
+ "additive" : True,
+ "action" : self.optionDirList,
+ "name" : "test_module_dirs"}
+
+ def slave_scheme(self):
self.options['cache'] = dict()
- self.options['cache']['dir'] = os.path.abspath(os.path.join(
- os.path.dirname(sys.argv[0]), './cache'))
-
- self.options['cache']['expiration_period'] = 7*24*60*60 # 1 week
+ self.options['cache']['dir'] = {\
+ "value" : os.path.abspath(os.path.join(
+ os.path.dirname(sys.argv[0]), './cache')),
+ "additive" : False,
+ "action" : self.optionPath,
+ "name" : "cache_dir"}
+
+ self.options['cache']['expiration_period'] = {\
+ "value" : 7*24*60*60, # 1 week
+ "additive" : False,
+ "action" : self.optionTimeval,
+ "name" : "expiration_period"}
def get_config(self):
return self.options
@@ -72,7 +103,7 @@ class Config():
if option not in sect:
msg = 'Unknown option: %s in section: %s' % (option, section)
raise ConfigError(msg)
- return sect[option]
+ return sect[option]["value"]
def load_config(self, path):
'''Parse and load the config file'''
@@ -83,90 +114,51 @@ class Config():
sections = parser._sections
- if self._scheme == "controller":
- self.sectionsCntl(sections, abs_path)
- elif self._scheme == "slave":
- self.sectionsSlave(sections, abs_path)
- else:
- msg = "Unknow scheme: '%s', can't parse sections." \
- % self._scheme
- raise ConfigError(msg)
+ self.handleSections(sections, abs_path)
- def sectionsCntl(self, sections, path):
+ def handleSections(self, sections, path):
for section in sections:
- if section == "log":
- self.sectionLogs(sections[section], path)
- elif section == "environment":
- self.sectionEnvironment(sections[section], path)
+ if section in self.options:
+ self.handleOptions(section, sections[section], path)
else:
msg = "Unknown section: %s" % section
raise ConfigError(msg)
- def sectionsSlave(self, sections, path):
- for section in sections:
- if section == "cache":
- self.sectionCache(sections[section], path)
- else:
- msg = "Unknown section: %s" % section
- raise ConfigError(msg)
-
- def sectionLogs(self, config, cfg_path):
- section = self.options['log']
+ def handleOptions(self, section_name, config, cfg_path):
+ section = self.options[section_name]
config.pop('__name__', None)
- for option in config:
- if option == 'path':
- section['path'] = self.optionPath(config[option], cfg_path)
+ for opt in config:
+ option = self._find_option_by_name(section, opt)
+ if option != None:
+ if option[1]: #additive?
+ option[0]["value"] +=\
+ option[0]["action"](config[opt], cfg_path)
+ else:
+ option[0]["value"] =\
+ option[0]["action"](config[opt], cfg_path)
else:
- msg = "Unknown option: %s in section log" % option
+ msg = "Unknown option: %s in section %s" % (opt, section_name)
raise ConfigError(msg)
- def sectionCache(self, config, cfg_path):
- section = self.options['cache']
-
- config.pop('__name__', None)
- for option in config:
- if option == 'cache_dir':
- section['dir'] = self.optionPath(config[option], cfg_path)
- elif option == 'expiration_period':
- value = self.optionTimeval(config[option])
- section['expiration_period'] = value
- else:
- msg = "Unknown option: %s in section cache" % option
- raise ConfigError(msg)
+ def _find_option_by_name(self, section, opt_name):
+ match = re.match(r'^(\w*)(\s+\+)$', opt_name)
+ if match != None:
+ additive = True
+ opt_name = match.groups()[0]
+ else:
+ additive = False
- def sectionEnvironment(self, config, cfg_path):
- section = self.options['environment']
+ for option in section.itervalues():
+ if option["name"] == opt_name:
+ if (not option["additive"]) and additive:
+ msg = "Operator += cannot be used in option %s" % opt_name
+ raise ConfigError(msg)
+ return (option, additive)
- config.pop('__name__', None)
- for option in config:
- if option == 'mac_pool_range':
- section['mac_pool_range'] = self.optionMacRange(config[option])
- elif option == 'rpcport':
- section['rpcport'] = self.optionPort(config[option])
- elif option == 'machine_pool_dirs':
- section['pool_dirs'] = self.optionDirList(config[option],
- cfg_path)
- elif re.match(r'^machine_pool_dirs\s+\+$', option):
- section['pool_dirs'] += self.optionDirList(config[option],
- cfg_path)
- elif option == 'test_module_dirs':
- section['module_dirs'] = self.optionDirList(config[option],
- cfg_path)
- elif re.match(r'^test_module_dirs\s+\+$', option):
- section['module_dirs'] += self.optionDirList(config[option],
- cfg_path)
- elif option == 'test_tool_dirs':
- section['tool_dirs'] = self.optionDirList(config[option],
- cfg_path)
- elif re.match(r'^test_tool_dirs\s+\+$', option):
- section['tool_dirs'] += self.optionDirList(config[option],
- cfg_path)
- else:
- msg = "Unknown option: %s in section environment" % option
- raise ConfigError(msg)
+ return None
- def optionPort(self, option):
+ def optionPort(self, option, cfg_path):
try:
int(option)
except ValueError:
@@ -180,7 +172,7 @@ class Config():
norm_path = os.path.normpath(abs_path)
return norm_path
- def optionMacRange(self, option):
+ def optionMacRange(self, option, cfg_path):
vals = option.split()
if len(vals) != 2:
msg = "Option mac_pool_range expects 2"\
@@ -206,7 +198,7 @@ class Config():
return dirs
- def optionTimeval(self, option):
+ def optionTimeval(self, option, cfg_path):
timeval_re = "^(([0-9]+)days?)?\s*(([0-9]+)hours?)?\s*" \
"(([0-9]+)minutes?)?\s*(([0-9]+)seconds?)?$"
timeval_match = re.match(timeval_re, option)
--
1.7.11.7
11 years, 5 months
[lnst] ctl-config: Adding default path to tools and modules
by Jiří Pírko
commit 3d200949dfc901809b5f0ab54b92a81f810f01de
Author: Radek Pazdera <rpazdera(a)redhat.com>
Date: Thu Nov 8 16:17:54 2012 +0100
ctl-config: Adding default path to tools and modules
This commit adds the git-default paths to controller config for
test modules and test tools.
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
lnst-ctl.conf | 4 ++--
1 files changed, 2 insertions(+), 2 deletions(-)
---
diff --git a/lnst-ctl.conf b/lnst-ctl.conf
index 79451b3..cb4ee11 100644
--- a/lnst-ctl.conf
+++ b/lnst-ctl.conf
@@ -2,7 +2,7 @@
mac_pool_range = 52:54:01:00:00:01 52:54:01:FF:FF:FF
rpcport = 9999
machine_pool_dirs =
-test_tool_dirs =
-test_module_dirs =
+test_tool_dirs = ./test_tools
+test_module_dirs = ./test_modules
[log]
path = ./Logs
11 years, 5 months
[lnst] xmlrpc: Modifying 'hello' and adding 'bye' method
by Jiří Pírko
commit 6eee498372621909f5bdfd0fdb1be1b921f33f0a
Author: Radek Pazdera <rpazdera(a)redhat.com>
Date: Thu Nov 8 16:17:53 2012 +0100
xmlrpc: Modifying 'hello' and adding 'bye' method
This commit adds new 'bye' method -- an equivalent to 'hello'
method.
Hello is allways sent prior to any other method from controller to
slave. The bye method will be used to indicate that no method will
follow in this controller session.
The methods are used to initialize and cleanup some internal slave
structures and caches.
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
NetTest/NetTestController.py | 2 +-
NetTest/NetTestSlave.py | 7 +++++++
2 files changed, 8 insertions(+), 1 deletions(-)
---
diff --git a/NetTest/NetTestController.py b/NetTest/NetTestController.py
index 903c8ad..45c4941 100644
--- a/NetTest/NetTestController.py
+++ b/NetTest/NetTestController.py
@@ -353,7 +353,7 @@ class NetTestController:
if "rpc" not in info or "configured_interfaces" not in info:
continue
- self._rpc_call(machine_id, "clear_resource_table")
+ self._rpc_call(machine_id, "bye")
for if_id in reversed(info["configured_interfaces"]):
self._rpc_call(machine_id, 'deconfigure_interface', if_id)
diff --git a/NetTest/NetTestSlave.py b/NetTest/NetTestSlave.py
index 2d15dfa..07d2de8 100644
--- a/NetTest/NetTestSlave.py
+++ b/NetTest/NetTestSlave.py
@@ -47,8 +47,15 @@ class NetTestSlaveXMLRPC:
self._resource_table = {}
def hello(self):
+ self.clear_resource_table()
+ self._cache.del_old_entries()
return "hello"
+ def bye(self):
+ self.clear_resource_table()
+ self._cache.del_old_entries()
+ return "bye"
+
def get_new_logs(self):
buffer = Logs.get_buffer()
logs = buffer.flush()
11 years, 5 months
[lnst] Config: Adding cache expiration_period to slave config
by Jiří Pírko
commit 19d6c84b39354b57278f731e87fe182042d5680a
Author: Radek Pazdera <rpazdera(a)redhat.com>
Date: Thu Nov 8 16:17:52 2012 +0100
Config: Adding cache expiration_period to slave config
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
Common/Config.py | 26 ++++++++++++++++++++++++++
lnst-slave.conf | 1 +
2 files changed, 27 insertions(+), 0 deletions(-)
---
diff --git a/Common/Config.py b/Common/Config.py
index 41438a6..9cd18b0 100644
--- a/Common/Config.py
+++ b/Common/Config.py
@@ -56,6 +56,8 @@ class Config():
self.options['cache']['dir'] = os.path.abspath(os.path.join(
os.path.dirname(sys.argv[0]), './cache'))
+ self.options['cache']['expiration_period'] = 7*24*60*60 # 1 week
+
def get_config(self):
return self.options
@@ -126,6 +128,9 @@ class Config():
for option in config:
if option == 'cache_dir':
section['dir'] = self.optionPath(config[option], cfg_path)
+ elif option == 'expiration_period':
+ value = self.optionTimeval(config[option])
+ section['expiration_period'] = value
else:
msg = "Unknown option: %s in section cache" % option
raise ConfigError(msg)
@@ -200,3 +205,24 @@ class Config():
dirs.append(norm_path)
return dirs
+
+ def optionTimeval(self, option):
+ timeval_re = "^(([0-9]+)days?)?\s*(([0-9]+)hours?)?\s*" \
+ "(([0-9]+)minutes?)?\s*(([0-9]+)seconds?)?$"
+ timeval_match = re.match(timeval_re, option)
+ if timeval_match:
+ values = timeval_match.groups()
+ timeval = 0
+ if values[1]:
+ timeval += int(values[1])*24*60*60
+ if values[3]:
+ timeval += int(values[3])*60*60
+ if values[5]:
+ timeval += int(values[5])*60
+ if values[7]:
+ timeval += int(values[7])
+ else:
+ msg = "Incorrect timeval format."
+ raise ConfigError(msg)
+
+ return timeval
diff --git a/lnst-slave.conf b/lnst-slave.conf
index 6a9bfe1..924d0c7 100644
--- a/lnst-slave.conf
+++ b/lnst-slave.conf
@@ -1,2 +1,3 @@
[cache]
cache_dir = ./cache
+expiration_period = 7days
11 years, 5 months
[lnst] nettestslave: Pass config to the RPC methods
by Jiří Pírko
commit 3131bfe33c3535f3c2025ee0d7d7e0499f0cc4c9
Author: Radek Pazdera <rpazdera(a)redhat.com>
Date: Thu Nov 8 16:17:51 2012 +0100
nettestslave: Pass config to the RPC methods
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
NetTest/NetTestSlave.py | 6 +++---
nettestslave.py | 5 +++--
2 files changed, 6 insertions(+), 5 deletions(-)
---
diff --git a/NetTest/NetTestSlave.py b/NetTest/NetTestSlave.py
index 7f0b035..2d15dfa 100644
--- a/NetTest/NetTestSlave.py
+++ b/NetTest/NetTestSlave.py
@@ -33,7 +33,7 @@ class NetTestSlaveXMLRPC:
'''
Exported xmlrpc methods
'''
- def __init__(self, command_context):
+ def __init__(self, command_context, config):
self._netconfig = None
self._packet_captures = {}
self._netconfig = NetConfig()
@@ -225,7 +225,7 @@ class MySimpleXMLRPCServer(Server):
pass
class NetTestSlave:
- def __init__(self, port = DefaultRPCPort):
+ def __init__(self, config, port = DefaultRPCPort):
die_when_parent_die()
command_context = NetTestCommandContext()
@@ -235,7 +235,7 @@ class NetTestSlave:
server.register_die_signal(signal.SIGHUP)
server.register_die_signal(signal.SIGINT)
server.register_die_signal(signal.SIGTERM)
- server.register_instance(NetTestSlaveXMLRPC(command_context))
+ server.register_instance(NetTestSlaveXMLRPC(command_context, config))
self._server = server
def run(self):
diff --git a/nettestslave.py b/nettestslave.py
index c9cb0c9..6367d73 100755
--- a/nettestslave.py
+++ b/nettestslave.py
@@ -76,9 +76,10 @@ def main():
logging.info("Started")
if port:
- nettestslave = NetTestSlave(port=port)
+ nettestslave = NetTestSlave(config, port=port)
else:
- nettestslave = NetTestSlave()
+ nettestslave = NetTestSlave(config)
+
if daemon:
daemon = Daemon(pidfile)
daemon.daemonize()
11 years, 5 months
[lnst] NetTestController: Caching support implementation
by Jiří Pírko
commit 7a34f777cc7006c2235d7962c0cffe0dcd033aeb
Author: Radek Pazdera <rpazdera(a)redhat.com>
Date: Thu Nov 8 16:17:50 2012 +0100
NetTestController: Caching support implementation
This patch adds test caching support to the NetTestController class.
The new way of handling test resources implies, that controller can no
longer execute commands directly on itself, unless a configured slave
is runing on the same machine as well. Therefore the support for this
was removed in this patch as well.
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
NetTest/NetTestController.py | 119 +++++++++++++++++++++++++++++++++++------
1 files changed, 101 insertions(+), 18 deletions(-)
---
diff --git a/NetTest/NetTestController.py b/NetTest/NetTestController.py
index c01eecd..903c8ad 100644
--- a/NetTest/NetTestController.py
+++ b/NetTest/NetTestController.py
@@ -14,7 +14,9 @@ jpirko(a)redhat.com (Jiri Pirko)
import logging
import socket
import os
+import re
import pickle
+import tempfile
from xmlrpclib import Binary
from Common.Logs import Logs, log_exc_traceback
from Common.SshUtils import scp_from_remote
@@ -25,7 +27,7 @@ from Common.SlaveUtils import prepare_client_session
from Common.NetUtils import MacPool
from NetTest.NetTestCommand import NetTestCommandContext, NetTestCommand, str_command
from Common.VirtUtils import VirtNetCtl, VirtDomainCtl, BridgeCtl
-from Common.Utils import wait_for
+from Common.Utils import wait_for, md5sum, dir_md5sum, create_tar_archive
from NetTest.MachinePool import MachinePool
class NetTestError(Exception):
@@ -43,7 +45,6 @@ class NetTestController:
self._remote_capture_files = {}
self._config = config
self._log_root_path = Logs.get_logging_root_path()
- self._command_context = NetTestCommandContext()
self._machine_pool = MachinePool(config.get_option('environment',
'pool_dirs'))
@@ -70,6 +71,12 @@ class NetTestController:
ntparse.register_event_handler("provisioning_requirements_ready",
self._prepare_provisioning)
+ modules_dirs = config.get_option('environment', 'module_dirs')
+ tools_dirs = config.get_option('environment', 'tool_dirs')
+
+ self._resource_table = {}
+ self._resource_table["module"] = self._load_test_modules(modules_dirs)
+ self._resource_table["tools"] = self._load_test_tools(tools_dirs)
self._ntparse = ntparse
@@ -241,6 +248,39 @@ class NetTestController:
info["configured_interfaces"] = []
+ self._rpc_call(machine_id, "clear_resource_table")
+ required = self._resource_table
+
+ for res_type, resources in self._resource_table.iteritems():
+ for res_name, res in resources.iteritems():
+ has_resource = self._rpc_call(machine_id, "has_resource",
+ res["hash"])
+ if not has_resource:
+ msg = "Transfering %s %s to machine %s" % \
+ (res_name, res_type, machine_id)
+ logging.info(msg)
+
+ local_path = required[res_type][res_name]["path"]
+
+ if res_type == "tools":
+ archive = tempfile.NamedTemporaryFile(delete=False)
+ archive_path = archive.name
+ archive.close()
+
+ create_tar_archive(local_path, archive_path, True)
+ local_path = archive_path
+
+ remote_path = self._copy_to_slave(local_path, machine_id)
+ self._rpc_call(machine_id, "add_resource_to_cache",
+ res["hash"], remote_path, res_name,
+ res["path"], res_type)
+
+ if res_type == "tools":
+ os.unlink(archive_path)
+
+ self._rpc_call(machine_id, "map_resource",
+ res["hash"], res_type, res_name)
+
# Some additional initialization is necessary in case the
# underlying machine is provisioned from the pool
prov_id = self._machine_pool.get_provisioner_id(machine_id)
@@ -312,6 +352,9 @@ class NetTestController:
info = self._get_machineinfo(machine_id)
if "rpc" not in info or "configured_interfaces" not in info:
continue
+
+ self._rpc_call(machine_id, "clear_resource_table")
+
for if_id in reversed(info["configured_interfaces"]):
self._rpc_call(machine_id, 'deconfigure_interface', if_id)
@@ -360,21 +403,18 @@ class NetTestController:
except KeyError:
pass
- if machine_id == "0":
- cmd_res = NetTestCommand(self._command_context, command).run()
- else:
- if "timeout" in command:
- timeout = command["timeout"]
- logging.debug("Setting socket timeout to \"%d\"", timeout)
- socket.setdefaulttimeout(timeout)
- try:
- cmd_res = self._rpc_call(machine_id, 'run_command', command)
- except socket.timeout:
- msg = "RPC connection to machine %s timed out" % machine_id
- raise NetTestError(msg)
- if "timeout" in command:
- logging.debug("Setting socket timeout to default value")
- socket.setdefaulttimeout(None)
+ if "timeout" in command:
+ timeout = command["timeout"]
+ logging.debug("Setting socket timeout to \"%d\"", timeout)
+ socket.setdefaulttimeout(timeout)
+ try:
+ cmd_res = self._rpc_call(machine_id, 'run_command', command)
+ except socket.timeout:
+ msg = "RPC connection to machine %s timed out" % machine_id
+ raise NetTestError(msg)
+ if "timeout" in command:
+ logging.debug("Setting socket timeout to default value")
+ socket.setdefaulttimeout(None)
if command["type"] == "system_config":
if cmd_res["passed"]:
@@ -438,7 +478,6 @@ class NetTestController:
self._deconfigure_slaves()
self._disconnect_slaves()
- self._command_context.cleanup()
if not err:
return res
@@ -566,3 +605,47 @@ class NetTestController:
# return remote path
rpath = self._rpc_call(machine_id, "finish_copy")
return rpath
+
+ def _load_test_modules(self, dirs):
+ modules = {}
+ for dir_name in dirs:
+ files = os.listdir(dir_name)
+ for f in files:
+ test_path = os.path.abspath("%s/%s" % (dir_name, f))
+ if os.path.isfile(test_path):
+ match = re.match("Test(.+)\.py$", f)
+ if match:
+ test_name = match.group(1)
+ test_hash = md5sum(test_path)
+
+ if test_name in modules:
+ msg = "Overriding previously defined test '%s' " \
+ "from %s with a different one located in " \
+ "%s" % (test_name, test_path,
+ modules[test_name]["path"])
+ logging.warn(msg)
+
+ modules[test_name] = {"path": test_path,
+ "hash": test_hash}
+ return modules
+
+ def _load_test_tools(self, dirs):
+ packages = {}
+ for dir_name in dirs:
+ files = os.listdir(dir_name)
+ for f in files:
+ pkg_path = os.path.abspath("%s/%s" % (dir_name, f))
+ if os.path.isdir(pkg_path):
+ pkg_name = os.path.basename(pkg_path.rstrip("/"))
+ pkg_hash = dir_md5sum(pkg_path)
+
+ if pkg_name in packages:
+ msg = "Overriding previously defined tools " \
+ "package '%s' from %s with a different " \
+ "one located in %s" % (pkg_name, pkg_path,
+ packages[pkg_name]["path"])
+ logging.warn(msg)
+
+ packages[pkg_name] = {"path": pkg_path,
+ "hash": pkg_hash}
+ return packages
11 years, 5 months
[lnst] NetTestCommand: Adding caching support
by Jiří Pírko
commit ff4c05792310f4c397a594e561b5ffab8bdc9496
Author: Radek Pazdera <rpazdera(a)redhat.com>
Date: Thu Nov 8 16:17:49 2012 +0100
NetTestCommand: Adding caching support
This patch changes NetTestCommand so it can access tests stored
in the cache rather than directly from the tree.
The resource table with paths to all available modules and tools
is passed to the command.
Also new feature of command type exec was added. It can now be
issued with 'from' attribute that indicates what tools should
the command be executed from. For instance:
<command machine_id="1" timeout="30" type="exec" from="multicast"
value="./igmp_query" />
Signed-off-by: Radek Pazdera <rpazdera(a)redhat.com>
NetTest/NetTestCommand.py | 53 +++++++++++++++++++++++++++++++++------------
NetTest/NetTestParse.py | 3 ++
2 files changed, 42 insertions(+), 14 deletions(-)
---
diff --git a/NetTest/NetTestCommand.py b/NetTest/NetTestCommand.py
index 4fde1ee..0efae44 100644
--- a/NetTest/NetTestCommand.py
+++ b/NetTest/NetTestCommand.py
@@ -122,12 +122,15 @@ class NetTestCommandContext:
self._kill_all_bg_cmds()
self._dict = {}
-def NetTestCommandTest(command):
+def NetTestCommandTest(command, resource_table):
test_name = command["value"]
+ if not test_name in resource_table["module"]:
+ msg = "Test module '%s' not found" % test_name
+
+ module_path = resource_table["module"][test_name]
module_name = "Test%s" % test_name
- fp, pathname, description = imp.find_module("Tests/%s" % module_name)
- module = imp.load_module("Tests/%s" % module_name,
- fp, pathname, description)
+
+ module = imp.load_source(module_name, module_path)
test_class = getattr(module, module_name)
return test_class(command)
@@ -135,6 +138,7 @@ class NetTestCommandGeneric:
def __init__(self, command):
self._command = command
self._result = None
+ self._resource_table = None
def run(self):
pass
@@ -164,10 +168,27 @@ class NetTestCommandGeneric:
def set_handle_intr(self):
pass
+ def set_resource_table(self, res_table):
+ self._resource_table = res_table
+
+ def exec_cmd(self, cmd, *args, **kwargs):
+ return exec_cmd(cmd, *args, **kwargs)
+
+ def exec_from(self, tools_name, cmd, *args, **kwargs):
+ if not tools_name in self._resource_table["tools"]:
+ msg = "Tools '%s' not found" % tools_name
+ raise CommandException(msg)
+
+ tools_path = self._resource_table["tools"][tools_name]
+ return exec_cmd("cd \"%s\" && %s" % (tools_path, cmd), *args, **kwargs)
+
class NetTestCommandExec(NetTestCommandGeneric):
def run(self):
try:
- exec_cmd(self._command["value"])
+ if "from" in self._command:
+ self.exec_from(self._command["from"], self._command["value"])
+ else:
+ self.exec_cmd(self._command["value"])
self.set_pass()
except ExecCmdFail:
if "bg_id" in self._command:
@@ -253,27 +274,31 @@ class NetTestCommandKill(NetTestCommandControl):
self._command_context.del_bg_cmd(bg_cmd)
self.set_result({"passed": True})
-def get_command_class(command_context, command):
+def get_command_class(command_context, command, resource_table):
cmd_type = command["type"]
if cmd_type == "exec":
- return NetTestCommandExec(command)
+ cmd_cls = NetTestCommandExec(command)
elif cmd_type == "test":
- return NetTestCommandTest(command)
+ cmd_cls = NetTestCommandTest(command, resource_table)
elif cmd_type == "wait":
- return NetTestCommandWait(command_context, command)
+ cmd_cls = NetTestCommandWait(command_context, command)
elif cmd_type == "intr":
- return NetTestCommandIntr(command_context, command)
+ cmd_cls = NetTestCommandIntr(command_context, command)
elif cmd_type == "kill":
- return NetTestCommandKill(command_context, command)
+ cmd_cls = NetTestCommandKill(command_context, command)
elif cmd_type == "system_config":
- return NetTestCommandSystemConfig(command)
+ cmd_cls = NetTestCommandSystemConfig(command)
else:
logging.error("Unknown comamnd type \"%s\"" % cmd_type)
raise Exception("Unknown command type \"%s\"" % cmd_type)
+ cmd_cls.set_resource_table(resource_table)
+ return cmd_cls
+
class NetTestCommand:
- def __init__(self, command_context, command):
- self._command_class = get_command_class(command_context, command)
+ def __init__(self, command_context, command, resource_table):
+ self._command_class = get_command_class(command_context, command,
+ resource_table)
self._command_context = command_context
self._command = command
diff --git a/NetTest/NetTestParse.py b/NetTest/NetTestParse.py
index 0d8678c..8d16638 100644
--- a/NetTest/NetTestParse.py
+++ b/NetTest/NetTestParse.py
@@ -586,6 +586,9 @@ class CommandParse(RecipeParser):
bool_it)
else:
command["persistent"] = False
+ elif command["type"] == "exec":
+ if self._has_attribute(node, "from"):
+ command["from"] = self._get_attribute(node, "from")
scheme = {"options": self._options}
self._process_child_nodes(node, scheme)
11 years, 5 months