From 2861a3f05af2a284173744c2715015d8380b22ee Mon Sep 17 00:00:00 2001 From: Sebastien Robin <seb@nexedi.com> Date: Mon, 29 Oct 2012 16:54:50 +0100 Subject: [PATCH] erp5testnode: more cleanup and add skeleton of test - various cleanup and split of code to make it more testable - start writing some tests --- erp5/util/testnode/SlapOSControler.py | 75 ++++++++------ erp5/util/testnode/testnode.py | 139 ++++++++++++++------------ erp5/util/tests/testERP5TestNode.py | 50 +++++++++ setup.py | 3 +- 4 files changed, 174 insertions(+), 93 deletions(-) create mode 100644 erp5/util/tests/testERP5TestNode.py diff --git a/erp5/util/testnode/SlapOSControler.py b/erp5/util/testnode/SlapOSControler.py index c347fa9205..d2aac18bed 100644 --- a/erp5/util/testnode/SlapOSControler.py +++ b/erp5/util/testnode/SlapOSControler.py @@ -25,6 +25,7 @@ # ############################################################################## import os +import pkg_resources import slapos.slap import subprocess import time @@ -35,25 +36,43 @@ import glob MAX_PARTIONS = 10 MAX_SR_RETRIES = 3 +def createFolder(folder): + if not(os.path.exists(folder)): + os.mkdir(folder) + class SlapOSControler(object): - def __init__(self, config, log, - slapproxy_log=None, process_manager=None, reset_software=False): + def __init__(self, working_directory, config, log, slapproxy_log=None, + process_manager=None, reset_software=False, software_path_list=None): + self.software_path_list = software_path_list log('SlapOSControler, initialize, reset_software: %r' % reset_software) self.log = log self.config = config self.process_manager = process_manager + self.software_root = os.path.join(working_directory, 'soft') + self.instance_root = os.path.join(working_directory, 'inst') + self.slapos_config = os.path.join(working_directory, 'slapos.cfg') + self.proxy_database = os.path.join(working_directory, 'proxy.db') + slapos_config_dict = config.copy() + slapos_config_dict.update(software_root=self.software_root, + instance_root=self.instance_root, + proxy_database=self.proxy_database) + open(self.slapos_config, 'w').write(pkg_resources.resource_string( + 'erp5.util.testnode', 'template/slapos.cfg.in') % + slapos_config_dict) + createFolder(self.software_root) + createFolder(self.instance_root) # By erasing everything, we make sure that we are able to "update" # existing profiles. This is quite dirty way to do updates... - if os.path.exists(config['proxy_database']): - os.unlink(config['proxy_database']) + if os.path.exists(self.proxy_database): + os.unlink(self.proxy_database) kwargs = dict(close_fds=True, preexec_fn=os.setsid) if slapproxy_log is not None: slapproxy_log_fp = open(slapproxy_log, 'w') kwargs['stdout'] = slapproxy_log_fp kwargs['stderr'] = slapproxy_log_fp proxy = subprocess.Popen([config['slapproxy_binary'], - config['slapos_config']], **kwargs) + self.slapos_config], **kwargs) process_manager.process_pid_set.add(proxy.pid) # XXX: dirty, giving some time for proxy to being able to accept # connections @@ -62,8 +81,6 @@ class SlapOSControler(object): self.slap = slap self.slap.initializeConnection(config['master_url']) # register software profile - self.software_path_list = config.get("software_list", []) - self.software_path_list.append(config['custom_profile_path']) for path in self.software_path_list: slap.registerSupply().supply( path, @@ -71,13 +88,12 @@ class SlapOSControler(object): computer = slap.registerComputer(config['computer_id']) # Reset all previously generated software if needed if reset_software: - software_root = config['software_root'] - log('SlapOSControler : GOING TO RESET ALL SOFTWARE : %r' % (software_root,)) - if os.path.exists(software_root): - shutil.rmtree(software_root) - os.mkdir(software_root) - os.chmod(software_root, 0750) - instance_root = config['instance_root'] + log('SlapOSControler : GOING TO RESET ALL SOFTWARE : %r' % (self.software_root,)) + if os.path.exists(self.software_root): + shutil.rmtree(self.software_root) + os.mkdir(self.software_root) + os.chmod(self.software_root, 0750) + instance_root = self.instance_root if os.path.exists(instance_root): # delete old paritions which may exists in order to not get its data # (ex. MySQL db content) from previous testnode's runs @@ -96,20 +112,19 @@ class SlapOSControler(object): os.mkdir(partition_path) os.chmod(partition_path, 0750) computer.updateConfiguration(xml_marshaller.xml_marshaller.dumps({ - 'address': config['ipv4_address'], - 'instance_root': instance_root, - 'netmask': '255.255.255.255', - 'partition_list': [{'address_list': [{'addr': config['ipv4_address'], - 'netmask': '255.255.255.255'}, - {'addr': config['ipv6_address'], - 'netmask': 'ffff:ffff:ffff::'},], - 'path': partition_path, - 'reference': partition_reference, - 'tap': {'name': partition_reference}, - } - ], - 'reference': config['computer_id'], - 'software_root': config['software_root']})) + 'address': config['ipv4_address'], + 'instance_root': instance_root, + 'netmask': '255.255.255.255', + 'partition_list': [ + {'address_list': [{'addr': config['ipv4_address'], + 'netmask': '255.255.255.255'}, + {'addr': config['ipv6_address'], + 'netmask': 'ffff:ffff:ffff::'},], + 'path': partition_path, + 'reference': partition_reference, + 'tap': {'name': partition_reference},}], + 'reference': config['computer_id'], + 'software_root': self.software_root})) def spawn(self, *args, **kw): return self.process_manager.spawn(*args, **kw) @@ -123,7 +138,7 @@ class SlapOSControler(object): # so be tolerant and run it a few times before giving up for runs in range(0, MAX_SR_RETRIES): status_dict = self.spawn(config['slapgrid_software_binary'], '-v', '-c', - config['slapos_config'], raise_error_if_fail=False, + self.slapos_config, raise_error_if_fail=False, log_prefix='slapgrid_sr', get_output=False) if status_dict['status_code'] == 0: break @@ -150,7 +165,7 @@ class SlapOSControler(object): sleep_time = 0 for runs in range(0, MAX_PARTIONS): status_dict = self.spawn(config['slapgrid_partition_binary'], '-v', '-c', - config['slapos_config'], raise_error_if_fail=False, + self.slapos_config, raise_error_if_fail=False, log_prefix='slapgrid_cp', get_output=False) self.log('slapgrid_cp status_dict : %r' % (status_dict,)) if status_dict['status_code'] in (0,): diff --git a/erp5/util/testnode/testnode.py b/erp5/util/testnode/testnode.py index 8e828e17ae..d0da2bf995 100644 --- a/erp5/util/testnode/testnode.py +++ b/erp5/util/testnode/testnode.py @@ -34,7 +34,6 @@ import SlapOSControler import json import time import shutil -import pkg_resources from ProcessManager import SubprocessError, ProcessManager, CancellationError from subprocess import CalledProcessError from Updater import Updater @@ -51,12 +50,19 @@ class DummyLogger(object): 'critical', 'fatal'): setattr(self, name, func) -class NodeTestSuite(object): +class SlapOSInstance(object): - def __init__(self, reference=None): - self.reference = reference + def __init__(self, working_directory): self.retry_software_count = 0 self.retry = False + self.working_directory = working_directory + +class NodeTestSuite(SlapOSInstance): + + def __init__(self, reference, working_directory): + super(NodeTestSuite, self).__init__(working_directory) + self.reference = reference + self.working_directory = os.path.join(working_directory, reference) def edit(self, **kw): self.__dict__.update(**kw) @@ -68,6 +74,10 @@ class TestNode(object): self.config = config self.process_manager = ProcessManager(log) self.node_test_suite_dict = {} + # hack until slapos.cookbook is updated + if config['working_directory'].endswith("slapos/"): + config['working_directory'] = config[ + 'working_directory'][:-(len("slapos/"))] def checkOldTestSuite(self,test_suite_data): config = self.config @@ -87,44 +97,24 @@ class TestNode(object): node_test_suite = self.node_test_suite_dict.get(reference) if node_test_suite is None: node_test_suite = self.node_test_suite_dict[reference] = NodeTestSuite( - reference=reference) + reference, working_directory) return node_test_suite def delNodeTestSuite(self, reference): if self.node_test_suite_dict.has_key(reference): self.node_test_suite_dict.pop(reference) - def updateConfigForTestSuite(self, test_suite): + def updateConfigForTestSuite(self, test_suite, node_test_suite): config = self.config - node_test_suite = self.getNodeTestSuite(test_suite["test_suite_reference"]) node_test_suite.edit(project_title=test_suite["project_title"], test_suite=test_suite["test_suite"], test_suite_title=test_suite["test_suite_title"]) - try: - config["additional_bt5_repository_id"] = test_suite["additional-bt5-repository-id"] - except KeyError: - pass config["vcs_repository_list"] = test_suite["vcs_repository_list"] config['working_directory'] = os.path.join(config['slapos_directory'], node_test_suite.reference) - if not(os.path.exists(config['working_directory'])): - os.mkdir(config['working_directory']) - config['instance_root'] = os.path.join(config['working_directory'], - 'inst') - if not(os.path.exists(config['instance_root'])): - os.mkdir(config['instance_root']) - config['software_root'] = os.path.join(config['working_directory'], - 'soft') - if not(os.path.exists(config['software_root'])): - os.mkdir(config['software_root']) - config['proxy_database'] = os.path.join(config['working_directory'], - 'proxy.db') + SlapOSControler.createFolder(config['working_directory']) custom_profile_path = os.path.join(config['working_directory'], 'software.cfg') config['custom_profile_path'] = custom_profile_path - config['slapos_config'] = os.path.join(config['working_directory'], - 'slapos.cfg') - open(config['slapos_config'], 'w').write(pkg_resources.resource_string( - 'erp5.util.testnode', 'template/slapos.cfg.in') % config) def constructProfile(self): vcs_repository_list = self.config['vcs_repository_list'] @@ -182,7 +172,6 @@ branch = %(branch)s full_revision_list = [] config = self.config log = self.log - process_manager = self.process_manager vcs_repository_list = self.config['vcs_repository_list'] for vcs_repository in vcs_repository_list: repository_path = vcs_repository['repository_path'] @@ -196,7 +185,7 @@ branch = %(branch)s log(subprocess.check_output(parameter_list, stderr=subprocess.STDOUT)) # Make sure we have local repository updater = Updater(repository_path, git_binary=config['git_binary'], - log=log, process_manager=process_manager) + log=log, process_manager=self.process_manager) updater.checkout() revision = "-".join(updater.getRevision()) full_revision_list.append('%s=%s' % (repository_id, revision)) @@ -216,7 +205,6 @@ branch = %(branch)s def checkRevision(self, test_result, node_test_suite, vcs_repository_list): config = self.config log = self.log - process_manager = self.process_manager if node_test_suite.revision != test_result.revision: log('Disagreement on tested revision, checking out: %r' % ( (node_test_suite.revision,test_result.revision),)) @@ -229,35 +217,55 @@ branch = %(branch)s log(' %s at %s' % (repository_path, node_test_suite.revision)) updater = Updater(repository_path, git_binary=config['git_binary'], revision=revision, log=log, - process_manager=process_manager) + process_manager=self.process_manager) updater.checkout() node_test_suite.revision = test_result.revision - def prepareSlapOSForTestSuite(self,node_test_suite): - config = self.config - log = self.log - process_manager = self.process_manager - slapproxy_log = os.path.join(config['log_directory'], + def _prepareSlapOS(self, working_directory, slapos_instance, + create_partition=1, software_path_list=None, **kw): + """ + Launch slapos to build software and partitions + """ + slapproxy_log = os.path.join(self.config['log_directory'], 'slapproxy.log') - log('Configured slapproxy log to %r' % slapproxy_log) - retry_software_count = node_test_suite.retry_software_count - log('testnode, retry_software_count : %r' % retry_software_count) - slapos_controler = SlapOSControler.SlapOSControler(config, - log=log, slapproxy_log=slapproxy_log, process_manager=process_manager, - reset_software=(retry_software_count>0 and retry_software_count%10 == 0)) - for method_name in ("runSoftwareRelease", "runComputerPartition",): + self.log('Configured slapproxy log to %r' % slapproxy_log) + reset_software = slapos_instance.retry_software_count > 10 + self.log('testnode, retry_software_count : %r' % \ + slapos_instance.retry_software_count) + slapos_controler = SlapOSControler.SlapOSControler( + working_directory, self.config, log=self.log, slapproxy_log=slapproxy_log, + process_manager=self.process_manager, reset_software=reset_software, + software_path_list=software_path_list) + method_list= ["runSoftwareRelease"] + if create_partition: + method_list.append("runComputerPartition") + for method_name in method_list: slapos_method = getattr(slapos_controler, method_name) - status_dict = slapos_method(config, - environment=config['environment'], + status_dict = slapos_method(self.config, + environment=self.config['environment'], ) if status_dict['status_code'] != 0: - node_test_suite.retry = True - node_test_suite.retry_software_count += 1 + slapos_instance.retry = True + slapos_instance.retry_software_count += 1 raise SubprocessError(status_dict) else: - node_test_suite.retry_software_count = 0 + slapos_instance.retry_software_count = 0 return status_dict + def prepareSlapOSForTestNode(self, test_node_slapos): + """ + We will build slapos software needed by the testnode itself, + like the building of selenium-runner by default + """ + return self._prepareSlapOS(self.config['slapos_directory'], + test_node_slapos, create_partition=0, + software_path_list=self.config.get("software_list")) + + def prepareSlapOSForTestSuite(self, node_test_suite): + return self._prepareSlapOS(node_test_suite.working_directory, + node_test_suite, + software_path_list=[self.config.get("custom_profile_path")]) + def _dealShebang(self,run_test_suite_path): line = open(run_test_suite_path, 'r').readline() invocation_list = [] @@ -265,10 +273,11 @@ branch = %(branch)s invocation_list = line[2:].split() return invocation_list - def runTestSuite(self, node_test_suite, portal_url): + def runTestSuite(self, node_test_suite, portal_url, slapos_controler): config = self.config - run_test_suite_path_list = glob.glob("%s/*/bin/runTestSuite" %config['instance_root']) + run_test_suite_path_list = glob.glob("%s/*/bin/runTestSuite" % \ + slapos_controler.instance_root) if not len(run_test_suite_path_list): raise ValueError('No runTestSuite provided in installed partitions.') run_test_suite_path = run_test_suite_path_list[0] @@ -281,10 +290,12 @@ branch = %(branch)s '--test_suite_title', node_test_suite.test_suite_title, '--node_quantity', config['node_quantity'], '--master_url', portal_url]) - firefox_bin_list = glob.glob("%s/*/parts/firefox/firefox-slapos" % config["software_root"]) + firefox_bin_list = glob.glob("%s/*/parts/firefox/firefox-slapos" % \ + config["slapos_directory"]) if len(firefox_bin_list): invocation_list.extend(["--firefox_bin", firefox_bin_list[0]]) - xvfb_bin_list = glob.glob("%s/*/parts/xserver/bin/Xvfb" % config["software_root"]) + xvfb_bin_list = glob.glob("%s/*/parts/xserver/bin/Xvfb" % \ + config["slapos_directory"]) if len(xvfb_bin_list): invocation_list.extend(["--xvfb_bin", xvfb_bin_list[0]]) bt5_path_list = config.get("bt5_path") @@ -298,10 +309,9 @@ branch = %(branch)s log_prefix='runTestSuite', get_output=False) def cleanUp(self,test_result): - process_manager = self.process_manager log = self.log log('Testnode.run, finally close') - process_manager.killPreviousRun() + self.process_manager.killPreviousRun() if test_result is not None: try: test_result.removeWatch(self.config['log_file']) @@ -311,15 +321,17 @@ branch = %(branch)s def run(self): log = self.log config = self.config - process_manager = self.process_manager slapgrid = None previous_revision_dict = {} revision_dict = {} test_result = None + test_node_slapos = SlapOSInstance(self.config['slapos_directory']) try: while True: try: + remote_test_result_needs_cleanup = False begin = time.time() + self.prepareSlapOSForTestNode(test_node_slapos) portal_url = config['test_suite_master_url'] portal = taskdistribution.TaskDistributionTool(portal_url, logger=DummyLogger(log)) test_suite_portal = taskdistribution.TaskDistributor(portal_url, logger=DummyLogger(log)) @@ -330,15 +342,18 @@ branch = %(branch)s #Clean-up test suites self.checkOldTestSuite(test_suite_data) for test_suite in test_suite_data: - self.updateConfigForTestSuite(test_suite) - node_test_suite = self.getNodeTestSuite(test_suite["test_suite_reference"]) + remote_test_result_needs_cleanup = False + node_test_suite = self.getNodeTestSuite( + test_suite["test_suite_reference"], + self.config['working_directory']) + self.updateConfigForTestSuite(test_suite, node_test_suite) run_software = True - self.process_manager.supervisord_pid_file = os.path.join(config['instance_root'], 'var', 'run', - 'supervisord.pid') + self.process_manager.supervisord_pid_file = os.path.join(\ + slapos_controler.instance_root, 'var', 'run', 'supervisord.pid') # Write our own software.cfg to use the local repository vcs_repository_list = self.constructProfile() # kill processes from previous loop if any - process_manager.killPreviousRun() + self.process_manager.killPreviousRun() self.getAndUpdateFullRevisionList(node_test_suite) # Make sure we have local repository test_result = portal.createTestResult(node_test_suite.revision, [], @@ -356,7 +371,7 @@ branch = %(branch)s # as partitions can be of any kind we have and likely will never have # a reliable way to check if they are up or not ... time.sleep(20) - self.runTestSuite(node_test_suite,portal_url) + self.runTestSuite(node_test_suite,portal_url, slapos_controler) test_result.removeWatch(log_file_name) except (SubprocessError, CalledProcessError) as e: log("SubprocessError", exc_info=sys.exc_info()) @@ -376,7 +391,7 @@ branch = %(branch)s node_test_suite.retry_software_count += 1 except CancellationError, e: log("CancellationError", exc_info=sys.exc_info()) - process_manager.under_cancellation = False + self.process_manager.under_cancellation = False node_test_suite.retry = True continue except: diff --git a/erp5/util/tests/testERP5TestNode.py b/erp5/util/tests/testERP5TestNode.py new file mode 100644 index 0000000000..4a0611a2f1 --- /dev/null +++ b/erp5/util/tests/testERP5TestNode.py @@ -0,0 +1,50 @@ +from unittest import TestCase + +import sys +sys.path[0:0] = [ + '/srv/slapgrid/slappart80/srv/runner/software/ba1e09f3364989dc92da955b64e72f8d/eggs/slapos.cookbook-0.65-py2.7.egg', + '/srv/slapgrid/slappart80/srv/runner/software/ba1e09f3364989dc92da955b64e72f8d/eggs/zc.recipe.egg-1.3.2-py2.7.egg', + '/srv/slapgrid/slappart80/srv/runner/software/ba1e09f3364989dc92da955b64e72f8d/eggs/zc.buildout-1.6.0_dev_SlapOS_010-py2.7.egg', + '/srv/slapgrid/slappart80/srv/runner/software/ba1e09f3364989dc92da955b64e72f8d/eggs/xml_marshaller-0.9.7-py2.7.egg', + '/srv/slapgrid/slappart80/srv/runner/software/ba1e09f3364989dc92da955b64e72f8d/eggs/slapos.core-0.28.5-py2.7.egg', + '/srv/slapgrid/slappart80/srv/runner/software/ba1e09f3364989dc92da955b64e72f8d/eggs/inotifyx-0.2.0-py2.7-linux-x86_64.egg', + '/srv/slapgrid/slappart80/srv/runner/software/ba1e09f3364989dc92da955b64e72f8d/eggs/setuptools-0.6c12dev_r88846-py2.7.egg', + '/srv/slapgrid/slappart80/srv/runner/software/ba1e09f3364989dc92da955b64e72f8d/eggs/netaddr-0.7.10-py2.7.egg', + '/srv/slapgrid/slappart80/srv/runner/software/ba1e09f3364989dc92da955b64e72f8d/develop-eggs/lxml-2.3.6-py2.7-linux-x86_64.egg', + '/srv/slapgrid/slappart80/srv/runner/software/ba1e09f3364989dc92da955b64e72f8d/eggs/PyXML-0.8.4-py2.7-linux-x86_64.egg', + '/srv/slapgrid/slappart80/srv/runner/software/ba1e09f3364989dc92da955b64e72f8d/eggs/zope.interface-4.0.1-py2.7-linux-x86_64.egg', + '/srv/slapgrid/slappart80/srv/runner/software/ba1e09f3364989dc92da955b64e72f8d/eggs/supervisor-3.0a12-py2.7.egg', + '/srv/slapgrid/slappart80/srv/runner/software/ba1e09f3364989dc92da955b64e72f8d/eggs/netifaces-0.8-py2.7-linux-x86_64.egg', + '/srv/slapgrid/slappart80/srv/runner/software/ba1e09f3364989dc92da955b64e72f8d/eggs/Flask-0.9-py2.7.egg', + '/srv/slapgrid/slappart80/srv/runner/software/ba1e09f3364989dc92da955b64e72f8d/eggs/meld3-0.6.8-py2.7.egg', + '/srv/slapgrid/slappart80/srv/runner/software/ba1e09f3364989dc92da955b64e72f8d/eggs/Jinja2-2.6-py2.7.egg', + '/srv/slapgrid/slappart80/srv/runner/software/ba1e09f3364989dc92da955b64e72f8d/eggs/Werkzeug-0.8.3-py2.7.egg', + ] + +from erp5.util.testnode import TestNode +import tempfile +import shutil +import os + +class ERP5TestNode(TestCase): + + def setUp(self): + self._tempdir = tempfile.mkdtemp() + self.software_root = os.path.join(self._tempdir, 'software') + self.instance_root = os.path.join(self._tempdir, 'instance') + + def tearDown(self): + shutil.rmtree(self._tempdir, True) + + def test_01_GetDelNodeTestSuite(self): + test_node = TestNode(None, None) + node_test_suite = test_node.getNodeTestSuite('foo') + self.assertEquals(0, node_test_suite.retry_software_count) + node_test_suite.retry_software_count = 2 + self.assertEquals(2, node_test_suite.retry_software_count) + node_test_suite = test_node.delNodeTestSuite('foo') + node_test_suite = test_node.getNodeTestSuite('foo') + self.assertEquals(0, node_test_suite.retry_software_count) + + def test_01_GetNodeTestSuite(self): + pass diff --git a/setup.py b/setup.py index eaf8878b28..ebc7cb5182 100644 --- a/setup.py +++ b/setup.py @@ -71,7 +71,8 @@ setup(name=name, 'erp5.util.benchmark.report:generateReport [benchmark-report]', 'web_checker_utility = erp5.util.webchecker:web_checker_utility' ], - } + }, + test_suite='erp5.tests' ) # cleanup garbage -- 2.30.9