[autotest] Implement zygote configuration.

Override the install_ssp and install_control_file implementations for
the Zygote class, to enable configuration of zygotes.

Add tests.

BUG=chromium:720219

TEST=sudo python container_unittest.py
TEST=sudo python container_bucket_unittest.py
TEST=sudo python zygote_unittest.py
TEST=sudo python lxc_functional_test.py

Change-Id: I2a2b8cc42a5d46b983d3149c57c686ab8aec4a89
Reviewed-on: https://chromium-review.googlesource.com/572509
Commit-Ready: Ben Kwa <kenobi@chromium.org>
Tested-by: Ben Kwa <kenobi@chromium.org>
Reviewed-by: Ben Kwa <kenobi@chromium.org>
diff --git a/site_utils/lxc/container_unittest.py b/site_utils/lxc/container_unittest.py
index c18696b..bea733b 100644
--- a/site_utils/lxc/container_unittest.py
+++ b/site_utils/lxc/container_unittest.py
@@ -147,7 +147,7 @@
 
     def testInstallSsp(self):
         """Verifies that installing the ssp in the container works."""
-        # Hard-codedpath to some golden data for this test.
+        # Hard-coded path to some golden data for this test.
         test_ssp = os.path.join(
                 common.autotest_dir,
                 'site_utils', 'lxc', 'test', 'test_ssp.tar.bz2')
diff --git a/site_utils/lxc/zygote.py b/site_utils/lxc/zygote.py
index b939e54..21b5f2b 100644
--- a/site_utils/lxc/zygote.py
+++ b/site_utils/lxc/zygote.py
@@ -2,12 +2,16 @@
 # Use of this source code is governed by a BSD-style license that can be
 # found in the LICENSE file.
 
+import logging
 import os
+import tempfile
 
 import common
 from autotest_lib.client.bin import utils
+from autotest_lib.client.common_lib import error
 from autotest_lib.site_utils.lxc import Container
 from autotest_lib.site_utils.lxc import constants
+from autotest_lib.site_utils.lxc import lxc
 from autotest_lib.site_utils.lxc import utils as lxc_utils
 
 
@@ -72,6 +76,55 @@
             super(Zygote, self).set_hostname(hostname)
 
 
+    def install_ssp(self, ssp_url):
+        """Downloads and installs the given server package.
+
+        @param ssp_url: The URL of the ssp to download and install.
+        """
+        # The host dir is mounted directly on /usr/local/autotest within the
+        # container.  The SSP structure assumes it gets untarred into the
+        # /usr/local directory of the container's rootfs.  In order to unpack
+        # with the correct directory structure, create a tmpdir, mount the
+        # container's host dir as ./autotest, and unpack the SSP.
+        tmpdir = None
+        autotest_tmp = None
+        try:
+            tmpdir = tempfile.mkdtemp(dir=self.container_path,
+                                      prefix='%s.' % self.name,
+                                      suffix='.tmp')
+            autotest_tmp = os.path.join(tmpdir, 'autotest')
+            os.mkdir(autotest_tmp)
+            utils.run(
+                    'sudo mount --bind %s %s' % (self.host_path, autotest_tmp))
+            download_tmp = os.path.join(tmpdir,
+                                        'autotest_server_package.tar.bz2')
+            lxc.download_extract(ssp_url, download_tmp, tmpdir)
+        finally:
+            if autotest_tmp is not None:
+                try:
+                    utils.run('sudo umount %s' % autotest_tmp)
+                except error.CmdError:
+                    logging.exception('Failure while cleaning up SSP tmpdir.')
+            if tmpdir is not None:
+                utils.run('sudo rm -rf %s' % tmpdir)
+
+
+    def install_control_file(self, control_file):
+        """Installs the given control file.
+
+        The given file will be moved into the container.
+
+        @param control_file: Path to the control file to install.
+        """
+        # Compute the control temp path relative to the host mount.
+        dst_path = os.path.join(
+                self.host_path,
+                os.path.relpath(constants.CONTROL_TEMP_PATH,
+                                constants.CONTAINER_AUTOTEST_DIR))
+        utils.run('sudo mkdir -p %s' % dst_path)
+        utils.run('sudo mv %s %s' % (control_file, dst_path))
+
+
     def _cleanup_host_mount(self):
         """Unmount and remove the host dir for this container."""
         lxc_utils.cleanup_host_mount(self.host_path);
diff --git a/site_utils/lxc/zygote_unittest.py b/site_utils/lxc/zygote_unittest.py
index e84f325..5668556 100644
--- a/site_utils/lxc/zygote_unittest.py
+++ b/site_utils/lxc/zygote_unittest.py
@@ -15,6 +15,8 @@
 import common
 from autotest_lib.client.bin import utils
 from autotest_lib.site_utils import lxc
+from autotest_lib.site_utils.lxc import constants
+from autotest_lib.site_utils.lxc import unittest_http
 from autotest_lib.site_utils.lxc import unittest_logging
 from autotest_lib.site_utils.lxc import utils as lxc_utils
 
@@ -152,6 +154,49 @@
             self.assertEqual(test_string, test_output)
 
 
+    def testInstallSsp(self):
+        """Verifies that installing the ssp in the container works."""
+        # Hard-coded path to some golden data for this test.
+        test_ssp = os.path.join(
+                common.autotest_dir,
+                'site_utils', 'lxc', 'test', 'test_ssp.tar.bz2')
+        # Create a container, install the self-served ssp, then check that it is
+        # installed into the container correctly.
+        with self.createZygote() as zygote:
+            # Note: start the zygote first, then install the SSP.  This mimics
+            # the way things would work in the production environment.
+            zygote.start(wait_for_network=False)
+            with unittest_http.serve_locally(test_ssp) as url:
+                zygote.install_ssp(url)
+
+            # The test ssp just contains a couple of text files, in known
+            # locations.  Verify the location and content of those files in the
+            # container.
+            cat = lambda path: zygote.attach_run('cat %s' % path).stdout
+            test0 = cat(os.path.join(constants.CONTAINER_AUTOTEST_DIR,
+                                     'test.0'))
+            test1 = cat(os.path.join(constants.CONTAINER_AUTOTEST_DIR,
+                                     'dir0', 'test.1'))
+            self.assertEquals('the five boxing wizards jumped quickly',
+                              test0)
+            self.assertEquals('the quick brown fox jumps over the lazy dog',
+                              test1)
+
+
+    def testInstallControlFile(self):
+        """Verifies that installing a control file in the container works."""
+        _unused, tmpfile = tempfile.mkstemp()
+        with self.createZygote() as zygote:
+            # Note: start the zygote first.  This mimics the way things would
+            # work in the production environment.
+            zygote.start(wait_for_network=False)
+            zygote.install_control_file(tmpfile)
+            # Verify that the file is found in the zygote.
+            zygote.attach_run(
+                'test -f %s' % os.path.join(lxc.CONTROL_TEMP_PATH,
+                                            os.path.basename(tmpfile)))
+
+
     @contextmanager
     def createZygote(self,
                      name = None,
@@ -214,7 +259,7 @@
     # Hack: python unittest also processes args.  Construct an argv to pass to
     # it, that filters out the options it won't recognize.
     if args.verbose:
-        argv.append('-v')
+        argv.insert(0, '-v')
     argv.insert(0, sys.argv[0])
 
     return args, argv