Commit 518df332 authored by Julien Muchembled's avatar Julien Muchembled Committed by Klaus Wölfel

testnode: some code clean up

A lot was found with pylint.
parent bb63c34b
...@@ -235,7 +235,7 @@ class ERP5TestNode(TestCase): ...@@ -235,7 +235,7 @@ class ERP5TestNode(TestCase):
node_test_suite.custom_profile_path) node_test_suite.custom_profile_path)
profile = open(node_test_suite.custom_profile_path, 'r') profile = open(node_test_suite.custom_profile_path, 'r')
if my_test_type=='UnitTest': if my_test_type=='UnitTest':
expected_profile = """ expected_profile = """\
[buildout] [buildout]
extends = %(temp_dir)s/testnode/foo/rep0/software.cfg extends = %(temp_dir)s/testnode/foo/rep0/software.cfg
...@@ -254,7 +254,7 @@ develop = false ...@@ -254,7 +254,7 @@ develop = false
else: else:
revision1 = "azerty" revision1 = "azerty"
revision2 = "qwerty" revision2 = "qwerty"
expected_profile = """ expected_profile = """\
[buildout] [buildout]
extends = %(temp_dir)s/testnode/foo/rep0/software.cfg extends = %(temp_dir)s/testnode/foo/rep0/software.cfg
...@@ -818,17 +818,17 @@ develop = false ...@@ -818,17 +818,17 @@ develop = false
test_node.node_test_suite_dict test_node.node_test_suite_dict
rand_part_set = set() rand_part_set = set()
self.assertEquals(2, len(test_node.node_test_suite_dict)) self.assertEquals(2, len(test_node.node_test_suite_dict))
assert(test_node.suite_log is not None) self.assertIsNot(test_node.suite_log, None)
assert(isinstance(test_node.suite_log, types.MethodType)) self.assertTrue(isinstance(test_node.suite_log, types.MethodType))
for ref, suite in test_node.node_test_suite_dict.items(): for ref, suite in test_node.node_test_suite_dict.items():
self.assertTrue('var/log/testnode/%s' % suite.reference in \ self.assertTrue('var/log/testnode/%s' % suite.reference in \
suite.suite_log_path, suite.suite_log_path,
"Incorrect suite log path : %r" % suite.suite_log_path) "Incorrect suite log path : %r" % suite.suite_log_path)
assert(suite.suite_log_path.endswith('suite.log')) self.assertTrue(suite.suite_log_path.endswith('suite.log'))
m = re.match('.*\-(.*)\/suite.log', suite.suite_log_path) m = re.match('.*\-(.*)\/suite.log', suite.suite_log_path)
rand_part = m.groups()[0] rand_part = m.groups()[0]
assert(len(rand_part) == 32) self.assertEqual(len(rand_part), 32)
assert(rand_part not in rand_part_set) self.assertNotIn(rand_part, rand_part_set)
rand_part_set.add(rand_part) rand_part_set.add(rand_part)
suite_log = open(suite.suite_log_path, 'r') suite_log = open(suite.suite_log_path, 'r')
self.assertEquals(1, len([x for x in suite_log.readlines() \ self.assertEquals(1, len([x for x in suite_log.readlines() \
......
...@@ -24,23 +24,13 @@ ...@@ -24,23 +24,13 @@
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# #
############################################################################## ##############################################################################
from datetime import datetime,timedelta import errno
import os import os
import subprocess
import sys
import time
import glob import glob
import SlapOSControler
import json
import time
import shutil import shutil
import logging
import string import string
import random import random
from ProcessManager import SubprocessError, ProcessManager, CancellationError from .Utils import createFolder
from subprocess import CalledProcessError
from Updater import Updater
from erp5.util import taskdistribution
class SlapOSInstance(object): class SlapOSInstance(object):
""" """
...@@ -67,22 +57,19 @@ class NodeTestSuite(SlapOSInstance): ...@@ -67,22 +57,19 @@ class NodeTestSuite(SlapOSInstance):
self.reference = reference self.reference = reference
self.cluster_configuration = {} self.cluster_configuration = {}
def edit(self, **kw):
super(NodeTestSuite, self).edit(**kw)
def _checkData(self): def _checkData(self):
if getattr(self, "working_directory", None) is not None: if getattr(self, "working_directory", None) is not None:
if not(self.working_directory.endswith(os.path.sep + self.reference)): if not(self.working_directory.endswith(os.path.sep + self.reference)):
self.working_directory = os.path.join(self.working_directory, self.working_directory = os.path.join(self.working_directory,
self.reference) self.reference)
SlapOSControler.createFolder(self.working_directory) createFolder(self.working_directory)
self.test_suite_directory = os.path.join( self.test_suite_directory = os.path.join(
self.working_directory, "test_suite") self.working_directory, "test_suite")
self.custom_profile_path = os.path.join(self.working_directory, self.custom_profile_path = os.path.join(self.working_directory,
'software.cfg') 'software.cfg')
if getattr(self, "vcs_repository_list", None) is not None: if getattr(self, "vcs_repository_list", None) is not None:
for vcs_repository in self.vcs_repository_list: for vcs_repository in self.vcs_repository_list:
buildout_section_id = vcs_repository.get('buildout_section_id', None) buildout_section_id = vcs_repository.get('buildout_section_id')
repository_id = buildout_section_id or \ repository_id = buildout_section_id or \
vcs_repository.get('url').split('/')[-1].split('.')[0] vcs_repository.get('url').split('/')[-1].split('.')[0]
repository_path = os.path.join(self.working_directory,repository_id) repository_path = os.path.join(self.working_directory,repository_id)
...@@ -92,18 +79,20 @@ class NodeTestSuite(SlapOSInstance): ...@@ -92,18 +79,20 @@ class NodeTestSuite(SlapOSInstance):
def createSuiteLog(self): def createSuiteLog(self):
# /srv/slapgrid/slappartXX/srv/var/log/testnode/az-mlksjfmlk234Sljssdflkj23KSdfslj/suite.log # /srv/slapgrid/slappartXX/srv/var/log/testnode/az-mlksjfmlk234Sljssdflkj23KSdfslj/suite.log
alphabets = string.digits + string.letters alphabets = string.digits + string.letters
rand_part = ''.join(random.choice(alphabets) for i in xrange(32)) while 1:
random_suite_folder_id = '%s-%s' % (self.reference, rand_part) log_folder_name = '%s-%s' % (self.reference,
suite_log_directory = os.path.join(self.log_directory, ''.join(random.choice(alphabets) for i in xrange(32)))
random_suite_folder_id) log_folder_path = os.path.join(self.log_directory, log_folder_name)
SlapOSControler.createFolders(suite_log_directory) try:
os.makedirs(log_folder_path)
except OSError, e:
if e.errno != errno.EEXIST:
raise
else:
break
# XXX copy the whole content of the log viewer app # XXX copy the whole content of the log viewer app
for fname in glob.glob(os.path.join(os.path.dirname(__file__), 'js-logtail', '*')): for fname in glob.glob(os.path.join(os.path.dirname(__file__), 'js-logtail', '*')):
shutil.copy(fname, suite_log_directory) shutil.copy(fname, log_folder_path)
self.suite_log_path = os.path.join(suite_log_directory, self.suite_log_path = os.path.join(log_folder_path, 'suite.log')
'suite.log') return self.suite_log_path, log_folder_name
return self.getSuiteLogPath(), random_suite_folder_id
def getSuiteLogPath(self):
return getattr(self,"suite_log_path", None)
...@@ -131,14 +131,14 @@ class ProcessManager(object): ...@@ -131,14 +131,14 @@ class ProcessManager(object):
stdin = file(os.devnull) stdin = file(os.devnull)
def __init__(self, log, *args, **kw): def __init__(self, log, max_timeout=MAX_TIMEOUT):
self.log = log self.log = log
self.process_pid_set = set() self.process_pid_set = set()
signal.signal(signal.SIGTERM, self.sigterm_handler) signal.signal(signal.SIGTERM, self.sigterm_handler)
self.under_cancellation = False self.under_cancellation = False
self.p = None self.p = None
self.result = None self.result = None
self.max_timeout = kw.get("max_timeout") or MAX_TIMEOUT self.max_timeout = max_timeout
self.timer_set = set() self.timer_set = set()
def spawn(self, *args, **kw): def spawn(self, *args, **kw):
...@@ -186,10 +186,9 @@ class ProcessManager(object): ...@@ -186,10 +186,9 @@ class ProcessManager(object):
raise SubprocessError(result) raise SubprocessError(result)
return result return result
def getSupportedParameterSet(self, program_path ,parameter_list): def getSupportedParameterList(self, program_path):
help_string = self.spawn(*[program_path,'--help'])['stdout'] return re.findall(r'^ (--\w+)',
help_words = set(help_string.split()) self.spawn(program_path, '--help')['stdout'], re.M)
return help_words.intersection(set(parameter_list))
def killall(self, name): def killall(self, name):
""" """
...@@ -212,13 +211,15 @@ class ProcessManager(object): ...@@ -212,13 +211,15 @@ class ProcessManager(object):
continue continue
except (psutil.AccessDenied, psutil.NoSuchProcess): except (psutil.AccessDenied, psutil.NoSuchProcess):
continue continue
self.log('ProcesssManager, killall on %s having pid %s' % (name, process.pid)) self.log('ProcesssManager, killall on %s having pid %s',
name, process.pid)
to_kill_list.append(process.pid) to_kill_list.append(process.pid)
for pid in to_kill_list: for pid in to_kill_list:
killCommand(pid, self.log) killCommand(pid, self.log)
def killPreviousRun(self, cancellation=False): def killPreviousRun(self, cancellation=False):
self.log('ProcessManager killPreviousRun, going to kill %r' % (self.process_pid_set,)) self.log('ProcessManager killPreviousRun, going to kill %r',
self.process_pid_set)
if cancellation: if cancellation:
self.under_cancellation = True self.under_cancellation = True
for timer in self.timer_set: for timer in self.timer_set:
...@@ -227,12 +228,13 @@ class ProcessManager(object): ...@@ -227,12 +228,13 @@ class ProcessManager(object):
killCommand(pgpid, self.log) killCommand(pgpid, self.log)
try: try:
if os.path.exists(self.supervisord_pid_file): if os.path.exists(self.supervisord_pid_file):
supervisor_pid = int(open(self.supervisord_pid_file).read().strip()) with open(self.supervisord_pid_file) as f:
self.log('ProcessManager killPreviousRun, going to kill supervisor with pid %r' % supervisor_pid) supervisor_pid = int(f.read().strip())
self.log('ProcessManager killPreviousRun, going to kill supervisor with pid %r',
supervisor_pid)
os.kill(supervisor_pid, signal.SIGTERM) os.kill(supervisor_pid, signal.SIGTERM)
except: except Exception:
self.log('ProcessManager killPreviousRun, exception when killing supervisor') self.log('ProcessManager killPreviousRun, exception when killing supervisor')
pass
self.process_pid_set.clear() self.process_pid_set.clear()
def sigterm_handler(self, signal, frame): def sigterm_handler(self, signal, frame):
......
...@@ -30,40 +30,14 @@ import slapos.slap ...@@ -30,40 +30,14 @@ import slapos.slap
import subprocess import subprocess
import time import time
import xml_marshaller import xml_marshaller
import shutil
import sys import sys
import glob
import argparse import argparse
import json
from slapos import client from slapos import client
from .Utils import createFolder
MAX_PARTIONS = 10 MAX_PARTITIONS = 10
MAX_SR_RETRIES = 3 MAX_SR_RETRIES = 3
def createFolder(folder, clean=False):
if clean and os.path.exists(folder):
shutil.rmtree(folder)
if not(os.path.exists(folder)):
os.mkdir(folder)
def createFolders(folder):
if not(os.path.exists(folder)):
os.makedirs(folder)
def isDir(folder):
return os.path.isdir(folder)
def createFile(path, mode, content):
f = open(path, mode)
if os.path.exists(path):
f.write(content)
f.close()
else:
# error
pass
class SlapOSControler(object): class SlapOSControler(object):
def __init__(self, working_directory, config, log): def __init__(self, working_directory, config, log):
...@@ -91,9 +65,12 @@ class SlapOSControler(object): ...@@ -91,9 +65,12 @@ class SlapOSControler(object):
slapos_url, slapos_url,
slapos_account_certificate_path, slapos_account_certificate_path,
slapos_account_key_path) slapos_account_key_path)
createFile(slapos_account_key_path, "w", key) with open(slapos_account_key_path, "w") as f:
createFile(slapos_account_certificate_path, "w", certificate) f.write(key)
createFile(configuration_file_path, "w", configuration_file_value) with open(slapos_account_certificate_path, "w") as f:
f.write(certificate)
with open(configuration_file_path, "w") as f:
f.write(configuration_file_value)
self.configuration_file_path = configuration_file_path self.configuration_file_path = configuration_file_path
return slapos_account_key_path, slapos_account_certificate_path, configuration_file_path return slapos_account_key_path, slapos_account_certificate_path, configuration_file_path
...@@ -115,26 +92,17 @@ class SlapOSControler(object): ...@@ -115,26 +92,17 @@ class SlapOSControler(object):
try: try:
local = client.init(config) local = client.init(config)
local['supply'](software_url, computer_guid=computer_id, state=state) local['supply'](software_url, computer_guid=computer_id, state=state)
self.log('SlapOSControler : supply %s %s %s' %(software_url, computer_id, state)) self.log('SlapOSControler : supply %s %s %s', software_url, computer_id, state)
except: except Exception:
self.log("SlapOSControler.supply, \ self.log("SlapOSControler.supply", exc_info=sys.exc_info())
exception in registerOpenOrder", exc_info=sys.exc_info())
raise ValueError("Unable to supply (or remove)") raise ValueError("Unable to supply (or remove)")
else: else:
raise ValueError("Configuration file not found.") raise ValueError("Configuration file not found.")
def destroy(self, software_url, computer_id):
"""
Request Deletetion of a software release on a specific node
Ex :
my_controler.destroy('kvm.cfg', 'COMP-726')
"""
self.supply(self, software_url, computer_id, state="destroyed")
def getInstanceRequestedState(self, reference): def getInstanceRequestedState(self, reference):
try: try:
return self.instance_config[reference]['requested_state'] return self.instance_config[reference]['requested_state']
except: except Exception:
raise ValueError("Instance '%s' not exist" %self.instance_config[reference]) raise ValueError("Instance '%s' not exist" %self.instance_config[reference])
def request(self, reference, software_url, software_type=None, def request(self, reference, software_url, software_type=None,
...@@ -182,11 +150,11 @@ class SlapOSControler(object): ...@@ -182,11 +150,11 @@ class SlapOSControler(object):
self.instance_config[reference]['partition'] = partition self.instance_config[reference]['partition'] = partition
if state == 'destroyed': if state == 'destroyed':
del self.instance_config[reference] del self.instance_config[reference]
if state == 'started': elif state == 'started':
self.log('Instance started with configuration: %s' %str(software_configuration)) self.log('Instance started with configuration: %s',
except: software_configuration)
self.log("SlapOSControler.request, \ except Exception:
exception in registerOpenOrder", exc_info=sys.exc_info()) self.log("SlapOSControler.request", exc_info=sys.exc_info())
raise ValueError("Unable to do this request") raise ValueError("Unable to do this request")
else: else:
raise ValueError("Configuration file not found.") raise ValueError("Configuration file not found.")
...@@ -204,31 +172,30 @@ class SlapOSControler(object): ...@@ -204,31 +172,30 @@ class SlapOSControler(object):
self.log('SlapOSControler : delete instance') self.log('SlapOSControler : delete instance')
try: try:
self._requestSpecificState(reference, 'destroyed') self._requestSpecificState(reference, 'destroyed')
except: except Exception:
raise ValueError("Can't delete instance '%s' (instance may not been created?)" %reference) raise ValueError("Can't delete instance %r (instance not created?)" % reference)
def stopInstance(self, reference): def stopInstance(self, reference):
self.log('SlapOSControler : stop instance') self.log('SlapOSControler : stop instance')
try: try:
self._requestSpecificState(reference, 'stopped') self._requestSpecificState(reference, 'stopped')
except: except Exception:
raise ValueError("Can't stop instance '%s' (instance may not been created?)" %reference) raise ValueError("Can't stop instance %r (instance not created?)" % reference)
def startInstance(self, reference): def startInstance(self, reference):
self.log('SlapOSControler : start instance') self.log('SlapOSControler : start instance')
try: try:
self._requestSpecificState(reference, 'started') self._requestSpecificState(reference, 'started')
except: except Exception:
raise ValueError("Can't start instance '%s' (instance may not been created?)" %reference) raise ValueError("Can't start instance %r (instance not created?)" % reference)
def updateInstanceXML(self, reference, software_configuration): def updateInstanceXML(self, reference, software_configuration):
""" """
Update the XML configuration of an instance Update the XML configuration of an instance
# Request same instance with different parameters. # Request same instance with different parameters.
""" """
self.log('SlapOSControler : updateInstanceXML')
self.log('SlapOSControler : updateInstanceXML will request same' self.log('SlapOSControler : updateInstanceXML will request same'
'instance with new XML configuration...') ' instance with new XML configuration...')
try: try:
self.request(reference, self.request(reference,
...@@ -238,32 +205,28 @@ class SlapOSControler(object): ...@@ -238,32 +205,28 @@ class SlapOSControler(object):
self.instance_config[reference]['computer_guid'], self.instance_config[reference]['computer_guid'],
state='started' state='started'
) )
except: except Exception:
raise ValueError("Can't update instance '%s' (may not exist?)" %reference) raise ValueError("Can't update instance '%s' (may not exist?)" %reference)
def _resetSoftware(self): def _resetSoftware(self):
self.log('SlapOSControler : GOING TO RESET ALL SOFTWARE : %r' % self.log('SlapOSControler : GOING TO RESET ALL SOFTWARE : %r',
(self.software_root,)) self.software_root)
if os.path.exists(self.software_root): createFolder(self.software_root, True)
shutil.rmtree(self.software_root)
os.mkdir(self.software_root)
os.chmod(self.software_root, 0750)
def initializeSlapOSControler(self, slapproxy_log=None, process_manager=None, def initializeSlapOSControler(self, slapproxy_log=None, process_manager=None,
reset_software=False, software_path_list=None): reset_software=False, software_path_list=None):
self.process_manager = process_manager self.process_manager = process_manager
self.software_path_list = software_path_list self.software_path_list = software_path_list
self.log('SlapOSControler, initialize, reset_software: %r' % reset_software) self.log('SlapOSControler, initialize, reset_software: %r', reset_software)
config = self.config config = self.config
slapos_config_dict = self.config.copy() slapos_config_dict = self.config.copy()
slapos_config_dict.update(software_root=self.software_root, slapos_config_dict.update(software_root=self.software_root,
instance_root=self.instance_root, instance_root=self.instance_root,
proxy_database=self.proxy_database) proxy_database=self.proxy_database)
open(self.slapos_config, 'w').write(pkg_resources.resource_string( with open(self.slapos_config, 'w') as f:
f.write(pkg_resources.resource_string(
'erp5.util.testnode', 'template/slapos.cfg.in') % 'erp5.util.testnode', 'template/slapos.cfg.in') %
slapos_config_dict) slapos_config_dict)
createFolder(self.software_root)
createFolder(self.instance_root)
# By erasing everything, we make sure that we are able to "update" # By erasing everything, we make sure that we are able to "update"
# existing profiles. This is quite dirty way to do updates... # existing profiles. This is quite dirty way to do updates...
if os.path.exists(self.proxy_database): if os.path.exists(self.proxy_database):
...@@ -282,32 +245,29 @@ class SlapOSControler(object): ...@@ -282,32 +245,29 @@ class SlapOSControler(object):
# connections # connections
time.sleep(10) time.sleep(10)
try: try:
slap = slapos.slap.slap() slap = self.slap = slapos.slap.slap()
self.slap = slap slap.initializeConnection(config['master_url'])
self.slap.initializeConnection(config['master_url'])
# register software profile # register software profile
for path in self.software_path_list: for path in self.software_path_list:
slap.registerSupply().supply( slap.registerSupply().supply(
path, path,
computer_guid=config['computer_id']) computer_guid=config['computer_id'])
computer = slap.registerComputer(config['computer_id']) computer = slap.registerComputer(config['computer_id'])
except: except Exception:
self.log("SlapOSControler.initializeSlapOSControler, \ self.log("SlapOSControler.initializeSlapOSControler",
exception in registerSupply", exc_info=sys.exc_info()) exc_info=sys.exc_info())
raise ValueError("Unable to registerSupply") raise ValueError("Unable to registerSupply")
# Reset all previously generated software if needed # Reset all previously generated software if needed
if reset_software: if reset_software:
self._resetSoftware() self._resetSoftware()
else:
createFolder(self.software_root)
instance_root = self.instance_root instance_root = self.instance_root
if os.path.exists(instance_root): # Delete any existing partition in order to not get its data (ex.
# delete old paritions which may exists in order to not get its data # MySQL DB content) from previous runs. To support changes of partition
# (ex. MySQL db content) from previous testnode's runs # naming scheme (which already happened), do this at instance_root level.
# In order to be able to change partition naming scheme, do this at createFolder(instance_root, True)
# instance_root level (such change happened already, causing problems). for i in xrange(MAX_PARTITIONS):
shutil.rmtree(instance_root)
if not(os.path.exists(instance_root)):
os.mkdir(instance_root)
for i in range(0, MAX_PARTIONS):
# create partition and configure computer # create partition and configure computer
# XXX: at the moment all partitions do share same virtual interface address # XXX: at the moment all partitions do share same virtual interface address
# this is not a problem as usually all services are on different ports # this is not a problem as usually all services are on different ports
...@@ -336,17 +296,17 @@ class SlapOSControler(object): ...@@ -336,17 +296,17 @@ class SlapOSControler(object):
def runSoftwareRelease(self, config, environment, **kw): def runSoftwareRelease(self, config, environment, **kw):
self.log("SlapOSControler.runSoftwareRelease") self.log("SlapOSControler.runSoftwareRelease")
cpu_count = os.sysconf("SC_NPROCESSORS_ONLN") cpu_count = str(os.sysconf("SC_NPROCESSORS_ONLN"))
os.putenv('MAKEFLAGS', '-j%s' % cpu_count) os.environ['MAKEFLAGS'] = '-j' + cpu_count
os.putenv('NPY_NUM_BUILD_JOBS', '%s' % cpu_count) os.environ['NPY_NUM_BUILD_JOBS'] = cpu_count
os.putenv('BUNDLE_JOBS', '%s' % cpu_count) os.environ['BUNDLE_JOBS'] = cpu_count
os.environ['PATH'] = environment['PATH'] os.environ['PATH'] = environment['PATH']
# a SR may fail for number of reasons (incl. network failures) # a SR may fail for number of reasons (incl. network failures)
# so be tolerant and run it a few times before giving up # so be tolerant and run it a few times before giving up
for runs in range(0, MAX_SR_RETRIES): for _ in xrange(MAX_SR_RETRIES):
status_dict = self.spawn(config['slapos_binary'], status_dict = self.spawn(config['slapos_binary'],
'node', 'software', '--all', 'node', 'software', '--all',
'--pidfile', '%s/software.pid' % self.software_root, '--pidfile', os.path.join(self.software_root, 'slapos-node.pid'),
'--cfg', self.slapos_config, raise_error_if_fail=False, '--cfg', self.slapos_config, raise_error_if_fail=False,
log_prefix='slapgrid_sr', get_output=False) log_prefix='slapgrid_sr', get_output=False)
if status_dict['status_code'] == 0: if status_dict['status_code'] == 0:
...@@ -355,30 +315,30 @@ class SlapOSControler(object): ...@@ -355,30 +315,30 @@ class SlapOSControler(object):
def runComputerPartition(self, config, environment, def runComputerPartition(self, config, environment,
stdout=None, stderr=None, cluster_configuration=None, **kw): stdout=None, stderr=None, cluster_configuration=None, **kw):
self.log("SlapOSControler.runComputerPartition with cluster_config: %r" % (cluster_configuration,)) self.log("SlapOSControler.runComputerPartition with cluster_config: %r",
cluster_configuration)
for path in self.software_path_list: for path in self.software_path_list:
try: try:
self.slap.registerOpenOrder().request(path, self.slap.registerOpenOrder().request(path,
partition_reference='testing partition %s' % \ partition_reference='testing partition %s' % \
self.software_path_list.index(path), self.software_path_list.index(path),
partition_parameter_kw=cluster_configuration) partition_parameter_kw=cluster_configuration)
except: except Exception:
self.log("SlapOSControler.runComputerPartition, \ self.log("SlapOSControler.runComputerPartition", exc_info=sys.exc_info())
exception in registerOpenOrder", exc_info=sys.exc_info())
raise ValueError("Unable to registerOpenOrder") raise ValueError("Unable to registerOpenOrder")
# try to run for all partitions as one partition may in theory request another one # try to run for all partitions as one partition may in theory request another one
# this not always is required but curently no way to know how "tree" of partitions # this not always is required but curently no way to know how "tree" of partitions
# may "expand" # may "expand"
sleep_time = 0 for _ in xrange(MAX_PARTITIONS):
for runs in range(0, MAX_PARTIONS):
status_dict = self.spawn(config['slapos_binary'], 'node', 'instance', status_dict = self.spawn(config['slapos_binary'], 'node', 'instance',
'--pidfile', '%s/instance.pid' % self.software_root, '--pidfile', os.path.join(self.instance_root, 'slapos-node.pid'),
'--cfg', self.slapos_config, raise_error_if_fail=False, '--cfg', self.slapos_config, raise_error_if_fail=False,
log_prefix='slapgrid_cp', get_output=False) log_prefix='slapgrid_cp', get_output=False)
self.log('slapgrid_cp status_dict : %r' % (status_dict,)) self.log('slapgrid_cp status_dict : %r', status_dict)
if status_dict['status_code'] in (0,): if not status_dict['status_code']:
break break
else:
# some hack to handle promise issues (should be only one of the two # some hack to handle promise issues (should be only one of the two
# codes, but depending on slapos versions, we have inconsistent status # codes, but depending on slapos versions, we have inconsistent status
if status_dict['status_code'] in (1,2): if status_dict['status_code'] in (1,2):
......
import datetime import datetime
import json import json
import sys
import traceback import traceback
import time import time
#import feedparser #import feedparser
from functools import wraps
from uritemplate import expand from uritemplate import expand
import slapos.slap import slapos.slap
from slapos.slap import SoftwareProductCollection from slapos.slap import SoftwareProductCollection
from slapos.slap.slap import ConnectionError
from requests.exceptions import HTTPError from requests.exceptions import HTTPError
from erp5.util.taskdistribution import SAFE_RPC_EXCEPTION_LIST from ..taskdistribution import SAFE_RPC_EXCEPTION_LIST
# max time to instance changing state: 2 hour # max time to instance changing state: 2 hour
MAX_INSTANCE_TIME = 60*60*2 MAX_INSTANCE_TIME = 60*60*2
...@@ -41,36 +39,29 @@ TESTER_STATE_INSTANCE_UNINSTALLED = "TESTER_STATE_INSTANCE_UNINSTALLED" ...@@ -41,36 +39,29 @@ TESTER_STATE_INSTANCE_UNINSTALLED = "TESTER_STATE_INSTANCE_UNINSTALLED"
# Simple decorator to prevent raise due small # Simple decorator to prevent raise due small
# network failures. # network failures.
def retryOnNetworkFailure(func): def retryOnNetworkFailure(func,
def wrapper(*args, **kwargs): _except_list = SAFE_RPC_EXCEPTION_LIST + (
HTTPError, slapos.slap.ConnectionError),
):
def wrapper(*args, **kw):
retry_time = 64 retry_time = 64
while True: while True:
try: try:
return func(*args, **kwargs) return func(*args, **kw)
except SAFE_RPC_EXCEPTION_LIST, e: except _except_list:
print 'Network failure: %s , %s' % (sys.exc_info(), e) traceback.print_exc()
except HTTPError, e:
print 'Network failure: %s , %s' % (sys.exc_info(), e) print 'Network failure. Retry method %s in %i seconds' % (func, retry_time)
except ConnectionError, e:
print 'Network failure: %s , %s' % (sys.exc_info(), e)
except slapos.slap.ConnectionError, e:
print 'Network failure: %s , %s' % (sys.exc_info(), e)
print 'Retry method %s in %i seconds' % (func, retry_time)
time.sleep(retry_time) time.sleep(retry_time)
retry_time = min(retry_time*1.5, 640) retry_time = min(retry_time*1.5, 640)
return wraps(func)(wrapper)
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
return wrapper
class SlapOSMasterCommunicator(object): class SlapOSMasterCommunicator(object):
latest_state = None latest_state = None
def __init__(self, slap, slap_supply, slap_order, url, logger): def __init__(self, slap, slap_supply, slap_order, url, logger):
self._logger = logger self._logger = logger
self.slap = slap self.slap = slap
self.slap_order = slap_order self.slap_order = slap_order
...@@ -102,8 +93,7 @@ class SlapOSMasterCommunicator(object): ...@@ -102,8 +93,7 @@ class SlapOSMasterCommunicator(object):
if instance_title is not None: if instance_title is not None:
self.name = instance_title self.name = instance_title
if request_kw is not None: if request_kw is not None:
if isinstance(request_kw, str) or \ if isinstance(request_kw, basestring):
isinstance(request_kw, unicode):
self.request_kw = json.loads(request_kw) self.request_kw = json.loads(request_kw)
else: else:
self.request_kw = request_kw self.request_kw = request_kw
...@@ -116,12 +106,11 @@ class SlapOSMasterCommunicator(object): ...@@ -116,12 +106,11 @@ class SlapOSMasterCommunicator(object):
**self.request_kw) **self.request_kw)
def isInstanceRequested(self, instance_title): def isInstanceRequested(self, instance_title):
hateoas = getattr(self.slap, '_hateoas_navigator', None) hateoas = self._hateoas_navigator
return instance_title in hateoas.getHostingSubscriptionDict() return instance_title in hateoas.getHostingSubscriptionDict()
@retryOnNetworkFailure @retryOnNetworkFailure
def _hateoas_getComputer(self, reference): def _hateoas_getComputer(self, reference):
root_document = self.hateoas_navigator.getRootDocument() root_document = self.hateoas_navigator.getRootDocument()
search_url = root_document["_links"]['raw_search']['href'] search_url = root_document["_links"]['raw_search']['href']
...@@ -147,7 +136,6 @@ class SlapOSMasterCommunicator(object): ...@@ -147,7 +136,6 @@ class SlapOSMasterCommunicator(object):
@retryOnNetworkFailure @retryOnNetworkFailure
def getSoftwareInstallationList(self): def getSoftwareInstallationList(self):
# XXX Move me to slap.py API # XXX Move me to slap.py API
computer = self._hateoas_getComputer(self.computer_guid) computer = self._hateoas_getComputer(self.computer_guid)
# Not a list ? # Not a list ?
...@@ -191,7 +179,6 @@ class SlapOSMasterCommunicator(object): ...@@ -191,7 +179,6 @@ class SlapOSMasterCommunicator(object):
@retryOnNetworkFailure @retryOnNetworkFailure
def getInstanceUrlList(self): def getInstanceUrlList(self):
if self.hosting_subscription_url is None: if self.hosting_subscription_url is None:
hosting_subscription_dict = self.hateoas_navigator._hateoas_getHostingSubscriptionDict() hosting_subscription_dict = self.hateoas_navigator._hateoas_getHostingSubscriptionDict()
for hs in hosting_subscription_dict: for hs in hosting_subscription_dict:
...@@ -207,7 +194,6 @@ class SlapOSMasterCommunicator(object): ...@@ -207,7 +194,6 @@ class SlapOSMasterCommunicator(object):
@retryOnNetworkFailure @retryOnNetworkFailure
def getNewsFromInstance(self, url): def getNewsFromInstance(self, url):
result = self.hateoas_navigator.GET(url) result = self.hateoas_navigator.GET(url)
result = json.loads(result) result = json.loads(result)
if result['_links'].get('action_object_slap', None) is None: if result['_links'].get('action_object_slap', None) is None:
...@@ -221,7 +207,6 @@ class SlapOSMasterCommunicator(object): ...@@ -221,7 +207,6 @@ class SlapOSMasterCommunicator(object):
@retryOnNetworkFailure @retryOnNetworkFailure
def getInformationFromInstance(self, url): def getInformationFromInstance(self, url):
result = self.hateoas_navigator.GET(url) result = self.hateoas_navigator.GET(url)
result = json.loads(result) result = json.loads(result)
if result['_links'].get('action_object_slap', None) is None: if result['_links'].get('action_object_slap', None) is None:
...@@ -329,7 +314,7 @@ class SlapOSMasterCommunicator(object): ...@@ -329,7 +314,7 @@ class SlapOSMasterCommunicator(object):
self._logger('Got an error requesting partition for ' self._logger('Got an error requesting partition for '
'its state') 'its state')
return INSTANCE_STATE_UNKNOWN return INSTANCE_STATE_UNKNOWN
except: except Exception:
self._logger("ERROR getting instance state") self._logger("ERROR getting instance state")
return INSTANCE_STATE_UNKNOWN return INSTANCE_STATE_UNKNOWN
...@@ -377,6 +362,7 @@ class SlapOSMasterCommunicator(object): ...@@ -377,6 +362,7 @@ class SlapOSMasterCommunicator(object):
return {'error_message' : None} return {'error_message' : None}
class SlapOSTester(SlapOSMasterCommunicator): class SlapOSTester(SlapOSMasterCommunicator):
def __init__(self, def __init__(self,
name, name,
logger, logger,
...@@ -387,7 +373,6 @@ class SlapOSTester(SlapOSMasterCommunicator): ...@@ -387,7 +373,6 @@ class SlapOSTester(SlapOSMasterCommunicator):
computer_guid=None, # computer for supply if desired computer_guid=None, # computer for supply if desired
request_kw=None request_kw=None
): ):
super(SlapOSTester, self).__init__( super(SlapOSTester, self).__init__(
slap, slap_supply, slap_order, url, logger) slap, slap_supply, slap_order, url, logger)
...@@ -464,7 +449,6 @@ class SoftwareReleaseTester(SlapOSTester): ...@@ -464,7 +449,6 @@ class SoftwareReleaseTester(SlapOSTester):
software_timeout=3600, software_timeout=3600,
instance_timeout=3600, instance_timeout=3600,
): ):
super(SoftwareReleaseTester, self).__init__( super(SoftwareReleaseTester, self).__init__(
name, logger, slap, slap_order, slap_supply, url, computer_guid, request_kw) name, logger, slap, slap_order, slap_supply, url, computer_guid, request_kw)
......
...@@ -24,28 +24,22 @@ ...@@ -24,28 +24,22 @@
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
# #
############################################################################## ##############################################################################
from datetime import datetime,timedelta
import os import os
import subprocess
import sys
import time
import glob import glob
import SlapOSControler
import json import json
import time from .ProcessManager import SubprocessError
import shutil from .SlapOSControler import SlapOSControler
import logging from .Utils import createFolder
import string
import random
from ProcessManager import SubprocessError, ProcessManager, CancellationError
from subprocess import CalledProcessError
from NodeTestSuite import SlapOSInstance
from Updater import Updater
from Utils import dealShebang
from erp5.util import taskdistribution
from slapos.grid.utils import md5digest from slapos.grid.utils import md5digest
class UnitTestRunner(): def dealShebang(run_test_suite_path):
with open(run_test_suite_path) as f:
if f.read(2) == '#!':
return f.readline().split(None, 1)
return []
class UnitTestRunner(object):
def __init__(self, testnode): def __init__(self, testnode):
self.testnode = testnode self.testnode = testnode
...@@ -53,12 +47,11 @@ class UnitTestRunner(): ...@@ -53,12 +47,11 @@ class UnitTestRunner():
""" """
Create a SlapOSControler Create a SlapOSControler
""" """
return SlapOSControler.SlapOSControler( return SlapOSControler(
working_directory, working_directory,
self.testnode.config, self.testnode.config,
self.testnode.log) self.testnode.log)
def _prepareSlapOS(self, working_directory, slapos_instance, log, def _prepareSlapOS(self, working_directory, slapos_instance, log,
create_partition=1, software_path_list=None, **kw): create_partition=1, software_path_list=None, **kw):
""" """
...@@ -66,11 +59,11 @@ class UnitTestRunner(): ...@@ -66,11 +59,11 @@ class UnitTestRunner():
""" """
slapproxy_log = os.path.join(self.testnode.config['log_directory'], slapproxy_log = os.path.join(self.testnode.config['log_directory'],
'slapproxy.log') 'slapproxy.log')
log('Configured slapproxy log to %r' % slapproxy_log) log('Configured slapproxy log to %r', slapproxy_log)
reset_software = slapos_instance.retry_software_count > 10 reset_software = slapos_instance.retry_software_count > 10
if reset_software: if reset_software:
slapos_instance.retry_software_count = 0 slapos_instance.retry_software_count = 0
log('testnode, retry_software_count : %r' % \ log('testnode, retry_software_count : %r',
slapos_instance.retry_software_count) slapos_instance.retry_software_count)
# XXX Create a new controler because working_directory can be # XXX Create a new controler because working_directory can be
...@@ -108,26 +101,22 @@ class UnitTestRunner(): ...@@ -108,26 +101,22 @@ class UnitTestRunner():
""" """
# report-url, report-project and suite-url are required to seleniumrunner # report-url, report-project and suite-url are required to seleniumrunner
# instance. This is a hack which must be removed. # instance. This is a hack which must be removed.
cluster_configuration = {}
config = self.testnode.config config = self.testnode.config
cluster_configuration['report-url'] = config.get("report-url", "") return self._prepareSlapOS(config['slapos_directory'],
cluster_configuration['report-project'] = config.get("report-project", "")
cluster_configuration['suite-url'] = config.get("suite-url", "")
return self._prepareSlapOS(self.testnode.config['slapos_directory'],
test_node_slapos, self.testnode.log, create_partition=0, test_node_slapos, self.testnode.log, create_partition=0,
software_path_list=self.testnode.config.get("software_list"), software_path_list=config.get("software_list"),
cluster_configuration=cluster_configuration cluster_configuration={
) 'report-url': config.get("report-url", ""),
'report-project': config.get("report-project", ""),
'suite-url': config.get("suite-url", ""),
})
def prepareSlapOSForTestSuite(self, node_test_suite): def prepareSlapOSForTestSuite(self, node_test_suite):
""" """
Build softwares needed by testsuites Build softwares needed by testsuites
""" """
log = self.testnode.log
if log is None:
log = self.testnode.log
return self._prepareSlapOS(node_test_suite.working_directory, return self._prepareSlapOS(node_test_suite.working_directory,
node_test_suite, log, node_test_suite, self.testnode.log,
software_path_list=[node_test_suite.custom_profile_path], software_path_list=[node_test_suite.custom_profile_path],
cluster_configuration={'_': json.dumps(node_test_suite.cluster_configuration)}) cluster_configuration={'_': json.dumps(node_test_suite.cluster_configuration)})
...@@ -173,8 +162,7 @@ class UnitTestRunner(): ...@@ -173,8 +162,7 @@ class UnitTestRunner():
# From this point, test runner becomes responsible for updating test # From this point, test runner becomes responsible for updating test
# result. We only do cleanup if the test runner itself is not able # result. We only do cleanup if the test runner itself is not able
# to run. # to run.
SlapOSControler.createFolder(node_test_suite.test_suite_directory, createFolder(node_test_suite.test_suite_directory, clean=True)
clean=True)
self.testnode.process_manager.spawn(*invocation_list, self.testnode.process_manager.spawn(*invocation_list,
cwd=node_test_suite.test_suite_directory, cwd=node_test_suite.test_suite_directory,
log_prefix='runTestSuite', get_output=False) log_prefix='runTestSuite', get_output=False)
......
...@@ -28,11 +28,8 @@ import errno ...@@ -28,11 +28,8 @@ import errno
import os import os
import re import re
import shutil import shutil
import subprocess
import sys import sys
import threading from .ProcessManager import SubprocessError
from ProcessManager import SubprocessError
SVN_UP_REV = re.compile(r'^(?:At|Updated to) revision (\d+).$') SVN_UP_REV = re.compile(r'^(?:At|Updated to) revision (\d+).$')
SVN_CHANGED_REV = re.compile(r'^Last Changed Rev.*:\s*(\d+)', re.MULTILINE) SVN_CHANGED_REV = re.compile(r'^Last Changed Rev.*:\s*(\d+)', re.MULTILINE)
...@@ -44,9 +41,8 @@ SVN_TYPE = 'svn' ...@@ -44,9 +41,8 @@ SVN_TYPE = 'svn'
class Updater(object): class Updater(object):
_git_cache = {} _git_cache = {}
stdin = file(os.devnull)
def __init__(self, repository_path, log, revision=None, git_binary=None, def __init__(self, repository_path, log, revision=None, git_binary='git',
branch=None, realtime_output=True, process_manager=None, url=None, branch=None, realtime_output=True, process_manager=None, url=None,
working_directory=None): working_directory=None):
self.log = log self.log = log
...@@ -81,7 +77,7 @@ class Updater(object): ...@@ -81,7 +77,7 @@ class Updater(object):
def deletePycFiles(self, path): def deletePycFiles(self, path):
"""Delete *.pyc files so that deleted/moved files can not be imported""" """Delete *.pyc files so that deleted/moved files can not be imported"""
for path, dir_list, file_list in os.walk(path): for path, _, file_list in os.walk(path):
for file in file_list: for file in file_list:
if file[-4:] in ('.pyc', '.pyo'): if file[-4:] in ('.pyc', '.pyo'):
# allow several processes clean the same folder at the same time # allow several processes clean the same folder at the same time
...@@ -115,17 +111,17 @@ class Updater(object): ...@@ -115,17 +111,17 @@ class Updater(object):
git_repository_path = os.path.join(self.getRepositoryPath(), '.git') git_repository_path = os.path.join(self.getRepositoryPath(), '.git')
name = os.path.basename(os.path.normpath(self.getRepositoryPath())) name = os.path.basename(os.path.normpath(self.getRepositoryPath()))
git_repository_link_path = os.path.join(self.getRepositoryPath(), '%s.git' %name) git_repository_link_path = os.path.join(self.getRepositoryPath(), '%s.git' %name)
self.log("checking link %s -> %s.." self.log("checking link %s -> %s..",
%(git_repository_link_path,git_repository_path)) git_repository_link_path, git_repository_path)
if ( not os.path.lexists(git_repository_link_path) and \ if ( not os.path.lexists(git_repository_link_path) and \
not os.path.exists(git_repository_link_path) ): not os.path.exists(git_repository_link_path) ):
try: try:
os.symlink(git_repository_path, git_repository_link_path) os.symlink(git_repository_path, git_repository_link_path)
self.log("link: %s -> %s created" self.log("link: %s -> %s created",
%(git_repository_link_path,git_repository_path)) git_repository_link_path, git_repository_path)
except: except OSError:
self.log("Cannot create link from %s -> %s" self.log("Cannot create link from %s -> %s",
%(git_repository_link_path,git_repository_path)) git_repository_link_path, git_repository_path)
def _git_find_rev(self, ref): def _git_find_rev(self, ref):
try: try:
...@@ -152,7 +148,7 @@ class Updater(object): ...@@ -152,7 +148,7 @@ class Updater(object):
raise NotImplementedError raise NotImplementedError
def deleteRepository(self): def deleteRepository(self):
self.log("Wrong repository or wrong url, deleting repos %s" % \ self.log("Wrong repository or wrong url, deleting repos %s",
self.repository_path) self.repository_path)
shutil.rmtree(self.repository_path) shutil.rmtree(self.repository_path)
...@@ -165,7 +161,7 @@ class Updater(object): ...@@ -165,7 +161,7 @@ class Updater(object):
remote_url = self._git("config", "--get", "remote.origin.url") remote_url = self._git("config", "--get", "remote.origin.url")
if remote_url == self.url: if remote_url == self.url:
correct_url = True correct_url = True
except (SubprocessError,) as e: except SubprocessError:
self.log("SubprocessError", exc_info=sys.exc_info()) self.log("SubprocessError", exc_info=sys.exc_info())
if not(correct_url): if not(correct_url):
self.deleteRepository() self.deleteRepository()
......
import sys import os
import json
import shutil import shutil
import string
from random import choice def createFolder(folder, clean=False):
if os.path.exists(folder):
if not clean:
return
shutil.rmtree(folder)
os.mkdir(folder)
def deunicodeData(data): def deunicodeData(data):
if isinstance(data, list): if isinstance(data, list):
new_data = [] return map(deunicodeData, data)
for sub_data in data: if isinstance(data, unicode):
new_data.append(deunicodeData(sub_data)) return data.encode('utf8')
elif isinstance(data, unicode): if isinstance(data, dict):
new_data = data.encode('utf8') return {deunicodeData(key): deunicodeData(value)
elif isinstance(data, dict): for key, value in data.iteritems()}
new_data = {} return data
for key, value in data.iteritems():
key = deunicodeData(key)
value = deunicodeData(value)
new_data[key] = value
else:
new_data = data
return new_data
def dealShebang(run_test_suite_path):
line = open(run_test_suite_path, 'r').readline()
invocation_list = []
if line[:2] == '#!':
invocation_list = line[2:].split()
return invocation_list
...@@ -26,30 +26,24 @@ ...@@ -26,30 +26,24 @@
############################################################################## ##############################################################################
import os import os
import sys import sys
import time
import json import json
import time import time
import shutil import shutil
import logging import logging
import Utils
from slapos.slap.slap import ConnectionError from slapos.slap.slap import ConnectionError
import traceback from .ProcessManager import SubprocessError, ProcessManager, CancellationError
from ProcessManager import SubprocessError, ProcessManager, CancellationError
from subprocess import CalledProcessError from subprocess import CalledProcessError
from Updater import Updater from .Updater import Updater
from NodeTestSuite import NodeTestSuite, SlapOSInstance from .NodeTestSuite import NodeTestSuite, SlapOSInstance
from ScalabilityTestRunner import ScalabilityTestRunner from .ScalabilityTestRunner import ScalabilityTestRunner
from UnitTestRunner import UnitTestRunner from .UnitTestRunner import UnitTestRunner
from erp5.util import taskdistribution from .Utils import deunicodeData
from .. import taskdistribution
DEFAULT_SLEEP_TIMEOUT = 120 # time in seconds to sleep
MAX_LOG_TIME = 15 # time in days we should keep logs that we can see through MAX_LOG_TIME = 15 # time in days we should keep logs that we can see through
# httd # httd
MAX_TEMP_TIME = 0.01 # time in days we should keep temp files MAX_TEMP_TIME = 0.01 # time in days we should keep temp files
supervisord_pid_file = None
PROFILE_PATH_KEY = 'profile_path' PROFILE_PATH_KEY = 'profile_path'
...@@ -76,7 +70,6 @@ class TestNode(object): ...@@ -76,7 +70,6 @@ class TestNode(object):
def checkOldTestSuite(self,test_suite_data): def checkOldTestSuite(self,test_suite_data):
config = self.config
installed_reference_set = set(os.listdir(self.working_directory)) installed_reference_set = set(os.listdir(self.working_directory))
wished_reference_set = set([x['test_suite_reference'] for x in test_suite_data]) wished_reference_set = set([x['test_suite_reference'] for x in test_suite_data])
to_remove_reference_set = installed_reference_set.difference( to_remove_reference_set = installed_reference_set.difference(
...@@ -84,7 +77,7 @@ class TestNode(object): ...@@ -84,7 +77,7 @@ class TestNode(object):
for y in to_remove_reference_set: for y in to_remove_reference_set:
fpath = os.path.join(self.working_directory,y) fpath = os.path.join(self.working_directory,y)
self.delNodeTestSuite(y) self.delNodeTestSuite(y)
self.log("testnode.checkOldTestSuite, DELETING : %r" % (fpath,)) self.log("testnode.checkOldTestSuite, DELETING : %r", fpath)
if os.path.isdir(fpath): if os.path.isdir(fpath):
shutil.rmtree(fpath) shutil.rmtree(fpath)
else: else:
...@@ -103,26 +96,21 @@ class TestNode(object): ...@@ -103,26 +96,21 @@ class TestNode(object):
return node_test_suite return node_test_suite
def delNodeTestSuite(self, reference): def delNodeTestSuite(self, reference):
if self.node_test_suite_dict.has_key(reference): self.node_test_suite_dict.pop(reference, None)
self.node_test_suite_dict.pop(reference)
def constructProfile(self, node_test_suite, test_type, use_relative_path=False): def constructProfile(self, node_test_suite, test_type, use_relative_path=False):
config = self.config
profile_content = ''
assert len(node_test_suite.vcs_repository_list), "we must have at least one repository" assert len(node_test_suite.vcs_repository_list), "we must have at least one repository"
profile_path_count = 0 software_config_path = None
profile_content_list = [] profile_content_list = []
for vcs_repository in node_test_suite.vcs_repository_list: for vcs_repository in node_test_suite.vcs_repository_list:
url = vcs_repository['url'] buildout_section_id = vcs_repository.get('buildout_section_id')
buildout_section_id = vcs_repository.get('buildout_section_id', None)
repository_path = vcs_repository['repository_path'] repository_path = vcs_repository['repository_path']
try: try:
profile_path = vcs_repository[PROFILE_PATH_KEY] profile_path = vcs_repository[PROFILE_PATH_KEY]
except KeyError: except KeyError:
pass pass
else: else:
profile_path_count += 1 if software_config_path is not None:
if profile_path_count > 1:
raise ValueError(PROFILE_PATH_KEY + ' defined more than once') raise ValueError(PROFILE_PATH_KEY + ' defined more than once')
# Absolute path to relative path # Absolute path to relative path
...@@ -132,12 +120,6 @@ class TestNode(object): ...@@ -132,12 +120,6 @@ class TestNode(object):
node_test_suite.reference) node_test_suite.reference)
software_config_path = os.path.relpath(software_config_path, from_path) software_config_path = os.path.relpath(software_config_path, from_path)
profile_content_list.append("""
[buildout]
extends = %(software_config_path)s
""" % {'software_config_path': software_config_path})
# Construct sections # Construct sections
if not(buildout_section_id is None): if not(buildout_section_id is None):
# Absolute path to relative # Absolute path to relative
...@@ -175,14 +157,14 @@ develop = false ...@@ -175,14 +157,14 @@ develop = false
""" % {'buildout_section_id': buildout_section_id, """ % {'buildout_section_id': buildout_section_id,
'repository_path' : repository_path, 'repository_path' : repository_path,
'branch' : vcs_repository.get('branch','master')}) 'branch' : vcs_repository.get('branch','master')})
if not profile_path_count: if software_config_path is None:
raise ValueError(PROFILE_PATH_KEY + ' not defined') raise ValueError(PROFILE_PATH_KEY + ' not defined')
profile_content_list.sort()
# Write file # Write file
custom_profile = open(node_test_suite.custom_profile_path, 'w') with open(node_test_suite.custom_profile_path, 'w') as f:
# sort to have buildout section first f.write("[buildout]\nextends = %s\n%s" % (
profile_content_list.sort(key=lambda x: [x, ''][x.startswith('\n[buildout]')]) software_config_path,
custom_profile.write(''.join(profile_content_list)) ''.join(profile_content_list)))
custom_profile.close()
sys.path.append(repository_path) sys.path.append(repository_path)
def getAndUpdateFullRevisionList(self, node_test_suite): def getAndUpdateFullRevisionList(self, node_test_suite):
...@@ -200,13 +182,13 @@ develop = false ...@@ -200,13 +182,13 @@ develop = false
working_directory=node_test_suite.working_directory, working_directory=node_test_suite.working_directory,
url=vcs_repository["url"]) url=vcs_repository["url"])
updater.checkout() updater.checkout()
revision = "-".join(updater.getRevision()) revision_list.append((repository_id, updater.getRevision()))
full_revision_list.append('%s=%s' % (repository_id, revision)) except SubprocessError:
node_test_suite.revision = ','.join(full_revision_list) log("Error while getting repository, ignoring this test suite",
except SubprocessError, e: exc_info=1)
log("Error while getting repository, ignoring this test suite : %r" % (e,), exc_info=sys.exc_info()) return False
full_revision_list = None node_test_suite.revision_list = revision_list
return full_revision_list return True
def registerSuiteLog(self, test_result, node_test_suite): def registerSuiteLog(self, test_result, node_test_suite):
""" """
...@@ -218,7 +200,7 @@ develop = false ...@@ -218,7 +200,7 @@ develop = false
# TODO make the path into url # TODO make the path into url
test_result.reportStatus('LOG url', "%s/%s" % (self.config.get('httpd_url'), test_result.reportStatus('LOG url', "%s/%s" % (self.config.get('httpd_url'),
folder_id), '') folder_id), '')
self.log("going to switch to log %r" % suite_log_path) self.log("going to switch to log %r", suite_log_path)
self.process_manager.log = self.log = self.getSuiteLog() self.process_manager.log = self.log = self.getSuiteLog()
return suite_log_path return suite_log_path
...@@ -237,7 +219,7 @@ develop = false ...@@ -237,7 +219,7 @@ develop = false
self.file_handler = logging.FileHandler(filename=suite_log_path) self.file_handler = logging.FileHandler(filename=suite_log_path)
self.file_handler.setFormatter(formatter) self.file_handler.setFormatter(formatter)
logger.addHandler(self.file_handler) logger.addHandler(self.file_handler)
logger.info('Activated logfile %r output' % suite_log_path) logger.info('Activated logfile %r output', suite_log_path)
self.suite_log = logger.info self.suite_log = logger.info
def checkRevision(self, test_result, node_test_suite): def checkRevision(self, test_result, node_test_suite):
...@@ -264,14 +246,13 @@ develop = false ...@@ -264,14 +246,13 @@ develop = false
node_test_suite.revision = test_result.revision node_test_suite.revision = test_result.revision
def _cleanupLog(self): def _cleanupLog(self):
config = self.config
log_directory = self.config['log_directory'] log_directory = self.config['log_directory']
now = time.time() prune_time = time.time() - 86400 * self.max_log_time
for log_folder in os.listdir(log_directory): for log_folder in os.listdir(log_directory):
folder_path = os.path.join(log_directory, log_folder) folder_path = os.path.join(log_directory, log_folder)
if os.path.isdir(folder_path): if os.path.isdir(folder_path):
if (now - os.stat(folder_path).st_mtime)/86400 > self.max_log_time: if os.stat(folder_path).st_mtime < prune_time:
self.log("deleting log directory %r" % (folder_path,)) self.log("deleting log directory %r", folder_path)
shutil.rmtree(folder_path) shutil.rmtree(folder_path)
def _cleanupTemporaryFiles(self): def _cleanupTemporaryFiles(self):
...@@ -280,17 +261,16 @@ develop = false ...@@ -280,17 +261,16 @@ develop = false
missing disk space, remove old logs missing disk space, remove old logs
""" """
temp_directory = self.config["system_temp_folder"] temp_directory = self.config["system_temp_folder"]
now = time.time()
user_id = os.geteuid() user_id = os.geteuid()
prune_time = time.time() - 86400 * self.max_temp_time
for temp_folder in os.listdir(temp_directory): for temp_folder in os.listdir(temp_directory):
folder_path = os.path.join(temp_directory, temp_folder) folder_path = os.path.join(temp_directory, temp_folder)
if (temp_folder.startswith("tmp") or if (temp_folder.startswith("tmp") or
temp_folder.startswith("buildout")): temp_folder.startswith("buildout")):
try: try:
stat = os.stat(folder_path) stat = os.stat(folder_path)
if stat.st_uid == user_id and \ if stat.st_uid == user_id and stat.st_mtime < prune_time:
(now - stat.st_mtime)/86400 > self.max_temp_time: self.log("deleting temp directory %r", folder_path)
self.log("deleting temp directory %r" % (folder_path,))
if os.path.isdir(folder_path): if os.path.isdir(folder_path):
shutil.rmtree(folder_path) shutil.rmtree(folder_path)
else: else:
...@@ -298,9 +278,8 @@ develop = false ...@@ -298,9 +278,8 @@ develop = false
except OSError: except OSError:
self.log("_cleanupTemporaryFiles exception", exc_info=sys.exc_info()) self.log("_cleanupTemporaryFiles exception", exc_info=sys.exc_info())
def cleanUp(self,test_result): def cleanUp(self):
log = self.log self.log('Testnode.cleanUp')
log('Testnode.cleanUp')
self.process_manager.killPreviousRun() self.process_manager.killPreviousRun()
self._cleanupLog() self._cleanupLog()
self._cleanupTemporaryFiles() self._cleanupTemporaryFiles()
...@@ -308,19 +287,15 @@ develop = false ...@@ -308,19 +287,15 @@ develop = false
def run(self): def run(self):
log = self.log log = self.log
config = self.config config = self.config
slapgrid = None
previous_revision_dict = {}
revision_dict = {}
test_result = None
test_node_slapos = SlapOSInstance() test_node_slapos = SlapOSInstance()
test_node_slapos.edit(working_directory=self.config['slapos_directory']) test_node_slapos.edit(working_directory=config['slapos_directory'])
try: try:
while True: while True:
test_result = None
try: try:
node_test_suite = None node_test_suite = None
self.log = self.process_manager.log = self.testnode_log self.log = self.process_manager.log = self.testnode_log
self.cleanUp(None) self.cleanUp()
remote_test_result_needs_cleanup = False
begin = time.time() begin = time.time()
portal_url = config['test_suite_master_url'] portal_url = config['test_suite_master_url']
self.taskdistribution = taskdistribution.TaskDistributor( self.taskdistribution = taskdistribution.TaskDistributor(
...@@ -328,25 +303,25 @@ develop = false ...@@ -328,25 +303,25 @@ develop = false
logger=DummyLogger(log)) logger=DummyLogger(log))
node_configuration = self.taskdistribution.subscribeNode(node_title=config['test_node_title'], node_configuration = self.taskdistribution.subscribeNode(node_title=config['test_node_title'],
computer_guid=config['computer_id']) computer_guid=config['computer_id'])
if type(node_configuration) == str: if type(node_configuration) is str:
# Backward compatiblity # Backward compatiblity
node_configuration = json.loads(node_configuration) node_configuration = json.loads(node_configuration)
if node_configuration is not None and \ if node_configuration is not None and \
'process_timeout' in node_configuration \ 'process_timeout' in node_configuration \
and node_configuration['process_timeout'] is not None: and node_configuration['process_timeout'] is not None:
process_timeout = node_configuration['process_timeout'] process_timeout = node_configuration['process_timeout']
log('Received and using process timeout from master: %i' % ( log('Received and using process timeout from master: %i',
process_timeout)) process_timeout)
self.process_manager.max_timeout = process_timeout self.process_manager.max_timeout = process_timeout
test_suite_data = self.taskdistribution.startTestSuite( test_suite_data = self.taskdistribution.startTestSuite(
node_title=config['test_node_title'], node_title=config['test_node_title'],
computer_guid=config['computer_id']) computer_guid=config['computer_id'])
if type(test_suite_data) == str: if type(test_suite_data) is str:
# Backward compatiblity # Backward compatiblity
test_suite_data = json.loads(test_suite_data) test_suite_data = json.loads(test_suite_data)
test_suite_data = Utils.deunicodeData(test_suite_data) test_suite_data = deunicodeData(test_suite_data)
log("Got following test suite data from master : %r" % \ log("Got following test suite data from master : %r",
(test_suite_data,)) test_suite_data)
try: try:
my_test_type = self.taskdistribution.getTestType() my_test_type = self.taskdistribution.getTestType()
except Exception: except Exception:
...@@ -367,13 +342,12 @@ develop = false ...@@ -367,13 +342,12 @@ develop = false
# Clean-up test suites # Clean-up test suites
self.checkOldTestSuite(test_suite_data) self.checkOldTestSuite(test_suite_data)
for test_suite in test_suite_data: for test_suite in test_suite_data:
remote_test_result_needs_cleanup = False
node_test_suite = self.getNodeTestSuite( node_test_suite = self.getNodeTestSuite(
test_suite["test_suite_reference"]) test_suite["test_suite_reference"])
node_test_suite.edit( node_test_suite.edit(
working_directory=self.config['working_directory'], working_directory=config['working_directory'],
log_directory=self.config['log_directory']) log_directory=config['log_directory'])
node_test_suite.edit(**test_suite) node_test_suite.edit(**test_suite)
if my_test_type == 'UnitTest': if my_test_type == 'UnitTest':
...@@ -387,7 +361,6 @@ develop = false ...@@ -387,7 +361,6 @@ develop = false
# XXX: temporary hack to prevent empty test_suite # XXX: temporary hack to prevent empty test_suite
if not hasattr(node_test_suite, 'test_suite'): if not hasattr(node_test_suite, 'test_suite'):
node_test_suite.edit(test_suite='') node_test_suite.edit(test_suite='')
run_software = True
# kill processes from previous loop if any # kill processes from previous loop if any
self.process_manager.killPreviousRun() self.process_manager.killPreviousRun()
revision_list = self.getAndUpdateFullRevisionList(node_test_suite) revision_list = self.getAndUpdateFullRevisionList(node_test_suite)
...@@ -402,18 +375,17 @@ develop = false ...@@ -402,18 +375,17 @@ develop = false
config['test_node_title'], False, config['test_node_title'], False,
node_test_suite.test_suite_title, node_test_suite.test_suite_title,
node_test_suite.project_title) node_test_suite.project_title)
remote_test_result_needs_cleanup = True log("testnode, test_result : %r", test_result)
log("testnode, test_result : %r" % (test_result, ))
if test_result is not None: if test_result is not None:
self.registerSuiteLog(test_result, node_test_suite) self.registerSuiteLog(test_result, node_test_suite)
self.checkRevision(test_result,node_test_suite) self.checkRevision(test_result,node_test_suite)
node_test_suite.edit(test_result=test_result) node_test_suite.edit(test_result=test_result)
# get cluster configuration for this test suite, this is needed to # get cluster configuration for this test suite, this is needed to
# know slapos parameters to user for creating instances # know slapos parameters to user for creating instances
log("Getting configuration from test suite " + str(node_test_suite.test_suite_title)) log("Getting configuration from test suite %s", node_test_suite.test_suite_title)
generated_config = self.taskdistribution.generateConfiguration(node_test_suite.test_suite_title) generated_config = self.taskdistribution.generateConfiguration(node_test_suite.test_suite_title)
json_data = json.loads(generated_config) json_data = json.loads(generated_config)
cluster_configuration = Utils.deunicodeData(json_data['configuration_list'][0]) cluster_configuration = deunicodeData(json_data['configuration_list'][0])
node_test_suite.edit(cluster_configuration=cluster_configuration) node_test_suite.edit(cluster_configuration=cluster_configuration)
# Now prepare the installation of SlapOS and create instance # Now prepare the installation of SlapOS and create instance
status_dict = runner.prepareSlapOSForTestSuite(node_test_suite) status_dict = runner.prepareSlapOSForTestSuite(node_test_suite)
...@@ -446,11 +418,12 @@ develop = false ...@@ -446,11 +418,12 @@ develop = false
# break the loop to get latest priorities from master # break the loop to get latest priorities from master
break break
self.cleanUp(test_result) self.cleanUp()
except (SubprocessError, CalledProcessError, ConnectionError) as e: except (SubprocessError, CalledProcessError, ConnectionError) as e:
log("SubprocessError or ConnectionError : %r" % (e,), exc_info=sys.exc_info()) log("", exc_info=1)
if remote_test_result_needs_cleanup: if test_result is not None:
status_dict = e.status_dict or {} status_dict = getattr(e, "status_dict", None) or {
'stderr': "%s: %s" % (e.__class__.__name__, e)}
test_result.reportFailure( test_result.reportFailure(
command=status_dict.get('command'), command=status_dict.get('command'),
stdout=status_dict.get('stdout'), stdout=status_dict.get('stdout'),
...@@ -462,7 +435,7 @@ develop = false ...@@ -462,7 +435,7 @@ develop = false
log("ValueError : %r" % (e,), exc_info=sys.exc_info()) log("ValueError : %r" % (e,), exc_info=sys.exc_info())
if node_test_suite is not None: if node_test_suite is not None:
node_test_suite.retry_software_count += 1 node_test_suite.retry_software_count += 1
if remote_test_result_needs_cleanup: if test_result is not None:
test_result.reportFailure( test_result.reportFailure(
command='', stdout='', command='', stdout='',
stderr="ValueError was raised : %s" % (e,), stderr="ValueError was raised : %s" % (e,),
...@@ -472,16 +445,10 @@ develop = false ...@@ -472,16 +445,10 @@ develop = false
self.process_manager.under_cancellation = False self.process_manager.under_cancellation = False
node_test_suite.retry = True node_test_suite.retry = True
continue continue
except: self.cleanUp()
ex_type, ex, tb = sys.exc_info() sleep_time = 120 - (time.time() - begin)
traceback.print_tb(tb) if sleep_time > 0:
log("erp5testnode exception", exc_info=sys.exc_info()) log("End of processing, going to sleep %s", sleep_time)
raise
now = time.time()
self.cleanUp(test_result)
if (now-begin) < 120:
sleep_time = 120 - (now-begin)
log("End of processing, going to sleep %s" % sleep_time)
time.sleep(sleep_time) time.sleep(sleep_time)
except Exception as e: except Exception as e:
log("Exception in error handling : %r" % (e,), exc_info=sys.exc_info()) log("Exception in error handling : %r" % (e,), exc_info=sys.exc_info())
...@@ -492,5 +459,5 @@ develop = false ...@@ -492,5 +459,5 @@ develop = false
# groups working only in POSIX compilant systems # groups working only in POSIX compilant systems
# Exceptions are swallowed during cleanup phase # Exceptions are swallowed during cleanup phase
log("GENERAL EXCEPTION, QUITING") log("GENERAL EXCEPTION, QUITING")
self.cleanUp(test_result) self.cleanUp()
log("GENERAL EXCEPTION, QUITING, cleanup finished") log("GENERAL EXCEPTION, QUITING, cleanup finished")
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment