Commit ea88d9ab authored by Roque's avatar Roque Committed by Sebastien Robin

testnode: refactoring in SlapOS communicator and Scalability runner

- method waitInstance moved from runner to communicator
- new simple methods in communicator for request and wait instance
- new methods to modularize runner code
- communicator initialization code moved in runner
parent 388a508b
......@@ -68,7 +68,6 @@ class ScalabilityTestRunner():
# Create the slapos account configuration file and dir
key = self.testnode.test_suite_portal.getSlaposAccountKey()
certificate = self.testnode.test_suite_portal.getSlaposAccountCertificate()
# Get Slapos Master Url
self.slapos_url = ''
try:
......@@ -106,7 +105,7 @@ class ScalabilityTestRunner():
self.log("testnode, supply : %s %s", software_path, computer_guid)
if self.authorize_supply :
self.remaining_software_installation_dict[computer_guid] = software_path
self.slapos_communicator._supply()
self.slapos_communicator.supply(software_path, computer_guid)
return {'status_code' : 0}
else:
raise ValueError("Too late to supply now. ('self.authorize_supply' is False)")
......@@ -150,7 +149,7 @@ class ScalabilityTestRunner():
config = self._generateInstanceXML(software_configuration,
test_result, test_suite)
request_kw = {"partition_parameter_kw": {"_" : json.dumps(config)} }
self.slapos_communicator._request(SlapOSMasterCommunicator.INSTANCE_STATE_STARTED, instance_title, request_kw)
self.slapos_communicator.requestStart(instance_title, request_kw)
self.authorize_request = False
return {'status_code' : 0}
else:
......@@ -211,29 +210,9 @@ late a SlapOS (positive) answer." %(str(os.getpid()),str(os.getpid()),))
config = self._generateInstanceXML(software_configuration,
test_result, test_suite)
request_kw = {"partition_parameter_kw": {"_" : json.dumps(config)} }
self.slapos_communicator._request(SlapOSMasterCommunicator.INSTANCE_STATE_STARTED, instance_title, request_kw)
self.slapos_communicator.requestStart(instance_title, request_kw)
return {'status_code' : 0}
def _waitInstance(self, instance_title, state, max_time=MAX_INSTANCE_TIME):
"""
Wait for 'max_time' an instance specific state
"""
self.log("Waiting for instance state: %s" %state)
start_time = time.time()
while (not self.slapos_communicator._getInstanceState() == state
and (max_time > (time.time()-start_time))):
self.log("Instance(s) not in %s state yet." % state)
time.sleep(15)
if (time.time()-start_time) > max_time:
error_message = "Instance '%s' not '%s' after %s seconds" %(instance_title, state, str(time.time()-start_time))
self.log(error_message)
self.log("Do you use instance state propagation in your project?")
self.log("Instance '%s' will be stopped and test aborted." %instance_title)
self.slapos_communicator._request(SlapOSMasterCommunicator.INSTANCE_STATE_STOPPED)
time.sleep(60)
raise ValueError(error_message)
self.log("Instance correctly '%s' after %s seconds." %(state, str(time.time()-start_time)))
def _waitInstanceCreation(self, instance_title, max_time=MAX_CREATION_INSTANCE_TIME):
"""
Wait for 'max_time' the instance creation
......@@ -248,16 +227,10 @@ late a SlapOS (positive) answer." %(str(os.getpid()),str(os.getpid()),))
raise ValueError("Instance '%s' not found after %s seconds" %(instance_title, max_time))
self.log("Instance found on slapOSMaster")
def prepareSlapOSForTestSuite(self, node_test_suite):
def _initializeSlapOSConnection(self):
"""
Install testsuite softwares
Initialize communication with slapos
"""
self.log('Preparing SlapOS for Test Suite...')
max_time = MAX_PREPARE_TEST_SUITE
interval_time = 60
start_time = time.time()
# Initialize communication with slapos
slap = slapos.slap.slap()
retry = 0
while True:
......@@ -279,32 +252,11 @@ late a SlapOS (positive) answer." %(str(os.getpid()),str(os.getpid()),))
if getattr(slap, '_hateoas_navigator', None) is None:
raise ValueError("Fail to load _hateoas_navigator")
hateoas = getattr(slap, '_hateoas_navigator', None)
supply = slap.registerSupply()
order = slap.registerOpenOrder()
return slap, supply, order
# Only master testnode must order software installation
if self.testnode.test_suite_portal.isMasterTestnode(
self.testnode.config['test_node_title']):
# Get from ERP5 Master the configuration of the cluster for the test
test_configuration = Utils.deunicodeData(
json.loads(self.testnode.test_suite_portal.generateConfiguration(
node_test_suite.test_suite_title)))
self.involved_nodes_computer_guid = test_configuration['involved_nodes_computer_guid']
self.launchable = test_configuration['launchable']
self.error_message = test_configuration['error_message']
self.randomized_path = test_configuration['randomized_path']
if not self.launchable:
self.log("Test suite %s is not actually launchable with \
the current cluster configuration." %(node_test_suite.test_suite_title,))
self.log("ERP5 Master indicates : %s" %(self.error_message,))
return {'status_code' : 1}
involved_nodes_computer_guid = test_configuration['involved_nodes_computer_guid']
configuration_list = test_configuration['configuration_list']
node_test_suite.edit(configuration_list=configuration_list)
self.launcher_nodes_computer_guid = test_configuration['launcher_nodes_computer_guid']
def createSoftwareReachableProfilePath(self, node_test_suite):
# Create an obfuscated link to the testsuite directory
path_to_suite = os.path.join(
self.testnode.config['working_directory'],
......@@ -331,7 +283,6 @@ late a SlapOS (positive) answer." %(str(os.getpid()),str(os.getpid()),))
# ROQUE XXX: below urls are hardcoded until we find ways to make buildout extending through unsafe https
self.reachable_address = "https://lab.nexedi.com/rporchetto/telecom/tree/master"
self.reachable_profile = "https://lab.nexedi.com/rporchetto/telecom/raw/master/software_release/software.cfg"
# Write the reachable address in the software.cfg file,
# by replacing <obfuscated_url> occurences by the current reachable address.
software_file = open(node_test_suite.custom_profile_path, "r")
......@@ -345,19 +296,55 @@ late a SlapOS (positive) answer." %(str(os.getpid()),str(os.getpid()),))
for line in new_file_content:
software_file.write(line)
software_file.close()
self.log("Software reachable profile path is : %s "
%(self.reachable_profile,))
# Ask for SR installation
for computer_guid in self.involved_nodes_computer_guid:
def prepareSlapOSForTestSuite(self, node_test_suite):
"""
Install testsuite softwares
"""
self.log('Preparing SlapOS for Test Suite...')
max_time = MAX_PREPARE_TEST_SUITE
interval_time = 60
start_time = time.time()
# Initialize communication with slapos
slap, supply, order = self._initializeSlapOSConnection()
# Only master testnode must order software installation
if self.testnode.test_suite_portal.isMasterTestnode(self.testnode.config['test_node_title']):
# Get from ERP5 Master the configuration of the cluster for the test
test_configuration = Utils.deunicodeData(
json.loads(self.testnode.test_suite_portal.generateConfiguration(
node_test_suite.test_suite_title)))
self.involved_nodes_computer_guid = test_configuration['involved_nodes_computer_guid']
self.launchable = test_configuration['launchable']
self.error_message = test_configuration['error_message']
self.randomized_path = test_configuration['randomized_path']
if not self.launchable:
self.log("Test suite %s is not actually launchable with \
the current cluster configuration." %(node_test_suite.test_suite_title,))
self.log("ERP5 Master indicates : %s" %(self.error_message,))
return {'status_code' : 1}
configuration_list = test_configuration['configuration_list']
node_test_suite.edit(configuration_list=configuration_list)
self.launcher_nodes_computer_guid = test_configuration['launcher_nodes_computer_guid']
self.instance_title = self._generateInstanceTitle(node_test_suite.test_suite_title)
self.createSoftwareReachableProfilePath(node_test_suite)
self.log("Software reachable profile path is : %s " %(self.reachable_profile))
# Initialize SlapOS Master Communicator
self.slapos_communicator = SlapOSMasterCommunicator.SoftwareReleaseTester(
"SlaposMasterCommunicator",
self.instance_title,
self.log,
slap,
order,
supply,
self.reachable_profile,
computer_guid=computer_guid)
computer_guid=self.launcher_nodes_computer_guid[0])
# Ask for SR installation
for computer_guid in self.involved_nodes_computer_guid:
self._prepareSlapOS(self.reachable_profile, computer_guid)
# From the line below we would not supply any more softwares
self.authorize_supply = False
......@@ -378,7 +365,6 @@ late a SlapOS (positive) answer." %(str(os.getpid()),str(os.getpid()),))
self.authorize_request = True
self.log("Softwares installed.")
# Launch instance
self.instance_title = self._generateInstanceTitle(node_test_suite.test_suite_title)
try:
self._createInstance(self.reachable_profile,
configuration_list[0],
......@@ -410,20 +396,20 @@ late a SlapOS (positive) answer." %(str(os.getpid()),str(os.getpid()),))
count = 0
error_message = None
self._waitInstance(self.instance_title, SlapOSMasterCommunicator.INSTANCE_STATE_STARTED)
self.slapos_communicator.waitInstanceStarted(self.instance_title)
# Each cluster configuration are tested
for configuration in configuration_list:
# First configuration doesn't need XML configuration update.
if count > 0:
self.slapos_communicator._request(SlapOSMasterCommunicator.INSTANCE_STATE_STOPPED)
self._waitInstance(self.instance_title, SlapOSMasterCommunicator.INSTANCE_STATE_STOPPED)
self.slapos_communicator.requestStop()
self.slapos_communicator.waitInstanceStopped(self.instance_title)
self._updateInstanceXML(configuration, self.instance_title,
node_test_suite.test_result, node_test_suite.test_suite)
self._waitInstance(self.instance_title, SlapOSMasterCommunicator.INSTANCE_STATE_STARTED)
self.slapos_communicator._request(SlapOSMasterCommunicator.INSTANCE_STATE_STARTED)
self.slapos_communicator.waitInstanceStarted(self.instance_title)
self.slapos_communicator.requestStart()
self._waitInstance(self.instance_title, SlapOSMasterCommunicator.INSTANCE_STATE_STARTED)
self.slapos_communicator.waitInstanceStarted(self.instance_title)
self.log("[DEBUG] INSTANCE CORRECTLY STARTED")
# ROQUE XXX : for debug
......@@ -465,11 +451,12 @@ late a SlapOS (positive) answer." %(str(os.getpid()),str(os.getpid()),))
break
# Stop current instance
self.slapos_communicator._request(SlapOSMasterCommunicator.INSTANCE_STATE_STOPPED)
self._waitInstance(self.instance_title, SlapOSMasterCommunicator.INSTANCE_STATE_STOPPED)
self.slapos_communicator.requestStop()
self.slapos_communicator.waitInstanceStopped(self.instance_title)
# Delete old instances
self._cleanUpOldInstance()
# ROQUE: for now, the instance is not removed.
#self._cleanUpOldInstance()
# If error appears then that's a test failure.
if error_message:
......
......@@ -13,10 +13,11 @@ from slapos.slap.slap import ConnectionError
from requests.exceptions import HTTPError
from erp5.util.taskdistribution import SAFE_RPC_EXCEPTION_LIST
# max time to instance changing state: 2 hour
MAX_INSTANCE_TIME = 60*60*2
SOFTWARE_PRODUCT_NAMESPACE = "product."
SOFTWARE_STATE_UNKNOWN = "SOFTWARE_STATE_UNKNOWN"
SOFTWARE_STATE_INSTALLING = "SOFTWARE_STATE_INSTALLING"
SOFTWARE_STATE_INSTALLED = "SOFTWARE_STATE_INSTALLED"
......@@ -28,7 +29,7 @@ INSTANCE_STATE_STARTED = "started"
INSTANCE_STATE_STARTED_WITH_ERROR = "INSTANCE_STATE_STARTED_WITH_ERROR"
INSTANCE_STATE_STOPPING = "INSTANCE_STATE_STOPPING"
INSTANCE_STATE_STOPPED = "stopped"
INSTANCE_STATE_DESTROYING = "INSTANCE_STATE_DESTROYING"
INSTANCE_STATE_DESTROYED = "destroyed"
TESTER_STATE_INITIAL = "TESTER_STATE_INITIAL"
TESTER_STATE_NOTHING = "TESTER_STATE_NOTHING"
......@@ -66,7 +67,7 @@ def retryOnNetworkFailure(func):
class SlapOSMasterCommunicator(object):
latest_state = None
def __init__(self, slap, slap_supply, slap_order, url, logger ):
......@@ -91,7 +92,7 @@ class SlapOSMasterCommunicator(object):
@retryOnNetworkFailure
def _supply(self):
if self.computer_guid is None:
self._logger ('Nothing to supply for %s.' % (self.name))
self._logger('Nothing to supply for %s.' % (self.name))
return None
self._logger('Supply %s@%s', self.url, self.computer_guid)
return self.slap_supply.supply(self.url, self.computer_guid)
......@@ -233,10 +234,137 @@ class SlapOSMasterCommunicator(object):
result = self.hateoas_navigator.GET(object_link)
return json.loads(result)
def _getSoftwareState(self):
if self.computer_guid is None:
return SOFTWARE_STATE_INSTALLED
message = self.getSoftwareInstallationNews()
if message.startswith("#error no data found"):
return SOFTWARE_STATE_UNKNOWN
if message.startswith('#access software release'):
return SOFTWARE_STATE_INSTALLED
if message.startswith('#error'):
return SOFTWARE_STATE_INSTALLING
return SOFTWARE_STATE_UNKNOWN
@retryOnNetworkFailure
def _getInstanceState(self):
latest_state = self.latest_state
self._logger('latest_state = %r', latest_state)
if latest_state is None:
return INSTANCE_STATE_UNKNOWN
message_list = []
try:
for instance in self.getInstanceUrlList():
news = self.getNewsFromInstance(instance["href"])
information = self.getInformationFromInstance(instance["href"])
state = INSTANCE_STATE_UNKNOWN
monitor_information_dict = {}
info_created_at = "-1"
is_slave = information['slave']
if is_slave:
if (information["connection_dict"]) > 0:
state = INSTANCE_STATE_STARTED
else:
# not slave
instance_state = news[0]
if instance_state.get('created_at', '-1') != "-1":
# the following does NOT take TZ into account
created_at = datetime.datetime.strptime(instance_state['created_at'],
'%a, %d %b %Y %H:%M:%S %Z')
gmt_now = datetime.datetime(*time.gmtime()[:6])
info_created_at = '%s (%d)' % (
instance_state['created_at'], (gmt_now - created_at).seconds)
if instance_state['text'].startswith('#access'):
state = INSTANCE_STATE_STARTED
if instance_state['text'].startswith('#access Instance correctly stopped'):
state = INSTANCE_STATE_STOPPED
if instance_state['text'].startswith('#error'):
state = INSTANCE_STATE_STARTED_WITH_ERROR
if state == INSTANCE_STATE_STARTED_WITH_ERROR:
# search for monitor url
monitor_v6_url = information["connection_dict"].get("monitor_v6_url")
try:
monitor_information_dict = self.getRSSEntryFromMonitoring(monitor_v6_url)
except Exception:
self._logger('Unable to download promises for: %s' % (instance["title"]))
self._logger(traceback.format_exc())
monitor_information_dict = {"message": "Unable to download"}
message_list.append({
'title': instance["title"],
'slave': is_slave,
'news': news[0],
'information': information,
'monitor': monitor_information_dict,
'state': state
})
except slapos.slap.ServerError:
self._logger('Got an error requesting partition for '
'its state')
return INSTANCE_STATE_UNKNOWN
except:
self._logger("ERROR getting instance state")
return INSTANCE_STATE_UNKNOWN
started = 0
stopped = 0
self.message_history.append(message_list)
for instance in message_list:
if not instance['slave'] and \
instance['state'] in (INSTANCE_STATE_UNKNOWN, INSTANCE_STATE_STARTED_WITH_ERROR):
return instance['state']
elif not instance['slave'] and instance['state'] == INSTANCE_STATE_STARTED:
started = 1
elif not instance['slave'] and instance['state'] == INSTANCE_STATE_STOPPED:
stopped = 1
if instance['slave'] and instance['state'] == INSTANCE_STATE_UNKNOWN:
return instance['state']
if started and stopped:
return INSTANCE_STATE_STOPPED
return INSTANCE_STATE_UNKNOWN
if started:
return INSTANCE_STATE_STARTED
if stopped:
return INSTANCE_STATE_STOPPED
@retryOnNetworkFailure
def _waitInstance(self, instance_title, state, max_time=MAX_INSTANCE_TIME):
"""
Wait for 'max_time' an instance specific state
"""
self._logger("Waiting for instance state: %s" %state)
start_time = time.time()
while (not self._getInstanceState() == state
and (max_time > (time.time()-start_time))):
self._logger("Instance(s) not in %s state yet." % state)
self._logger("Current state: %s" % self._getInstanceState())
time.sleep(15)
if (time.time()-start_time) > max_time:
error_message = "Instance '%s' not '%s' after %s seconds" %(instance_title, state, str(time.time()-start_time))
return {'error_message' : error_message}
self._logger("Instance correctly '%s' after %s seconds." %(state, str(time.time()-start_time)))
return {'error_message' : None}
class SoftwareReleaseTester(SlapOSMasterCommunicator):
deadline = None
latest_state = None
def __init__(self,
name,
......@@ -288,21 +416,21 @@ class SoftwareReleaseTester(SlapOSMasterCommunicator):
None,
),
TESTER_STATE_SOFTWARE_INSTALLED: (
lambda t: t._request("started"),
lambda t: t._request(INSTANCE_STATE_STARTED),
int(instance_timeout),
TESTER_STATE_INSTANCE_STARTED,
None,
INSTANCE_STATE_STARTED,
),
TESTER_STATE_INSTANCE_STARTED: (
lambda t: t._request("destroyed"),
lambda t: t._request(INSTANCE_STATE_DESTROYED),
int(1200),
TESTER_STATE_INSTANCE_UNINSTALLED,
None,
INSTANCE_STATE_STOPPED,
),
TESTER_STATE_INSTANCE_UNINSTALLED: (
lambda t: t._supply("destroyed"),
lambda t: t._supply(INSTANCE_STATE_DESTROYED),
int(1200),
None,
None,
......@@ -310,6 +438,46 @@ class SoftwareReleaseTester(SlapOSMasterCommunicator):
),
}
def supply(self, software_path=None, computer_guid=None):
if software_path is not None:
self.url = software_path
if computer_guid is not None:
self.computer_guid = computer_guid
self._supply()
def requestStart(self, instance_title=None, request_kw=None):
self._request(INSTANCE_STATE_STARTED, instance_title, request_kw)
def requestStop(self, instance_title=None, request_kw=None):
self._request(INSTANCE_STATE_STOPPED, instance_title, request_kw)
def requestDestroy(self, instance_title=None, request_kw=None):
self._request(INSTANCE_STATE_DESTROYED, instance_title, request_kw)
def waitInstanceStarted(self, instance_title):
error_message = self._waitInstance(instance_title, INSTANCE_STATE_STARTED)["error_message"]
if error_message is not None:
self._logger(error_message)
self._logger("Do you use instance state propagation in your project?")
self._logger("Instance '%s' will be stopped and test aborted." %instance_title)
self.requestStop()
time.sleep(60)
raise ValueError(error_message)
def waitInstanceStopped(self, instance_title):
error_message = self._waitInstance(instance_title, INSTANCE_STATE_STOPPED)["error_message"]
if error_message is not None:
self._logger(error_message)
self._logger("Do you use instance state propagation in your project?")
raise ValueError(error_message)
def waitInstanceDestroyed(self, instance_title):
error_message = self._waitInstance(instance_title, INSTANCE_STATE_DESTROYED)["error_message"]
if error_message is not None:
self._logger(error_message)
self._logger("Do you use instance state propagation in your project?")
raise ValueError(error_message)
def __repr__(self):
deadline = self.deadline
if deadline is not None:
......@@ -352,22 +520,6 @@ class SoftwareReleaseTester(SlapOSMasterCommunicator):
return summary + message
def _getSoftwareState(self):
if self.computer_guid is None:
return SOFTWARE_STATE_INSTALLED
message = self.getSoftwareInstallationNews()
if message.startswith("#error no data found"):
return SOFTWARE_STATE_UNKNOWN
if message.startswith('#access software release'):
return SOFTWARE_STATE_INSTALLED
if message.startswith('#error'):
return SOFTWARE_STATE_INSTALLING
return SOFTWARE_STATE_UNKNOWN
@retryOnNetworkFailure
def getRSSEntryFromMonitoring(self, base_url):
if base_url is None:
......@@ -383,110 +535,16 @@ class SoftwareReleaseTester(SlapOSMasterCommunicator):
return {}
@retryOnNetworkFailure
def _getInstanceState(self):
latest_state = self.latest_state
self._logger('latest_state = %r', latest_state)
if latest_state is None:
return INSTANCE_STATE_UNKNOWN
message_list = []
try:
for instance in self.getInstanceUrlList():
news = self.getNewsFromInstance(instance["href"])
information = self.getInformationFromInstance(instance["href"])
state = INSTANCE_STATE_UNKNOWN
monitor_information_dict = {}
info_created_at = "-1"
is_slave = information['slave']
if is_slave:
if (information["connection_dict"]) > 0:
state = INSTANCE_STATE_STARTED
else:
# not slave
instance_state = news[0]
if instance_state.get('created_at', '-1') != "-1":
# the following does NOT take TZ into account
created_at = datetime.datetime.strptime(instance_state['created_at'],
'%a, %d %b %Y %H:%M:%S %Z')
gmt_now = datetime.datetime(*time.gmtime()[:6])
info_created_at = '%s (%d)' % (
instance_state['created_at'], (gmt_now - created_at).seconds)
if instance_state['text'].startswith('#access'):
state = INSTANCE_STATE_STARTED
if instance_state['text'].startswith('#access Instance correctly stopped'):
state = INSTANCE_STATE_STOPPED
if instance_state['text'].startswith('#error'):
state = INSTANCE_STATE_STARTED_WITH_ERROR
if state == INSTANCE_STATE_STARTED_WITH_ERROR:
# search for monitor url
monitor_v6_url = information["connection_dict"].get("monitor_v6_url")
try:
monitor_information_dict = self.getRSSEntryFromMonitoring(monitor_v6_url)
except Exception:
self._logger ('Unable to download promises for: %s' % (instance["title"]))
self._logger (traceback.format_exc())
monitor_information_dict = {"message": "Unable to download"}
message_list.append({
'title': instance["title"],
'slave': is_slave,
'news': news[0],
'information': information,
'monitor': monitor_information_dict,
'state': state
})
except slapos.slap.ServerError:
self._logger ('Got an error requesting partition for '
'its state')
return INSTANCE_STATE_UNKNOWN
except:
self._logger("ERROR getting instance state")
return INSTANCE_STATE_UNKNOWN
started = 0
stopped = 0
self.message_history.append(message_list)
for instance in message_list:
if not instance['slave'] and \
instance['state'] in (INSTANCE_STATE_UNKNOWN, INSTANCE_STATE_STARTED_WITH_ERROR):
return instance['state']
elif not instance['slave'] and instance['state'] == INSTANCE_STATE_STARTED:
started = 1
elif not instance['slave'] and instance['state'] == INSTANCE_STATE_STOPPED:
stopped = 1
if instance['slave'] and instance['state'] == INSTANCE_STATE_UNKNOWN:
return instance['state']
if started and stopped:
return INSTANCE_STATE_STOPPED
return INSTANCE_STATE_UNKNOWN
if started:
return INSTANCE_STATE_STARTED
if stopped:
return INSTANCE_STATE_STOPPED
@retryOnNetworkFailure
def teardown(self):
"""
Interrupt a running test sequence, putting it in idle state.
"""
self._logger ('Invoking TearDown for %s@%s' % (self.url, self.name))
self._logger('Invoking TearDown for %s@%s' % (self.url, self.name))
if self.request_kw is not None:
self._request('destroyed')
self._request(INSTANCE_STATE_DESTROYED)
if self.computer_guid is not None:
self._supply('destroyed')
self._supply(INSTANCE_STATE_DESTROYED)
self.state = TESTER_STATE_INSTANCE_UNINSTALLED
def tic(self, now):
......@@ -494,7 +552,7 @@ class SoftwareReleaseTester(SlapOSMasterCommunicator):
Check for missed deadlines (-> test failure), conditions for moving to
next state, and actually moving to next state (executing its payload).
"""
self._logger ('[DEBUG] TIC')
self._logger('[DEBUG] TIC')
deadline = self.deadline
if deadline < now and deadline is not None:
......@@ -508,7 +566,7 @@ class SoftwareReleaseTester(SlapOSMasterCommunicator):
instance_state is None or
instance_state == self._getInstanceState()):
self._logger ('[DEBUG] Going to state %s (%r)', next_state, instance_state)
self._logger('[DEBUG] Going to state %s (%r)', next_state, instance_state)
if next_state is None:
return None
......@@ -517,4 +575,3 @@ class SoftwareReleaseTester(SlapOSMasterCommunicator):
self.deadline = now + delay
stepfunc(self)
return self.deadline
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