base_job.py revision fa2e889be2e704a8b3c723900f26cfe33a9cbb7d
1fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanskiimport os, copy, logging, errno, cPickle as pickle, fcntl
2da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
3da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanskifrom autotest_lib.client.common_lib import autotemp, error
4da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
5da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
6da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanskiclass job_directory(object):
7da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski    """Represents a job.*dir directory."""
8da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
9da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
10fc3da5bbdcd63840cd224282812c050c5520db11mbligh    class JobDirectoryException(error.AutotestError):
11fc3da5bbdcd63840cd224282812c050c5520db11mbligh        """Generic job_directory exception superclass."""
12fc3da5bbdcd63840cd224282812c050c5520db11mbligh
13fc3da5bbdcd63840cd224282812c050c5520db11mbligh
14fc3da5bbdcd63840cd224282812c050c5520db11mbligh    class MissingDirectoryException(JobDirectoryException):
15da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        """Raised when a directory required by the job does not exist."""
16da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        def __init__(self, path):
17da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            Exception.__init__(self, 'Directory %s does not exist' % path)
18da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
19da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
20fc3da5bbdcd63840cd224282812c050c5520db11mbligh    class UncreatableDirectoryException(JobDirectoryException):
21da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        """Raised when a directory required by the job is missing and cannot
22da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        be created."""
23da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        def __init__(self, path, error):
24da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            msg = 'Creation of directory %s failed with exception %s'
25da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            msg %= (path, error)
26da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            Exception.__init__(self, msg)
27da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
28da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
29fc3da5bbdcd63840cd224282812c050c5520db11mbligh    class UnwritableDirectoryException(JobDirectoryException):
30da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        """Raised when a writable directory required by the job exists
31da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        but is not writable."""
32da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        def __init__(self, path):
33da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            msg = 'Directory %s exists but is not writable' % path
34da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            Exception.__init__(self, msg)
35da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
36da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
37da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski    def __init__(self, path, is_writable=False):
38da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        """
39da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        Instantiate a job directory.
40da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
41da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        @param path The path of the directory. If None a temporary directory
42da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            will be created instead.
43da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        @param is_writable If True, expect the directory to be writable.
44da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
45da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        @raises MissingDirectoryException raised if is_writable=False and the
46da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            directory does not exist.
47da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        @raises UnwritableDirectoryException raised if is_writable=True and
48da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            the directory exists but is not writable.
49da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        @raises UncreatableDirectoryException raised if is_writable=True, the
50da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            directory does not exist and it cannot be created.
51da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        """
52da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        if path is None:
53da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            if is_writable:
54da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski                self._tempdir = autotemp.tempdir(unique_id='autotest')
55da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski                self.path = self._tempdir.name
56da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            else:
57da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski                raise self.MissingDirectoryException(path)
58da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        else:
59da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            self._tempdir = None
60da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            self.path = path
61da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        self._ensure_valid(is_writable)
62da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
63da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
64da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski    def _ensure_valid(self, is_writable):
65da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        """
66da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        Ensure that this is a valid directory.
67da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
68da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        Will check if a directory exists, can optionally also enforce that
69da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        it be writable. It can optionally create it if necessary. Creation
70da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        will still fail if the path is rooted in a non-writable directory, or
71da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        if a file already exists at the given location.
72da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
73da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        @param dir_path A path where a directory should be located
74da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        @param is_writable A boolean indicating that the directory should
75da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            not only exist, but also be writable.
76da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
77da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        @raises MissingDirectoryException raised if is_writable=False and the
78da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            directory does not exist.
79da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        @raises UnwritableDirectoryException raised if is_writable=True and
80da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            the directory is not wrtiable.
81da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        @raises UncreatableDirectoryException raised if is_writable=True, the
82da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            directory does not exist and it cannot be created
83da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        """
848054b0da315c94d249aaf80512bc133d4cb55b92mbligh        # ensure the directory exists
858054b0da315c94d249aaf80512bc133d4cb55b92mbligh        if is_writable:
868054b0da315c94d249aaf80512bc133d4cb55b92mbligh            try:
878054b0da315c94d249aaf80512bc133d4cb55b92mbligh                os.makedirs(self.path)
888054b0da315c94d249aaf80512bc133d4cb55b92mbligh            except OSError, e:
89fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh                if e.errno != errno.EEXIST or not os.path.isdir(self.path):
90da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski                    raise self.UncreatableDirectoryException(self.path, e)
918054b0da315c94d249aaf80512bc133d4cb55b92mbligh        elif not os.path.isdir(self.path):
928054b0da315c94d249aaf80512bc133d4cb55b92mbligh            raise self.MissingDirectoryException(self.path)
93da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
94da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        # if is_writable=True, also check that the directory is writable
95da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        if is_writable and not os.access(self.path, os.W_OK):
96da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            raise self.UnwritableDirectoryException(self.path)
97da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
98da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
99da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski    @staticmethod
100da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski    def property_factory(attribute):
101da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        """
102da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        Create a job.*dir -> job._*dir.path property accessor.
103da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
104da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        @param attribute A string with the name of the attribute this is
105da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            exposed as. '_'+attribute must then be attribute that holds
106da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            either None or a job_directory-like object.
107da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
108da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        @returns A read-only property object that exposes a job_directory path
109da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        """
110da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        @property
111da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        def dir_property(self):
112da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            underlying_attribute = getattr(self, '_' + attribute)
113da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            if underlying_attribute is None:
114da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski                return None
115da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            else:
116da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski                return underlying_attribute.path
117da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        return dir_property
118da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
119da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
120fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mblighclass job_state(object):
121fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh    """A class for managing explicit job and user state, optionally persistent.
122fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
123fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh    The class allows you to save state by name (like a dictionary). Any state
124fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh    stored in this class should be picklable and deep copyable. While this is
125fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh    not enforced it is recommended that only valid python identifiers be used
126fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh    as names. Additionally, the namespace 'stateful_property' is used for
127fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh    storing the valued associated with properties constructed using the
128fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh    property_factory method.
129fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh    """
130fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
131fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh    NO_DEFAULT = object()
132fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh    PICKLE_PROTOCOL = 2  # highest protocol available in python 2.4
133fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
134fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
135fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh    def __init__(self):
136fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """Initialize the job state."""
137fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        self._state = {}
138fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        self._backing_file = None
139fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        self._backing_file_initialized = False
140fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        self._backing_file_lock = None
141fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
142fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
143fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski    def _with_backing_file(method):
144fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        """A decorator to perform a lock-read-*-write-unlock cycle.
145fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
146fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        When applied to a method, this decorator will automatically wrap
147fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        calls to the method in a lock-and-read before the call followed by a
148fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        write-and-unlock. Any operation that is reading or writing state
149fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        should be decorated with this method to ensure that backing file
150fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        state is consistently maintained.
151fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """
152fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        def wrapped_method(self, *args, **dargs):
153fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            already_have_lock = self._backing_file_lock is not None
154fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            if not already_have_lock:
155fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                self._lock_backing_file()
156fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            try:
157fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                self._read_from_backing_file()
158fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                try:
159fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                    return method(self, *args, **dargs)
160fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                finally:
161fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                    self._write_to_backing_file()
162fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            finally:
163fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                if not already_have_lock:
164fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                    self._unlock_backing_file()
165fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        wrapped_method.__name__ = method.__name__
166fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        wrapped_method.__doc__ = method.__doc__
167fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        return wrapped_method
168fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski
169fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski
170fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski    def _lock_backing_file(self):
171fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        """Acquire a lock on the backing file."""
172fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        if self._backing_file:
173fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            self._backing_file_lock = open(self._backing_file, 'a')
174fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            fcntl.lockf(self._backing_file_lock, fcntl.LOCK_EX)
175fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
176fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
177fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski    def _unlock_backing_file(self):
178fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        """Release a lock on the backing file."""
179fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        if self._backing_file_lock:
180fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            fcntl.lockf(self._backing_file_lock, fcntl.LOCK_UN)
181fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            self._backing_file_lock.close()
182fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            self._backing_file_lock = None
183fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski
184fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski
185fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski    def read_from_file(self, file_path, merge=True):
186fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """Read in any state from the file at file_path.
187fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
188fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        When merge=True, any state specified only in-memory will be preserved.
189fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        Any state specified on-disk will be set in-memory, even if an in-memory
190fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        setting already exists.
191fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski
192fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        @param file_path The path where the state should be read from. It must
193fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            exist but it can be empty.
194fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        @param merge If true, merge the on-disk state with the in-memory
195fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            state. If false, replace the in-memory state with the on-disk
196fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            state.
197fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski
198fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        @warning This method is intentionally concurrency-unsafe. It makes no
199fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            attempt to control concurrent access to the file at file_path.
200fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """
201fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski
202fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        # we can assume that the file exists
203fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        if os.path.getsize(file_path) == 0:
204fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            on_disk_state = {}
205fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        else:
206fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            on_disk_state = pickle.load(open(file_path))
207fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski
208fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        if merge:
209fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            # merge the on-disk state with the in-memory state
210fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            for namespace, namespace_dict in on_disk_state.iteritems():
211fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                in_memory_namespace = self._state.setdefault(namespace, {})
212fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                for name, value in namespace_dict.iteritems():
213fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                    if name in in_memory_namespace:
214fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                        if in_memory_namespace[name] != value:
215fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                            logging.info('Persistent value of %s.%s from %s '
216fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                                         'overridding existing in-memory '
217fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                                         'value', namespace, name, file_path)
218fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                            in_memory_namespace[name] = value
219fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                        else:
220fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                            logging.debug('Value of %s.%s is unchanged, '
221fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                                          'skipping import', namespace, name)
222fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh                    else:
223fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                        logging.debug('Importing %s.%s from state file %s',
224fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                                      namespace, name, file_path)
225fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                        in_memory_namespace[name] = value
226fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        else:
227fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            # just replace the in-memory state with the on-disk state
228fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            self._state = on_disk_state
229fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            logging.debug('Replacing in-memory state with on-disk state '
230fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski                          'from %s', file_path)
231fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski
232fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        self._write_to_backing_file()
233fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
234fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
235fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh    def write_to_file(self, file_path):
236fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """Write out the current state to the given path.
237fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
238fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        @param file_path The path where the state should be written out to.
239fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            Must be writable.
240fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski
241fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        @warning This method is intentionally concurrency-unsafe. It makes no
242fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            attempt to control concurrent access to the file at file_path, or
243fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            to the backing file if one exists.
244fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """
245fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        outfile = open(file_path, 'w')
246fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        try:
247fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            pickle.dump(self._state, outfile, self.PICKLE_PROTOCOL)
248fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        finally:
249fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            outfile.close()
250fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        logging.debug('Persistent state flushed to %s', file_path)
251fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
252fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
253fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski    def _read_from_backing_file(self):
254fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        """Refresh the current state from the backing file.
255fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski
256fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        If the backing file has never been read before (indicated by checking
257fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        self._backing_file_initialized) it will merge the file with the
258fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        in-memory state, rather than overwriting it.
259fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        """
260fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        if self._backing_file:
261fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            merge_backing_file = not self._backing_file_initialized
262fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            self.read_from_file(self._backing_file, merge=merge_backing_file)
263fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            self._backing_file_initialized = True
264fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski
265fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski
266fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh    def _write_to_backing_file(self):
267fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """Flush the current state to the backing file."""
268fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        if self._backing_file:
269fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            self.write_to_file(self._backing_file)
270fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
271fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
272fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski    @_with_backing_file
273fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski    def _synchronize_backing_file(self):
274fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        """Synchronizes the contents of the in-memory and on-disk state."""
275fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        # state is implicitly synchronized in _with_backing_file methods
276fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        pass
277fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski
278fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski
279fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh    def set_backing_file(self, file_path):
280fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """Change the path used as the backing file for the persistent state.
281fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
282fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        When a new backing file is specified if a file already exists then
283fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        its contents will be added into the current state, with conflicts
284fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        between the file and memory being resolved in favor of the file
285fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        contents. The file will then be kept in sync with the (combined)
286fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        in-memory state. The syncing can be disabled by setting this to None.
287fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
288fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        @param file_path A path on the filesystem that can be read from and
289fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            written to, or None to turn off the backing store.
290fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """
291fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        self._synchronize_backing_file()
292fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        self._backing_file = file_path
293fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        self._backing_file_initialized = False
294fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        self._synchronize_backing_file()
295fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski
296fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski
297fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski    @_with_backing_file
298fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski    def get(self, namespace, name, default=NO_DEFAULT):
299fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        """Returns the value associated with a particular name.
300fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski
301fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        @param namespace The namespace that the property should be stored in.
302fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        @param name The name the value was saved with.
303fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        @param default A default value to return if no state is currently
304fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            associated with var.
305fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski
306fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        @returns A deep copy of the value associated with name. Note that this
307fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            explicitly returns a deep copy to avoid problems with mutable
308fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            values; mutations are not persisted or shared.
309fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        @raises KeyError raised when no state is associated with var and a
310fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            default value is not provided.
311fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        """
312fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        if self.has(namespace, name):
313fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            return copy.deepcopy(self._state[namespace][name])
314fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        elif default is self.NO_DEFAULT:
315fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            raise KeyError('No key %s in namespace %s' % (name, namespace))
316fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski        else:
317fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski            return default
318fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
319fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
320fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski    @_with_backing_file
321fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh    def set(self, namespace, name, value):
322fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """Saves the value given with the provided name.
323fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
324fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        @param namespace The namespace that the property should be stored in.
325fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        @param name The name the value should be saved with.
326fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        @param value The value to save.
327fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """
328fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        namespace_dict = self._state.setdefault(namespace, {})
329fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        namespace_dict[name] = copy.deepcopy(value)
330fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        logging.debug('Persistent state %s.%s now set to %r', namespace,
331fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh                      name, value)
332fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
333fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
334fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski    @_with_backing_file
335fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh    def has(self, namespace, name):
336fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """Return a boolean indicating if namespace.name is defined.
337fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
338fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        @param namespace The namespace to check for a definition.
339fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        @param name The name to check for a definition.
340fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
341fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        @returns True if the given name is defined in the given namespace and
342fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            False otherwise.
343fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """
344fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        return namespace in self._state and name in self._state[namespace]
345fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
346fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
347fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski    @_with_backing_file
348fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh    def discard(self, namespace, name):
349fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """If namespace.name is a defined value, deletes it.
350fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
351fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        @param namespace The namespace that the property is stored in.
352fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        @param name The name the value is saved with.
353fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """
354fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        if self.has(namespace, name):
355fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            del self._state[namespace][name]
356fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            if len(self._state[namespace]) == 0:
357fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh                del self._state[namespace]
358fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            logging.debug('Persistent state %s.%s deleted', namespace, name)
359fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        else:
360fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            logging.debug(
361fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh                'Persistent state %s.%s not defined so nothing is discarded',
362fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh                namespace, name)
363fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
364fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
365fa2e889be2e704a8b3c723900f26cfe33a9cbb7djadmanski    @_with_backing_file
366fc3da5bbdcd63840cd224282812c050c5520db11mbligh    def discard_namespace(self, namespace):
367fc3da5bbdcd63840cd224282812c050c5520db11mbligh        """Delete all defined namespace.* names.
368fc3da5bbdcd63840cd224282812c050c5520db11mbligh
369fc3da5bbdcd63840cd224282812c050c5520db11mbligh        @param namespace The namespace to be cleared.
370fc3da5bbdcd63840cd224282812c050c5520db11mbligh        """
371fc3da5bbdcd63840cd224282812c050c5520db11mbligh        if namespace in self._state:
372fc3da5bbdcd63840cd224282812c050c5520db11mbligh            del self._state[namespace]
373fc3da5bbdcd63840cd224282812c050c5520db11mbligh        logging.debug('Persistent state %s.* deleted', namespace)
374fc3da5bbdcd63840cd224282812c050c5520db11mbligh
375fc3da5bbdcd63840cd224282812c050c5520db11mbligh
376fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh    @staticmethod
377fc3da5bbdcd63840cd224282812c050c5520db11mbligh    def property_factory(state_attribute, property_attribute, default,
378fc3da5bbdcd63840cd224282812c050c5520db11mbligh                         namespace='global_properties'):
379fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """
380fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        Create a property object for an attribute using self.get and self.set.
381fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
382fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        @param state_attribute A string with the name of the attribute on
383fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            job that contains the job_state instance.
384fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        @param property_attribute A string with the name of the attribute
385fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            this property is exposed as.
386fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        @param default A default value that should be used for this property
387fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            if it is not set.
388fc3da5bbdcd63840cd224282812c050c5520db11mbligh        @param namespace The namespace to store the attribute value in.
389fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
390fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        @returns A read-write property object that performs self.get calls
391fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            to read the value and self.set calls to set it.
392fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """
393fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        def getter(job):
394fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            state = getattr(job, state_attribute)
395fc3da5bbdcd63840cd224282812c050c5520db11mbligh            return state.get(namespace, property_attribute, default)
396fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        def setter(job, value):
397fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            state = getattr(job, state_attribute)
398fc3da5bbdcd63840cd224282812c050c5520db11mbligh            state.set(namespace, property_attribute, value)
399fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        return property(getter, setter)
400fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
401fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
402da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanskiclass base_job(object):
403da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski    """An abstract base class for the various autotest job classes.
404da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
405da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski    Properties:
406da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        autodir
407da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            The top level autotest directory.
408da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        clientdir
409da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            The autotest client directory.
410da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        serverdir
411da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            The autotest server directory. [OPTIONAL]
412da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        resultdir
413da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            The directory where results should be written out. If not specified
414da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            then results should not be written anywhere. [WRITABLE]
415da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
416da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        pkgdir
417da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            The job packages directory. [WRITABLE]
418da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        tmpdir
419da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            The job temporary directory. [WRITABLE]
420da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        testdir
421da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            The job test directory. [WRITABLE]
422da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        site_testdir
423da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            The job site test directory. [WRITABLE]
424da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
425da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        bindir
426da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            The client bin/ directory.
427da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        configdir
428da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            The client config/ directory.
429da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        profdir
430da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            The client profilers/ directory.
431da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        toolsdir
432da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            The client tools/ directory.
433da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
434da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        conmuxdir
435da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            The conmux directory. [OPTIONAL]
436da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
437da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        control
438da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            A path to the control file to be executed. [OPTIONAL]
439da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        hosts
440da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            A set of all live Host objects currently in use by the job.
441fc3da5bbdcd63840cd224282812c050c5520db11mbligh            Code running in the context of a local client can safely assume
442fc3da5bbdcd63840cd224282812c050c5520db11mbligh            that this set contains only a single entry.
443da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        machines
444da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            A list of the machine names associated with the job.
445da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        user
446da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            The user executing the job.
447da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        tag
448da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            A tag identifying the job. Often used by the scheduler to give
4499de6ed7ab19c29b5072d52439f434453df16bec0mbligh            a name of the form NUMBER-USERNAME/HOSTNAME.
450da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
451da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        last_boot_tag
452fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            The label of the kernel from the last reboot. [OPTIONAL,PERSISTENT]
453fc3da5bbdcd63840cd224282812c050c5520db11mbligh        automatic_test_tag
454fc3da5bbdcd63840cd224282812c050c5520db11mbligh            A string which, if set, will be automatically added to the test
455fc3da5bbdcd63840cd224282812c050c5520db11mbligh            name when running tests.
456fc3da5bbdcd63840cd224282812c050c5520db11mbligh
457da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        default_profile_only
458da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            A boolean indicating the default value of profile_only used
459fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            by test.execute. [PERSISTENT]
460da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        drop_caches
461da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            A boolean indicating if caches should be dropped before each
462da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            test is executed.
463da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        drop_caches_between_iterations
464da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            A boolean indicating if caches should be dropped before each
465da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            test iteration is executed.
466fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        run_test_cleanup
467fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            A boolean indicating if test.cleanup should be run by default
468fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            after a test completes, if the run_cleanup argument is not
469fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            specified. [PERSISTENT]
470da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
471da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        num_tests_run
472da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            The number of tests run during the job. [OPTIONAL]
473da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        num_tests_failed
474da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            The number of tests failed during the job. [OPTIONAL]
475da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
476da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        bootloader
477da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            An instance of the boottool class. May not be available on job
478da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            instances where access to the bootloader is not available
479da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            (e.g. on the server running a server job). [OPTIONAL]
480da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        harness
481da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            An instance of the client test harness. Only available in contexts
482da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            where client test execution happens. [OPTIONAL]
483da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        logging
484da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            An instance of the logging manager associated with the job.
485da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        profilers
486da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            An instance of the profiler manager associated with the job.
487da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        sysinfo
488da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            An instance of the sysinfo object. Only available in contexts
489da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            where it's possible to collect sysinfo.
490da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        warning_manager
491da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            A class for managing which types of WARN messages should be
492da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            logged and which should be supressed. [OPTIONAL]
493da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        warning_loggers
494da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            A set of readable streams that will be monitored for WARN messages
495da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            to be logged. [OPTIONAL]
496da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
497da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski    Abstract methods:
498da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        _find_base_directories [CLASSMETHOD]
499da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            Returns the location of autodir, clientdir and serverdir
500da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
501da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        _find_resultdir
502da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            Returns the location of resultdir. Gets a copy of any parameters
503da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            passed into base_job.__init__. Can return None to indicate that
504da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            no resultdir is to be used.
505da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski    """
506da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
507fc3da5bbdcd63840cd224282812c050c5520db11mbligh   # capture the dependency on several helper classes with factories
508fc3da5bbdcd63840cd224282812c050c5520db11mbligh    _job_directory = job_directory
509fc3da5bbdcd63840cd224282812c050c5520db11mbligh    _job_state = job_state
510fc3da5bbdcd63840cd224282812c050c5520db11mbligh
511fc3da5bbdcd63840cd224282812c050c5520db11mbligh
512da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski    # all the job directory attributes
513fc3da5bbdcd63840cd224282812c050c5520db11mbligh    autodir = _job_directory.property_factory('autodir')
514fc3da5bbdcd63840cd224282812c050c5520db11mbligh    clientdir = _job_directory.property_factory('clientdir')
515fc3da5bbdcd63840cd224282812c050c5520db11mbligh    serverdir = _job_directory.property_factory('serverdir')
516fc3da5bbdcd63840cd224282812c050c5520db11mbligh    resultdir = _job_directory.property_factory('resultdir')
517fc3da5bbdcd63840cd224282812c050c5520db11mbligh    pkgdir = _job_directory.property_factory('pkgdir')
518fc3da5bbdcd63840cd224282812c050c5520db11mbligh    tmpdir = _job_directory.property_factory('tmpdir')
519fc3da5bbdcd63840cd224282812c050c5520db11mbligh    testdir = _job_directory.property_factory('testdir')
520fc3da5bbdcd63840cd224282812c050c5520db11mbligh    site_testdir = _job_directory.property_factory('site_testdir')
521fc3da5bbdcd63840cd224282812c050c5520db11mbligh    bindir = _job_directory.property_factory('bindir')
522fc3da5bbdcd63840cd224282812c050c5520db11mbligh    configdir = _job_directory.property_factory('configdir')
523fc3da5bbdcd63840cd224282812c050c5520db11mbligh    profdir = _job_directory.property_factory('profdir')
524fc3da5bbdcd63840cd224282812c050c5520db11mbligh    toolsdir = _job_directory.property_factory('toolsdir')
525fc3da5bbdcd63840cd224282812c050c5520db11mbligh    conmuxdir = _job_directory.property_factory('conmuxdir')
526fc3da5bbdcd63840cd224282812c050c5520db11mbligh
527fc3da5bbdcd63840cd224282812c050c5520db11mbligh
528fc3da5bbdcd63840cd224282812c050c5520db11mbligh    # all the generic persistent properties
5299de6ed7ab19c29b5072d52439f434453df16bec0mbligh    tag = _job_state.property_factory('_state', 'tag', '')
530fc3da5bbdcd63840cd224282812c050c5520db11mbligh    default_profile_only = _job_state.property_factory(
531fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        '_state', 'default_profile_only', False)
532fc3da5bbdcd63840cd224282812c050c5520db11mbligh    run_test_cleanup = _job_state.property_factory(
533fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        '_state', 'run_test_cleanup', True)
534fc3da5bbdcd63840cd224282812c050c5520db11mbligh    last_boot_tag = _job_state.property_factory(
535fc3da5bbdcd63840cd224282812c050c5520db11mbligh        '_state', 'last_boot_tag', None)
536fc3da5bbdcd63840cd224282812c050c5520db11mbligh    automatic_test_tag = _job_state.property_factory(
537fc3da5bbdcd63840cd224282812c050c5520db11mbligh        '_state', 'automatic_test_tag', None)
538fc3da5bbdcd63840cd224282812c050c5520db11mbligh
539fc3da5bbdcd63840cd224282812c050c5520db11mbligh    # the use_sequence_number property
540fc3da5bbdcd63840cd224282812c050c5520db11mbligh    _sequence_number = _job_state.property_factory(
541fc3da5bbdcd63840cd224282812c050c5520db11mbligh        '_state', '_sequence_number', None)
542fc3da5bbdcd63840cd224282812c050c5520db11mbligh    def _get_use_sequence_number(self):
543fc3da5bbdcd63840cd224282812c050c5520db11mbligh        return bool(self._sequence_number)
544fc3da5bbdcd63840cd224282812c050c5520db11mbligh    def _set_use_sequence_number(self, value):
545fc3da5bbdcd63840cd224282812c050c5520db11mbligh        if value:
546fc3da5bbdcd63840cd224282812c050c5520db11mbligh            self._sequence_number = 1
547fc3da5bbdcd63840cd224282812c050c5520db11mbligh        else:
548fc3da5bbdcd63840cd224282812c050c5520db11mbligh            self._sequence_number = None
549fc3da5bbdcd63840cd224282812c050c5520db11mbligh    use_sequence_number = property(_get_use_sequence_number,
550fc3da5bbdcd63840cd224282812c050c5520db11mbligh                                   _set_use_sequence_number)
551da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
552da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
553da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski    def __init__(self, *args, **dargs):
554da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        # initialize the base directories, all others are relative to these
555da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        autodir, clientdir, serverdir = self._find_base_directories()
556da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        self._autodir = self._job_directory(autodir)
557da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        self._clientdir = self._job_directory(clientdir)
558da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        if serverdir:
559da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            self._serverdir = self._job_directory(serverdir)
560da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        else:
561da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            self._serverdir = None
562da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
563da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        # initialize all the other directories relative to the base ones
564da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        self._initialize_dir_properties()
565da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        self._resultdir = self._job_directory(
566da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            self._find_resultdir(*args, **dargs), True)
567da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        self._execution_contexts = []
568da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
569fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        # initialize all the job state
570fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        self._state = self._job_state()
571fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
572da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
573da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski    @classmethod
574da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski    def _find_base_directories(cls):
575da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        raise NotImplementedError()
576da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
577da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
578da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski    def _initialize_dir_properties(self):
579da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        """
580da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        Initializes all the secondary self.*dir properties. Requires autodir,
581da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        clientdir and serverdir to already be initialized.
582da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        """
583da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        # create some stubs for use as shortcuts
584da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        def readonly_dir(*args):
585da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            return self._job_directory(os.path.join(*args))
586da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        def readwrite_dir(*args):
587da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            return self._job_directory(os.path.join(*args), True)
588da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
589da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        # various client-specific directories
590da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        self._bindir = readonly_dir(self.clientdir, 'bin')
591da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        self._configdir = readonly_dir(self.clientdir, 'config')
592da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        self._profdir = readonly_dir(self.clientdir, 'profilers')
593da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        self._pkgdir = readwrite_dir(self.clientdir, 'packages')
594da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        self._toolsdir = readonly_dir(self.clientdir, 'tools')
595da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
596da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        # directories which are in serverdir on a server, clientdir on a client
597da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        if self.serverdir:
598da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            root = self.serverdir
599da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        else:
600da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            root = self.clientdir
601da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        self._tmpdir = readwrite_dir(root, 'tmp')
602da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        self._testdir = readwrite_dir(root, 'tests')
603da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        self._site_testdir = readwrite_dir(root, 'site_tests')
604da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
605da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        # various server-specific directories
606da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        if self.serverdir:
607da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            self._conmuxdir = readonly_dir(self.autodir, 'conmux')
608da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        else:
609da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            self._conmuxdir = None
610da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
611da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
612da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski    def _find_resultdir(self, *args, **dargs):
613da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        raise NotImplementedError()
614da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
615da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
616da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski    def push_execution_context(self, resultdir):
617da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        """
618da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        Save off the current context of the job and change to the given one.
619da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
620da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        In practice method just changes the resultdir, but it may become more
621da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        extensive in the future. The expected use case is for when a child
622da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        job needs to be executed in some sort of nested context (for example
623da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        the way parallel_simple does). The original context can be restored
624da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        with a pop_execution_context call.
625da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
626da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        @param resultdir The new resultdir, relative to the current one.
627da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        """
628da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        new_dir = self._job_directory(
629da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            os.path.join(self.resultdir, resultdir), True)
630da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        self._execution_contexts.append(self._resultdir)
631da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        self._resultdir = new_dir
632da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
633da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
634da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski    def pop_execution_context(self):
635da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        """
636da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        Reverse the effects of the previous push_execution_context call.
637da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski
638da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        @raises IndexError raised when the stack of contexts is empty.
639da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        """
640da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        if not self._execution_contexts:
641da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski            raise IndexError('No old execution context to restore')
642da2f143ff7658eca507041cd2393ead3b68cfa5ejadmanski        self._resultdir = self._execution_contexts.pop()
643fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
644fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
645fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh    def get_state(self, name, default=_job_state.NO_DEFAULT):
646fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """Returns the value associated with a particular name.
647fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
648fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        @param name The name the value was saved with.
649fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        @param default A default value to return if no state is currently
650fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            associated with var.
651fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
652fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        @returns A deep copy of the value associated with name. Note that this
653fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            explicitly returns a deep copy to avoid problems with mutable
654fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            values; mutations are not persisted or shared.
655fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        @raises KeyError raised when no state is associated with var and a
656fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            default value is not provided.
657fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """
658fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        try:
659fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            return self._state.get('public', name, default=default)
660fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        except KeyError:
661fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh            raise KeyError(name)
662fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
663fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
664fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh    def set_state(self, name, value):
665fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """Saves the value given with the provided name.
666fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
667fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        @param name The name the value should be saved with.
668fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        @param value The value to save.
669fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """
670fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        self._state.set('public', name, value)
671fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
672fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
673fc3da5bbdcd63840cd224282812c050c5520db11mbligh    def _build_tagged_test_name(self, testname, dargs):
674fc3da5bbdcd63840cd224282812c050c5520db11mbligh        """Builds the fully tagged testname and subdirectory for job.run_test.
675fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
676fc3da5bbdcd63840cd224282812c050c5520db11mbligh        @param testname The base name of the test
677fc3da5bbdcd63840cd224282812c050c5520db11mbligh        @param dargs The ** arguments passed to run_test. And arguments
678fc3da5bbdcd63840cd224282812c050c5520db11mbligh            consumed by this method will be removed from the dictionary.
679fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh
680fc3da5bbdcd63840cd224282812c050c5520db11mbligh        @returns A 3-tuple of the full name of the test, the subdirectory it
681fc3da5bbdcd63840cd224282812c050c5520db11mbligh            should be stored in, and the full tag of the subdir.
682fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """
683fc3da5bbdcd63840cd224282812c050c5520db11mbligh        tag_parts = []
684fc3da5bbdcd63840cd224282812c050c5520db11mbligh
685fc3da5bbdcd63840cd224282812c050c5520db11mbligh        # build up the parts of the tag used for the test name
686fc3da5bbdcd63840cd224282812c050c5520db11mbligh        base_tag = dargs.pop('tag', None)
687fc3da5bbdcd63840cd224282812c050c5520db11mbligh        if base_tag:
688fc3da5bbdcd63840cd224282812c050c5520db11mbligh            tag_parts.append(str(base_tag))
689fc3da5bbdcd63840cd224282812c050c5520db11mbligh        if self.use_sequence_number:
690fc3da5bbdcd63840cd224282812c050c5520db11mbligh            tag_parts.append('_%02d_' % self._sequence_number)
691fc3da5bbdcd63840cd224282812c050c5520db11mbligh            self._sequence_number += 1
692fc3da5bbdcd63840cd224282812c050c5520db11mbligh        if self.automatic_test_tag:
693fc3da5bbdcd63840cd224282812c050c5520db11mbligh            tag_parts.append(self.automatic_test_tag)
694fc3da5bbdcd63840cd224282812c050c5520db11mbligh        full_testname = '.'.join([testname] + tag_parts)
695fc3da5bbdcd63840cd224282812c050c5520db11mbligh
696fc3da5bbdcd63840cd224282812c050c5520db11mbligh        # build up the subdir and tag as well
697fc3da5bbdcd63840cd224282812c050c5520db11mbligh        subdir_tag = dargs.pop('subdir_tag', None)
698fc3da5bbdcd63840cd224282812c050c5520db11mbligh        if subdir_tag:
699fc3da5bbdcd63840cd224282812c050c5520db11mbligh            tag_parts.append(subdir_tag)
700fc3da5bbdcd63840cd224282812c050c5520db11mbligh        subdir = '.'.join([testname] + tag_parts)
701fc3da5bbdcd63840cd224282812c050c5520db11mbligh        tag = '.'.join(tag_parts)
702fc3da5bbdcd63840cd224282812c050c5520db11mbligh
703fc3da5bbdcd63840cd224282812c050c5520db11mbligh        return full_testname, subdir, tag
704fc3da5bbdcd63840cd224282812c050c5520db11mbligh
705fc3da5bbdcd63840cd224282812c050c5520db11mbligh
706fc3da5bbdcd63840cd224282812c050c5520db11mbligh    def _make_test_outputdir(self, subdir):
707fc3da5bbdcd63840cd224282812c050c5520db11mbligh        """Creates an output directory for a test to run it.
708fc3da5bbdcd63840cd224282812c050c5520db11mbligh
709fc3da5bbdcd63840cd224282812c050c5520db11mbligh        @param subdir The subdirectory of the test. Generally computed by
710fc3da5bbdcd63840cd224282812c050c5520db11mbligh            _build_tagged_test_name.
711fc3da5bbdcd63840cd224282812c050c5520db11mbligh
712fc3da5bbdcd63840cd224282812c050c5520db11mbligh        @returns A job_directory instance corresponding to the outputdir of
713fc3da5bbdcd63840cd224282812c050c5520db11mbligh            the test.
714fc3da5bbdcd63840cd224282812c050c5520db11mbligh        @raises A TestError if the output directory is invalid.
715fbf73aecdd357094ae05e7d1e4ea99b1ecf93ee4mbligh        """
716fc3da5bbdcd63840cd224282812c050c5520db11mbligh        # explicitly check that this subdirectory is new
717fc3da5bbdcd63840cd224282812c050c5520db11mbligh        path = os.path.join(self.resultdir, subdir)
718fc3da5bbdcd63840cd224282812c050c5520db11mbligh        if os.path.exists(path):
719fc3da5bbdcd63840cd224282812c050c5520db11mbligh            msg = ('%s already exists; multiple tests cannot run with the '
720fc3da5bbdcd63840cd224282812c050c5520db11mbligh                   'same subdirectory' % subdir)
721fc3da5bbdcd63840cd224282812c050c5520db11mbligh            raise error.TestError(msg)
722fc3da5bbdcd63840cd224282812c050c5520db11mbligh
723fc3da5bbdcd63840cd224282812c050c5520db11mbligh        # create the outputdir and raise a TestError if it isn't valid
724fc3da5bbdcd63840cd224282812c050c5520db11mbligh        try:
725fc3da5bbdcd63840cd224282812c050c5520db11mbligh            outputdir = self._job_directory(path, True)
726fc3da5bbdcd63840cd224282812c050c5520db11mbligh            return outputdir
727fc3da5bbdcd63840cd224282812c050c5520db11mbligh        except self._job_directory.JobDirectoryException, e:
728fc3da5bbdcd63840cd224282812c050c5520db11mbligh            logging.exception('%s directory creation failed with %s',
729fc3da5bbdcd63840cd224282812c050c5520db11mbligh                              subdir, e)
730fc3da5bbdcd63840cd224282812c050c5520db11mbligh            raise error.TestError('%s directory creation failed' % subdir)
731