5
-> Download/Update relevant charm versions (for now all to latest)
19
from twisted.internet import reactor
22
REPO_DIR = "/tmp/test-repo"
25
log = logging.getLogger("juju.loader")
27
############################
30
def deploy(repo_dir, charm, service=None):
31
"""Deploy a charm as a service.
33
args = ["juju", "deploy",
34
"--repository", repo_dir,
37
args.append("%s" % service)
38
output = subprocess.check_output(args, stderr=subprocess.STDOUT)
39
log.info("Deployed %s" % (service or charm))
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))
51
def status(with_stderr=False):
52
"""Get a status dictionary.
54
args = ["juju", "status", "--format=json"]
56
output = subprocess.check_output(
57
args, stderr=subprocess.STDOUT)
60
output = subprocess.check_output(args, stderr=open("/dev/null"))
61
return json.loads(output)
64
def add_relation(relation):
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]))
74
############################
77
def setup_repository(services, repo_dir, series):
78
if not os.path.exists(repo_dir):
81
if not os.path.isdir(repo_dir):
82
raise ValueError("Invalid repository directory %s" % repo_dir)
84
series_dir = os.path.join(repo_dir, series)
86
if not os.path.exists(series_dir):
87
os.makedirs(series_dir)
89
for service in services:
90
_bzr_branch(service, series_dir)
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)
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)
103
output = subprocess.check_output(
104
["/usr/bin/bzr", "revision-info"], cwd=branch_dir,
105
stderr=subprocess.STDOUT)
106
revno, revid = output.strip().split()
108
if revid == info["revid"]:
110
"Retrieved charm %(charm)s from %(branch_spec)s@%(revid)s" % info)
113
log.debug("Updating branch lp:%s", info["branch_spec"])
115
output = subprocess.check_output(
116
["/usr/bin/bzr", "up", "-r", "revid:%s" % info["revid"]],
117
stderr=subprocess.STDOUT)
119
log.warning("Unable to fetch revision %s of %s ",
120
info['revid'], info['branch_spec'])
123
"Retrieved charm %(charm)s from %(branch_spec)s@%(revid)s" % info)
126
def verify_bootstrapped():
128
status(with_stderr=True)
134
def clean_juju_state(services):
135
from twisted.internet.defer import inlineCallbacks
136
from juju.environment.config import EnvironmentsConfig
138
env_config = EnvironmentsConfig()
139
env_config.load_or_write_sample()
140
environment = env_config.get_default()
143
def _clean_juju_state():
144
zookeeper.set_debug_level(0)
145
provider = environment.get_machine_provider()
146
storage = provider.get_file_storage()
148
client = yield provider.connect()
149
charms = yield client.get_children("/charms")
151
# Clear out any cached charm state in zookeeper
152
for s in services: # XXX fuzzy match..
155
yield client.delete("/charms/%s" % c)
157
# Clear out any cached storage state
158
for f in list(os.listdir(storage._path)):
161
os.remove(os.path.join(storage._path, f))
165
reactor.callWhenRunning(_clean_juju_state)
169
class EnvironmentLayer(object):
171
def __init__(self, repo_dir, destroy_repo=False):
172
self.repo_dir = repo_dir
173
self.destroy_repo = destroy_repo
176
def _state_path(self):
177
return os.path.join(self.repo_dir, "test-state")
180
if not os.path.exists(self._state_path):
182
with open(self._state_path) as fh:
183
previous = json.loads(fh.read())
186
current = current['services'].keys()
187
previous = previous['services'].keys()
189
added = set(current) - set(previous)
193
if self.destroy_repo:
194
shutil.rmtree(self.repo_dir)
196
# Still need to clear out the charms from zk
197
clean_juju_state(added)
199
def error(self, error, msg):
200
if not isinstance(error, subprocess.CalledProcessError):
203
if error.cmd[:2] == ["juju", "add-relation"]:
204
log.warning("Invalid plan\n\n%s" % error.output)
207
if error.cmd[:2] == ["juju", "deploy"]:
208
log.warning("Problem deploying %s\n\n%s" % (
209
error.cmd, error.output))
212
log.warning("Unknown problem %s\n\n%s" % (
213
error.cmd.args, error.output))
217
with open(os.path.join(self.repo_dir, "test-state"), "w") as fh:
218
fh.write(json.dumps(status()))
221
def __exit__(self, type, value, tb):
223
from traceback import format_exc
224
if (type, value, tb) == (None, None, None):
227
error_message = format_exc()
228
capture = self.error(value, error_message)
233
def load_test_plan(test_plan):
234
if not verify_bootstrapped():
235
print "Environment not bootstrapped"
239
test_plan['services'], REPO_DIR, test_plan['series'])
241
environment = EnvironmentLayer(REPO_DIR)
243
with environment as repo_dir:
244
for service in test_plan["services"]:
245
deploy(repo_dir, service['charm'], service['charm'])
247
for relation in test_plan["relations"]:
248
add_relation(relation)
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)
259
if __name__ == '__main__':
263
import pdb, traceback, sys
264
traceback.print_exc()
265
pdb.post_mortem(sys.exc_info()[-1])