~thomir-deactivatedaccount/adt-continuous-deployer/trunk-fix-warning-message

« back to all changes in this revision

Viewing changes to ci_automation/mojo_project_pool.py

  • Committer: Ubuntu CI Bot
  • Author(s): Thomi Richards
  • Date: 2015-05-28 06:22:24 UTC
  • mfrom: (54.2.3 trunk-add-mojo-project-pool)
  • Revision ID: ubuntu_ci_bot-20150528062224-ke6exobip6l9iu8f
Add mojo project pool code to cd.py. [r=Thomi Richards, Francis Ginther]

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2015 Canonical
 
2
#
 
3
# This program is free software: you can redistribute it and/or modify
 
4
# it under the terms of the GNU General Public License as published by
 
5
# the Free Software Foundation, either version 3 of the License, or
 
6
# (at your option) any later version.
 
7
#
 
8
# This program is distributed in the hope that it will be useful,
 
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
 
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
11
# GNU General Public License for more details.
 
12
#
 
13
# You should have received a copy of the GNU General Public License
 
14
# along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
15
#
 
16
 
 
17
"""CI Automation: mojo project pool.
 
18
 
 
19
Instead of maintaining a 1:1 mapping between a mojo project and a service it
 
20
deploys, let's use a pool of mojo projects.
 
21
 
 
22
The code in this file helps us do that while avoiding race conditions.
 
23
 
 
24
The MojoProjectPool class handles allocating and deallocating mojo projects.
 
25
It does this with a directory on disk. This directory is called the 'pool
 
26
directory' in documentation.
 
27
 
 
28
The pool directory must contain files, the names of which are each mojo
 
29
project name. These files must be created manually. If a mojo project is
 
30
represented in the pool directory, then it is used exclusively by the
 
31
deployer, and MUST NOT be used manually (since manual invocation of mojo
 
32
will not update lock files).
 
33
 
 
34
MojoProjectPool allocates projects by creating symlinks on disk to project
 
35
files. We use symlinks because:
 
36
- symlink creation is atomic
 
37
- symlink counts can be read from the lockfile inode.
 
38
 
 
39
Symlinks are created in the 'project lock directory'.
 
40
"""
 
41
 
 
42
from contextlib import contextmanager
 
43
import os
 
44
import os.path
 
45
import random
 
46
 
 
47
 
 
48
class NoProjectError(Exception):
 
49
 
 
50
    def __str__(self):
 
51
        return "No mojo projects available"
 
52
 
 
53
 
 
54
class MojoProjectPool(object):
 
55
 
 
56
    def __init__(self, project_dir, lock_dir):
 
57
        if not os.path.exists(project_dir):
 
58
            os.makedirs(project_dir)
 
59
        self.project_dir = project_dir
 
60
        if not os.path.exists(lock_dir):
 
61
            os.makedirs(lock_dir)
 
62
        self.lock_dir = lock_dir
 
63
 
 
64
    @contextmanager
 
65
    def acquire_mojo_project(self):
 
66
        """Get a mojo project to use.
 
67
 
 
68
        This contextmanager yields the mojo project name, so it can be used
 
69
        like so:
 
70
 
 
71
        with project_pool.acquire_mojo_project() as mojo_project_name:
 
72
            print("Using mojo project: {}".format(project_name))
 
73
 
 
74
        If no project can be found, a NoProjectError exception is raised.
 
75
 
 
76
        """
 
77
        candidates = os.listdir(self.project_dir)
 
78
        random.shuffle(candidates)
 
79
        for candidate_project in candidates:
 
80
            try:
 
81
                os.symlink(
 
82
                    candidate_project,
 
83
                    os.path.join(self.lock_dir, candidate_project)
 
84
                )
 
85
            except FileExistsError:
 
86
                continue
 
87
            # If we get here, the lock is acquired:
 
88
            try:
 
89
                yield candidate_project
 
90
            finally:
 
91
                os.unlink(os.path.join(self.lock_dir, candidate_project))
 
92
                return
 
93
        else:
 
94
            raise NoProjectError()