瀏覽代碼

Fix crash if system time is far into the future. Closes #213

Mike Naberezny 9 年之前
父節點
當前提交
d72eb478fc
共有 5 個文件被更改,包括 76 次插入10 次删除
  1. 12 0
      CHANGES.txt
  2. 15 5
      supervisor/rpcinterface.py
  3. 19 0
      supervisor/tests/test_rpcinterfaces.py
  4. 17 0
      supervisor/tests/test_xmlrpc.py
  5. 13 5
      supervisor/xmlrpc.py

+ 12 - 0
CHANGES.txt

@@ -1,3 +1,15 @@
+3.2.1 (Next 3.x Release)
+------------------------
+
+- Fixed a server exception ``OverflowError: int exceeds XML-RPC limits`` that
+  made ``supervisorctl status`` unusable if the system time was far into the
+  future.  The XML-RPC API returns timestamps as XML-RPC integers, but
+  timestamps will exceed the maximum value of an XML-RPC integer in January
+  2038 ("Year 2038 Problem").  For now, timestamps exceeding the maximum
+  integer will be capped at the maximum to avoid the exception and retain
+  compatibility with existing API clients.  In a future version of the API,
+  the return type for timestamps will be changed.
+
 3.2.0 (2015-11-30)
 ------------------
 

+ 15 - 5
supervisor/rpcinterface.py

@@ -19,8 +19,11 @@ from supervisor.events import notify
 from supervisor.events import RemoteCommunicationEvent
 
 from supervisor.http import NOT_DONE_YET
-from supervisor.xmlrpc import Faults
-from supervisor.xmlrpc import RPCError
+from supervisor.xmlrpc import (
+    capped_int,
+    Faults,
+    RPCError,
+    )
 
 from supervisor.states import SupervisorStates
 from supervisor.states import getSupervisorStateDescription
@@ -595,9 +598,12 @@ class SupervisorNamespaceRPCInterface:
         if process is None:
             raise RPCError(Faults.BAD_NAME, name)
 
-        start = int(process.laststart)
-        stop = int(process.laststop)
-        now = int(time.time())
+        # TODO timestamps are returned as xml-rpc integers for b/c but will
+        # saturate the xml-rpc integer type in jan 2038 ("year 2038 problem").
+        # future api versions should return timestamps as a different type.
+        start = capped_int(process.laststart)
+        stop = capped_int(process.laststop)
+        now = capped_int(self._now())
 
         state = process.get_state()
         spawnerr = process.spawnerr or ''
@@ -625,6 +631,10 @@ class SupervisorNamespaceRPCInterface:
         info['description'] = description
         return info
 
+    def _now(self): # pragma: no cover
+        # this is here to service stubbing in unit tests
+        return time.time()
+
     def getAllProcessInfo(self):
         """ Get info about all processes
 

+ 19 - 0
supervisor/tests/test_rpcinterfaces.py

@@ -1271,6 +1271,25 @@ class SupervisorNamespaceXMLRPCInterfaceTests(TestBase):
         self._assertRPCError(xmlrpc.Faults.BAD_NAME,
                              interface.getProcessInfo, 'foo:')
 
+    def test_getProcessInfo_caps_timestamps_exceeding_xmlrpc_maxint(self):
+        import xmlrpclib
+        options = DummyOptions()
+        config = DummyPConfig(options, 'foo', '/bin/foo',
+                              stdout_logfile=None)
+        process = DummyProcess(config)
+        process.laststart = float(xmlrpclib.MAXINT + 1)
+        process.laststop = float(xmlrpclib.MAXINT + 1)
+        pgroup_config = DummyPGroupConfig(options, name='foo')
+        pgroup = DummyProcessGroup(pgroup_config)
+        pgroup.processes = {'foo':process}
+        supervisord = DummySupervisor(process_groups={'foo':pgroup})
+        interface = self._makeOne(supervisord)
+        interface._now = lambda: float(xmlrpclib.MAXINT + 1)
+        data = interface.getProcessInfo('foo')
+        self.assertEqual(data['start'], xmlrpclib.MAXINT)
+        self.assertEqual(data['stop'], xmlrpclib.MAXINT)
+        self.assertEqual(data['now'], xmlrpclib.MAXINT)
+
     def test_getAllProcessInfo(self):
         from supervisor.process import ProcessStates
         options = DummyOptions()

+ 17 - 0
supervisor/tests/test_xmlrpc.py

@@ -727,6 +727,23 @@ class Test_gettags(unittest.TestCase):
             [(0, None, None, None, ''), (0, 'foo', 'array', 'name', 'text')]
             )
 
+class Test_capped_int(unittest.TestCase):
+    def _callFUT(self, comment):
+        from supervisor.xmlrpc import capped_int
+        return capped_int(comment)
+
+    def test_converts_value_to_integer(self):
+        self.assertEqual(self._callFUT('42'), 42)
+
+    def test_caps_value_below_minint(self):
+        import xmlrpclib
+        self.assertEqual(self._callFUT(xmlrpclib.MININT - 1), xmlrpclib.MININT)
+
+    def test_caps_value_above_maxint(self):
+        import xmlrpclib
+        self.assertEqual(self._callFUT(xmlrpclib.MAXINT + 1), xmlrpclib.MAXINT)
+
+
 class DummyResponse:
     def __init__(self, status=200, body='', reason='reason'):
         self.status = status

+ 13 - 5
supervisor/xmlrpc.py

@@ -328,6 +328,19 @@ class RootRPCInterface:
         for name, rpcinterface in subinterfaces:
             setattr(self, name, rpcinterface)
 
+def capped_int(value):
+    i = int(value)
+    if i < xmlrpclib.MININT:
+        i = xmlrpclib.MININT
+    elif i > xmlrpclib.MAXINT:
+        i = xmlrpclib.MAXINT
+    return i
+
+def make_datetime(text):
+    return datetime.datetime(
+        *time.strptime(text, "%Y%m%dT%H:%M:%S")[:6]
+    )
+
 class supervisor_xmlrpc_handler(xmlrpc_handler):
     path = '/RPC2'
     IDENT = 'Supervisor XML-RPC Handler'
@@ -561,11 +574,6 @@ if iterparse is not None:
     import datetime, time
     from base64 import decodestring
 
-    def make_datetime(text):
-        return datetime.datetime(
-            *time.strptime(text, "%Y%m%dT%H:%M:%S")[:6]
-        )
-
     unmarshallers = {
         "int": lambda x: int(x.text),
         "i4": lambda x: int(x.text),