← Back to team overview

cf-charmers team mailing list archive

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

 

Benjamin Saller has proposed merging lp:~bcsaller/charms/trusty/cloudfoundry/progressbar into lp:~cf-charmers/charms/trusty/cloudfoundry/trunk.

Requested reviews:
  Cloud Foundry Charmers (cf-charmers)

For more details, see:
https://code.launchpad.net/~bcsaller/charms/trusty/cloudfoundry/progressbar/+merge/241618
-- 
https://code.launchpad.net/~bcsaller/charms/trusty/cloudfoundry/progressbar/+merge/241618
Your team Cloud Foundry Charmers is requested to review the proposed merge of lp:~bcsaller/charms/trusty/cloudfoundry/progressbar into lp:~cf-charmers/charms/trusty/cloudfoundry/trunk.
=== modified file 'README.rst'
--- README.rst	2014-11-06 23:37:55 +0000
+++ README.rst	2014-11-12 20:10:11 +0000
@@ -24,11 +24,6 @@
 We provide a set of helper scripts (in bash) which can be used to assist in the deployment and
 management of CF.
 
-You will need the cf command line client. This can easily be installed using the following:
-
-  wget http://go-cli.s3-website-us-east-1.amazonaws.com/releases/latest/cf-cli_amd64.deb
-  dpkg -i cf-cli_amd64.deb
-
 
 Deployment
 ----------
@@ -51,7 +46,10 @@
 will block until the deployment is fully up and running. It also will create a
 CF "admin" user with the given password, leaving you logged in and ready to push
 a CF app. Note that while this can take quite some time, the whole process is
-automated.
+automated. 
+
+If you don't have the cf command line client the latest one will be installed
+from a .deb package to your local system as part of cfdeploy.
 
 Once the deploy completes, you can push apps. A sample application we recommend
 to become familiar with the system is GitHub High Score website. This can be

=== modified file 'cfdeploy'
--- cfdeploy	2014-11-07 00:06:24 +0000
+++ cfdeploy	2014-11-12 20:10:11 +0000
@@ -1,8 +1,11 @@
 #!/usr/bin/env python
 # modeline vim:set syntax=python
+execfile('.tox/py27/bin/activate_this.py',
+         dict(__file__='.tox/py27/bin/activate_this.py'))
 import argparse
 import logging
-import webbrowser
+import sys
+from contextlib import contextmanager
 from functools import partial
 
 from cloudfoundry.releases import RELEASES
@@ -15,9 +18,13 @@
                                 login,
                                 reconciler_endpoint,
                                 webadmin_endpoint,
+                                sh,
                                 socket_open,
                                 until,
-                                wait_for)
+                                which
+                                )
+
+from progress.bar import Bar
 
 
 def setup():
@@ -35,31 +42,91 @@
     return options
 
 
+# This is done because the default webbrowser invocation will output
+# on stderr and muck with the progress bar. By hiding the output
+# with devnull it proceeds as expected.
+show = sh.check('xdg-open')
+
+
+@contextmanager
+def devnull():
+    _save = sys.stderr
+    fp = open('/dev/null', 'w')
+    sys.stderr = fp
+    yield
+    sys.stderr = _save
+
+
 def show_reconciler():
-    uri = "http://%s:8888/"; % reconciler_endpoint()
-    webbrowser.open_new_tab(uri)
+    with devnull():
+        uri = "http://%s:8888/"; % reconciler_endpoint()
+        show(uri)
 
 
 def show_webadmin():
-    uri = "http://%s:8070/"; % webadmin_endpoint()
-    webbrowser.open_new_tab(uri)
+    with devnull():
+        uri = "http://%s:8070/"; % webadmin_endpoint()
+        show(uri)
+
+
+class ProgressBar(Bar):
+    message = "Deploying CloudFoundry"
+    fill = "."
+    suffix = "%(percent).1f%% %(elapsed_td)s"
+
+    def next(self, i=1, message=None):
+        if message:
+            self.message = message
+        super(ProgressBar, self).next(i)
+
+
+def install_deps():
+    if not which('cf'):
+        from platform import machine
+        if machine() != "x86_64":
+            print "Unable to install CF CLI for your architecture. "
+            "Deploy will not work as expected."
+            return
+        sh.wget('http://go-cli.s3-website-us-east-1.amazonaws.com/'
+                'releases/latest/cf-cli_amd64.deb')
+        print "Installing CF CLI (this requires sudo access)"
+        sh.sudo('dpkg', '-i', 'cf-cli_amd64.deb')
 
 
 def main():
     options = setup()
     logging.basicConfig(level=options.log_level)
+    install_deps()
+
+    bar = ProgressBar('Deploying CloudFoundry', max=10)
+    bar.start()
+    bar.next(message='Bootstrapping')
     bootstrap()
