Merge lp:~frankban/juju-quickstart/uncommitted-bundles into lp:juju-quickstart

Proposed by Francesco Banconi
Status: Merged
Merged at revision: 135
Proposed branch: lp:~frankban/juju-quickstart/uncommitted-bundles
Merge into: lp:juju-quickstart
Diff against target: 665 lines (+332/-57)
13 files modified
quickstart/__init__.py (+1/-1)
quickstart/app.py (+62/-6)
quickstart/juju.py (+9/-0)
quickstart/jujugui.py (+28/-0)
quickstart/jujutools.py (+2/-5)
quickstart/manage.py (+31/-21)
quickstart/settings.py (+7/-2)
quickstart/tests/functional/test_functional.py (+8/-0)
quickstart/tests/test_app.py (+99/-16)
quickstart/tests/test_juju.py (+12/-0)
quickstart/tests/test_jujugui.py (+46/-0)
quickstart/tests/test_manage.py (+23/-2)
tox.ini (+4/-4)
To merge this branch: bzr merge lp:~frankban/juju-quickstart/uncommitted-bundles
Reviewer Review Type Date Requested Status
Brad Crittenden (community) code + qa Approve
Review via email: mp+261200@code.launchpad.net

Commit message

Add support for uncommitted bundles.

Description of the change

Add support for uncommitted bundles.

Introduce the -u/--uncommitted flag, which enables
uncommitted bundle support.

Improve output messages and tokens handling.

Also update jujubundlelib dep to latest version.

TESTS:
`make fcheck` and wait a while for the functional tests
to complete.

QA:
- deploy bundles as usual:
  `devenv/bin/juju-quickstart mediawiki-single`;
- deploy uncommitted bundles:
  `devenv/bin/juju-quickstart -u u/openstack-charmers/openstack`;

https://codereview.appspot.com/247800043/

To post a comment you must log in.
Revision history for this message
Francesco Banconi (frankban) wrote :

Reviewers: mp+261200_code.launchpad.net,

Message:
Please take a look.

Description:
Add support for uncommitted bundles.

Introduce the -u/--uncommitted flag, which enables
uncommitted bundle support.

Improve output messages and tokens handling.

Also update jujubundlelib dep to latest version.

TESTS:
`make fcheck` and wait a while for the functional tests
to complete.

QA:
- deploy bundles as usual:
   `devenv/bin/juju-quickstart mediawiki-single`;
- deploy uncommitted bundles:
   `devenv/bin/juju-quickstart -u u/openstack-charmers/openstack`;

https://code.launchpad.net/~frankban/juju-quickstart/uncommitted-bundles/+merge/261200

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/247800043/

Affected files (+329, -57 lines):
   A [revision details]
   M quickstart/__init__.py
   M quickstart/app.py
   M quickstart/juju.py
   M quickstart/jujugui.py
   M quickstart/jujutools.py
   M quickstart/manage.py
   M quickstart/settings.py
   M quickstart/tests/functional/test_functional.py
   M quickstart/tests/test_app.py
   M quickstart/tests/test_juju.py
   M quickstart/tests/test_jujugui.py
   M quickstart/tests/test_manage.py
   M tox.ini

Revision history for this message
Richard Harding (rharding) wrote :

LGTM, I think most of my questions are answered in some way or another.
I understand that this is really checking is the version of the charm
ready, not the gui, so checking the version.js isn't a perfect match. I
still can't help but feel that the charm revision is tied to the gui
release inside and maybe it's a better overall solution to this.
However, this is a perfectly reasonable way to go about it and should
work out most of the time.

https://codereview.appspot.com/247800043/diff/1/quickstart/app.py
File quickstart/app.py (right):

https://codereview.appspot.com/247800043/diff/1/quickstart/app.py#newcode659
quickstart/app.py:659: 'deployed Juju GUI charm ({}): requested bundle
deployment '
should we hint at a juju-gui upgrade here?

