# 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)