13
from os.path import dirname, abspath
18
def timed_out(signal, frame):
19
log.error("Deployment timed out after %s sec.", opts.timeout)
22
start_time = time.time()
24
parser = optparse.OptionParser()
25
parser.add_option('-c', '--config',
26
help=('File containing deployment(s) config. This '
27
'option can be repeated, with later files overriding '
28
'values in earlier ones.'),
29
dest='configs', action='append')
30
parser.add_option('-d', '--debug', help='Enable debugging to stdout',
32
action="store_true", default=False)
33
parser.add_option('-L', '--local-mods',
34
help='Allow deployment of locally-modified charms',
35
dest="no_local_mods", default=True, action='store_false')
36
parser.add_option('-u', '--update-charms',
37
help='Update existing charm branches',
38
dest="update_charms", default=False, action="store_true")
39
parser.add_option('-l', '--ls', help='List available deployments',
40
dest="list_deploys", action="store_true", default=False)
41
parser.add_option('-D', '--destroy-services',
42
help='Destroy all services (do not terminate machines)',
43
dest="destroy_services", action="store_true",
45
parser.add_option('-S', '--scrub-zk', action='store_true', default=False,
47
help='Remove charm nodes from ZK after service destroy.')
48
parser.add_option('-T', '--terminate-machines',
49
help=('Terminate all machines but the bootstrap node. '
50
'Destroy any services that exist on each'),
51
dest="terminate_machines", action="store_true",
53
parser.add_option('-t', '--timeout',
54
help='Timeout (sec) for entire deployment (45min default)',
55
dest='timeout', action='store', type='int', default=2700)
56
parser.add_option("-f", '--find-service', action="store", type="string",
57
help='Find hostname from first unit of a specific service.',
59
parser.add_option("-m", '--max-concurrent', action="store", type="int",
60
help=("Maximum number of concurrent deployments to send "
61
" to provider. Default: no limit"),
62
dest="max_concur_deploy", default=0)
63
parser.add_option('-s', '--deploy-delay', action='store', type='float',
64
help=("Time in seconds to sleep between 'deploy' commands, "
65
"to allow machine provider to process requests. This "
66
"delay is also enforced between calls to"
68
dest="deploy_delay", default=0)
69
parser.add_option('-e', '--environment', action='store', dest='juju_env',
70
help='Deploy to a specific Juju environment.',
71
default=os.getenv('JUJU_ENV'))
72
parser.add_option('-o', '--override', action='append', type='string',
73
help=('Override *all* config options of the same name '
74
'across all services. Input as key=value.'),
75
dest='overrides', default=None)
76
parser.add_option('-w', '--relation-wait', action='store', dest='rel_wait',
78
help=('Number of seconds to wait before checking for '
79
'relation errors after all relations have been added '
80
'and subordinates started. (default: 60)'))
81
(opts, args) = parser.parse_args()
84
opts.configs = ['deployments.cfg']
85
update_charms = opts.update_charms
87
# temporarily abuse __builtin__ till this is setup properly
88
__builtin__.juju_log = juju_log = open("juju.log", "w")
89
__builtin__.juju_cmds = []
91
init_logging("debug.log", opts.debug)
95
if opts.destroy_services is True or opts.terminate_machines is True:
96
destroy_all(juju_status(opts.juju_env), opts.juju_env,
97
terminate_machines=opts.terminate_machines,
98
scrub_zk=opts.scrub_zk,
99
delay=opts.deploy_delay)
102
if opts.find_service is not None:
103
rc = find_service(juju_status(opts.juju_env), opts.find_service)
106
# load the configuration for possible deployments.
107
missing_configs = [c for c in opts.configs if not os.path.exists(c)]
109
log.error("Configuration not found: {}".format(", ".join(missing_configs)))
112
debug_msg("Loading deployments from {}".format(", ".join(opts.configs)))
113
cfg, include_dirs = load_config(opts.configs)
115
if opts.list_deploys:
120
log.error("You must specify a deployment.")
125
SERIES, CHARMS, RELATIONS, overrides = load_deployment(cfg, deployment)
126
series_store = "%s/%s" % (ORIGCWD, SERIES)
128
# series store ends up being the local juju charm repository
129
if not os.path.exists(series_store):
130
debug_msg("Creating series charm store: %s" % series_store)
131
os.mkdir(series_store)
133
debug_msg("Series charm store already exists: %s" % series_store)
135
# either clone all charms if we dont have them or update branches
136
for k in CHARMS.keys():
137
charm_path = "%s/%s" % (series_store, k)
138
debug_msg("Charm '%s' - using charm path '%s'" % (k, charm_path))
139
(branch, sep, revno) = CHARMS[k].get("branch", '').partition('@')
140
needs_build = update_charms
142
debug_msg("Branch: {}, revision: {}".format(branch, revno))
144
debug_msg("No remote branch specified")
146
if os.path.exists(charm_path):
147
if opts.no_local_mods:
149
# is there a better way to check for changes?
150
bzrstatus = subprocess.check_output(['bzr', 'st']).strip()
151
if bzrstatus not in (
152
"", "working tree is out of date, run 'bzr update'"):
153
log.error("Charm is locally modified: {}".format(
155
log.error("Aborting")
157
debug_msg("Charm path exists @ %s." % charm_path)
158
if update_charms and branch:
159
debug_msg("Updating charm branch '%s'" % k)
160
code = subprocess.call(
161
["bzr", "pull", "-d", charm_path, '--remember', branch])
163
log.error("Could not update branch at {} from {}".format(
167
print "- Cloning %s from %s" % (k, branch)
168
subprocess.call(["bzr", "branch", branch, charm_path])
171
cmd = ["bzr", "update", charm_path]
172
revno != 'tip' and cmd.extend(['-r', revno])
173
code = subprocess.call(cmd)
175
log.error("Unable to check out branch revision {}".format(revno))
177
if CHARMS[k].get("build") is not None and needs_build:
178
cmd = CHARMS[k]["build"]
179
debug_msg("Running build command at {}...".format(charm_path))
181
code = subprocess.call(cmd)
183
log.error("Failed to build charm {}".format(k))
185
# load charms metadata
186
if not os.path.isdir(charm_path):
187
print "Branch for {} does not exist ({})".format(k, charm_path)
189
mdf = open("%s/metadata.yaml" % charm_path, "r")
190
debug_msg("Loading metadata from %s/metadata.yaml" % charm_path)
191
CHARMS[k]["metadata"] = yaml.load(mdf)
193
# load charms config.yaml if it has one
194
if os.path.exists("%s/config.yaml" % charm_path):
195
debug_msg("Loading config.yaml from %s/config.yaml" % charm_path)
196
conf = open("%s/config.yaml" % charm_path)
197
CHARMS[k]["config"] = yaml.safe_load(conf)["options"]
199
if "units" not in CHARMS[k].keys():
200
CHARMS[k]["units"] = 1
203
for override in opts.overrides:
204
spl = override.split('=')
206
value = '='.join(spl[1:])
207
overrides[key] = value
209
# apply overrides to relevant charms
210
for k, v in overrides.iteritems():
212
if k in CHARMS[svc]['config']:
213
if 'options' not in CHARMS[svc]:
214
CHARMS[svc]['options'] = {}
216
CHARMS[svc]['options'][k] = v
218
# create a temporary deploy-time config yaml
219
temp = tempfile.NamedTemporaryFile()
220
deploy_config = temp.name
221
CONFIG = generate_deployment_config(temp, CHARMS, include_dirs)
222
log.debug("Using the following config:\n%s", pprint.pformat(CONFIG))
224
# make sure we're bootstrapped
225
status = juju_status(opts.juju_env)
227
log.error("Is juju bootstrapped?")
229
if (status["machines"][0]["instance-state"] != "provisioned" and
230
status["machines"][0]["agent-state"] != "running"):
231
log.error("Bootstrap node not running?")
234
debug_msg("Deploying with timeout %s sec." % opts.timeout)
235
signal.signal(signal.SIGALRM, timed_out)
236
signal.alarm(opts.timeout)
238
# figure out what needs to be done
240
for c in CHARMS.keys():
241
if c not in status["services"].keys():
244
print "* Services '%s' already deployed. Skipping" % c
246
if (len(to_deploy) < opts.max_concur_deploy or opts.max_concur_deploy == 0):
247
groups_of = len(to_deploy)
249
groups_of = opts.max_concur_deploy
253
# go through to_deploy in chunks of group_by
254
start_groups = [to_deploy[i:i + groups_of]
255
for i in range(0, len(to_deploy), groups_of)]
256
for group_num in range(0, len(start_groups)):
257
for c in start_groups[group_num]:
258
print ("- Deploying %s in group %d/%d" %
259
(c, group_num + 1, len(start_groups)))
261
if "units" in CHARMS[c]:
262
cmd += " -n %s" % CHARMS[c]["units"]
263
if "constraints" in CHARMS[c]:
264
cmd += " --constraints=%s" % CHARMS[c]["constraints"]
265
if c in CONFIG.keys():
266
cmd += " --config=%s" % deploy_config
267
cmd += " --repository=%s local:%s %s" % (
268
ORIGCWD, CHARMS[c]["metadata"]["name"], c)
270
cmd += " -e %s" % opts.juju_env
272
if opts.deploy_delay > 0:
273
debug_msg("Delaying %s sec. between deployment" %
275
time.sleep(opts.deploy_delay)
276
wait_for_started(opts.debug, opts.juju_env, sleep=3.0,
277
msg="- Waiting for started: %s" %
278
start_groups[group_num])
280
if len(start_groups) != 0:
281
status = juju_status(opts.juju_env)
283
# add additional units to any services that want/need them
284
# TODO: need max_concur_deploy support here also
285
for c in CHARMS.keys():
286
if CHARMS[c]["units"] > 1:
287
if len(status["services"][c]["units"]) < CHARMS[c]["units"]:
288
needed_units = (int(CHARMS[c]["units"]) -
289
len(status["services"][c]["units"].keys()))
291
print "- Adding %d more units to %s" % (needed_units, c)
292
cmd = "add-unit --num-units %d %s" % (needed_units, c)
295
debug_msg("Service '%s' does not need any more units added." % c)
297
# poll juju status until all services report strated. fail on any error
298
wait_for_started(opts.debug, opts.juju_env,
299
"- Waiting for all service units to reach 'started' state.")
301
# add all relations, ordered by weight
303
print "- Adding relations:"
304
for w in sorted(RELATIONS, reverse=True):
305
for r in RELATIONS[w]:
306
print " -> Relation: %s <-> %s" % (r[0], r[1])
307
cmd = "add-relation %s %s" % (r[0], r[1])
309
cmd += " -e %s" % opts.juju_env
310
juju_call(cmd, ignore_failure=True)
314
# Subordinates units spawn after relations have been added.
315
# Ensure they're started, if they exist.
316
wait_for_subordinates_started(opts.debug, opts.juju_env)
319
print "- Sleeping for %s before ensuring relation state." % opts.rel_wait
320
# give all relations a minute to settle down, and make sure no errors are
322
time.sleep(float(opts.rel_wait))
324
if not ensure_relations_up(juju_status(opts.juju_env)):
327
print ("- Deployment complete in %d seconds.\n\n" %
328
(int(time.time() - start_time)))
330
print "- Juju command log:"
331
for c in __builtin__.juju_cmds: