Merge lp:~bcsaller/charms/trusty/cloudfoundry/progressbar into lp:~cf-charmers/charms/trusty/cloudfoundry/trunk

Proposed by Benjamin Saller
Status: Merged
Merged at revision: 160
Proposed branch: lp:~bcsaller/charms/trusty/cloudfoundry/progressbar
Merge into: lp:~cf-charmers/charms/trusty/cloudfoundry/trunk
Diff against target: 449 lines (+169/-50)
9 files modified
README.rst (+4/-6)
cfdeploy (+114/-11)
cloudfoundry/utils.py (+41/-23)
reconciler/app.py (+2/-3)
reconciler/strategy.py (+2/-2)
reconciler/tactics.py (+2/-2)
tests/test_strategy.py (+1/-1)
tests/test_utils.py (+2/-2)
tox.ini (+1/-0)
To merge this branch: bzr merge lp:~bcsaller/charms/trusty/cloudfoundry/progressbar
Reviewer Review Type Date Requested Status
Cory Johns (community) Needs Fixing
Review via email: mp+241618@code.launchpad.net
To post a comment you must log in.
178. By Benjamin Saller

attempt to install deps from the wheelhouse, needs verification

Revision history for this message
Cory Johns (johnsca) wrote :

See inline comments.

179. By Benjamin Saller

change func name

180. By Benjamin Saller

build out a virtualenv with deps prior to kickoff

Revision history for this message
Benjamin Saller (bcsaller) wrote :

I re-pushed this with a venv.

Revision history for this message
Cory Johns (johnsca) wrote :

Typo in python-virtualenv package, noted inline, below.

review: Needs Fixing
Revision history for this message
Cory Johns (johnsca) wrote :

Actually, I can just fix this while merging.

Revision history for this message
Cory Johns (johnsca) wrote :

