Merge lp:~tvansteenburgh/juju-deployer/1269783-force-terminate into lp:juju-deployer

Proposed by Tim Van Steenburgh
Status: Merged
Approved by: David Britton
Approved revision: no longer in the source branch.
Merged at revision: 158
Proposed branch: lp:~tvansteenburgh/juju-deployer/1269783-force-terminate
Merge into: lp:juju-deployer
Diff against target: 300 lines (+79/-35)
7 files modified
HACKING (+11/-2)
deployer/cli.py (+7/-6)
deployer/env/base.py (+8/-5)
deployer/env/go.py (+20/-14)
deployer/tests/test_goenv.py (+29/-6)
deployer/tests/test_importer.py (+3/-2)
deployer/utils.py (+1/-0)
To merge this branch: bzr merge lp:~tvansteenburgh/juju-deployer/1269783-force-terminate
Reviewer Review Type Date Requested Status
David Britton (community) Approve
Review via email: mp+272130@code.launchpad.net

Description of the change

Add force-termination option using -TT

To post a comment you must log in.
Revision history for this message
David Britton (dpb) wrote :

excellent.

review: Approve
158. By Tim Van Steenburgh

Add force-termination option using -TT

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'HACKING'
2--- HACKING 2014-01-23 13:30:32 +0000
3+++ HACKING 2015-09-23 15:36:10 +0000
4@@ -10,10 +10,19 @@
5 $ PYTHONPATH=. python deployer/cli.py
6
7
8-Running tests
9--------------
10+Running unit tests
11+------------------
12
13 Tests are compatible with nose and can be run using, e.g.:
14
15 $ nosetests -s --verbosity=2 deployer/tests
16
17+
18+Running live environment tests
19+------------------------------
20+
21+ $ juju bootstrap -e local
22+ $ JUJU_HOME=~/.juju \
23+ JUJU_ENV=local \
24+ TEST_ENDPOINT=`juju api-endpoints -e local` \
25+ nosetests -s --verbosity=2 deployer/tests/test_goenv.py
26
27=== modified file 'deployer/cli.py'
28--- deployer/cli.py 2014-10-01 10:18:36 +0000
29+++ deployer/cli.py 2015-09-23 15:36:10 +0000
30@@ -50,10 +50,10 @@
31 default=False)
32 parser.add_argument(
33 '-T', '--terminate-machines',
34- help=('Terminate all machines but the bootstrap node. '
35- 'Destroy any services that exist on each'),
36- dest="terminate_machines", action="store_true",
37- default=False)
38+ help=('Terminate all machines but the bootstrap node. '
39+ 'Destroy any services that exist on each. '
40+ 'Use -TT to forcefully terminate.'),
41+ dest="terminate_machines", action="count", default=0)
42 parser.add_argument(
43 '-t', '--timeout',
44 help='Timeout (sec) for entire deployment (45min default)',
45@@ -171,9 +171,10 @@
46 if options.destroy_services or options.terminate_machines:
47 log.info("Resetting environment...")
48 env.connect()
49- env.reset(terminate_machines=options.terminate_machines,
50+ env.reset(terminate_machines=bool(options.terminate_machines),
51 terminate_delay=options.deploy_delay,
52- watch=options.watch)
53+ watch=options.watch,
54+ force_terminate=options.terminate_machines > 1)
55 log.info("Environment reset in %0.2f", time.time() - start_time)
56 sys.exit(0)
57
58
59=== modified file 'deployer/env/base.py'
60--- deployer/env/base.py 2015-08-04 12:47:05 +0000
61+++ deployer/env/base.py 2015-09-23 15:36:10 +0000
62@@ -111,13 +111,14 @@
63 self._check_call(
64 params, self.log, "Error exposing service %r", name)
65
66- def terminate_machine(self, mid, wait=False):
67+ def terminate_machine(self, mid, wait=False, force=False):
68 """Terminate a machine.
69
70- The machine can't have any running units, after removing the units or
71- destroying the service, use wait_for_units to know when its safe to
72- delete the machine (ie units have finished executing stop hooks and are
73- removed)
74+ Unless ``force=True``, the machine can't have any running units.
75+ After removing the units or destroying the service, use wait_for_units
76+ to know when its safe to delete the machine (i.e., units have finished
77+ executing stop hooks and are removed).
78+
79 """
80 if ((isinstance(mid, int) and mid == 0) or
81 (mid.isdigit() and int(mid) == 0)):
82@@ -125,6 +126,8 @@
83 raise RuntimeError("Can't terminate machine 0")
84 params = self._named_env(["juju", "terminate-machine"])
85 params.append(mid)
86+ if force:
87+ params.append('--force')
88 try:
89 self._check_call(
90 params, self.log, "Error terminating machine %r" % mid)
91
92=== modified file 'deployer/env/go.py'
93--- deployer/env/go.py 2015-03-12 18:41:43 +0000
94+++ deployer/env/go.py 2015-09-23 15:36:10 +0000
95@@ -27,18 +27,18 @@
96 self.api_endpoint = endpoint
97 self.client = None
98
99- def add_machine(self, series="",constraints={}):
100+ def add_machine(self, series="", constraints={}):
101 """Add a top level machine to the Juju environment.
102
103 Use the given series and constraints.
104 Return the machine identifier (e.g. "1").
105-
106+
107 series: a string such as 'precise' or 'trusty'.
108 constraints: a map of constraints (such as mem, arch, etc.) which
109 can be parsed by utils.parse_constraints
110 """
111 return self.client.add_machine(
112- series=series,
113+ series=series,
114 constraints=parse_constraints(constraints))['Machine']
115
116 def add_unit(self, service_name, machine_spec):
117@@ -82,7 +82,9 @@
118 def reset(self,
119 terminate_machines=False,
120 terminate_delay=0,
121- timeout=360, watch=False):
122+ timeout=360,
123+ watch=False,
124+ force_terminate=False):
125 """Destroy/reset the environment."""
126 status = self.status()
127 destroyed = False
128@@ -95,8 +97,10 @@
129 # Mark any errors as resolved so destruction can proceed.
130 self.resolve_errors()
131
132- # Wait for units
133- self.wait_for_units(timeout, goal_state='removed', watch=watch)
134+ if not (terminate_machines and force_terminate):
135+ # Wait for units to be removed. Only necessary if we're not
136+ # force-terminating machines.
137+ self.wait_for_units(timeout, goal_state='removed', watch=watch)
138
139 # The only value to not terminating is keeping the data on the
140 # machines around.
141@@ -104,13 +108,15 @@
142 self.log.info(
143 " warning: juju-core machines are not reusable for units")
144 return
145- self._terminate_machines(status, watch, terminate_delay)
146+ self._terminate_machines(
147+ status, watch, terminate_delay, force_terminate)
148
149- def _terminate_machines(self, status, watch, terminate_wait):
150+ def _terminate_machines(self, status, watch, terminate_wait, force):
151 """Terminate all machines, optionally wait for termination.
152 """
153 # Terminate machines
154- self.log.debug(" Terminating machines")
155+ self.log.debug(
156+ " Terminating machines %s", 'forcefully' if force else '')
157
158 # Don't bother if there are no service unit machines
159 if len(status['machines']) == 1:
160@@ -137,7 +143,7 @@
161 machines.sort(machine_sort)
162
163 for mid in machines:
164- self._terminate_machine(mid, container_hosts)
165+ self._terminate_machine(mid, container_hosts, force=force)
166
167 if containers:
168 watch = self.client.get_watch(120)
169@@ -145,20 +151,20 @@
170 watch, containers).run(self._delta_event_log)
171
172 for mid in container_hosts:
173- self._terminate_machine(mid)
174+ self._terminate_machine(mid, force=force)
175
176 if terminate_wait:
177 self.log.info(" Waiting for machine termination")
178 callback = watch and self._delta_event_log or None
179- self.client.wait_for_no_machines(None, callback)
180+ self.client.wait_for_no_machines(terminate_wait, callback)
181
182- def _terminate_machine(self, mid, container_hosts=()):
183+ def _terminate_machine(self, mid, container_hosts=(), force=False):
184 if mid == "0":
185 return
186 if mid in container_hosts:
187 return
188 self.log.debug(" Terminating machine %s", mid)
189- self.terminate_machine(mid)
190+ self.terminate_machine(mid, force=force)
191
192 def _check_timeout(self, etime):
193 w_timeout = etime - time.time()
194
195=== modified file 'deployer/tests/test_goenv.py'
196--- deployer/tests/test_goenv.py 2015-03-12 18:41:43 +0000
197+++ deployer/tests/test_goenv.py 2015-09-23 15:36:10 +0000
198@@ -15,6 +15,24 @@
199 "Test env must be defined: TEST_ENDPOINT")
200 class LiveEnvironmentTest(Base):
201
202+ @classmethod
203+ def setUpClass(cls):
204+ """Base class sets JUJU_HOME to a new tmp dir, but for these
205+ tests we need a real JUJU_HOME so that calls to
206+ ``juju api-endpoints`` work properly.
207+
208+ """
209+ if not os.environ.get('JUJU_HOME'):
210+ raise RuntimeError('JUJU_HOME must be set')
211+
212+ @classmethod
213+ def tearDownClass(cls):
214+ """Base class deletes the tmp JUJU_HOME dir it created.
215+ Override so we don't delete our real JUJU_HOME!
216+
217+ """
218+ pass
219+
220 def setUp(self):
221 self.endpoint = os.environ.get("TEST_ENDPOINT")
222 self.output = self.capture_logging(
223@@ -25,10 +43,14 @@
224 status = self.env.status()
225 self.assertFalse(status.get('services'))
226 # Destroy everything.. consistent baseline
227- self.env.reset(terminate_machines=len(status['machines'].keys()) > 1, terminate_delay=240)
228+ self.env.reset(
229+ terminate_machines=len(status['machines'].keys()) > 1,
230+ terminate_delay=240)
231
232 def tearDown(self):
233- self.env.reset(terminate_machines=True, terminate_delay=240)
234+ self.env.reset(
235+ terminate_machines=True, terminate_delay=240,
236+ force_terminate=True)
237 self.env.close()
238
239 def test_env(self):
240@@ -54,7 +76,7 @@
241
242 def test_add_machine(self):
243 machine_name = self.env.add_machine()
244-
245+
246 # Sleep because juju core watches are eventually consistent (5s window)
247 # and status rpc is broken (http://pad.lv/1203105)
248 time.sleep(6)
249@@ -64,12 +86,13 @@
250
251 def test_set_annotation(self):
252 machine_name = self.env.add_machine()
253- self.env.set_annotation(machine_name, {'foo': 'bar'}, entity_type='machine')
254-
255+ self.env.set_annotation(
256+ machine_name, {'foo': 'bar'}, entity_type='machine')
257+
258 # Sleep because juju core watches are eventually consistent (5s window)
259 # and status rpc is broken (http://pad.lv/1203105)
260 time.sleep(6)
261- status = self.env.status()
262+ self.env.status()
263
264 self.assertIn('foo', self.env.client.get_annotation(
265 machine_name, 'machine')['Annotations'])
266
267=== modified file 'deployer/tests/test_importer.py'
268--- deployer/tests/test_importer.py 2015-08-06 08:40:16 +0000
269+++ deployer/tests/test_importer.py 2015-09-23 15:36:10 +0000
270@@ -4,7 +4,6 @@
271
272 from deployer.config import ConfigStack
273 from deployer.action.importer import Importer
274-from deployer.utils import yaml_dump, yaml_load
275
276 from base import (
277 Base,
278@@ -62,7 +61,9 @@
279 config = {'name': '$hi_world _are_you_there? {guess_who}'}
280 self.assertEqual(
281 env.method_calls[3], mock.call.deploy(
282- 'wiki', 'cs:precise/mediawiki', '', config, None, 1, None))
283+ 'wiki', 'cs:precise/mediawiki',
284+ os.environ.get("JUJU_REPOSITORY", ""),
285+ config, None, 1, None))
286 env.add_relation.assert_called_once_with('wiki', 'db')
287
288 @skip_if_offline
289
290=== modified file 'deployer/utils.py'
291--- deployer/utils.py 2015-09-03 14:25:56 +0000
292+++ deployer/utils.py 2015-09-23 15:36:10 +0000
293@@ -380,6 +380,7 @@
294 raise ValueError("No Environment specified")
295 return conf['default']
296
297+
298 def x_in_y(x, y):
299 """Check to see if the second argument is named in the first
300 argument's unit placement spec.

Subscribers

People subscribed via source and target branches