From 7191925a66dfb8698d8970f93365500b6d09102e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Perrin?= Date: Fri, 15 Nov 2019 03:11:58 +0100 Subject: [PATCH 1/2] testcase: wip/example of how we could run an integration upgrade test --- slapos/testing/testcase.py | 122 ++++++++++++++++++++++++++++--------- 1 file changed, 93 insertions(+), 29 deletions(-) diff --git a/slapos/testing/testcase.py b/slapos/testing/testcase.py index bcf1f2a65..a34348cc0 100644 --- a/slapos/testing/testcase.py +++ b/slapos/testing/testcase.py @@ -241,27 +241,8 @@ def installSoftwareUrlList(cls, software_url_list, max_retry=2, debug=False): raise e -class SlapOSInstanceTestCase(unittest.TestCase): - """Install one slapos instance. - - This test case install software(s) and request one instance - during `setUpClass` and destroy that instance during `tearDownClass`. - - Software Release URL, Instance Software Type and Instance Parameters - can be defined on the class. - - All tests from the test class will run with the same instance. - - The following class attributes are available: - - * `computer_partition`: the `slapos.slap.slap.ComputerPartition` - computer partition instance. - - * `computer_partition_root_path`: the path of the instance root - directory. - - This class is not supposed to be imported directly, but needs to be setup by - calling makeModuleSetUpAndTestCaseClass. +class BaseSlapOSInstanceTestCase(unittest.TestCase): + """TODO: docs """ # can set this to true to enable debugging utilities _debug = False @@ -283,6 +264,7 @@ class SlapOSInstanceTestCase(unittest.TestCase): slap = None # type: StandaloneSlapOS _ipv4_address = "" _ipv6_address = "" + _software_url = "" # the "current" software URL # a short name of that software URL. # eg. helloworld instead of @@ -325,11 +307,8 @@ class SlapOSInstanceTestCase(unittest.TestCase): """ return "" - # Unittest methods @classmethod def setUpClass(cls): - """Request an instance. - """ cls._instance_parameter_dict = cls.getInstanceParameterDict() try: @@ -345,7 +324,7 @@ class SlapOSInstanceTestCase(unittest.TestCase): getattr(cls, '__partition_reference__', '{}-'.format(cls.__name__))) # request - cls.requestDefaultInstance() + cls.requestDefaultInstance(cls._software_url) # slapos node instance cls.logger.debug("Waiting for instance") @@ -365,7 +344,7 @@ class SlapOSInstanceTestCase(unittest.TestCase): # expose some class attributes so that tests can use them: # the main ComputerPartition instance, to use getInstanceParameterDict - cls.computer_partition = cls.requestDefaultInstance() + cls.computer_partition = cls.requestDefaultInstance(cls._software_url) # the path of the instance on the filesystem, for low level inspection cls.computer_partition_root_path = os.path.join( @@ -475,15 +454,100 @@ class SlapOSInstanceTestCase(unittest.TestCase): @classmethod def requestDefaultInstance(cls, state='started'): - software_url = cls.getSoftwareURL() software_type = cls.getInstanceSoftwareType() cls.logger.debug( 'requesting "%s" software:%s type:%s state:%s parameters:%s', - cls.default_partition_reference, software_url, software_type, state, + cls.default_partition_reference, cls._software_url, software_type, state, cls._instance_parameter_dict) return cls.slap.request( - software_release=software_url, + software_release=cls._software_url, software_type=software_type, partition_reference=cls.default_partition_reference, partition_parameter_kw=cls._instance_parameter_dict, state=state) + + +class SlapOSInstanceTestCase(BaseSlapOSInstanceTestCase): + """Install one slapos instance. + + This test case install software(s) and request one instance + during `setUpClass` and destroy that instance during `tearDownClass`. + + Software Release URL, Instance Software Type and Instance Parameters + can be defined on the class. + + All tests from the test class will run with the same instance. + + The following class attributes are available: + + * `computer_partition`: the `slapos.slap.slap.ComputerPartition` + computer partition instance. + + * `computer_partition_root_path`: the path of the instance root + directory. + + This class is not supposed to be imported directly, but needs to be setup by + calling makeModuleSetUpAndTestCaseClass. + """ + + # Methods to be defined by subclasses. + @classmethod + def getSoftwareURL(cls): + """Return URL of software release to request instance. + + This method will be defined when initialising the class + with makeModuleSetUpAndTestCaseClass. + """ + raise NotImplementedError() + + # Unittest methods + @classmethod + def setUpClass(cls): + """Request an instance. + """ + cls._software_url = cls.getSoftwareURL() + super(SlapOSInstanceTestCase).setUpClass() + + +class SlapOSInstanceUpgradeTestCase(BaseSlapOSInstanceTestCase): + """SlapOSInstanceTestCase for upgrades. + + This provides a starting point for the typical scenario: + - install old software + - create an instance + - create some data in instance + - install new software + - update the instance to use new software + and check that the instance is in correct state after upgrade. + """ + # Methods to be defined by subclasses. + @classmethod + def getOldSoftwareUrl(cls): + """Software URL before upgrade + """ + raise NotImplementedError() + + @classmethod + def getNewSoftwareUrl(cls): + """Software URL after upgrade + """ + raise NotImplementedError() + + # hooks + @classmethod + def prepareOldInstance(cls): + """Prepare the instance before upgrade process. + + This can be used also to create some initial data. + """ + + # unittest methods + @classmethod + def setUpClass(cls): + cls._software_url = cls.getOldSoftwareUrl() + super(SlapOSInstanceUpgradeTestCase, cls).setUpClass() + cls.prepareOldInstance() + cls._software_url = cls.getNewSoftwareUrl() + cls.requestDefaultInstance() + + -- 2.30.9 From bf2c854a10cb0633a1faca6d8ab615351d9cc08c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9rome=20Perrin?= Date: Fri, 15 Nov 2019 03:12:37 +0100 Subject: [PATCH 2/2] XXX: example upgrader test for ERP5 --- slapos/testing/testcase.py | 62 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/slapos/testing/testcase.py b/slapos/testing/testcase.py index a34348cc0..97dcd1e84 100644 --- a/slapos/testing/testcase.py +++ b/slapos/testing/testcase.py @@ -551,3 +551,65 @@ class SlapOSInstanceUpgradeTestCase(BaseSlapOSInstanceTestCase): cls.requestDefaultInstance() + +class ExampleERP5UpgradeTestCase(SlapOSInstanceUpgradeTestCase): + @classmethod + def getOldSoftwareUrl(cls): + # an old version, we don't change this URL often (maybe never ?) + return 'https://lab.nexedi.com/nexedi/slapos/blob/1.0.124/software/erp5/software.cfg' + @classmethod + def getNewSoftwareUrl(cls): + return os.path.join('..', '..', 'software.cfg') # we are in slapos git repository + + @classmethod + def _getERP5XMLRPCClient(cls): + param_dict = json.loads( + cls.computer_partition.getConnectionParameterDict()['_']) + url = urlparse.urljoin(param_dict['family-default'], param_dict['site-id']) + # TODO credentials in url + return xmlrpclib.ServerProxy(url) + + @classmethod + def prepareOldInstance(cls): + erp5 = cls._getERP5XMLRPCClient() + erp5.portal_configurator.configure... + + def test(self): + erp5 = this._getERP5XMLRPCClient() + + repo_bt_list = "TODO: paths of repositories from new software" + + # update repository + try: + erp5.portal_templates.updateRepositoryBusinessTemplateList(repo_bt_list) + except xmlrpclib.ProtocolError as e: + if e.errcode != 302 and not "business templates updated successfully" in e.headers['location']: + raise + repo_bt_list = erp5.portal_templates.getRepositoryList() + self.logger.info('Updated repository to %s', repo_bt_list) + + try: + erp5.portal_templates.download('/'.join([erp5_bt_repo, 'erp5_upgrader'])) + except xmlrpclib.ProtocolError as e: + if e.errcode != 302 and not "Business templates downloaded successfully" in e.headers['Location']: + raise + bt = e.headers['Location'].split('?')[0].split('/')[-1] + getattr(erp5.portal_templates, bt).install() + self.assertEqual("installed", getattr(erp5.portal_templates, bt).getInstallationState()) + self.logger.info("Installed erp5_upgrader as %s", bt) + + + erp5.portal_alarms.promise_check_upgrade.activeSense() + self.logger.info("Running promise_check_upgrade active sense") + + for i in range(100): + time.sleep(i) + if erp5.portal_alarms.promise_check_upgrade.hasActivity(): + # TODO: this breaks too early this check is not correct. + # TODO: maybe check portal_activities has activity instead ? + break + else: + self.fail("Error, promise_check_upgrade is still running") + + self.assertEqual('looks good', erp5.inspectState()) + -- 2.30.9