I propose this change to make it less chatty: http://pastebin.ubuntu.com/8988136/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'README.rst'
--- README.rst 2014-11-06 23:37:55 +0000
+++ README.rst 2014-11-13 00:05:21 +0000
@@ -24,11 +24,6 @@
24We provide a set of helper scripts (in bash) which can be used to assist in the deployment and24We provide a set of helper scripts (in bash) which can be used to assist in the deployment and
25management of CF.25management of CF.
2626
27You will need the cf command line client. This can easily be installed using the following:
28
29 wget http://go-cli.s3-website-us-east-1.amazonaws.com/releases/latest/cf-cli_amd64.deb
30 dpkg -i cf-cli_amd64.deb
31
3227
33Deployment28Deployment
34----------29----------
@@ -51,7 +46,10 @@
51will block until the deployment is fully up and running. It also will create a46will block until the deployment is fully up and running. It also will create a
52CF "admin" user with the given password, leaving you logged in and ready to push47CF "admin" user with the given password, leaving you logged in and ready to push
53a CF app. Note that while this can take quite some time, the whole process is48a CF app. Note that while this can take quite some time, the whole process is
54automated.49automated.
50
51If you don't have the cf command line client the latest one will be installed
52from a .deb package to your local system as part of cfdeploy.
5553
56Once the deploy completes, you can push apps. A sample application we recommend54Once the deploy completes, you can push apps. A sample application we recommend
57to become familiar with the system is GitHub High Score website. This can be55to become familiar with the system is GitHub High Score website. This can be
5856
=== modified file 'cfdeploy'
--- cfdeploy 2014-11-07 00:06:24 +0000
+++ cfdeploy 2014-11-13 00:05:21 +0000
@@ -2,9 +2,48 @@
2# modeline vim:set syntax=python2# modeline vim:set syntax=python
3import argparse3import argparse
4import logging4import logging
5import webbrowser5import os
6import subprocess
7import sys
8from contextlib import contextmanager
6from functools import partial9from functools import partial
710
11
12def verify_dep(name):
13 try:
14 __import__(name)
15 return True
16 except ImportError:
17 pass
18 return False
19
20
21def install_python_deps():
22 print "Setting up virutalenv."
23 subprocess.check_output(['pip', 'install', 'wheel'])
24 for dep in ['progress', 'requests', 'jujuclient']:
25 subprocess.check_output(['pip', 'install',
26 '--use-wheel', '--find-links',
27 os.path.abspath('wheelhouse'),
28 dep])
29
30
31def prepare_runtime():
32 activate = "bin/activate_this.py"
33 if not verify_dep('virtualenv'):
34 print "(sudo) installing python-virtualenv package."
35 subprocess.check_call(
36 ['sudo', 'apt-get', 'install', '-yq', 'python-virutalenv'])
37 import virtualenv
38 venv = os.path.join(os.getcwd(), '.venv')
39 if not os.path.exists(venv):
40 virtualenv.create_environment(venv, site_packages=False)
41 venv_launcher = os.path.join(venv, activate)
42 execfile(venv_launcher, dict(__file__=venv_launcher))
43 install_python_deps()
44
45prepare_runtime()
46
8from cloudfoundry.releases import RELEASES47from cloudfoundry.releases import RELEASES
9from cloudfoundry.utils import (bootstrap,48from cloudfoundry.utils import (bootstrap,
10 cf_service,49 cf_service,
@@ -15,9 +54,13 @@
15 login,54 login,
16 reconciler_endpoint,55 reconciler_endpoint,
17 webadmin_endpoint,56 webadmin_endpoint,
57 sh,
18 socket_open,58 socket_open,
19 until,59 until,
20 wait_for)60 which
61 )
62
63from progress.bar import Bar
2164
2265
23def setup():66def setup():
@@ -35,31 +78,91 @@
35 return options78 return options
3679
3780
81# This is done because the default webbrowser invocation will output
82# on stderr and muck with the progress bar. By hiding the output
83# with devnull it proceeds as expected.
84show = sh.check('xdg-open')
85
86
87@contextmanager
88def devnull():
89 _save = sys.stderr
90 fp = open('/dev/null', 'w')
91 sys.stderr = fp
92 yield
93 sys.stderr = _save
94
95
38def show_reconciler():96def show_reconciler():
39 uri = "http://%s:8888/" % reconciler_endpoint()97 with devnull():
40 webbrowser.open_new_tab(uri)98 uri = "http://%s:8888/" % reconciler_endpoint()
99 show(uri)
41100
42101
43def show_webadmin():102def show_webadmin():
44 uri = "http://%s:8070/" % webadmin_endpoint()103 with devnull():
45 webbrowser.open_new_tab(uri)104 uri = "http://%s:8070/" % webadmin_endpoint()
105 show(uri)
106
107
108class ProgressBar(Bar):
109 message = "Deploying CloudFoundry"
110 fill = "."
111 suffix = "%(percent).1f%% %(elapsed_td)s"
112
113 def next(self, i=1, message=None):
114 if message:
115 self.message = message
116 super(ProgressBar, self).next(i)
117
118
119def install_deps():
120 if not which('cf'):
121 from platform import machine
122 if machine() != "x86_64":
123 print "Unable to install CF CLI for your architecture. "
124 "Deploy will not work as expected."
125 return
126 sh.wget('http://go-cli.s3-website-us-east-1.amazonaws.com/'
127 'releases/latest/cf-cli_amd64.deb')
128 print "Installing CF CLI (this requires sudo access)"
129 sh.sudo('dpkg', '-i', 'cf-cli_amd64.deb')
46130
47131
48def main():132def main():
49 options = setup()133 options = setup()
50 logging.basicConfig(level=options.log_level)134 logging.basicConfig(level=options.log_level)
135 install_deps()
136
137 bar = ProgressBar('Deploying CloudFoundry', max=10)
138 bar.start()
139 bar.next(message='Bootstrapping')
51 bootstrap()140 bootstrap()
52 until(juju_state_server)141 until(juju_state_server, bar=bar, message="Waiting for State Server")
142 bar.next(message='Deploying Orchestrator')
53 deploy(constraints=options.constraints,143 deploy(constraints=options.constraints,
54 generate_dependents=options.generate,144 generate_dependents=options.generate,
55 admin_password=options.admin_password)145 admin_password=options.admin_password)
56 until(lambda: socket_open(reconciler_endpoint(), 8888))146 until(lambda: socket_open(reconciler_endpoint(), 8888),
147 bar=bar, message="Waiting on Reconciler")
148 bar.next(message='Showing Reconciler')
57 show_reconciler()149 show_reconciler()
150
58 # Wait forever, its in the reconciler's hands now.151 # Wait forever, its in the reconciler's hands now.
59 wait_for(0, 30, cf_service, endpoint, partial(login, options.admin_password))152 until(cf_service, bar=bar, message="Waiting for Orchestrator Charm")
60 until(lambda: socket_open(webadmin_endpoint(), 8070))153 until(endpoint, bar=bar, message='Waiting for CloudFoundry API endpoint')
154 until(partial(login, options.admin_password),
155 bar=bar, message='Waiting to login to CloudFoundry (long)')
156 until(lambda: socket_open(webadmin_endpoint(), 8070),
157 bar=bar, message="Waiting on webadmin.")
158 bar.next(message="Opening Admin Console")
61 show_webadmin()159 show_webadmin()
62 print "You should now be logged into a running CF deployment"160 bar.finish()
161
162 print "You should now be logged into a running CF deployment."
163 if which('cf'):
164 print "The 'cf' command line client is installed and available."
165
63166
64if __name__ == "__main__":167if __name__ == "__main__":
65 main()168 main()
66169
=== modified file 'cloudfoundry/utils.py'
--- cloudfoundry/utils.py 2014-11-07 00:06:24 +0000
+++ cloudfoundry/utils.py 2014-11-13 00:05:21 +0000
@@ -168,6 +168,7 @@
168168
169def command(*base_args, **kwargs):169def command(*base_args, **kwargs):
170 check = kwargs.pop('check', False)170 check = kwargs.pop('check', False)
171 throw = kwargs.pop('throw', True)
171172
172 def callable_command(*args, **kws):173 def callable_command(*args, **kws):
173 kwargs.update(kws)174 kwargs.update(kws)
@@ -176,10 +177,20 @@
176 if 'env' not in kwargs:177 if 'env' not in kwargs:
177 kwargs['env'] = os.environ178 kwargs['env'] = os.environ
178 logging.debug("invoke: %s", all_args)179 logging.debug("invoke: %s", all_args)
180
181 p = subprocess.Popen(all_args,
182 stdout=subprocess.PIPE,
183 stderr=subprocess.STDOUT,
184 **kwargs)
185 output, _ = p.communicate()
186 ret_code = p.poll()
187 logging.debug('result: %s', output)
179 if check is True:188 if check is True:
180 return subprocess.check_call(all_args, **kwargs)189 if ret_code != 0 and throw:
190 raise subprocess.CalledProcessError(ret_code, all_args, output=output)
191 return ret_code
181 else:192 else:
182 return subprocess.check_output(all_args, **kwargs).strip()193 return output.strip()
183 return callable_command194 return callable_command
184195
185196
@@ -196,7 +207,7 @@
196207
197sh = Commander()208sh = Commander()
198dig = command('dig', '+short')209dig = command('dig', '+short')
199api_endpoints = sh.check('juju', 'api-endpoints')210api_endpoints = sh.check('juju', 'api-endpoints', throw=False)
200211
201212
202def current_env():213def current_env():
@@ -210,7 +221,7 @@
210 'environments/%s.jenv' % cenv)221 'environments/%s.jenv' % cenv)
211222
212223
213def wait_for(timeout, interval, *callbacks):224def wait_for(timeout, interval, *callbacks, **kwargs):
214 """225 """
215 Repeatedly try callbacks until all return True226 Repeatedly try callbacks until all return True
216227
@@ -223,25 +234,35 @@
223 hardware fails, or the heat death of the universe.234 hardware fails, or the heat death of the universe.
224 """235 """
225 start = time.time()236 start = time.time()
237 if timeout:
238 end = start + timeout
239 else:
240 end = 0
241
242 bar = kwargs.get('bar', None)
243 message = kwargs.get('message', None)
244 once = 1
226 while True:245 while True:
227 passes = True246 passes = True
228 for callback in callbacks:247 if end > 0 and time.time() > end:
229 result = callback()
230 passes = passes & bool(result)
231 if passes is False:
232 break
233 if passes is True:
234 break
235 current = time.time()
236 if timeout != 0 and (
237 current - start >= timeout or
238 (current - start) + interval > timeout):
239 raise OSError("Timeout exceeded in wait_for")248 raise OSError("Timeout exceeded in wait_for")
240 time.sleep(interval)249 if bar:
241250 bar.next(once, message=message)
242251 if once == 1:
243def until(*callbacks):252 once = 0
244 return wait_for(0, 20, *callbacks)253 if int(time.time()) % interval == 0:
254 for callback in callbacks:
255 result = callback()
256 passes = passes & bool(result)
257 if passes is False:
258 break
259 if passes is True:
260 break
261 time.sleep(1)
262
263
264def until(*callbacks, **kwargs):
265 return wait_for(0, 20, *callbacks, **kwargs)
245266
246267
247def juju_state_server():268def juju_state_server():
@@ -249,7 +270,6 @@
249 return False270 return False
250 endpoints = json.loads(sh.juju('api-endpoints', '--format=json'))271 endpoints = json.loads(sh.juju('api-endpoints', '--format=json'))
251 for ep in endpoints:272 for ep in endpoints:
252 print "Attempt to connect to juju state server:", ep
253 host, port = ep.split(':', 1)273 host, port = ep.split(':', 1)
254 result = socket_open(host, int(port))274 result = socket_open(host, int(port))
255 if result is True:275 if result is True:
@@ -266,7 +286,6 @@
266 # .jenv with the state server286 # .jenv with the state server
267 sh.juju('status')287 sh.juju('status')
268 data = yaml.load(open(get_jenv()))288 data = yaml.load(open(get_jenv()))
269 print "Waiting for state server(s)", data['state-servers']
270 return data.get('state-servers', []) != []289 return data.get('state-servers', []) != []
271 current = current_env()290 current = current_env()
272 wait_for(1000, 20, env_connection)291 wait_for(1000, 20, env_connection)
@@ -387,7 +406,6 @@
387 if constraints:406 if constraints:
388 args.append('--constraints=%s' % constraints)407 args.append('--constraints=%s' % constraints)
389 args.append('local:trusty/cloudfoundry')408 args.append('local:trusty/cloudfoundry')
390 print "Deploying:", ' '.join(args)
391 sh.juju(*args)409 sh.juju(*args)
392 time.sleep(5)410 time.sleep(5)
393 sh.juju('expose', 'cloudfoundry')411 sh.juju('expose', 'cloudfoundry')
394412
=== modified file 'reconciler/app.py'
--- reconciler/app.py 2014-11-03 16:13:57 +0000
+++ reconciler/app.py 2014-11-13 00:05:21 +0000
@@ -49,7 +49,6 @@
49import tornado.web49import tornado.web
5050
51from tornado.options import define, options51from tornado.options import define, options
52from cloudfoundry import config
53from cloudfoundry import utils52from cloudfoundry import utils
54from cloudfoundry.path import path53from cloudfoundry.path import path
55from reconciler import strategy54from reconciler import strategy
@@ -57,6 +56,8 @@
57from reconciler.ui.app import DashboardIO56from reconciler.ui.app import DashboardIO
58from reconciler.ui.app import static_resources57from reconciler.ui.app import static_resources
5958
59import config
60
60application = None61application = None
61env_name = None62env_name = None
62server = None63server = None
@@ -283,7 +284,6 @@
283 loop = tornado.ioloop.IOLoop.instance()284 loop = tornado.ioloop.IOLoop.instance()
284 # call poll_health ASAP, to populate the initial health data, and285 # call poll_health ASAP, to populate the initial health data, and
285 # also schedule poll_health for every 60s to refresh health data286 # also schedule poll_health for every 60s to refresh health data
286
287 loop.add_callback(poll_health)287 loop.add_callback(poll_health)
288 tornado.ioloop.PeriodicCallback(poll_health, 60000, io_loop=loop).start()288 tornado.ioloop.PeriodicCallback(poll_health, 60000, io_loop=loop).start()
289 tornado.ioloop.PeriodicCallback(poll_current_state, 60000,289 tornado.ioloop.PeriodicCallback(poll_current_state, 60000,
@@ -294,6 +294,5 @@
294 io_loop=loop).start()294 io_loop=loop).start()
295 loop.start()295 loop.start()
296296
297
298if __name__ == "__main__":297if __name__ == "__main__":
299 main()298 main()
300299
=== renamed file 'cloudfoundry/config.py' => 'reconciler/config.py'
=== modified file 'reconciler/strategy.py'
--- reconciler/strategy.py 2014-11-05 23:40:05 +0000
+++ reconciler/strategy.py 2014-11-13 00:05:21 +0000
@@ -7,7 +7,7 @@
7from tornado import gen7from tornado import gen
88
9from reconciler import tactics9from reconciler import tactics
10from cloudfoundry.config import (PENDING, COMPLETE, FAILED, RUNNING)10from reconciler.config import (PENDING, COMPLETE, FAILED, RUNNING)
11from cloudfoundry import utils11from cloudfoundry import utils
12from cloudfoundry import tsort12from cloudfoundry import tsort
1313
@@ -160,7 +160,7 @@
160 elif expose_intent is False and exposed is True:160 elif expose_intent is False and exposed is True:
161 result.append(tactics.UnexposeTactic(service=service))161 result.append(tactics.UnexposeTactic(service=service))
162162
163 logging.debug("Build New %s", result)163 logging.debug("Build Strategy %s", result)
164 return result164 return result
165165
166 def build_relations(self):166 def build_relations(self):
167167
=== modified file 'reconciler/tactics.py'
--- reconciler/tactics.py 2014-11-05 23:40:05 +0000
+++ reconciler/tactics.py 2014-11-13 00:05:21 +0000
@@ -3,7 +3,7 @@
3import os3import os
4import shutil4import shutil
55
6from cloudfoundry.config import (6from reconciler.config import (
7 PENDING, COMPLETE, RUNNING, FAILED, STATES7 PENDING, COMPLETE, RUNNING, FAILED, STATES
8 )8 )
99
@@ -169,7 +169,7 @@
169class UnexposeTactic(Tactic):169class UnexposeTactic(Tactic):
170 name = "Unexpose Service"170 name = "Unexpose Service"
171171
172 def run(self, env, **kwargs):172 def _run(self, env, **kwargs):
173 s = kwargs['service']173 s = kwargs['service']
174 env.unexpose(s['service_name'])174 env.unexpose(s['service_name'])
175175
176176
=== modified file 'tests/test_strategy.py'
--- tests/test_strategy.py 2014-10-30 18:34:30 +0000
+++ tests/test_strategy.py 2014-11-13 00:05:21 +0000
@@ -2,7 +2,7 @@
2import unittest2import unittest
3import mock3import mock
44
5from cloudfoundry import config5from reconciler import config
6from reconciler import tactics6from reconciler import tactics
7from reconciler import strategy7from reconciler import strategy
88
99
=== modified file 'tests/test_utils.py'
--- tests/test_utils.py 2014-10-27 19:48:56 +0000
+++ tests/test_utils.py 2014-11-13 00:05:21 +0000
@@ -7,10 +7,10 @@
77
88
9class TestUtils(unittest.TestCase):9class TestUtils(unittest.TestCase):
10 @mock.patch('subprocess.check_output')10 @mock.patch('cloudfoundry.utils.sh.juju')
11 def test_current_env(self, check_output):11 def test_current_env(self, check_output):
12 utils.current_env()12 utils.current_env()
13 check_output.assert_called_once_with(('juju', 'switch'), env=mock.ANY)13 check_output.assert_called_once_with('switch')
1414
15 def test_flatten_relations(self):15 def test_flatten_relations(self):
16 r = utils.flatten_relations([16 r = utils.flatten_relations([
1717
=== modified file 'tox.ini'
--- tox.ini 2014-09-30 21:15:05 +0000
+++ tox.ini 2014-11-13 00:05:21 +0000
@@ -30,3 +30,4 @@
30 clint30 clint
31 path.py31 path.py
32 subparse32 subparse
33 progress
3334
=== added file 'wheelhouse/progress-1.2-py2-none-any.whl'
34Binary files wheelhouse/progress-1.2-py2-none-any.whl 1970-01-01 00:00:00 +0000 and wheelhouse/progress-1.2-py2-none-any.whl 2014-11-13 00:05:21 +0000 differ35Binary files wheelhouse/progress-1.2-py2-none-any.whl 1970-01-01 00:00:00 +0000 and wheelhouse/progress-1.2-py2-none-any.whl 2014-11-13 00:05:21 +0000 differ

Subscribers

People subscribed via source and target branches