1# Copyright 2017 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5import logging 6 7import common 8from autotest_lib.client.bin import utils 9from autotest_lib.client.common_lib import error 10from autotest_lib.site_utils.lxc import constants 11from autotest_lib.site_utils.lxc import container 12 13try: 14 from chromite.lib import metrics 15except ImportError: 16 metrics = utils.metrics_mock 17 18 19class ContainerFactory(object): 20 """A factory class for creating LXC container objects.""" 21 22 def __init__(self, base_container, container_class=container.Container, 23 snapshot=True, force_cleanup=False, 24 lxc_path=constants.DEFAULT_CONTAINER_PATH): 25 """Initializes a ContainerFactory. 26 27 @param base_container: The base container from which other containers 28 are cloned. 29 @param container_class: (optional) The Container class to instantiate. 30 By default, lxc.Container is instantiated. 31 @param snapshot: (optional) If True, creates LXC snapshot clones instead 32 of full clones. By default, snapshot clones are used. 33 @param force_cleanup: (optional) If True, if a container is created with 34 a name and LXC directory matching an existing 35 container, the existing container is destroyed, 36 and the new container created in its place. By 37 default, existing containers are not destroyed and 38 a ContainerError is raised. 39 @param lxc_path: (optional) The default LXC path that will be used for 40 new containers. If one is not provided, the 41 DEFAULT_CONTAINER_PATH from lxc.constants will be used. 42 Note that even if a path is provided here, it can still 43 be overridden when create_container is called. 44 """ 45 self._container_class = container_class 46 self._base_container = base_container 47 self._snapshot = snapshot 48 self._force_cleanup = force_cleanup 49 self._lxc_path = lxc_path 50 51 52 def create_container(self, cid=None, lxc_path=None): 53 """Creates a new container. 54 55 @param cid: (optional) A ContainerId for the new container. If an ID is 56 provided, it determines both the name and the ID of the 57 container. If no ID is provided, a random name is generated 58 for the container, and it is not assigned an ID. 59 @param lxc_path: (optional) The LXC path for the new container. If one 60 is not provided, the factory's default lxc_path 61 (specified when the factory was constructed) is used. 62 """ 63 name = str(cid) if cid else None 64 if lxc_path is None: 65 lxc_path = self._lxc_path 66 67 logging.debug('Creating new container (name: %s, lxc_path: %s)', 68 name, lxc_path) 69 70 # If an ID is provided, use it as the container name. 71 new_container = self._create_from_base(name, lxc_path) 72 # If an ID is provided, assign it to the container. When the container 73 # is created just-in-time by the container bucket, this ensures that the 74 # resulting container is correctly registered with the autoserv system. 75 # If the container is being created by a container pool, the ID will be 76 # assigned later, when the continer is bound to an actual test process. 77 if cid: 78 new_container.id = cid 79 return new_container 80 81 82 # create_from_base_duration is the original name of the metric. Keep this 83 # so we have history. 84 @metrics.SecondsTimerDecorator( 85 '%s/create_from_base_duration' % constants.STATS_KEY) 86 def _create_from_base(self, name, lxc_path): 87 """Creates a container from the base container. 88 89 @param name: Name of the container. 90 @param lxc_path: The LXC path of the new container. 91 92 @return: A Container object for the created container. 93 94 @raise ContainerError: If the container already exist. 95 @raise error.CmdError: If lxc-clone call failed for any reason. 96 """ 97 use_snapshot = constants.SUPPORT_SNAPSHOT_CLONE and self._snapshot 98 99 try: 100 return self._container_class.clone(src=self._base_container, 101 new_name=name, 102 new_path=lxc_path, 103 snapshot=use_snapshot, 104 cleanup=self._force_cleanup) 105 except error.CmdError: 106 if not use_snapshot: 107 raise 108 else: 109 logging.debug( 110 'Creating snapshot clone failed.' 111 ' Attempting without snapshot...' 112 ' This forces cleanup of old cloned container.' 113 ) 114 return self._container_class.clone(src=self._base_container, 115 new_name=name, 116 new_path=lxc_path, 117 snapshot=False, 118 cleanup=True) 119