Merge lp:~johnsca/charms/trusty/cloudfoundry/cfdeploy-errors into lp:~cf-charmers/charms/trusty/cloudfoundry/trunk

Proposed by Cory Johns
Status: Merged
Merged at revision: 163
Proposed branch: lp:~johnsca/charms/trusty/cloudfoundry/cfdeploy-errors
Merge into: lp:~cf-charmers/charms/trusty/cloudfoundry/trunk
Diff against target: 198 lines (+70/-36)
3 files modified
cfdeploy (+13/-7)
cloudfoundry/utils.py (+50/-26)
reconciler/ui/app.py (+7/-3)
To merge this branch: bzr merge lp:~johnsca/charms/trusty/cloudfoundry/cfdeploy-errors
Reviewer Review Type Date Requested Status
Benjamin Saller (community) Approve
Review via email: mp+243607@code.launchpad.net

Description of the change

Fixed cfdeploy handling of errors during bootstrap and deploy
Improved -l option to log to a file and assume DEBUG

To post a comment you must log in.
Revision history for this message
Benjamin Saller (bcsaller) wrote :

LGTM +1

review: Approve
164. By Cory Johns

Improvements to reconciler websocket handling to hopefully prevent errors in log and long delays on initial load

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'cfdeploy'
2--- cfdeploy 2014-11-13 16:22:27 +0000
3+++ cfdeploy 2014-12-04 19:30:36 +0000
4@@ -61,6 +61,8 @@
5 webadmin_endpoint,
6 sh,
7 socket_open,
8+ retry,
9+ wait_for,
10 until,
11 which
12 )
13@@ -72,7 +74,8 @@
14 parser = argparse.ArgumentParser()
15 parser.add_argument('-e', '--env', default=current_env())
16 parser.add_argument('-v', '--version', default="latest")
17- parser.add_argument('-l', '--log-level', default=logging.INFO)
18+ parser.add_argument('-l', '--log', action='store_true',
19+ help='Write debug log to cfdeploy.log')
20 parser.add_argument('-c', '--constraints')
21 parser.add_argument('-g', '--generate', action='store_true')
22 parser.add_argument('admin_password')
23@@ -136,18 +139,21 @@
24
25 def main():
26 options = setup()
27- logging.basicConfig(level=options.log_level)
28+ if options.log:
29+ root_logger = logging.getLogger()
30+ root_logger.handlers = [logging.FileHandler('cfdeploy.log', 'w')]
31+ root_logger.setLevel(logging.DEBUG)
32 install_deps()
33
34 bar = ProgressBar('Deploying CloudFoundry', max=10)
35 bar.start()
36- bar.next(message='Bootstrapping')
37- bootstrap()
38+ retry(3, bootstrap, bar=bar, message='Bootstrapping')
39 until(juju_state_server, bar=bar, message="Waiting for State Server")
40 bar.next(message='Deploying Orchestrator')
41- deploy(constraints=options.constraints,
42- generate_dependents=options.generate,
43- admin_password=options.admin_password)
44+ wait_for(30, 5, partial(deploy,
45+ constraints=options.constraints,
46+ generate_dependents=options.generate,
47+ admin_password=options.admin_password))
48 until(lambda: socket_open(reconciler_endpoint(), 8888),
49 bar=bar, message="Waiting on Reconciler")
50 bar.next(message='Showing Reconciler')
51
52=== modified file 'cloudfoundry/utils.py'
53--- cloudfoundry/utils.py 2014-11-12 18:24:39 +0000
54+++ cloudfoundry/utils.py 2014-12-04 19:30:36 +0000
55@@ -184,7 +184,7 @@
56 **kwargs)
57 output, _ = p.communicate()
58 ret_code = p.poll()
59- logging.debug('result: %s', output)
60+ logging.debug('output: %s', output)
61 if check is True:
62 if ret_code != 0 and throw:
63 raise subprocess.CalledProcessError(ret_code, all_args, output=output)
64@@ -265,6 +265,23 @@
65 return wait_for(0, 20, *callbacks, **kwargs)
66
67
68+def retry(attempts, *callbacks, **kwargs):
69+ """
70+ Repeatedly try callbacks a fixed number of times or until all return True
71+ """
72+ for attempt in xrange(attempts):
73+ if 'bar' in kwargs:
74+ kwargs['bar'].next(attempt == 0, message=kwargs.get('message'))
75+ for callback in callbacks:
76+ if not callback():
77+ break
78+ else:
79+ break
80+ else:
81+ raise OSError("Retry attempts exceeded")
82+ return True
83+
84+
85 def juju_state_server():
86 if api_endpoints() != 0:
87 return False
88@@ -356,7 +373,9 @@
89
90 def bootstrap():
91 if not os.path.exists(get_jenv()) or api_endpoints() != 0:
92- sh.juju('bootstrap')
93+ juju = sh.check('juju', throw=False)
94+ return juju('bootstrap') != 0
95+ return True
96
97
98 def get_admin_password():
99@@ -386,30 +405,35 @@
100 def deploy(**config):
101 status = get_client().status()
102 constraints = config.pop('constraints', None)
103- if 'cloudfoundry' not in status['Services']:
104- # create an up to date config
105- config = dict({
106- 'admin_secret': get_admin_password(),
107- 'cf_version': 'latest',
108- 'placement': 'dense',
109- }, **config)
110-
111- fd, fn = tempfile.mkstemp(suffix='.yaml')
112- os.close(fd)
113- with open(fn, 'w') as fp:
114- yaml.dump({'cloudfoundry': config}, fp)
115-
116- repo_path = get_repo_path()
117-
118- args = ['deploy', '--config=%s' % fn,
119- '--repository=%s' % repo_path]
120- if constraints:
121- args.append('--constraints=%s' % constraints)
122- args.append('local:trusty/cloudfoundry')
123- sh.juju(*args)
124- time.sleep(5)
125- sh.juju('expose', 'cloudfoundry')
126- os.unlink(fn)
127+ if 'cloudfoundry' in status['Services']:
128+ return True
129+ # create an up to date config
130+ config = dict({
131+ 'admin_secret': get_admin_password(),
132+ 'cf_version': 'latest',
133+ 'placement': 'dense',
134+ }, **config)
135+
136+ fd, fn = tempfile.mkstemp(suffix='.yaml')
137+ os.close(fd)
138+ with open(fn, 'w') as fp:
139+ yaml.dump({'cloudfoundry': config}, fp)
140+
141+ repo_path = get_repo_path()
142+
143+ args = ['deploy', '--config=%s' % fn,
144+ '--repository=%s' % repo_path]
145+ if constraints:
146+ args.append('--constraints=%s' % constraints)
147+ args.append('local:trusty/cloudfoundry')
148+ juju = sh.check('juju', throw=False)
149+ if juju(*args) != 0:
150+ return False
151+ time.sleep(5)
152+ if juju('expose', 'cloudfoundry') != 0:
153+ return False
154+ os.unlink(fn)
155+ return True
156
157
158 def login(password):
159
160=== modified file 'reconciler/ui/app.py'
161--- reconciler/ui/app.py 2014-10-17 22:34:42 +0000
162+++ reconciler/ui/app.py 2014-12-04 19:30:36 +0000
163@@ -1,4 +1,5 @@
164 import json
165+import logging
166 from path import path
167 from tornado.websocket import WebSocketHandler
168 import tornado.ioloop
169@@ -29,7 +30,7 @@
170 listeners = {}
171
172 def open(self):
173- self.connections.add(self)
174+ DashboardIO.connections.add(self)
175
176 def on_message(self, message):
177 """
178@@ -43,7 +44,7 @@
179 listener(data)
180
181 def on_close(self):
182- self.connections.remove(self)
183+ DashboardIO.connections.discard(self)
184
185 @classmethod
186 def send(cls, message):
187@@ -51,7 +52,10 @@
188 Send a message out over all connected WebSockets.
189 """
190 for connection in cls.connections:
191- connection.write_message(message)
192+ try:
193+ connection.write_message(message)
194+ except:
195+ logging.error('Error sending message', exc_info=True)
196
197 @classmethod
198 def add_listener(cls, message_type, listener):

Subscribers

People subscribed via source and target branches