Factor out container cloning.

BUG=chromium:720219
TEST=sudo python site_utils/lxc_functional_test.py -v
     2017-05-24 11:27:03,185 All tests passed.

Change-Id: Iaab416c297e6e092ea03a2d230fe2e62d0f8399c
Reviewed-on: https://chromium-review.googlesource.com/514148
Commit-Ready: Ben Kwa <kenobi@chromium.org>
Tested-by: Ben Kwa <kenobi@chromium.org>
Reviewed-by: Dan Shi <dshi@google.com>
diff --git a/site_utils/lxc.py b/site_utils/lxc.py
index 829d9dd..3e182e3 100644
--- a/site_utils/lxc.py
+++ b/site_utils/lxc.py
@@ -789,10 +789,59 @@
         if self.exist(name) and not force_cleanup:
             raise error.ContainerError('Container %s already exists.' % name)
 
+        use_snapshot = SUPPORT_SNAPSHOT_CLONE and not disable_snapshot_clone
+
+        try:
+            return self.clone_container(path=self.container_path,
+                                        name=BASE,
+                                        new_path=self.container_path,
+                                        new_name=name,
+                                        snapshot=use_snapshot,
+                                        cleanup=force_cleanup)
+        except error.CmdError:
+            if not use_snapshot:
+                raise
+            else:
+                # Snapshot clone failed, retry clone without snapshot.
+                container = self.clone_container(path=self.container_path,
+                                                 name=BASE,
+                                                 new_path=self.container_path,
+                                                 new_name=name,
+                                                 snapshot=False,
+                                                 cleanup=force_cleanup)
+                # Report metadata about retry success.
+                autotest_es.post(use_http=True,
+                                 type_str=CONTAINER_CREATE_RETRY_METADB_TYPE,
+                                 metadata={'drone': socket.gethostname(),
+                                           'name': name,
+                                           'success': True})
+                return container
+
+
+    def clone_container(self, path, name, new_path, new_name, snapshot=False,
+                        cleanup=False):
+        """Clone one container from another.
+
+        @param path: LXC path for the source container.
+        @param name: Name of the source container.
+        @param new_path: LXC path for the cloned container.
+        @param new_name: Name for the cloned container.
+        @param snapshot: Whether to snapshot, or create a full clone.
+        @param cleanup: If a container with the given name and path already
+                exist, clean it up first.
+
+        @return: A Container object for the created container.
+
+        @raise ContainerError: If the container already exists.
+        @raise error.CmdError: If lxc-clone call failed for any reason.
+        """
         # Cleanup existing container with the given name.
-        container_folder = os.path.join(self.container_path, name)
-        if lxc_utils.path_exists(container_folder) and force_cleanup:
-            container = Container(self.container_path, {'name': name})
+        container_folder = os.path.join(new_path, new_name)
+        if lxc_utils.path_exists(container_folder):
+            if not cleanup:
+                raise error.ContainerError('Container %s already exists.' %
+                                           new_name)
+            container = Container(new_path, {'name': name})
             try:
                 container.destroy()
             except error.CmdError as e:
@@ -802,33 +851,16 @@
                              name, e)
                 utils.run('sudo rm -rf "%s"' % container_folder)
 
-        use_snapshot = SUPPORT_SNAPSHOT_CLONE and not disable_snapshot_clone
-        snapshot = '-s' if  use_snapshot else ''
+        snapshot_arg = '-s' if snapshot else ''
         # overlayfs is the default clone backend storage. However it is not
         # supported in Ganeti yet. Use aufs as the alternative.
-        aufs = '-B aufs' if utils.is_vm() and use_snapshot else ''
-        cmd = ('sudo lxc-clone -p %s -P %s %s' %
-               (self.container_path, self.container_path,
-                ' '.join([BASE, name, snapshot, aufs])))
-        try:
-            utils.run(cmd)
-            return self.get(name)
-        except error.CmdError:
-            if not use_snapshot:
-                raise
-            else:
-                # Snapshot clone failed, retry clone without snapshot. The retry
-                # won't hit the code here and cause an infinite loop as
-                # disable_snapshot_clone is set to True.
-                container = self.create_from_base(
-                        name, disable_snapshot_clone=True, force_cleanup=True)
-                # Report metadata about retry success.
-                autotest_es.post(use_http=True,
-                                 type_str=CONTAINER_CREATE_RETRY_METADB_TYPE,
-                                 metadata={'drone': socket.gethostname(),
-                                           'name': name,
-                                           'success': True})
-                return container
+        aufs_arg = '-B aufs' if utils.is_vm() and snapshot else ''
+        cmd = (('sudo lxc-clone --lxcpath %s --newpath %s '
+                '--orig %s --new %s %s %s') %
+               (path, new_path, name, new_name, snapshot_arg, aufs_arg))
+
+        utils.run(cmd)
+        return self.get(new_name)
 
 
     @cleanup_if_fail()