Skip to content

Commit

Permalink
Fix system.multicall() broken by faster start/stop patch
Browse files Browse the repository at this point in the history
In past versions, startProcess() and stopProcess() would always return
a callback.  50d1857 changed this
so they may return either a callback or a bool, but the code that handles
system.multicall() was not updated.  If multicall was used with stopProcess()
followed by startProcess(), it would try to start the process before it had
finished stopping.  This broke the restart process link on the web interface.
  • Loading branch information
mnaberez committed Dec 1, 2015
1 parent 4d34d90 commit d948fc5
Show file tree
Hide file tree
Showing 4 changed files with 216 additions and 135 deletions.
36 changes: 13 additions & 23 deletions supervisor/tests/test_rpcinterfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -2163,44 +2163,34 @@ def test_allMethodDocs(self):

def test_multicall_simplevals(self):
interface = self._makeOne()
callback = interface.multicall([
results = interface.multicall([
{'methodName':'system.methodHelp', 'params':['system.methodHelp']},
{'methodName':'system.listMethods', 'params':[]},
])
from supervisor import http
result = http.NOT_DONE_YET
while result is http.NOT_DONE_YET:
result = callback()
self.assertEqual(result[0], interface.methodHelp('system.methodHelp'))
self.assertEqual(result[1], interface.listMethods())
self.assertEqual(results[0], interface.methodHelp('system.methodHelp'))
self.assertEqual(results[1], interface.listMethods())

def test_multicall_recursion_guard(self):
from supervisor import xmlrpc
interface = self._makeOne()
callback = interface.multicall([
results = interface.multicall([
{'methodName': 'system.multicall', 'params': []},
])

from supervisor import http
result = http.NOT_DONE_YET
while result is http.NOT_DONE_YET:
result = callback()

code = xmlrpc.Faults.INCORRECT_PARAMETERS
desc = xmlrpc.getFaultDescription(code)
recursion_fault = {'faultCode': code, 'faultString': desc}

self.assertEqual(result, [recursion_fault])
e = xmlrpc.RPCError(xmlrpc.Faults.INCORRECT_PARAMETERS,
'Recursive system.multicall forbidden')
recursion_fault = {'faultCode': e.code, 'faultString': e.text}
self.assertEqual(results, [recursion_fault])

def test_multicall_nested_callback(self):
from supervisor import http
interface = self._makeOne()
callback = interface.multicall([
{'methodName':'supervisor.stopAllProcesses'}])
from supervisor import http
result = http.NOT_DONE_YET
while result is http.NOT_DONE_YET:
result = callback()
self.assertEqual(result[0], [])
results = http.NOT_DONE_YET
while results is http.NOT_DONE_YET:
results = callback()
self.assertEqual(results[0], [])

def test_methodHelp(self):
from supervisor import xmlrpc
Expand Down
176 changes: 121 additions & 55 deletions supervisor/tests/test_xmlrpc.py
Original file line number Diff line number Diff line change
Expand Up @@ -569,86 +569,152 @@ def foo(self):
inst = self._makeOne([('ns1', ns1)])
self.assertRaises(RPCError, inst.methodSignature, 'ns1.foo')

def test_multicall_recursion_forbidden(self):
def test_multicall_faults_for_recursion(self):
from supervisor.xmlrpc import Faults
inst = self._makeOne()
call = {'methodName':'system.multicall'}
multiproduce = inst.multicall([call])
result = multiproduce()
calls = [{'methodName':'system.multicall'}]
results = inst.multicall(calls)
self.assertEqual(
result,
[{'faultCode': 2, 'faultString': 'INCORRECT_PARAMETERS'}]
results,
[{'faultCode': Faults.INCORRECT_PARAMETERS,
'faultString': ('INCORRECT_PARAMETERS: Recursive '
'system.multicall forbidden')}]
)

def test_multicall_other_exception(self):
def test_multicall_faults_for_missing_methodName(self):
from supervisor.xmlrpc import Faults
inst = self._makeOne()
call = {} # no methodName
multiproduce = inst.multicall([call])
result = multiproduce()
self.assertEqual(len(result), 1)
self.assertEqual(result[0]['faultCode'], 1)
calls = [{}]
results = inst.multicall(calls)
self.assertEqual(
results,
[{'faultCode': Faults.INCORRECT_PARAMETERS,
'faultString': 'INCORRECT_PARAMETERS: No methodName'}]
)

def test_multicall_no_calls(self):
def test_multicall_faults_for_methodName_bad_namespace(self):
from supervisor.xmlrpc import Faults
inst = self._makeOne()
multiproduce = inst.multicall([])
result = multiproduce()
self.assertEqual(result, [])
calls = [{'methodName': 'bad.stopProcess'}]
results = inst.multicall(calls)
self.assertEqual(
results,
[{'faultCode': Faults.UNKNOWN_METHOD,
'faultString': 'UNKNOWN_METHOD'}]
)

def test_multicall_callback_raises_RPCError(self):
from supervisor.xmlrpc import RPCError, Faults
def test_multicall_faults_for_methodName_good_ns_bad_method(self):
from supervisor.xmlrpc import Faults
class DummyNamespace(object):
def foo(self):
""" @param string name The thing"""
raise RPCError(Faults.UNKNOWN_METHOD)

pass
ns1 = DummyNamespace()
inst = self._makeOne([('ns1', ns1)])
multiproduce = inst.multicall([{'methodName':'ns1.foo'}])
result = multiproduce()
calls = [{'methodName': 'ns1.bad'}]
results = inst.multicall(calls)
self.assertEqual(
result, [{'faultString': 'UNKNOWN_METHOD', 'faultCode': 1}]
results,
[{'faultCode': Faults.UNKNOWN_METHOD,
'faultString': 'UNKNOWN_METHOD'}]
)

def test_multicall_callback_returns_function_returns_NOT_DONE_YET(self):
def test_multicall_returns_empty_results_for_empty_calls(self):
inst = self._makeOne()
calls = []
results = inst.multicall(calls)
self.assertEqual(results, [])

def test_multicall_performs_noncallback_functions_serially(self):
class DummyNamespace(object):
def say(self, name):
""" @param string name Process name"""
return name
ns1 = DummyNamespace()
inst = self._makeOne([('ns1', ns1)])
calls = [
{'methodName': 'ns1.say', 'params': ['Alvin']},
{'methodName': 'ns1.say', 'params': ['Simon']},
{'methodName': 'ns1.say', 'params': ['Theodore']}
]
results = inst.multicall(calls)
self.assertEqual(results, ['Alvin', 'Simon', 'Theodore'])

def test_multicall_catches_noncallback_exceptions(self):
import errno
from supervisor.xmlrpc import RPCError, Faults
class DummyNamespace(object):
def bad_name(self):
raise RPCError(Faults.BAD_NAME, 'foo')
def os_error(self):
raise OSError(errno.ENOENT)
ns1 = DummyNamespace()
inst = self._makeOne([('ns1', ns1)])
calls = [{'methodName': 'ns1.bad_name'}, {'methodName': 'ns1.os_error'}]
results = inst.multicall(calls)

bad_name = {'faultCode': Faults.BAD_NAME,
'faultString': 'BAD_NAME: foo'}
os_error = {'faultCode': Faults.FAILED,
'faultString': "FAILED: %s:2" % OSError}
self.assertEqual(results, [bad_name, os_error])

def test_multicall_catches_callback_exceptions(self):
import errno
from supervisor.xmlrpc import RPCError, Faults
from supervisor.http import NOT_DONE_YET
class DummyNamespace(object):
def __init__(self):
self.results = [NOT_DONE_YET, 1]
def foo(self):
""" @param string name The thing"""
def bad_name(self):
def inner():
raise RPCError(Faults.BAD_NAME, 'foo')
return inner
def os_error(self):
def inner():
return self.results.pop(0)
raise OSError(errno.ENOENT)
return inner
ns1 = DummyNamespace()
inst = self._makeOne([('ns1', ns1)])
multiproduce = inst.multicall([{'methodName':'ns1.foo'}])
result = multiproduce()
self.assertEqual(
result,
NOT_DONE_YET
)
result = multiproduce()
self.assertEqual(
result,
[1]
)

def test_multicall_callback_returns_function_raises_RPCError(self):
from supervisor.xmlrpc import Faults, RPCError
calls = [{'methodName': 'ns1.bad_name'}, {'methodName': 'ns1.os_error'}]
callback = inst.multicall(calls)
results = NOT_DONE_YET
while results is NOT_DONE_YET:
results = callback()

bad_name = {'faultCode': Faults.BAD_NAME,
'faultString': 'BAD_NAME: foo'}
os_error = {'faultCode': Faults.FAILED,
'faultString': "FAILED: %s:2" % OSError}
self.assertEqual(results, [bad_name, os_error])

def test_multicall_performs_callback_functions_serially(self):
from supervisor.http import NOT_DONE_YET
class DummyNamespace(object):
def foo(self):
""" @param string name The thing"""
def __init__(self):
self.stop_results = [NOT_DONE_YET, NOT_DONE_YET,
NOT_DONE_YET, 'stop result']
self.start_results = ['start result']
def stopProcess(self, name):
def inner():
result = self.stop_results.pop(0)
if result is not NOT_DONE_YET:
self.stopped = True
return result
return inner
def startProcess(self, name):
def inner():
raise RPCError(Faults.UNKNOWN_METHOD)
if not self.stopped:
raise Exception("This should not raise")
return self.start_results.pop(0)
return inner
ns1 = DummyNamespace()
inst = self._makeOne([('ns1', ns1)])
multiproduce = inst.multicall([{'methodName':'ns1.foo'}])
result = multiproduce()
self.assertEqual(
result,
[{'faultString': 'UNKNOWN_METHOD', 'faultCode': 1}],
)

calls = [{'methodName': 'ns1.stopProcess',
'params': {'name': 'foo'}},
{'methodName': 'ns1.startProcess',
'params': {'name': 'foo'}}]
callback = inst.multicall(calls)
results = NOT_DONE_YET
while results is NOT_DONE_YET:
results = callback()
self.assertEqual(results, ['stop result', 'start result'])

class Test_gettags(unittest.TestCase):
def _callFUT(self, comment):
Expand Down
23 changes: 15 additions & 8 deletions supervisor/web.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,20 +414,27 @@ def stopdone():
return stopdone

elif action == 'restart':
callback = rpcinterface.system.multicall(
results_or_callback = rpcinterface.system.multicall(
[ {'methodName':'supervisor.stopProcess',
'params': [namespec]},
{'methodName':'supervisor.startProcess',
'params': [namespec]},
]
)
def restartprocess():
result = callback()
if result is NOT_DONE_YET:
return NOT_DONE_YET
return 'Process %s restarted' % namespec
restartprocess.delay = 0.05
return restartprocess
if callable(results_or_callback):
callback = results_or_callback
def restartprocess():
results = callback()
if results is NOT_DONE_YET:
return NOT_DONE_YET
return 'Process %s restarted' % namespec
restartprocess.delay = 0.05
return restartprocess
else:
def restartdone():
return 'Process %s restarted' % namespec
restartdone.delay = 0.05
return restartdone

elif action == 'clearlog':
try:
Expand Down
Loading

0 comments on commit d948fc5

Please sign in to comment.