~mark-mims/charmrunner/with-environment

« back to all changes in this revision

Viewing changes to charmrunner/loader.py

  • Committer: kapil.foss at gmail
  • Date: 2012-02-07 17:33:11 UTC
  • Revision ID: kapil.foss@gmail.com-20120207173311-dnlj7pmsyoflcllx
committing wip charmrunner

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
"""
 
2
 
 
3
From test job
 
4
 -> Parse test plan
 
5
 -> Download/Update relevant charm versions (for now all to latest)
 
6
 -> Load environment
 
7
 ->
 
8
 
 
9
"""
 
10
 
 
11
import logging
 
12
import json
 
13
import os
 
14
import shutil
 
15
import subprocess
 
16
import sys
 
17
import zookeeper
 
18
 
 
19
from twisted.internet import reactor
 
20
 
 
21
 
 
22
REPO_DIR = "/tmp/test-repo"
 
23
 
 
24
 
 
25
log = logging.getLogger("juju.loader")
 
26
 
 
27
############################
 
28
 
 
29
 
 
30
def deploy(repo_dir, charm, service=None):
 
31
    """Deploy a charm as a service.
 
32
    """
 
33
    args = ["juju", "deploy",
 
34
            "--repository", repo_dir,
 
35
            "local:%s" % charm]
 
36
    if service:
 
37
        args.append("%s" % service)
 
38
    output = subprocess.check_output(args, stderr=subprocess.STDOUT)
 
39
    log.info("Deployed %s" % (service or charm))
 
40
    return output
 
41
 
 
42
 
 
43
def destroy(service_name):
 
44
    output = subprocess.check_output(
 
45
        ["juju", "destroy-service", service_name],
 
46
        stderr=subprocess.STDOUT)
 
47
    log.info("Destroyed %s" % (service_name))
 
48
    return output
 
49
 
 
50
 
 
51
def status(with_stderr=False):
 
52
    """Get a status dictionary.
 
53
    """
 
54
    args = ["juju", "status", "--format=json"]
 
55
    if with_stderr:
 
56
        output = subprocess.check_output(
 
57
            args, stderr=subprocess.STDOUT)
 
58
        return output
 
59
    else:
 
60
        output = subprocess.check_output(args, stderr=open("/dev/null"))
 
61
        return json.loads(output)
 
62
 
 
63
 
 
64
def add_relation(relation):
 
65
    """Add a relation.
 
66
    """
 
67
    args = ["juju", "add-relation"]
 
68
    args.extend(relation[:2])
 
69
    output = subprocess.check_output(args, stderr=subprocess.STDOUT)
 
70
    log.info("Added relation %s -> %s (%s)" % (
 
71
        relation[0], relation[1], relation[2]))
 
72
    return output
 
73
 
 
74
############################
 
75
 
 
76
 
 
77
def setup_repository(services, repo_dir, series):
 
78
    if not os.path.exists(repo_dir):
 
79
        os.makedirs(repo_dir)
 
80
 
 
81
    if not os.path.isdir(repo_dir):
 
82
        raise ValueError("Invalid repository directory %s" % repo_dir)
 
83
 
 
84
    series_dir = os.path.join(repo_dir, series)
 
85
 
 
86
    if not os.path.exists(series_dir):
 
87
        os.makedirs(series_dir)
 
88
 
 
89
    for service in services:
 
90
        _bzr_branch(service, series_dir)
 
91
 
 
92
 
 
93
def _bzr_branch(info, series_dir):
 
94
    branch_dir = os.path.join(series_dir, info['charm'])
 
95
    log.debug("Branching charm lp:%s to %s", info["branch_spec"], branch_dir)
 
96
 
 
97
    if not os.path.exists(branch_dir):
 
98
        subprocess.check_output(
 
99
            ["/usr/bin/bzr", "co", "-q", "--lightweight",
 
100
             "lp:%s" % info["branch_spec"], branch_dir],
 
101
            stderr=subprocess.STDOUT)
 
102
 
 
103
    output = subprocess.check_output(
 
104
        ["/usr/bin/bzr", "revision-info"], cwd=branch_dir,
 
105
        stderr=subprocess.STDOUT)
 
106
    revno, revid = output.strip().split()
 
107
 
 
108
    if revid == info["revid"]:
 
109
        log.info(
 
110
            "Retrieved charm %(charm)s from %(branch_spec)s@%(revid)s" % info)
 
111
 
 
112
        return
 
113
    log.debug("Updating branch lp:%s", info["branch_spec"])
 
114
    try:
 
115
        output = subprocess.check_output(
 
116
            ["/usr/bin/bzr", "up", "-r", "revid:%s" % info["revid"]],
 
117
            stderr=subprocess.STDOUT)
 
118
    except:
 
119
        log.warning("Unable to fetch revision %s of %s ",
 
120
                    info['revid'], info['branch_spec'])
 
121
 
 
122
    log.info(
 
123
        "Retrieved charm %(charm)s from %(branch_spec)s@%(revid)s" % info)
 
124
 
 
125
 
 
126
def verify_bootstrapped():
 
127
    try:
 