https://codereview.appspot.com/247800043/diff/1/quickstart/app.py#newcode663
quickstart/app.py:663: print('requesting uncommitted deployment of {}
with the following '
"uncommitted deployment" to "uncomitted loading"? deployment just
strikes me as very...deployed.

https://codereview.appspot.com/247800043/diff/1/quickstart/app.py#newcode679
quickstart/app.py:679: return response['Token']
does this open the gui url as it does when you just run juju-quickstart
without the deployment?

https://codereview.appspot.com/247800043/diff/1/quickstart/jujugui.py
File quickstart/jujugui.py (right):

https://codereview.appspot.com/247800043/diff/1/quickstart/jujugui.py#newcode62
quickstart/jujugui.py:62: def is_promulgated(reference):
I don't get this part. Can this not work if the gui you deployed is a
local deployment of the GUI?

Should this better be a check for a flag/config/call to the GUI source
instead? Can it just check something like a check against version.js in
some way?

https://codereview.appspot.com/247800043/diff/1/quickstart/jujutools.py
File quickstart/jujutools.py (right):

https://codereview.appspot.com/247800043/diff/1/quickstart/jujutools.py#newcode60
quickstart/jujutools.py:60: if not jujugui.is_promulgated(charm_ref):
oic, so we assume. I still wonder about using version.js vs
is_promulgated.

https://codereview.appspot.com/247800043/

145. By Francesco Banconi

Changes as per review.

Revision history for this message
Francesco Banconi (frankban) wrote :

Please take a look.

https://codereview.appspot.com/247800043/diff/1/quickstart/app.py
File quickstart/app.py (right):

https://codereview.appspot.com/247800043/diff/1/quickstart/app.py#newcode659
quickstart/app.py:659: 'deployed Juju GUI charm ({}): requested bundle
deployment '
On 2015/06/05 12:28:41, rharding wrote:
> should we hint at a juju-gui upgrade here?

Good idea, done.

https://codereview.appspot.com/247800043/diff/1/quickstart/app.py#newcode663
quickstart/app.py:663: print('requesting uncommitted deployment of {}
with the following '
On 2015/06/05 12:28:41, rharding wrote:
> "uncommitted deployment" to "uncomitted loading"? deployment just
strikes me as
> very...deployed.

Done.

https://codereview.appspot.com/247800043/diff/1/quickstart/app.py#newcode679
quickstart/app.py:679: return response['Token']
On 2015/06/05 12:28:41, rharding wrote:
> does this open the gui url as it does when you just run
juju-quickstart without
> the deployment?

This token, if returned, is used to build the GUI URL.
Opening the GUI on the browser is done by manage.run.

https://codereview.appspot.com/247800043/diff/1/quickstart/jujutools.py
File quickstart/jujutools.py (right):

https://codereview.appspot.com/247800043/diff/1/quickstart/jujutools.py#newcode60
quickstart/jujutools.py:60: if not jujugui.is_promulgated(charm_ref):
On 2015/06/05 12:28:42, rharding wrote:
> oic, so we assume. I still wonder about using version.js vs
is_promulgated.

Well, some of the features we check (e.g. bundle deployment, uncommitted
bundles or new API endpoints) must be implemented principally on the
server (because qucikstart interacts mostly with the GUI server via its
API), and that's why in the cases we know the promulgated revision we
can safely check for their availability.
Given this is quickstart, I'd expect that we are dealing with
promulgated GUI charms 99% of the times, as you already mentioned. For
the remaining cases we trust the user and we expect her to be smart
enough to handle possible (and graceful) quickstart errors, which is the
only bad thing that can happen if the current customized charm does not
support a requested feature.

https://codereview.appspot.com/247800043/

Revision history for this message
Brad Crittenden (bac) wrote :

Francesco I can no longer log in to Rietveld so I'm making comments here only.

The code looks good. I'll do QA now.

Revision history for this message
Brad Crittenden (bac) wrote :

QA was OK.

review: Approve (code + qa)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'quickstart/__init__.py'
--- quickstart/__init__.py 2015-05-22 11:33:00 +0000
+++ quickstart/__init__.py 2015-06-05 13:07:56 +0000
@@ -45,7 +45,7 @@
45Once Juju has been installed, the command can also be run as a juju plugin,45Once Juju has been installed, the command can also be run as a juju plugin,
46without the hyphen ("juju quickstart").46without the hyphen ("juju quickstart").
47"""47"""
48VERSION = (2, 1, 2)48VERSION = (2, 2, 0)
4949
5050
51def get_version():51def get_version():
5252
=== modified file 'quickstart/app.py'
--- quickstart/app.py 2015-05-22 17:11:26 +0000
+++ quickstart/app.py 2015-06-05 13:07:56 +0000
@@ -621,23 +621,79 @@
621 return dict((k, v.get('value')) for k, v in config.items())621 return dict((k, v.get('value')) for k, v in config.items())
622622
623623
624def deploy_bundle(env, bundle):624def deploy_bundle(env, bundle, uncommitted, charm_ref):
625 """Deploy the given bundle connecting to the given environment.625 """Deploy the given bundle connecting to the given environment.
626626
627 Receive the environment connection to use for deploying the bundle and the627 Receive:
628 bundle object as an instance of "quickstart.models.bundles.Bundle".628 - env: the environment connection to use for deploying the bundle;
629629 - bundle: the bundle to be deployed, as an instance of
630 Raise a ProgramExit if the API server returns an error response.630 "quickstart.models.bundles.Bundle";
631 - uncommitted: a flag indicating whether the given bundle must be
632 deployed in uncommitted mode;
633 - charm_ref: the Juju GUI charm reference, used in the case uncommitted
634 is True in order to verify that the GUI service supports uncommitted
635 bundle deployments.
636
637 If uncommitted is True, do not actually deploy the workload, but instead
638 register it as a set of changes on the GUI server, and return a token
639 identifying the change set, to be reused later by the Juju GUI.
640 If uncommitted is False, start the bundle deployment and return None.
641
642 Raise a ProgramExit if any of the API server calls return an error.
631 """643 """
644 services = ', '.join(bundle.services().keys())
645 ref = bundle.reference
646 details = ''
647 if ref is not None:
648 details = (
649 '\nmore details about this bundle can be found at\n'
650 ' {}'.format(ref.jujucharms_url()))
651
652 if uncommitted:
653 # Ensure the currently deployed Juju GUI service supports change sets.
654 series = charm_ref.series
655 revision = settings.MINIMUM_REVISIONS_FOR_UNCOMMITTED_BUNDLES[series]
656 if jujugui.is_promulgated(charm_ref) and charm_ref.revision < revision:
657 logging.warn(
658 'uncommitted bundles loading is not supported by currently '
659 'deployed Juju GUI charm ({}): requested bundle deployment '
660 'will be ignored'.format(charm_ref))
661 logging.warn(
662 'run "juju upgrade-charm {}" to enable uncommitted bundles '
663 'support'.format(settings.JUJU_GUI_SERVICE_NAME))
664 return None
665 # Register the changes in the GUI server.
666 print('requesting uncommitted bundle loading of {} with the following '
667 'services:\n {}'.format(bundle, services))
668 try:
669 response = env.set_changes(bundle.serialize())
670 except jujuclient.EnvError as err:
671 raise ProgramExit(
672 'bad API server response: {}'.format(err.message))
673 errors = response.get('Errors')
674 if errors:
675 # This is unexpected, the bundle at this point is already validated
676 # using juju bundle lib.
677 msg = ', '.join(errors)
678 raise ProgramExit('cannot generate the change set: {}'.format(msg))
679 print('the bundle is ready to be deployed\n'
680 'use the GUI to configure the bundle and to start the '
681 'deployment process{}'.format(details))
682 return response['Token']
683
684 print('requesting a deployment of {} with the following services:\n'
685 ' {}'.format(bundle, services))
632 # XXX frankban 2015-02-26: use new bundle format if the GUI server is686 # XXX frankban 2015-02-26: use new bundle format if the GUI server is
633 # capable of handling bundle deployments with the API version 4.687 # capable of handling bundle deployments with the API version 4.
634 yaml = bundle.serialize_legacy()688 yaml = bundle.serialize_legacy()
635 version = 3689 version = 3
636 # XXX frankban 2015-02-26: find and implement a better way to increase the690 # XXX frankban 2015-02-26: find and implement a better way to increase the
637 # bundle deployments count.691 # bundle deployments count.
638 ref = bundle.reference
639 bundle_id = None if ref is None else ref.charmworld_id692 bundle_id = None if ref is None else ref.charmworld_id
640 try:693 try:
641 env.deploy_bundle(yaml, version, bundle_id=bundle_id)694 env.deploy_bundle(yaml, version, bundle_id=bundle_id)
642 except jujuclient.EnvError as err:695 except jujuclient.EnvError as err:
643 raise ProgramExit('bad API server response: {}'.format(err.message))696 raise ProgramExit('bad API server response: {}'.format(err.message))
697 print('bundle deployment request accepted\nuse the GUI to check the '
698 'bundle deployment progress{}'.format(details))
699 return None
644700
=== modified file 'quickstart/juju.py'
--- quickstart/juju.py 2015-02-26 05:03:10 +0000
+++ quickstart/juju.py 2015-06-05 13:07:56 +0000
@@ -75,6 +75,15 @@
75 }75 }
76 return self._rpc(request)76 return self._rpc(request)
7777
78 def set_changes(self, yaml):
79 """Register the bundle change set in the GUI server."""
80 request = {
81 'Type': 'ChangeSet',
82 'Request': 'SetChanges',
83 'Params': {'YAML': yaml},
84 }
85 return self._rpc(request)
86
78 def create_auth_token(self):87 def create_auth_token(self):
79 """Make an auth token creation request.88 """Make an auth token creation request.
8089
8190
=== modified file 'quickstart/jujugui.py'
--- quickstart/jujugui.py 2015-05-22 16:04:02 +0000
+++ quickstart/jujugui.py 2015-06-05 13:07:56 +0000
@@ -28,6 +28,22 @@
28from quickstart import settings28from quickstart import settings
2929
3030
31def add_tokens_to_url(url, auth_token, changes_token):
32 """Return an URL including the optional authentication and changes tokens.
33
34 If auth_token and changes_token are both None, the given URL is returned
35 without changes.
36 """
37 query = []
38 if auth_token is not None:
39 query.append('authtoken=' + auth_token)
40 if changes_token is not None:
41 query.append('changestoken=' + changes_token)
42 if query:
43 return '{}/?{}'.format(url, '&'.join(query))
44 return url
45
46
31def build_options(port, source):47def build_options(port, source):
32 """Create a configuration dict suitable to be used when deploying the GUI.48 """Create a configuration dict suitable to be used when deploying the GUI.
3349
@@ -43,6 +59,18 @@
43 return options or None59 return options or None
4460
4561
62def is_promulgated(reference):
63 """Report whether the given reference represents a GUI promulgated charm.
64
65 The given reference is an instance of "jujubundlelib.references.Reference".
66 """
67 return (
68 reference.name == settings.JUJU_GUI_CHARM_NAME and
69 not reference.user and
70 not reference.is_local()
71 )
72
73
46def normalize_config(options):74def normalize_config(options):
47 """Normalize the Juju GUI configuration options.75 """Normalize the Juju GUI configuration options.
4876
4977
=== modified file 'quickstart/jujutools.py'
--- quickstart/jujutools.py 2015-05-22 16:04:02 +0000
+++ quickstart/jujutools.py 2015-06-05 13:07:56 +0000
@@ -19,6 +19,7 @@
19from __future__ import unicode_literals19from __future__ import unicode_literals
2020
21from quickstart import (21from quickstart import (
22 jujugui,
22 serializers,23 serializers,
23 settings,24 settings,
24)25)
@@ -56,11 +57,7 @@
56 # If a customized Juju GUI charm is in use, there is no way to check if the57 # If a customized Juju GUI charm is in use, there is no way to check if the
57 # GUI server is recent enough to support the new Juju API endpoints.58 # GUI server is recent enough to support the new Juju API endpoints.
58 # In these cases, assume the customized charm is recent enough.59 # In these cases, assume the customized charm is recent enough.
59 if (60 if not jujugui.is_promulgated(charm_ref):
60 charm_ref.name != settings.JUJU_GUI_CHARM_NAME or
61 charm_ref.user or
62 charm_ref.is_local()
63 ):
64 return complete_url61 return complete_url
65 # This is the promulgated Juju GUI charm. Check if it supports new APIs.62 # This is the promulgated Juju GUI charm. Check if it supports new APIs.
66 revision, series = charm_ref.revision, charm_ref.series63 revision, series = charm_ref.revision, charm_ref.series
6764
=== modified file 'quickstart/manage.py'
--- quickstart/manage.py 2015-05-22 16:04:02 +0000
+++ quickstart/manage.py 2015-06-05 13:07:56 +0000
@@ -374,6 +374,8 @@
374 - open_browser: whether the GUI browser must be opened;374 - open_browser: whether the GUI browser must be opened;
375 - platform: The host platform;375 - platform: The host platform;
376 - port: the optional Juju GUI port, or None;376 - port: the optional Juju GUI port, or None;
377 - uncommitted: whether the provided bundle must be deployed using the
378 Juju GUI uncommitted state;
377 - upload_tools: whether to upload local version of tools;379 - upload_tools: whether to upload local version of tools;
378 - upload_series: the comma-separated series list for which tools will380 - upload_series: the comma-separated series list for which tools will
379 be uploaded, or None if not set.381 be uploaded, or None if not set.
@@ -444,15 +446,18 @@
444 '-i', '--interactive', action='store_true', dest='interactive',446 '-i', '--interactive', action='store_true', dest='interactive',
445 help='Start the environments management interactive session')447 help='Start the environments management interactive session')
446 parser.add_argument(448 parser.add_argument(
447 '--environments-file', dest='env_file',449 '-u', '--uncommitted', action='store_true', dest='uncommitted',
448 default=os.path.join(settings.JUJU_HOME, 'environments.yaml'),450 help='Do not start the bundle deployment. Open the GUI showing\n'
449 help='The path to the Juju environments YAML file\n(%(default)s)')451 'an uncommitted bundle state instead, so that the bundle\n'
452 'can be finely tuned and tweaked before committing the\n'
453 'changes. This option is ignored if no bundle deployment\n'
454 'is requested.')
450 parser.add_argument(455 parser.add_argument(
451 '--gui-charm-url', dest='charm_url',456 '--gui-charm-url', dest='charm_url',
452 help='The Juju GUI charm URL to deploy in the environment.\n'457 help='The Juju GUI charm URL to deploy in the environment.\n'
453 'If not provided, the last release of the GUI will be\n'458 'If not provided, the last release of the GUI will be\n'
454 'deployed. The charm URL must include the charm version,\n'459 'deployed. The charm URL must include the charm version,\n'
455 'e.g. "cs:~juju-gui/precise/juju-gui-162". This option is\n'460 'e.g. "cs:~juju-gui/trusty/juju-gui-51". This option is\n'
456 'ignored if the GUI is already present in the environment')461 'ignored if the GUI is already present in the environment')
457 parser.add_argument(462 parser.add_argument(
458 '--gui-port', dest='port', type=int,463 '--gui-port', dest='port', type=int,
@@ -466,6 +471,10 @@
466 '--no-browser', action='store_false', dest='open_browser',471 '--no-browser', action='store_false', dest='open_browser',
467 help='Avoid opening the browser to the GUI at the end of the\nprocess')472 help='Avoid opening the browser to the GUI at the end of the\nprocess')
468 parser.add_argument(473 parser.add_argument(
474 '--environments-file', dest='env_file',
475 default=os.path.join(settings.JUJU_HOME, 'environments.yaml'),
476 help='The path to the Juju environments YAML file\n(%(default)s)')
477 parser.add_argument(
469 '--distro-only', action='store_true', dest='distro_only',478 '--distro-only', action='store_true', dest='distro_only',
470 default=default_distro_only, help=distro_only_help)479 default=default_distro_only, help=distro_only_help)
471 parser.add_argument(480 parser.add_argument(
@@ -628,29 +637,30 @@
628 '{}:{}'.format(address, gui_config['port']), juju_version, env_uuid,637 '{}:{}'.format(address, gui_config['port']), juju_version, env_uuid,
629 path_prefix='ws', charm_ref=charm_ref,638 path_prefix='ws', charm_ref=charm_ref,
630 insecure=not gui_config['secure'])639 insecure=not gui_config['secure'])
640
641 # We need to connect to an API WebSocket server supporting bundle
642 # deployments and automatic auth-token login. The GUI builtin server,
643 # listening on the Juju GUI address, exposes a suitable API.
631 gui_env = app.connect(gui_api_url, username, password)644 gui_env = app.connect(gui_api_url, username, password)
632645 changes_token = None
633 # Handle bundle deployment.646 # Handle bundle deployment.
634 if options.bundle_source is not None:647 if options.bundle_source is not None:
635 services = ', '.join(options.bundle.services().keys())648 changes_token = app.deploy_bundle(
636 print('requesting a deployment of {} with the following services:\n'649 gui_env, options.bundle, options.uncommitted, charm_ref)
637 ' {}'.format(options.bundle, services))650 # Handle automatic auth-token login.
638 if options.bundle.reference is not None:651 auth_token = app.create_auth_token(gui_env)
639 print('more details about this bundle can be found at\n'652 gui_env.close()
640 ' {}'.format(options.bundle.reference.jujucharms_url()))
641 # We need to connect to an API WebSocket server supporting bundle
642 # deployments. The GUI builtin server, listening on the Juju GUI
643 # address, exposes an API suitable for deploying bundles.
644 app.deploy_bundle(gui_env, options.bundle)
645 print('bundle deployment request accepted\n'
646 'use the GUI to check the bundle deployment progress')
647653
654 # Calculate the complete URL and optionally open the default browser.
655 url = jujugui.add_tokens_to_url(url, auth_token, changes_token)
648 if options.open_browser:656 if options.open_browser:
649 token = app.create_auth_token(gui_env)657 print('opening the browser at\n{}'.format(url))
650 if token is not None:
651 url += '/?authtoken={}'.format(token)
652 webbrowser.open(url)658 webbrowser.open(url)
653 gui_env.close()659 else:
660 print('not opening the browser\n'
661 'you can automatically log in into the Juju GUI by visiting\n'
662 '{}\n(the login token will expire in two minutes)'.format(url))
663
654 print(664 print(
655 'done!\n\n'665 'done!\n\n'
656 'Run "juju quickstart -e {env_name}" again if you want\n'666 'Run "juju quickstart -e {env_name}" again if you want\n'
657667
=== modified file 'quickstart/settings.py'
--- quickstart/settings.py 2015-03-09 17:50:28 +0000
+++ quickstart/settings.py 2015-06-05 13:07:56 +0000
@@ -37,8 +37,8 @@
37# temporary connection/charm store errors.37# temporary connection/charm store errors.
38# Keep this list sorted by release date (older first).38# Keep this list sorted by release date (older first).
39DEFAULT_CHARM_URLS = collections.OrderedDict((39DEFAULT_CHARM_URLS = collections.OrderedDict((
40 ('precise', 'cs:precise/juju-gui-108'),40 ('precise', 'cs:precise/juju-gui-115'),
41 ('trusty', 'cs:trusty/juju-gui-21'),41 ('trusty', 'cs:trusty/juju-gui-28'),
42))42))
4343
44# The quickstart app short description.44# The quickstart app short description.
@@ -91,3 +91,8 @@
91# new endpoints.91# new endpoints.
92MINIMUM_REVISIONS_FOR_NEW_API_ENDPOINT = collections.defaultdict(92MINIMUM_REVISIONS_FOR_NEW_API_ENDPOINT = collections.defaultdict(
93 lambda: 0, {'precise': 107, 'trusty': 19})93 lambda: 0, {'precise': 107, 'trusty': 19})
94
95# The minimum Juju GUI charm revision supporting uncommitted bundle
96# deployments. Assume not listed series to always support uncommitted state.
97MINIMUM_REVISIONS_FOR_UNCOMMITTED_BUNDLES = collections.defaultdict(
98 lambda: 0, {'precise': 115, 'trusty': 28})
9499
=== modified file 'quickstart/tests/functional/test_functional.py'
--- quickstart/tests/functional/test_functional.py 2015-05-13 08:31:51 +0000
+++ quickstart/tests/functional/test_functional.py 2015-06-05 13:07:56 +0000
@@ -180,3 +180,11 @@
180 self.assertEqual(0, retcode)180 self.assertEqual(0, retcode)
181 self.assertIn('bundle deployment request accepted', output)181 self.assertIn('bundle deployment request accepted', output)
182 self.assertEqual('', error)182 self.assertEqual('', error)
183
184 def test_uncommitted_bundle_deployment(self):
185 # The application can be used to deploy uncommitted bundles.
186 retcode, output, error = run_quickstart(
187 self.env_name, '-u', 'u/openstack-charmers/openstack/25')
188 self.assertEqual(0, retcode)
189 self.assertIn('the bundle is ready to be deployed', output)
190 self.assertEqual('', error)
183191
=== modified file 'quickstart/tests/test_app.py'
--- quickstart/tests/test_app.py 2015-05-22 17:11:26 +0000
+++ quickstart/tests/test_app.py 2015-06-05 13:07:56 +0000
@@ -1747,49 +1747,132 @@
1747 app.get_service_config(env, 'my-service')1747 app.get_service_config(env, 'my-service')
17481748
17491749
1750@helpers.mock_print
1750class TestDeployBundle(ProgramExitTestsMixin, unittest.TestCase):1751class TestDeployBundle(ProgramExitTestsMixin, unittest.TestCase):
17511752
1752 bundle_data = {'services': {}}1753 bundle_data = {'services': {'django': {}, 'haproxy': {}}}
1753 bundle = bundles.Bundle(bundle_data)1754 bundle = bundles.Bundle(bundle_data)
1755 charm = references.Reference.from_string('cs:trusty/juju-gui-42')
17541756
1755 def test_bundle_deployment(self):1757 def test_bundle_deployment(self, mock_print):
1756 # A bundle is successfully deployed.1758 # A bundle is successfully deployed.
1757 env = mock.Mock()1759 env, uncommitted = mock.Mock(), False
1758 app.deploy_bundle(env, self.bundle)1760 token = app.deploy_bundle(env, self.bundle, uncommitted, self.charm)
1761 self.assertIsNone(token)
1759 # For the time being, the bundle version 3 is deployed by default.1762 # For the time being, the bundle version 3 is deployed by default.
1760 expected_yaml = yaml.safe_dump({'bundle': self.bundle_data})1763 expected_yaml = yaml.safe_dump({'bundle': self.bundle_data})
1761 env.deploy_bundle.assert_called_once_with(1764 env.deploy_bundle.assert_called_once_with(
1762 expected_yaml, 3, bundle_id=None)1765 expected_yaml, 3, bundle_id=None)
1766 self.assertFalse(env.set_changes.called)
1763 self.assertFalse(env.close.called)1767 self.assertFalse(env.close.called)
1768 self.assertEqual(2, mock_print.call_count)
1769 mock_print.assert_has_calls([
1770 mock.call(
1771 'requesting a deployment of bundle with the following '
1772 'services:\n django, haproxy'),
1773 mock.call(
1774 'bundle deployment request accepted\n'
1775 'use the GUI to check the bundle deployment progress'),
1776 ])
17641777
1765 def test_bundle_deployment_with_id(self):1778 def test_bundle_deployment_with_id(self, mock_print):
1766 # If the bundle reference includes the charmworld id, it is passed when1779 # If the bundle reference includes the charmworld id, it is passed when
1767 # calling the GUI server API.1780 # calling the GUI server API.
1768 # XXX frankban 2015-02-26: remove this test once we get rid of the1781 # XXX frankban 2015-02-26: remove this test once we get rid of the
1769 # charmworld id concept.1782 # charmworld id concept.
1770 env = mock.Mock()1783 env, uncommitted = mock.Mock(), False
1771 ref = references.Reference.from_fully_qualified_url(1784 ref = references.Reference.from_fully_qualified_url(
1772 'cs:bundle/django-single-42')1785 'cs:bundle/django-single-42')
1773 ref.charmworld_id = 'django/single'1786 ref.charmworld_id = 'django/single'
1774 bundle = bundles.Bundle(self.bundle_data, reference=ref)1787 bundle = bundles.Bundle(self.bundle_data, reference=ref)
1775 app.deploy_bundle(env, bundle)1788 app.deploy_bundle(env, bundle, uncommitted, self.charm)
1776 env.deploy_bundle.assert_called_once_with(1789 env.deploy_bundle.assert_called_once_with(
1777 self.bundle.serialize_legacy(), 3, bundle_id='django/single')1790 self.bundle.serialize_legacy(), 3, bundle_id='django/single')
17781791
1779 def test_api_error(self):1792 def test_api_error(self, mock_print):
1780 # A ProgramExit is raised if an error occurs in one of the API calls.1793 # A ProgramExit is raised if an error occurs while deploying the
1781 env = mock.Mock()1794 # workload.
1795 env, uncommitted = mock.Mock(), False
1782 env.deploy_bundle.side_effect = self.make_env_error(1796 env.deploy_bundle.side_effect = self.make_env_error(
1783 'bundle deployment failure')1797 'bundle deployment failure')
1784 expected_error = 'bad API server response: bundle deployment failure'1798 expected_error = 'bad API server response: bundle deployment failure'
1785 with self.assert_program_exit(expected_error):1799 with self.assert_program_exit(expected_error):
1786 app.deploy_bundle(env, self.bundle)1800 app.deploy_bundle(env, self.bundle, uncommitted, self.charm)
17871801
1788 def test_other_errors(self):1802 def test_other_errors(self, mock_print):
1789 # Any other errors occurred during the process are not trapped.1803 # Any other errors occurred during the bundle deployment process are
1790 env = mock.Mock()1804 # not trapped.
1805 env, uncommitted = mock.Mock(), False
1791 error = ValueError('explode!')1806 error = ValueError('explode!')
1792 env.deploy_bundle.side_effect = error1807 env.deploy_bundle.side_effect = error
1793 with self.assertRaises(ValueError) as context_manager:1808 with self.assertRaises(ValueError) as context_manager:
1794 app.deploy_bundle(env, self.bundle)1809 app.deploy_bundle(env, self.bundle, uncommitted, self.charm)
1795 self.assertIs(error, context_manager.exception)1810 self.assertIs(error, context_manager.exception)
1811
1812 def test_uncommitted_bundle_deployment(self, mock_print):
1813 # A bundle is successfully registered as an uncommitted set of changes
1814 # on the GUI server.
1815 env, uncommitted = mock.Mock(), True
1816 env.set_changes.return_value = {'Token': 'mytoken'}
1817 token = app.deploy_bundle(env, self.bundle, uncommitted, self.charm)
1818 self.assertEqual('mytoken', token)
1819 expected_yaml = yaml.safe_dump(self.bundle_data)
1820 env.set_changes.assert_called_once_with(expected_yaml)
1821 self.assertFalse(env.deploy_bundle.called)
1822 self.assertEqual(2, mock_print.call_count)
1823 mock_print.assert_has_calls([
1824 mock.call(
1825 'requesting uncommitted bundle loading of bundle with the '
1826 'following services:\n django, haproxy'),
1827 mock.call(
1828 'the bundle is ready to be deployed\n'
1829 'use the GUI to configure the bundle and to start the '
1830 'deployment process'),
1831 ])
1832
1833 def test_uncommitted_api_error(self, mock_print):
1834 # A ProgramExit is raised if an error occurs while registering the
1835 # change set to the GUI server.
1836 env, uncommitted = mock.Mock(), True
1837 env.set_changes.side_effect = self.make_env_error('bad wolf')
1838 with self.assert_program_exit('bad API server response: bad wolf'):
1839 app.deploy_bundle(env, self.bundle, uncommitted, self.charm)
1840
1841 def test_uncommitted_bundle_validation_error(self, mock_print):
1842 # A ProgramExit is raised if the GUI server returns bundle validation
1843 # errors in its response. This is really unlikely to happen.
1844 env, uncommitted = mock.Mock(), True
1845 env.set_changes.return_value = {'Errors': ['error 1', 'error2']}
1846 expected_error = 'cannot generate the change set: error 1, error2'
1847 with self.assert_program_exit(expected_error):
1848 app.deploy_bundle(env, self.bundle, uncommitted, self.charm)
1849
1850 def test_uncommitted_other_errors(self, mock_print):
1851 # Any other errors occurred during the change set process are not
1852 # trapped.
1853 env, uncommitted = mock.Mock(), True
1854 error = ValueError('explode!')
1855 env.set_changes.side_effect = error
1856 with self.assertRaises(ValueError) as context_manager:
1857 app.deploy_bundle(env, self.bundle, uncommitted, self.charm)
1858 self.assertIs(error, context_manager.exception)
1859
1860 def test_uncommitted_not_supported(self, mock_print):
1861 # The uncommitted set of changes cannot be registered if the Juju GUI
1862 # service deployed in the environment does not support it.
1863 env, uncommitted = mock.Mock(), True
1864 charm = references.Reference.from_string('cs:trusty/juju-gui-0')
1865 expected_logs = [
1866 'uncommitted bundles loading is not supported by currently '
1867 'deployed Juju GUI charm (cs:trusty/juju-gui-0): requested bundle '
1868 'deployment will be ignored',
1869 'run "juju upgrade-charm juju-gui" to enable uncommitted bundles '
1870 'support',
1871 ]
1872 with helpers.assert_logs(expected_logs, level='warn'):
1873 token = app.deploy_bundle(env, self.bundle, uncommitted, charm)
1874 self.assertIsNone(token)
1875 # No calls to deploy the bundle have been made.
1876 self.assertFalse(env.deploy_bundle.called)
1877 self.assertFalse(env.set_changes.called)
1878 self.assertFalse(mock_print.called)
17961879
=== modified file 'quickstart/tests/test_juju.py'
--- quickstart/tests/test_juju.py 2015-05-22 10:48:55 +0000
+++ quickstart/tests/test_juju.py 2015-06-05 13:07:56 +0000
@@ -305,7 +305,19 @@
305 })305 })
306306
307 @patch_rpc307 @patch_rpc
308 def test_set_changes(self, mock_rpc):
309 # The ChangeSet:SetChanges API call is properly generated.
310 self.env.set_changes('YAML content')
311 expected = {
312 'Type': 'ChangeSet',
313 'Request': 'SetChanges',
314 'Params': {'YAML': 'YAML content'},
315 }
316 mock_rpc.assert_called_once_with(expected)
317
318 @patch_rpc
308 def test_create_auth_token(self, mock_rpc):319 def test_create_auth_token(self, mock_rpc):
320 # The GUIToken:Create API call is properly generated.
309 self.env.create_auth_token()321 self.env.create_auth_token()
310 expected = dict(Type='GUIToken', Request='Create')322 expected = dict(Type='GUIToken', Request='Create')
311 mock_rpc.assert_called_once_with(expected)323 mock_rpc.assert_called_once_with(expected)
312324
=== modified file 'quickstart/tests/test_jujugui.py'
--- quickstart/tests/test_jujugui.py 2015-05-22 16:04:02 +0000
+++ quickstart/tests/test_jujugui.py 2015-06-05 13:07:56 +0000
@@ -27,6 +27,32 @@
27from quickstart.tests import helpers27from quickstart.tests import helpers
2828
2929
30class TestAddTokensToUrl(unittest.TestCase):
31
32 def test_tokens(self):
33 # The URL is properly generated with both authentication and changes
34 # tokens.
35 url = jujugui.add_tokens_to_url(
36 'https://1.2.3.4', 'myauth', 'mychanges')
37 self.assertEqual(
38 'https://1.2.3.4/?authtoken=myauth&changestoken=mychanges', url)
39
40 def test_auth_token_only(self):
41 # The URL is properly generated with the authentication token only.
42 url = jujugui.add_tokens_to_url('https://1.2.3.4', 'login', None)
43 self.assertEqual('https://1.2.3.4/?authtoken=login', url)
44
45 def test_changes_token_only(self):
46 # The URL is properly generated with the changes token only.
47 url = jujugui.add_tokens_to_url('https://1.2.3.4', None, 'changeset')
48 self.assertEqual('https://1.2.3.4/?changestoken=changeset', url)
49
50 def test_no_tokens(self):
51 # The URL is left untouched if no tokens are provided.
52 url = 'http://example.com'
53 self.assertEqual(url, jujugui.add_tokens_to_url(url, None, None))
54
55
30class TestBuildOptions(unittest.TestCase):56class TestBuildOptions(unittest.TestCase):
3157
32 def test_no_options(self):58 def test_no_options(self):
@@ -57,6 +83,26 @@
57 self.assertEqual(expected_options, options)83 self.assertEqual(expected_options, options)
5884
5985
86class TestIsPromulgated(unittest.TestCase):
87
88 def test_promulgated(self):
89 # Promulgated Juju GUI charm references are properly recognized.
90 for url in ('cs:precise/juju-gui-0', 'cs:trusty/juju-gui-42'):
91 ref = references.Reference.from_string(url)
92 self.assertTrue(jujugui.is_promulgated(ref), url)
93
94 def test_customized(self):
95 # Customized Juju GUI charm references are properly recognized.
96 tests = (
97 'local:precise/juju-gui-0',
98 'cs:precise/mygui-1',
99 'cs:~who/trusty/juju-gui-2',
100 )
101 for url in tests:
102 ref = references.Reference.from_string(url)
103 self.assertFalse(jujugui.is_promulgated(ref), url)
104
105
60class TestNormalizeConfig(unittest.TestCase):106class TestNormalizeConfig(unittest.TestCase):
61107
62 def test_normal_options(self):108 def test_normal_options(self):
63109
=== modified file 'quickstart/tests/test_manage.py'
--- quickstart/tests/test_manage.py 2015-05-22 14:28:23 +0000
+++ quickstart/tests/test_manage.py 2015-06-05 13:07:56 +0000
@@ -702,6 +702,7 @@
702 'gui_source': None,702 'gui_source': None,
703 'open_browser': True,703 'open_browser': True,
704 'port': None,704 'port': None,
705 'uncommitted': False,
705 }706 }
706 options.update(kwargs)707 options.update(kwargs)
707 return mock.Mock(**options)708 return mock.Mock(**options)
@@ -751,6 +752,8 @@
751 'watch': '1.2.3.5',752 'watch': '1.2.3.5',
752 # Retrieve the Juju GUI service configuration.753 # Retrieve the Juju GUI service configuration.
753 'get_service_config': {'port': None, 'secure': True},754 'get_service_config': {'port': None, 'secure': True},
755 # Optionally deploy a bundle.
756 'deploy_bundle': None,
754 # Create the login token for the Juju GUI.757 # Create the login token for the Juju GUI.
755 'create_auth_token': 'TOKEN',758 'create_auth_token': 'TOKEN',
756 }759 }
@@ -965,7 +968,7 @@
965968
966 def test_bundle(self, mock_app, mock_open):969 def test_bundle(self, mock_app, mock_open):
967 # A bundle is correctly deployed by the application.970 # A bundle is correctly deployed by the application.
968 env = self.configure_app(mock_app, create_auth_token=None)971 env = self.configure_app(mock_app)
969 bundle_source = 'mediawiki-single'972 bundle_source = 'mediawiki-single'
970 reference = references.Reference.from_jujucharms_url(bundle_source)973 reference = references.Reference.from_jujucharms_url(bundle_source)
971 bundle = bundles.Bundle(self.bundle_data, reference=reference)974 bundle = bundles.Bundle(self.bundle_data, reference=reference)
@@ -974,7 +977,25 @@
974 with self.patch_get_juju_command():977 with self.patch_get_juju_command():
975 manage.run(options)978 manage.run(options)
976 # Ensure the bundle is correctly deployed.979 # Ensure the bundle is correctly deployed.
977 mock_app.deploy_bundle.assert_called_once_with(env, bundle)980 ref = references.Reference.from_string('cs:trusty/juju-gui-42')
981 mock_app.deploy_bundle.assert_called_once_with(env, bundle, False, ref)
982
983 def test_uncommitted_bundle(self, mock_app, mock_open):
984 # An uncommitted bundle is correctly deployed by the application.
985 env = self.configure_app(mock_app, deploy_bundle='CHANGES-TOKEN')
986 bundle_source = 'mediawiki-single'
987 reference = references.Reference.from_jujucharms_url(bundle_source)
988 bundle = bundles.Bundle(self.bundle_data, reference=reference)
989 # Run the application.
990 options = self.make_options(
991 bundle_source=bundle_source, bundle=bundle, uncommitted=True)
992 with self.patch_get_juju_command():
993 manage.run(options)
994 # Ensure the bundle is correctly deployed.
995 ref = references.Reference.from_string('cs:trusty/juju-gui-42')
996 mock_app.deploy_bundle.assert_called_once_with(env, bundle, True, ref)
997 mock_open.assert_called_once_with(
998 'https://1.2.3.5/?authtoken=TOKEN&changestoken=CHANGES-TOKEN')
978999
979 def test_local_provider(self, mock_app, mock_open):1000 def test_local_provider(self, mock_app, mock_open):
980 # The application correctly handles working with local providers with1001 # The application correctly handles working with local providers with
9811002
=== modified file 'tox.ini'
--- tox.ini 2015-05-11 10:55:51 +0000
+++ tox.ini 2015-06-05 13:07:56 +0000
@@ -72,7 +72,7 @@
72 # See https://launchpad.net/~juju/+archive/ubuntu/stable.72 # See https://launchpad.net/~juju/+archive/ubuntu/stable.
73 websocket-client==0.18.073 websocket-client==0.18.0
74 jujuclient==0.50.174 jujuclient==0.50.1
75 jujubundlelib==0.1.775 jujubundlelib==0.1.8
76 urwid==1.2.176 urwid==1.2.1
77 # The distribution PyYAML requirement is used in this case.77 # The distribution PyYAML requirement is used in this case.
7878
@@ -82,7 +82,7 @@
82 # Ubuntu 14.04 (trusty) distro dependencies.82 # Ubuntu 14.04 (trusty) distro dependencies.
83 websocket-client==0.12.083 websocket-client==0.12.0
84 jujuclient==0.17.584 jujuclient==0.17.5
85 jujubundlelib==0.1.785 jujubundlelib==0.1.8
86 PyYAML==3.1086 PyYAML==3.10
87 urwid==1.1.187 urwid==1.1.1
8888
@@ -92,7 +92,7 @@
92 # Ubuntu 14.10 (utopic) distro dependencies.92 # Ubuntu 14.10 (utopic) distro dependencies.
93 websocket-client==0.12.093 websocket-client==0.12.0
94 jujuclient==0.17.594 jujuclient==0.17.5
95 jujubundlelib==0.1.795 jujubundlelib==0.1.8
96 PyYAML==3.1196 PyYAML==3.11
97 urwid==1.2.197 urwid==1.2.1
9898
@@ -102,7 +102,7 @@
102 # Ubuntu 15.04 (vivid) distro dependencies.102 # Ubuntu 15.04 (vivid) distro dependencies.
103 websocket-client==0.18.0103 websocket-client==0.18.0
104 jujuclient==0.18.5104 jujuclient==0.18.5
105 jujubundlelib==0.1.7105 jujubundlelib==0.1.8
106 PyYAML==3.11106 PyYAML==3.11
107 urwid==1.2.1107 urwid==1.2.1
108108

Subscribers

People subscribed via source and target branches