-    until(juju_state_server)
+    until(juju_state_server, bar=bar, message="Waiting for State Server")
+    bar.next(message='Deploying Orchestrator')
     deploy(constraints=options.constraints,
            generate_dependents=options.generate,
            admin_password=options.admin_password)
-    until(lambda: socket_open(reconciler_endpoint(), 8888))
+    until(lambda: socket_open(reconciler_endpoint(), 8888),
+          bar=bar, message="Waiting on Reconciler")
+    bar.next(message='Showing Reconciler')
     show_reconciler()
+
     # Wait forever, its in the reconciler's hands now.
-    wait_for(0, 30, cf_service, endpoint, partial(login, options.admin_password))
-    until(lambda: socket_open(webadmin_endpoint(), 8070))
+    until(cf_service, bar=bar, message="Waiting for Orchestrator Charm")
+    until(endpoint, bar=bar, message='Waiting for CloudFoundry API endpoint')
+    until(partial(login, options.admin_password),
+          bar=bar, message='Waiting to login to CloudFoundry (long)')
+    until(lambda: socket_open(webadmin_endpoint(), 8070),
+          bar=bar, message="Waiting on webadmin.")
+    bar.next(message="Opening Admin Console")
     show_webadmin()
-    print "You should now be logged into a running CF deployment"
+    bar.finish()
+
+    print "You should now be logged into a running CF deployment."
+    if which('cf'):
+        print "The 'cf' command line client is installed and available."
+
 
 if __name__ == "__main__":
     main()

=== modified file 'cloudfoundry/utils.py'
--- cloudfoundry/utils.py	2014-11-07 00:06:24 +0000
+++ cloudfoundry/utils.py	2014-11-12 20:10:11 +0000
@@ -168,6 +168,7 @@
 
 def command(*base_args, **kwargs):
     check = kwargs.pop('check', False)
+    throw = kwargs.pop('throw', True)
 
     def callable_command(*args, **kws):
         kwargs.update(kws)
@@ -176,10 +177,20 @@
         if 'env' not in kwargs:
             kwargs['env'] = os.environ
         logging.debug("invoke: %s", all_args)
+
+        p = subprocess.Popen(all_args,
+                             stdout=subprocess.PIPE,
+                             stderr=subprocess.STDOUT,
+                             **kwargs)
+        output, _ = p.communicate()
+        ret_code = p.poll()
+        logging.debug('result: %s', output)
         if check is True:
-            return subprocess.check_call(all_args, **kwargs)
+            if ret_code != 0 and throw:
+                raise subprocess.CalledProcessError(ret_code, all_args, output=output)
+            return ret_code
         else:
-            return subprocess.check_output(all_args, **kwargs).strip()
+            return output.strip()
     return callable_command
 
 
@@ -196,7 +207,7 @@
 
 sh = Commander()
 dig = command('dig', '+short')
-api_endpoints = sh.check('juju', 'api-endpoints')
+api_endpoints = sh.check('juju', 'api-endpoints', throw=False)
 
 
 def current_env():
@@ -210,7 +221,7 @@
                         'environments/%s.jenv' % cenv)
 
 
-def wait_for(timeout, interval, *callbacks):
+def wait_for(timeout, interval, *callbacks, **kwargs):
     """
     Repeatedly try callbacks until all return True
 
@@ -223,25 +234,35 @@
     hardware fails, or the heat death of the universe.
     """
     start = time.time()
+    if timeout:
+        end = start + timeout
+    else:
+        end = 0
+
+    bar = kwargs.get('bar', None)
+    message = kwargs.get('message', None)
+    once = 1
     while True:
         passes = True
-        for callback in callbacks:
-            result = callback()
-            passes = passes & bool(result)
-            if passes is False:
-                break
-        if passes is True:
-            break
-        current = time.time()
-        if timeout != 0 and (
-            current - start >= timeout or
-                (current - start) + interval > timeout):
+        if end > 0 and time.time() > end:
             raise OSError("Timeout exceeded in wait_for")
-        time.sleep(interval)
-
-
-def until(*callbacks):
-    return wait_for(0, 20, *callbacks)
+        if bar:
+            bar.next(once, message=message)
+            if once == 1:
+                once = 0
+        if int(time.time()) % interval == 0:
+            for callback in callbacks:
+                result = callback()
+                passes = passes & bool(result)
+                if passes is False:
+                    break
+            if passes is True:
+                break
+        time.sleep(1)
+
+
+def until(*callbacks, **kwargs):
+    return wait_for(0, 20, *callbacks, **kwargs)
 
 
 def juju_state_server():
@@ -249,7 +270,6 @@
         return False
     endpoints = json.loads(sh.juju('api-endpoints', '--format=json'))
     for ep in endpoints:
