[autotest] Bug fix for route_rpc_to_master decorator

Detail is in the comment in code.

BUG=chromium:444600
TEST=rpc_interface_unittest, site_rpc_interface_unittest.

Change-Id: If64632504f2c7826a451004378b2d4bd995126f1
Reviewed-on: https://chromium-review.googlesource.com/297591
Commit-Ready: Mungyung Ryu <mkryu@google.com>
Tested-by: Mungyung Ryu <mkryu@google.com>
Reviewed-by: Mungyung Ryu <mkryu@google.com>
diff --git a/frontend/afe/rpc_utils.py b/frontend/afe/rpc_utils.py
index 1c14350..1b91f88 100644
--- a/frontend/afe/rpc_utils.py
+++ b/frontend/afe/rpc_utils.py
@@ -1285,13 +1285,54 @@
 def route_rpc_to_master(func):
     """Route RPC to master AFE.
 
-    @param func: The function to decorate
+    When a shard receives an RPC decorated by this, the RPC is just
+    forwarded to the master.
+    When the master gets the RPC, the RPC function is executed.
 
-    @returns: The function to replace func with.
+    @param func: An RPC function to decorate
+
+    @returns: A function replacing the RPC func.
     """
     @wraps(func)
     def replacement(*args, **kwargs):
-        kwargs = inspect.getcallargs(func, *args, **kwargs)
+        """
+        We need a special care when decorating an RPC that can be called
+        directly using positional arguments. One example is
+        rpc_interface.create_job().
+        rpc_interface.create_job_page_handler() calls the function using
+        positional and keyword arguments.
+        Since frontend.RpcClient.run() takes only keyword arguments for
+        an RPC, positional arguments of the RPC function need to be
+        transformed to key-value pair (dictionary type).
+
+        inspect.getcallargs() is a useful utility to achieve the goal,
+        however, we need an additional effort when an RPC function has
+        **kwargs argument.
+        Let's say we have a following form of RPC function.
+
+        def rpcfunc(a, b, **kwargs)
+
+        When we call the function like "rpcfunc(1, 2, id=3, name='mk')",
+        inspect.getcallargs() returns a dictionary like below.
+
+        {'a':1, 'b':2, 'kwargs': {'id':3, 'name':'mk'}}
+
+        This is an incorrect form of arguments to pass to the rpc function.
+        Instead, the dictionary should be like this.
+
+        {'a':1, 'b':2, 'id':3, 'name':'mk'}
+        """
+        argspec = inspect.getargspec(func)
+        if argspec.varargs is not None:
+            raise Exception('RPC function must not have *args.')
+        funcargs = inspect.getcallargs(func, *args, **kwargs)
+        kwargs = dict()
+        for k, v in funcargs.iteritems():
+            if argspec.keywords and k in argspec.keywords:
+                kwargs.update(v)
+            else:
+                kwargs[k] = v
+
         if server_utils.is_shard():
             afe = frontend_wrappers.RetryingAFE(
                     server=get_global_afe_hostname())