128
        status(with_stderr=True)
 
129
    except:
 
130
        return False
 
131
    return True
 
132
 
 
133
 
 
134
def clean_juju_state(services):
 
135
    from twisted.internet.defer import inlineCallbacks
 
136
    from juju.environment.config import EnvironmentsConfig
 
137
 
 
138
    env_config = EnvironmentsConfig()
 
139
    env_config.load_or_write_sample()
 
140
    environment = env_config.get_default()
 
141
 
 
142
    @inlineCallbacks
 
143
    def _clean_juju_state():
 
144
        zookeeper.set_debug_level(0)
 
145
        provider = environment.get_machine_provider()
 
146
        storage = provider.get_file_storage()
 
147
 
 
148
        client = yield provider.connect()
 
149
        charms = yield client.get_children("/charms")
 
150
 
 
151
        # Clear out any cached charm state in zookeeper
 
152
        for s in services:  # XXX fuzzy match..
 
153
            for c in charms:
 
154
                if s in c:
 
155
                    yield client.delete("/charms/%s" % c)
 
156
 
 
157
        # Clear out any cached storage state
 
158
        for f in list(os.listdir(storage._path)):
 
159
            for s in services:
 
160
                if s in f:
 
161
                    os.remove(os.path.join(storage._path, f))
 
162
 
 
163
        reactor.stop()
 
164
 
 
165
    reactor.callWhenRunning(_clean_juju_state)
 
166
    reactor.run()
 
167
 
 
168
 
 
169
class EnvironmentLayer(object):
 
170
 
 
171
    def __init__(self, repo_dir, destroy_repo=False):
 
172
        self.repo_dir = repo_dir
 
173
        self.destroy_repo = destroy_repo
 
174
 
 
175
    @property
 
176
    def _state_path(self):
 
177
        return os.path.join(self.repo_dir, "test-state")
 
178
 
 
179
    def reset(self):
 
180
        if not os.path.exists(self._state_path):
 
181
            return
 
182
        with open(self._state_path) as fh:
 
183
            previous = json.loads(fh.read())
 
184
        current = status()
 
185
 
 
186
        current = current['services'].keys()
 
187
        previous = previous['services'].keys()
 
188
 
 
189
        added = set(current) - set(previous)
 
190
        for a in added:
 
191
            destroy(a)
 
192
 
 
193
        if self.destroy_repo:
 
194
            shutil.rmtree(self.repo_dir)
 
195
 
 
196
        # Still need to clear out the charms from zk
 
197
        clean_juju_state(added)
 
198
 
 
199
    def error(self, error, msg):
 
200
        if not isinstance(error, subprocess.CalledProcessError):
 
201
            log.warning(msg)
 
202
            return
 
203
        if error.cmd[:2] == ["juju", "add-relation"]:
 
204
            log.warning("Invalid plan\n\n%s" % error.output)
 
205
            return False
 
206
 
 
207
        if error.cmd[:2] == ["juju", "deploy"]:
 
208
            log.warning("Problem deploying %s\n\n%s" % (
 
209
                error.cmd, error.output))
 
210
            return False
 
211
 
 
212
        log.warning("Unknown problem %s\n\n%s" % (
 
213
            error.cmd.args, error.output))
 
214
        return False
 
215
 
 
216
    def __enter__(self):
 
217
        with open(os.path.join(self.repo_dir, "test-state"), "w") as fh:
 
218
            fh.write(json.dumps(status()))
 
219
        return self.repo_dir
 
220
 
 
221
    def __exit__(self, type, value, tb):
 
222
        capture = False
 
223
        from traceback import format_exc
 
224
        if (type, value, tb) == (None, None, None):
 
225
            pass
 
226
        else:
 
227
            error_message = format_exc()
 
228
            capture = self.error(value, error_message)
 
229
        self.reset()
 
230
        return capture
 
231
 
 
232
 
 
233
def load_test_plan(test_plan):
 
234
    if not verify_bootstrapped():
 
235
        print "Environment not bootstrapped"
 
236
        return
 
237
 
 
238
    setup_repository(
 
239
        test_plan['services'], REPO_DIR, test_plan['series'])
 
240
 
 
241
    environment = EnvironmentLayer(REPO_DIR)
 
242
 
 
243
    with environment as repo_dir:
 
244
        for service in test_plan["services"]:
 
245
            deploy(repo_dir, service['charm'], service['charm'])
 
246
 
 
247
        for relation in test_plan["relations"]:
 
248
            add_relation(relation)
 
249
 
 
250
 
 
251
def main(args):
 
252
    logging.basicConfig(level=logging.DEBUG)
 
253
    test_plan_path = args.pop()
 
254
    with open(test_plan_path) as fh:
 
255
        test_plan = json.loads(fh.read())
 
256
    load_test_plan(test_plan)
 
257
 
 
258
 
 
259
if __name__ == '__main__':
 
260
    try:
 
261
        main(sys.argv[1:])
 
262
    except:
 
263
        import pdb, traceback, sys
 
264
        traceback.print_exc()
 
265
        pdb.post_mortem(sys.exc_info()[-1])