• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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            logging.debug('Creating snapshot clone failed. Attempting without '
107                           'snapshot...')
108            if not use_snapshot:
109                raise
110            else:
111                # Snapshot clone failed, retry clone without snapshot.
112                return self._container_class.clone(src=self._base_container,
113                                                   new_name=name,
114                                                   new_path=lxc_path,
115                                                   snapshot=False,
116                                                   cleanup=self._force_cleanup)
117