Merge lp:~jason-hobbs/juju-deployer/juju-deployer-populate-first into lp:juju-deployer

Proposed by Jason Hobbs
Status: Rejected
Rejected by: Haw Loeung
Proposed branch: lp:~jason-hobbs/juju-deployer/juju-deployer-populate-first
Merge into: lp:juju-deployer
Diff against target: 545 lines (+255/-41)
12 files modified
Makefile (+5/-1)
deployer/action/importer.py (+137/-15)
deployer/cli.py (+7/-0)
deployer/deployment.py (+40/-8)
deployer/env/go.py (+27/-0)
deployer/env/py.py (+6/-0)
deployer/service.py (+11/-4)
deployer/tests/test_charm.py (+8/-0)
deployer/tests/test_deployment.py (+7/-9)
deployer/tests/test_diff.py (+3/-2)
deployer/tests/test_guiserver.py (+3/-2)
deployer/tests/test_importer.py (+1/-0)
To merge this branch: bzr merge lp:~jason-hobbs/juju-deployer/juju-deployer-populate-first
Reviewer Review Type Date Requested Status
juju-deployers Pending
Review via email: mp+261457@code.launchpad.net

Description of the change

Update of lp:~raharper/juju-deployer/populate-first - this merges a newer trunk. It also rename's rharper's add_machine to 'add_specific_machine' to avoid a conflict with the 'add_machine' from trunk. That needs a closer looking at to see how to use just one of those methods but I don't have time to figure it out right now.

To post a comment you must log in.
152. By Jason Hobbs

Fix syntax error and unit test.

153. By Jason Hobbs

Pull in lutostag's test fixes.

154. By Jason Hobbs

Force service sorting, and log it.

155. By Jason Hobbs

Specify series when adding a specific machine.

156. By Jason Hobbs

Use charm series if one provided by the charm.

This fixes deploying mixed Ubuntu/Windows bundles.

157. By Jason Hobbs

Timeout when acquiring a node takes too long.

Unmerged revisions

157. By Jason Hobbs

Timeout when acquiring a node takes too long.

156. By Jason Hobbs

Use charm series if one provided by the charm.

This fixes deploying mixed Ubuntu/Windows bundles.

155. By Jason Hobbs

Specify series when adding a specific machine.

154. By Jason Hobbs

Force service sorting, and log it.

153. By Jason Hobbs

Pull in lutostag's test fixes.

152. By Jason Hobbs

Fix syntax error and unit test.

151. By Jason Hobbs

Merge trunk.

150. By Ryan Harper

Allow nested placement when target uses maas= placement. Fix up debugging log message during deploy_services.

149. By Ryan Harper

Modify depoyment.deploy_services to bring services with placement and multiple units online to ensure subsequent services which placement can target previously deployed service units. Update get_machine to handle container placement.

148. By Ryan Harper

