Revert "devserver: Stop patching devserver internals for port=0"

This reverts commit 0ee73f408b6fa66b1f7defadb1073ff051567010.

In addition, this makes hacking --port=0 on cherrypy conditional on the
version of the cherrypy. The reason we need this is that our devservers
run on older linux and their cherrypy can't be upreved easily. But for
Chrome OS we have already upreved cherrypy and we don't need this
hacking.

There are also other pylint errors fixed.

BUG=chromium:1018237
TEST=devserver_integration_test

Change-Id: If65b75deb565ca78353803c8eab3f7cfc3851e5c
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/platform/dev-util/+/1914538
Tested-by: Amin Hassani <ahassani@chromium.org>
Reviewed-by: Allen Li <ayatane@chromium.org>
Commit-Queue: Amin Hassani <ahassani@chromium.org>
diff --git a/cherrypy_ext.py b/cherrypy_ext.py
index 0b56f88..2ebc189 100644
--- a/cherrypy_ext.py
+++ b/cherrypy_ext.py
@@ -1,4 +1,3 @@
-#!/usr/bin/env python2
 # -*- coding: utf-8 -*-
 # Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
 # Use of this source code is governed by a BSD-style license that can be
@@ -12,9 +11,12 @@
 premise is verified by the corresponding unit tests.
 """
 
-import cherrypy
+from __future__ import print_function
+
 import os
 
+import cherrypy  # pylint: disable=import-error
+
 
 class PortFile(cherrypy.process.plugins.SimplePlugin):
   """CherryPy plugin for maintaining a port file via a WSPBus.
@@ -71,7 +73,7 @@
     port = self.get_port_from_httpserver()
     if not port:
       return
-    with open(self.portfile, "wb") as f:
+    with open(self.portfile, 'wb') as f:
       f.write(str(port))
     self.written = True
     self.bus.log('Port %r written to %r.' % (port, self.portfile))
@@ -101,3 +103,64 @@
         raise
       except:
         self.bus.log('Failed to remove port file: %r.' % self.portfile)
+
+
+class ZeroPortPatcher(object):
+  """Patches a CherryPy module to support binding to any available port."""
+
+  # The cached value of the actual port bound by the HTTP server.
+  cached_port = 0
+
+  @classmethod
+  def _WrapWaitForPort(cls, cherrypy_module, func_name, use_cached):
+    """Ensures that a port is not zero before calling a wait-for-port function.
+
+    This wraps stock CherryPy module-level functions that wait for a port to be
+    free/occupied with a conditional that ensures the port argument isn't zero.
+    Prior to that, the wrapper attempts to pull the actual bound port number
+    from CherryPy's underlying HTTP server, if present. In this case, it'll
+    also cache the pulled out value, so it can be used in subsequent calls; one
+    such scenario is checking when a previously bound (actual) port has been
+    released after server shutdown.  This makes those functions do their
+    intended job when the server is configured to bind to an arbitrary
+    available port (server.socket_port is zero), a necessary feature.
+
+    Raises:
+      AttributeError: if func_name is not an attribute of cherrypy_module.
+    """
+    module = cherrypy_module.process.servers
+    func = getattr(module, func_name)  # Will fail if not present.
+
+    def wrapped_func(host, port):
+      if not port:
+        actual_port = PortFile.get_port_from_httpserver()
+        if use_cached:
+          port = cls.cached_port
+          using = 'cached'
+        else:
+          port = actual_port
+          using = 'actual'
+
+        if port:
+          cherrypy_module.engine.log('(%s) Waiting for %s port %s.' %
+                                     (func_name, using, port))
+        else:
+          cherrypy_module.engine.log('(%s) No %s port to wait for.' %
+                                     (func_name, using))
+
+        cls.cached_port = port
+
+      if port:
+        return func(host, port)
+
+    setattr(module, func_name, wrapped_func)
+
+  @classmethod
+  def DoPatch(cls, cherrypy_module):
+    """Patches a given CherryPy module.
+
+    Raises:
+      AttributeError: when fails to patch CherryPy.
+    """
+    cls._WrapWaitForPort(cherrypy_module, 'wait_for_free_port', True)
+    cls._WrapWaitForPort(cherrypy_module, 'wait_for_occupied_port', False)