| # Copyright 2017 The Chromium OS Authors. All rights reserved. |
| # Use of this source code is governed by a BSD-style license that can be |
| # found in the LICENSE file. |
| |
| import logging |
| |
| import common |
| from autotest_lib.client.bin import utils |
| from autotest_lib.client.common_lib import error |
| from autotest_lib.site_utils.lxc import constants |
| from autotest_lib.site_utils.lxc import container |
| |
| try: |
| from chromite.lib import metrics |
| except ImportError: |
| metrics = utils.metrics_mock |
| |
| |
| class ContainerFactory(object): |
| """A factory class for creating LXC container objects.""" |
| |
| def __init__(self, base_container, container_class=container.Container, |
| snapshot=True, force_cleanup=False, |
| lxc_path=constants.DEFAULT_CONTAINER_PATH): |
| """Initializes a ContainerFactory. |
| |
| @param base_container: The base container from which other containers |
| are cloned. |
| @param container_class: (optional) The Container class to instantiate. |
| By default, lxc.Container is instantiated. |
| @param snapshot: (optional) If True, creates LXC snapshot clones instead |
| of full clones. By default, snapshot clones are used. |
| @param force_cleanup: (optional) If True, if a container is created with |
| a name and LXC directory matching an existing |
| container, the existing container is destroyed, |
| and the new container created in its place. By |
| default, existing containers are not destroyed and |
| a ContainerError is raised. |
| @param lxc_path: (optional) The default LXC path that will be used for |
| new containers. If one is not provided, the |
| DEFAULT_CONTAINER_PATH from lxc.constants will be used. |
| Note that even if a path is provided here, it can still |
| be overridden when create_container is called. |
| """ |
| self._container_class = container_class |
| self._base_container = base_container |
| self._snapshot = snapshot |
| self._force_cleanup = force_cleanup |
| self._lxc_path = lxc_path |
| |
| |
| def create_container(self, cid=None, lxc_path=None): |
| """Creates a new container. |
| |
| @param cid: (optional) A ContainerId for the new container. If an ID is |
| provided, it determines both the name and the ID of the |
| container. If no ID is provided, a random name is generated |
| for the container, and it is not assigned an ID. |
| @param lxc_path: (optional) The LXC path for the new container. If one |
| is not provided, the factory's default lxc_path |
| (specified when the factory was constructed) is used. |
| """ |
| name = str(cid) if cid else None |
| if lxc_path is None: |
| lxc_path = self._lxc_path |
| |
| logging.debug('Creating new container (name: %s, lxc_path: %s)', |
| name, lxc_path) |
| |
| # If an ID is provided, use it as the container name. |
| new_container = self._create_from_base(name, lxc_path) |
| # If an ID is provided, assign it to the container. When the container |
| # is created just-in-time by the container bucket, this ensures that the |
| # resulting container is correctly registered with the autoserv system. |
| # If the container is being created by a container pool, the ID will be |
| # assigned later, when the continer is bound to an actual test process. |
| if cid: |
| new_container.id = cid |
| return new_container |
| |
| |
| # create_from_base_duration is the original name of the metric. Keep this |
| # so we have history. |
| @metrics.SecondsTimerDecorator( |
| '%s/create_from_base_duration' % constants.STATS_KEY) |
| def _create_from_base(self, name, lxc_path): |
| """Creates a container from the base container. |
| |
| @param name: Name of the container. |
| @param lxc_path: The LXC path of the new container. |
| |
| @return: A Container object for the created container. |
| |
| @raise ContainerError: If the container already exist. |
| @raise error.CmdError: If lxc-clone call failed for any reason. |
| """ |
| use_snapshot = constants.SUPPORT_SNAPSHOT_CLONE and self._snapshot |
| |
| try: |
| return self._container_class.clone(src=self._base_container, |
| new_name=name, |
| new_path=lxc_path, |
| snapshot=use_snapshot, |
| cleanup=self._force_cleanup) |
| except error.CmdError: |
| if not use_snapshot: |
| raise |
| else: |
| logging.debug( |
| 'Creating snapshot clone failed.' |
| ' Attempting without snapshot...' |
| ' This forces cleanup of old cloned container.' |
| ) |
| return self._container_class.clone(src=self._base_container, |
| new_name=name, |
| new_path=lxc_path, |
| snapshot=False, |
| cleanup=True) |