-        print "Attempt to connect to juju state server:", ep
         host, port = ep.split(':', 1)
         result = socket_open(host, int(port))
         if result is True:
@@ -266,7 +286,6 @@
             # .jenv with the state server
             sh.juju('status')
             data = yaml.load(open(get_jenv()))
-            print "Waiting for state server(s)", data['state-servers']
             return data.get('state-servers', []) != []
         current = current_env()
         wait_for(1000, 20, env_connection)
@@ -387,7 +406,6 @@
         if constraints:
             args.append('--constraints=%s' % constraints)
         args.append('local:trusty/cloudfoundry')
-        print "Deploying:", ' '.join(args)
         sh.juju(*args)
         time.sleep(5)
         sh.juju('expose', 'cloudfoundry')

=== modified file 'reconciler/app.py'
--- reconciler/app.py	2014-11-03 16:13:57 +0000
+++ reconciler/app.py	2014-11-12 20:10:11 +0000
@@ -49,7 +49,6 @@
 import tornado.web
 
 from tornado.options import define, options
-from cloudfoundry import config
 from cloudfoundry import utils
 from cloudfoundry.path import path
 from reconciler import strategy
@@ -57,6 +56,8 @@
 from reconciler.ui.app import DashboardIO
 from reconciler.ui.app import static_resources
 
+import config
+
 application = None
 env_name = None
 server = None
@@ -283,7 +284,6 @@
     loop = tornado.ioloop.IOLoop.instance()
     # call poll_health ASAP, to populate the initial health data, and
     # also schedule poll_health for every 60s to refresh health data
-
     loop.add_callback(poll_health)
     tornado.ioloop.PeriodicCallback(poll_health, 60000, io_loop=loop).start()
     tornado.ioloop.PeriodicCallback(poll_current_state, 60000,
@@ -294,6 +294,5 @@
                                         io_loop=loop).start()
     loop.start()
 
-
 if __name__ == "__main__":
     main()

=== 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-12 20:10:11 +0000
@@ -7,7 +7,7 @@
 from tornado import gen
 
 from reconciler import tactics
-from cloudfoundry.config import (PENDING, COMPLETE, FAILED, RUNNING)
+from reconciler.config import (PENDING, COMPLETE, FAILED, RUNNING)
 from cloudfoundry import utils
 from cloudfoundry import tsort
 
@@ -160,7 +160,7 @@
             elif expose_intent is False and exposed is True:
                 result.append(tactics.UnexposeTactic(service=service))
 
-        logging.debug("Build New %s", result)
+        logging.debug("Build Strategy %s", result)
         return result
 
     def build_relations(self):

=== modified file 'reconciler/tactics.py'
--- reconciler/tactics.py	2014-11-05 23:40:05 +0000
+++ reconciler/tactics.py	2014-11-12 20:10:11 +0000
@@ -3,7 +3,7 @@
 import os
 import shutil
 
-from cloudfoundry.config import (
+from reconciler.config import (
     PENDING, COMPLETE, RUNNING, FAILED, STATES
     )
 
@@ -169,7 +169,7 @@
 class UnexposeTactic(Tactic):
     name = "Unexpose Service"
 
-    def run(self, env, **kwargs):
+    def _run(self, env, **kwargs):
         s = kwargs['service']
         env.unexpose(s['service_name'])
 

=== modified file 'tests/test_strategy.py'
--- tests/test_strategy.py	2014-10-30 18:34:30 +0000
+++ tests/test_strategy.py	2014-11-12 20:10:11 +0000
@@ -2,7 +2,7 @@
 import unittest
 import mock
 
-from cloudfoundry import config
+from reconciler import config
 from reconciler import tactics
 from reconciler import strategy
 

=== modified file 'tests/test_utils.py'
--- tests/test_utils.py	2014-10-27 19:48:56 +0000
+++ tests/test_utils.py	2014-11-12 20:10:11 +0000
@@ -7,10 +7,10 @@
 
 
 class TestUtils(unittest.TestCase):
-    @mock.patch('subprocess.check_output')
+    @mock.patch('cloudfoundry.utils.sh.juju')
     def test_current_env(self, check_output):
         utils.current_env()
-        check_output.assert_called_once_with(('juju', 'switch'), env=mock.ANY)
+        check_output.assert_called_once_with('switch')
 
     def test_flatten_relations(self):
         r = utils.flatten_relations([

=== modified file 'tox.ini'
--- tox.ini	2014-09-30 21:15:05 +0000
+++ tox.ini	2014-11-12 20:10:11 +0000
@@ -30,3 +30,4 @@
     clint
     path.py
     subparse
+    progress

=== added file 'wheelhouse/progress-1.2-py2-none-any.whl'
Binary 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-12 20:10:11 +0000 differ

Follow ups