[autotest] Add a utility module to interact with LXC binaries.

Add a module (lxc.py) to interact with LXC binaries.

lxc_functional_test.py is a test script to test the module. It will
1. Set up base container in a tmp directory.
2. Create a test container from the base container.
3. Test site-packages and results directory can be mounted and shared between
   host and test container.
4. Run autoserv in test mode.

Also add a utility function in client/common_lib/site_utils.py to get the value
of the given argument for the function. It's used in decorator to check function
parameters.

BUG=chromium:453621
CQ-DEPEND=CL:251140

TEST=unittest,
sudo python site_utils/lxc_functional_test.py
sudo python site_utils/lxc.py -s -p /tmp/container_123
sudo python site_utils/lxc.py -s -p /tmp/container_123 -f
sudo python site_utils/lxc.py -p /tmp/container_123 -f

Change-Id: Id7b259c4bc03977974af44d6d88420b220706589
Reviewed-on: https://chromium-review.googlesource.com/247271
Reviewed-by: Dan Shi <dshi@chromium.org>
Commit-Queue: Dan Shi <dshi@chromium.org>
Trybot-Ready: Dan Shi <dshi@chromium.org>
Tested-by: Dan Shi <dshi@chromium.org>
diff --git a/client/common_lib/site_utils.py b/client/common_lib/site_utils.py
index b3a0b3a..b5e739c 100644
--- a/client/common_lib/site_utils.py
+++ b/client/common_lib/site_utils.py
@@ -2,6 +2,7 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import inspect
 import logging
 import os
 import re
@@ -326,13 +327,13 @@
     return ver, milestone
 
 
-
 def is_localhost(server):
     """Check if server is equivalent to localhost.
 
     @param server: Name of the server to check.
 
     @return: True if given server is equivalent to localhost.
+
     @raise socket.gaierror: If server name failed to be resolved.
     """
     if server in _LOCAL_HOST_LIST:
@@ -343,3 +344,35 @@
     except socket.gaierror:
         logging.error('Failed to resolve server name %s.', server)
         return False
+
+
+def get_function_arg_value(func, arg_name, args, kwargs):
+    """Get the value of the given argument for the function.
+
+    @param func: Function being called with given arguments.
+    @param arg_name: Name of the argument to look for value.
+    @param args: arguments for function to be called.
+    @param kwargs: keyword arguments for function to be called.
+
+    @return: The value of the given argument for the function.
+
+    @raise ValueError: If the argument is not listed function arguemnts.
+    @raise KeyError: If no value is found for the given argument.
+    """
+    if arg_name in kwargs:
+        return kwargs[arg_name]
+
+    argspec = inspect.getargspec(func)
+    index = argspec.args.index(arg_name)
+    try:
+        return args[index]
+    except IndexError:
+        try:
+            # The argument can use a default value. Reverse the default value
+            # so argument with default value can be counted from the last to
+            # the first.
+            return argspec.defaults[::-1][len(argspec.args) - index - 1]
+        except IndexError:
+            raise KeyError('Argument %s is not given a value. argspec: %s, '
+                           'args:%s, kwargs:%s' %
+                           (arg_name, argspec, args, kwargs))