Fix typo

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'Makefile'
2--- Makefile 2014-08-26 22:34:07 +0000
3+++ Makefile 2015-07-06 20:11:20 +0000
4@@ -1,5 +1,9 @@
5+ifeq (,$(wildcard $(HOME)/.bazaar/bazaar.conf))
6+ PREFIX=HOME=/tmp
7+endif
8 test:
9- nosetests -s --verbosity=2 deployer/tests
10+ /bin/bash -c 'if [ -n "$(PREFIX)" ]; then mkdir -p /tmp/.juju; else true; fi'
11+ /bin/bash -c "$(PREFIX) nosetests -s --verbosity=2 deployer/tests"
12
13 freeze:
14 pip install -d tools/dist -r requirements.txt
15
16=== modified file 'deployer/action/importer.py'
17--- deployer/action/importer.py 2015-05-28 13:04:30 +0000
18+++ deployer/action/importer.py 2015-07-06 20:11:20 +0000
19@@ -1,5 +1,6 @@
20 import logging
21 import time
22+import math
23
24 from .base import BaseAction
25 from ..env import watchers
26@@ -58,7 +59,8 @@
27 "Adding %d more units to %s" % (abs(delta), svc.name))
28 if svc.unit_placement:
29 # Reload status once after non placed services units are done.
30- if reloaded is False:
31+
32+ if self.options.placement_first is True or reloaded is False:
33 # Improved crappy workaround juju-core api inconsistency
34 delay = time.time() + 60
35 while delay > time.time():
36@@ -71,7 +73,8 @@
37
38 placement = self.deployment.get_unit_placement(svc, env_status)
39 for mid in range(cur_units, svc.num_units):
40- self.env.add_unit(svc.name, placement.get(mid))
41+ self.env.add_unit(svc.name,
42+ self.get_machine(placement.get(mid)))
43 else:
44 self.env.add_units(svc.name, abs(delta))
45
46@@ -181,13 +184,17 @@
47 charm = self.deployment.get_charm_for(svc.name)
48 self.log.info(
49 " Deploying service %s using %s", svc.name, charm.charm_url)
50+
51+ # Use charm series instead of deployment one if charm provides it.
52+ # If charm does not provide series, this will be set to None.
53+ charm_series = self.deployment.get_charm_series(svc.name)
54
55 if svc.unit_placement:
56 # We sorted all the non placed services first, so we only
57 # need to update status once after we're done with them, in
58 # the instance of v3 bundles; in the more complex case of v4
59 # bundles, we'll need to refresh each time.
60- if not reloaded:
61+ if self.options.placement_first or not reloaded:
62 self.log.debug(
63 " Refetching status for placement deploys")
64 time.sleep(5.1)
65@@ -210,18 +217,59 @@
66 num_units = None
67
68 placement = self.deployment.get_unit_placement(svc, env_status)
69-
70- if charm.is_subordinate():
71- num_units = None
72-
73- self.env.deploy(
74- svc.name,
75- charm.charm_url,
76- self.deployment.repo_path,
77- svc.config,
78- svc.constraints,
79- num_units,
80- placement.get(0))
81+ # allocate all of the machines up front for all units
82+ # to ensure we don't allocate a targeted machine to
83+ # a service without placement
84+ if svc.unit_placement and \
85+ svc.num_units > 1 and \
86+ self.options.placement_first is True:
87+ self.log.debug('Pre-allocating machines for %s' % svc.name)
88+ self.log.debug('Deploy base service: %s' % svc.name)
89+ p = placement.get(0)
90+ machine = self.get_machine(p, charm_series)
91+ self.log.debug('deploy_services: '
92+ 'service=%s unit=0 placement=%s machine=%s' %
93+ (svc.name, p, machine))
94+ num_units = 1
95+ # deploy base service
96+ self.env.deploy(
97+ svc.name,
98+ charm.charm_url,
99+ self.deployment.repo_path,
100+ svc.config,
101+ svc.constraints,
102+ num_units,
103+ machine)
104+
105+ # add additional units
106+ time.sleep(5.1)
107+ env_status = self.env.status()
108+ cur_units = len(env_status['services'][svc.name].get('units', ()))
109+ placement = self.deployment.get_unit_placement(svc, env_status)
110+ for uid in range(cur_units, svc.num_units):
111+ p = placement.get(uid)
112+ machine = self.get_machine(p, charm_series)
113+ self.log.debug('add_units: '
114+ 'service=%s unit=%s placement=%s machine=%s' %
115+ (svc.name, uid, p, machine))
116+ self.env.add_unit(svc.name, machine)
117+
118+
119+ else:
120+ # just let add_units handling bring additional units on-line
121+ num_units = 1
122+
123+ if charm.is_subordinate():
124+ num_units = None
125+
126+ self.env.deploy(
127+ svc.name,
128+ charm.charm_url,
129+ self.deployment.repo_path,
130+ svc.config,
131+ svc.constraints,
132+ num_units,
133+ self.get_machine(placement.get(0), charm_series))
134
135 if svc.annotations:
136 self.log.debug(" Setting annotations")
137@@ -294,6 +342,80 @@
138 int(timeout), watch=self.options.watch,
139 services=self.deployment.get_service_names(), on_errors=on_errors)
140
141+ def get_machine(self, u_idx, charm_series=None):
142+ # find the machine id that matches the target machine
143+ # unlike juju status output, the dns-name is one of the
144+ # many values returned from our env.status() in addresses
145+ if u_idx is None:
146+ return None
147+
148+ status = self.env.status()
149+ # lxc:1 kvm:1, or 1
150+ if ':' in u_idx or u_idx.isdigit():
151+ mid = [u_idx]
152+ else:
153+ mid = [x for x in status['machines'].keys()
154+ if u_idx in
155+ [v.get('Value') for v in
156+ status['machines'][x]['addresses']]]
157+ self.deployment.log.info('mid=%s' % mid)
158+ if mid:
159+ m = mid.pop()
160+ self.deployment.log.debug(
161+ 'Found juju machine (%s) matching placement: %s', m, u_idx)
162+ return m
163+ else:
164+ self.deployment.log.info(
165+ 'No match in juju machines for: %s', u_idx)
166+
167+ # if we don't find a match, we need to add it
168+ series = self.deployment.data['series']
169+ if (charm_series is not None) and (charm_series != series):
170+ # override series if different from deployment series
171+ series = charm_series
172+
173+ mid = self.env.add_specific_machine(u_idx, series)
174+
175+ # timeout set to ~17 minutes which is much larger than needed
176+ backoff = 2
177+ delay = 1
178+ timeout = math.pow(2, 10)
179+
180+ self.deployment.log.debug(
181+ 'Waiting for machine to show up in status.')
182+ while True:
183+ m = mid.get('Machine')
184+ if m in status['machines'].keys():
185+ s = [x for x in status['machines'].keys()
186+ if u_idx in
187+ [v.get('Value') for v in
188+ status['machines'][x]['addresses']]]
189+ self.deployment.log.debug('addresses: %s' % s)
190+ if m in s:
191+ break
192+ else:
193+ self.deployment.log.debug(
194+ 'Machine %s not in status yet' % m)
195+
196+ self.deployment.log.debug("Sleep for %s second(s).",
197+ str(delay))
198+ time.sleep(delay)
199+ delay *= backoff
200+
201+ # The case of a machine failing to show up is really a
202+ # an unrecoverable failure that can't be dealt with as upstream
203+ # is not handling this case. So, the deployment must fail here.
204+ if timeout-delay <= 0:
205+ self.deployment.log.error(
206+ "Deployment has failed. Machine %s did not show up.",
207+ u_idx)
208+ raise ErrorExit()
209+
210+
211+ status = self.env.status()
212+ self.deployment.log.debug('Machine %s up!' % m)
213+ return mid.get('Machine')
214+
215 def run(self):
216 options = self.options
217 self.start_time = time.time()
218
219=== modified file 'deployer/cli.py'
220--- deployer/cli.py 2014-10-01 10:18:36 +0000
221+++ deployer/cli.py 2015-07-06 20:11:20 +0000
222@@ -82,6 +82,13 @@
223 "machine removal."),
224 dest="deploy_delay", default=0)
225 parser.add_argument(
226+ '-P', '--placement-first', action='store_true', default=False,
227+ dest='placement_first',
228+ help=("Sort services with placement services first to "
229+ "ensure that the requirement machines are aquired "
230+ "before non-targeted services are deployed. Note "
231+ "this reverses the default sorting order."))
232+ parser.add_argument(
233 '-e', '--environment', action='store', dest='juju_env',
234 help='Deploy to a specific Juju environment.',
235 default=os.getenv('JUJU_ENV'))
236
237=== modified file 'deployer/deployment.py'
238--- deployer/deployment.py 2015-05-13 14:16:46 +0000
239+++ deployer/deployment.py 2015-07-06 20:11:20 +0000
240@@ -46,11 +46,10 @@
241 services = []
242 for name, svc_data in self.data.get('services', {}).items():
243 services.append(Service(name, svc_data))
244- if self.version == 3:
245- # Sort unplaced units first, then sort by name for placed units.
246- services.sort(key=lambda svc: (bool(svc.unit_placement), svc.name))
247- else:
248- services.sort(self._machines_placement_sort)
249+
250+ self.log.debug("Sorting services: %s" % services)
251+ services.sort(self._services_sort)
252+ self.log.debug("Sorted services: %s" % services)
253 return services
254
255 def set_machines(self, machines):
256@@ -81,7 +80,7 @@
257 return self.data.get('services', {}).keys()
258
259 @staticmethod
260- def _machines_placement_sort(svc_a, svc_b):
261+ def _services_sort(svc_a, svc_b):
262 """Sort machines with machine placement in mind.
263
264 If svc_a is colocated alongside svc_b, svc_b needs to be deployed
265@@ -90,6 +89,35 @@
266 whether or not the service has a unit placement, and then finally
267 based on the name of the service.
268 """
269+ def _placement_sort(svc_a, svc_b):
270+ """ Sorts unit_placement lists,
271+ putting maas= units at the front"""
272+ def maas_first(a, b):
273+ if a.startswith('maas='):
274+ if b.startswith('maas='):
275+ return cmp(a, b)
276+ return -1
277+ if b.startswith('maas='):
278+ return 1
279+
280+ if ':' in a:
281+ if ':' in b:
282+ return cmp(a, b)
283+ return 1
284+
285+ return cmp(a, b)
286+
287+ # sort both services' unit_placement lists
288+ # putting maas units first
289+ svc_a.unit_placement.sort(cmp=maas_first)
290+ svc_b.unit_placement.sort(cmp=maas_first)
291+
292+ # now compare the service placement lists,
293+ # first list with a maas placement goes first
294+ for x, y in zip(svc_a.unit_placement,
295+ svc_b.unit_placement):
296+ return maas_first(x, y)
297+
298 if svc_a.unit_placement:
299 if svc_b.unit_placement:
300 # Check for colocation. This naively assumes that there is no
301@@ -98,8 +126,8 @@
302 return -1
303 if x_in_y(svc_a, svc_b):
304 return 1
305- # If no colocation exists, simply compare names.
306- return cmp(svc_a.name, svc_b.name)
307+ # If no colocation exists, do a placement sort.
308+ return _placement_sort(svc_a, svc_b)
309 return 1
310 if svc_b.unit_placement:
311 return -1
312@@ -166,6 +194,10 @@
313 return Charm.from_service(
314 svc_name, self.repo_path, self.series, svc_data)
315
316+ def get_charm_series(self, svc_name):
317+ svc_data = self.data['services'][svc_name]
318+ return svc_data.get('series')
319+
320 def fetch_charms(self, update=False, no_local_mods=False):
321 for charm in self.get_charms():
322 if charm.is_local():
323
324=== modified file 'deployer/env/go.py'
325--- deployer/env/go.py 2015-03-12 18:41:43 +0000
326+++ deployer/env/go.py 2015-07-06 20:11:20 +0000
327@@ -41,6 +41,30 @@
328 series=series,
329 constraints=parse_constraints(constraints))['Machine']
330
331+ def add_specific_machine(self, machine, series=""):
332+ if ':' in machine:
333+ scope, directive = machine.split(':')
334+ else:
335+ scope = self.get_env_config()['Config']['uuid']
336+ directive = machine
337+
338+ machines = [{
339+ "Placement": {
340+ "Scope": scope,
341+ "Directive": directive,
342+ },
343+ "ParentId": "",
344+ "ContainerType": "",
345+ "Series": series,
346+ "Constraints": {},
347+ "Jobs": [
348+ "JobHostUnits"
349+ ]
350+ }]
351+ self.log.debug('Adding machine: %s:%s' % (scope, directive))
352+ # {u'Machines': [{u'Machine': u'7', u'Error': None}]}
353+ return self.client.add_machines(machines)['Machines'][0]
354+
355 def add_unit(self, service_name, machine_spec):
356 return self.client.add_unit(service_name, machine_spec)
357
358@@ -68,6 +92,9 @@
359 def get_config(self, svc_name):
360 return self.client.get_config(svc_name)
361
362+ def get_env_config(self):
363+ return self.client.get_env_config()
364+
365 def get_constraints(self, svc_name):
366 try:
367 return self.client.get_constraints(svc_name)
368
369=== modified file 'deployer/env/py.py'
370--- deployer/env/py.py 2014-02-18 12:16:46 +0000
371+++ deployer/env/py.py 2015-07-06 20:11:20 +0000
372@@ -12,6 +12,12 @@
373 self.name = name
374 self.options = options
375
376+ def add_machine(self, machine):
377+ params - self._named_env(["juju", "add-machine"])
378+ params.extend([machine])
379+ self._check_call(
380+ params, self.log, "Error adding machine %s", machine)
381+
382 def add_units(self, service_name, num_units):
383 params = self._named_env(["juju", "add-unit"])
384 if num_units > 1:
385
386=== modified file 'deployer/service.py'
387--- deployer/service.py 2015-03-18 18:19:43 +0000
388+++ deployer/service.py 2015-07-06 20:11:20 +0000
389@@ -152,12 +152,19 @@
390 feedback.error(
391 ("Service placement to machine"
392 "not supported %s to %s") % (
393- self.service.name, unit_placement[idx]))
394+ self.service.name, unit_placement[idx]))
395 elif p in services:
396 if services[p].unit_placement:
397- feedback.error(
398- "Nested placement not supported %s -> %s -> %s" % (
399- self.service.name, p, services[p].unit_placement))
400+ # nested placement is acceptable if the target
401+ # is using maas node placement
402+ for u in services[p].unit_placement:
403+ if not u.startswith('maas='):
404+ feedback.error(
405+ "Nested placement not supported"
406+ " %s -> %s -> %s" % (
407+ self.service.name, p,
408+ services[p].unit_placement))
409+ continue
410 elif self.deployment.get_charm_for(p).is_subordinate():
411 feedback.error(
412 "Cannot place to a subordinate service: %s -> %s" % (
413
414=== modified file 'deployer/tests/test_charm.py'
415--- deployer/tests/test_charm.py 2014-09-29 14:36:34 +0000
416+++ deployer/tests/test_charm.py 2015-07-06 20:11:20 +0000
417@@ -161,6 +161,14 @@
418 self._call(
419 ["git", "init", self.path],
420 "Could not initialize repo at %(path)s")
421+ self._call(
422+ ["git", "config",
423+ "user.email", "test@example.com"],
424+ "Could not config user.email at %(path)s")
425+ self._call(
426+ ["git", "config",
427+ "user.name", "test"],
428+ "Could not config user.name at %(path)s")
429
430 def write(self, files):
431 for f in files:
432
433=== modified file 'deployer/tests/test_deployment.py'
434--- deployer/tests/test_deployment.py 2015-03-18 18:19:43 +0000
435+++ deployer/tests/test_deployment.py 2015-07-06 20:11:20 +0000
436@@ -75,8 +75,6 @@
437 def test_maas_name_and_zone_placement(self):
438 d = self.get_named_deployment_v3("stack-placement-maas.yml", "stack")
439 d.validate_placement()
440- placement = d.get_unit_placement('ceph', {})
441- self.assertEqual(placement.get(0), "arnolt")
442 placement = d.get_unit_placement('heat', {})
443 self.assertEqual(placement.get(0), "zone=zebra")
444
445@@ -90,35 +88,35 @@
446 except ErrorExit:
447 self.fail("Should not fail")
448
449- def test_machines_placement_sort(self):
450+ def test_services_sort(self):
451 d = Deployment('test', None, None)
452 self.assertEqual(
453- d._machines_placement_sort(
454+ d._services_sort(
455 FauxService(unit_placement=1),
456 FauxService()
457 ), 1)
458 self.assertEqual(
459- d._machines_placement_sort(
460+ d._services_sort(
461 FauxService(),
462 FauxService(unit_placement=1)
463 ), -1)
464 self.assertEqual(
465- d._machines_placement_sort(
466+ d._services_sort(
467 FauxService(name="x", unit_placement=['asdf']),
468 FauxService(name="y", unit_placement=['lxc:x/1'])
469 ), 1)
470 self.assertEqual(
471- d._machines_placement_sort(
472+ d._services_sort(
473 FauxService(name="y", unit_placement=['lxc:x/1']),
474 FauxService(name="x", unit_placement=['asdf'])
475 ), -1)
476 self.assertEqual(
477- d._machines_placement_sort(
478+ d._services_sort(
479 FauxService(name="x", unit_placement=['asdf']),
480 FauxService(name="y", unit_placement=['hjkl'])
481 ), -1)
482 self.assertEqual(
483- d._machines_placement_sort(
484+ d._services_sort(
485 FauxService(name="x"),
486 FauxService(name="y")
487 ), -1)
488
489=== modified file 'deployer/tests/test_diff.py'
490--- deployer/tests/test_diff.py 2015-03-17 17:34:57 +0000
491+++ deployer/tests/test_diff.py 2015-07-06 20:11:20 +0000
492@@ -35,8 +35,9 @@
493 cls._dir = tempfile.mkdtemp()
494 os.mkdir(os.path.join(cls._dir, "precise"))
495 deployment.repo_path = cls._dir
496- deployment.fetch_charms()
497- deployment.resolve()
498+ if not TEST_OFFLINE:
499+ deployment.fetch_charms()
500+ deployment.resolve()
501 cls._deployment = deployment
502
503 @classmethod
504
505=== modified file 'deployer/tests/test_guiserver.py'
506--- deployer/tests/test_guiserver.py 2015-03-17 10:40:34 +0000
507+++ deployer/tests/test_guiserver.py 2015-07-06 20:11:20 +0000
508@@ -28,7 +28,7 @@
509 'find_service', 'ignore_errors', 'juju_env', 'list_deploys',
510 'no_local_mods', 'no_relations', 'overrides', 'rel_wait',
511 'retry_count', 'series', 'skip_unit_wait', 'terminate_machines',
512- 'timeout', 'update_charms', 'verbose', 'watch'
513+ 'timeout', 'update_charms', 'verbose', 'watch', 'placement_first',
514 ])
515 self.assertEqual(expected_keys, set(self.options.__dict__.keys()))
516
517@@ -58,6 +58,7 @@
518 self.assertFalse(options.update_charms)
519 self.assertFalse(options.verbose)
520 self.assertFalse(options.watch)
521+ self.assertFalse(options.placement_first)
522
523
524 class TestDeploymentError(unittest.TestCase):
525@@ -280,7 +281,7 @@
526 mock.call.status(),
527 mock.call.deploy(
528 'mysql', 'cs:precise/mysql-28', '', None,
529- {'arch': 'i386', 'cpu-cores': 4, 'mem': '4G'}, 2, None),
530+ {'arch': 'i386', 'cpu-cores': 4, 'mem': '4G'}, 1, None),
531 mock.call.set_annotation(
532 'mysql', {'gui-y': '164.547', 'gui-x': '494.347'}),
533 mock.call.deploy(
534
535=== modified file 'deployer/tests/test_importer.py'
536--- deployer/tests/test_importer.py 2015-05-13 14:06:34 +0000
537+++ deployer/tests/test_importer.py 2015-07-06 20:11:20 +0000
538@@ -34,6 +34,7 @@
539 'no_local_mods': True,
540 'no_relations': False,
541 'overrides': None,
542+ 'placement_first': False,
543 'rel_wait': 60,
544 'retry_count': 0,
545 'series': None,

Subscribers

People subscribed via source and target branches