Merge lp:~frankban/juju-quickstart/bundle-v4-api into lp:juju-quickstart

Proposed by Francesco Banconi
Status: Merged
Merged at revision: 140
Proposed branch: lp:~frankban/juju-quickstart/bundle-v4-api
Merge into: lp:juju-quickstart
Diff against target: 369 lines (+111/-36)
7 files modified
README.rst (+1/-1)
quickstart/app.py (+22/-4)
quickstart/models/bundles.py (+17/-12)
quickstart/settings.py (+9/-3)
quickstart/tests/models/test_bundles.py (+18/-1)
quickstart/tests/test_app.py (+34/-4)
tox.ini (+10/-11)
To merge this branch: bzr merge lp:~frankban/juju-quickstart/bundle-v4-api
Reviewer Review Type Date Requested Status
j.c.sackett (community) Approve
Madison Scott-Clary (community) code Approve
Review via email: mp+267807@code.launchpad.net

Description of the change

Send the actual bundle version to GUI server.

When deploying bundles, if not taken from the
charm store (in which case they are always v4 bundles),
check the bundle syntax version and properly
inform the GUI server about whether a legacy
or new-style bundle is being deployed.
This is done if the GUI unit supports new bundle
v4, otherwise still try to proceed with the
deployment process, pretending a v3 bundle has been
provided.

Drop support for utopic and add support for wily.

Also update default Juju GUI revisions.

Tests: `make check`.

QA: deploy a bundle v4, preferably with unit placement,
e.g.: `devenv/bin/juju-quickstart -u http://dpaste.com/2E1JA1E.txt`.
The uncommitted state should co-locate units in the proper way,
thanks to the GUI fixes from Madison. Now remove the -u flag:
`devenv/bin/juju-quickstart http://dpaste.com/2E1JA1E.txt` and check
that also the GUI server is able to properly deploy v4 bundles.
But also try the usual mediawiki-single, etc...
Run quickstart again, run the interactive mode, ensure
quickstart works as usual, this branch will be the 2.2.1 release.

To post a comment you must log in.
Revision history for this message
j.c.sackett (jcsackett) wrote :

Francesco--

Thanks, this looks good. I noted one typo, and I have one question about what things look like to the end user in the worst case; otherwise LGTM.

review: Approve
Revision history for this message
j.c.sackett (jcsackett) wrote :

QA notes:

When running with -u everything worked great.

When running without the -u I got the following error in the GUI.

"n error occurred while deploying the bundle: Invalid service placement nova-compute to ceph/0 Invalid service placement nova-compute to ceph/1 Invalid service placement nova-compute to ceph/2 Invalid service placement ceph-radosgw to lxc:ceph/1 Invalid service placement neutron-api to lxc:ceph/0 Invalid service placement nova-cloud-controller to lxc:ceph/2 Invalid service placement keystone to lxc:ceph/0 Invalid service placement cinder to lxc:ceph/2 Invalid service placement openstack-dashboard to lxc:ceph/1 Invalid service placement glance to lxc:ceph/1"

Not sure if this is a qa error or an indication that I've got the wrong thing running. I'm running trusty/juju-gui-38, which is the minimum listed revision for V4 bundles; is there a restriction on which providers this works on? I'm using a local env.

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

Thanks for the review JC, replied in line.

146. By Francesco Banconi

Fix typo.

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

I cannot dupe this QA problem.
Could you please run "devenv/bin/juju-quickstart -e ec2 http://dpaste.com/2E1JA1E.txt --debug" again? Thanks!

Revision history for this message
j.c.sackett (jcsackett) wrote :

On EC2 I'm getting the same issue, log is https://pastebin.canonical.com/137407/

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

From the logs it seems that quickstart is not considering the bundle a v4 one ("Version": 4 is not included in the params of the Import WebSocket request).
This is not the case here locally. So could you please make another attempt, just to exclude local misconfigurations? "make clean", then "make" and then "devenv/bin/juju-quickstart -e ec2 http://dpaste.com/2E1JA1E.txt --debug" again? Thanks a lot.

Revision history for this message
Madison Scott-Clary (makyo) wrote :

LGTM, thanks for the update

review: Approve (code)
Revision history for this message
j.c.sackett (jcsackett) wrote :

After blowing away the branch and getting this again, it seems to work. I see Version: 4 in the bundle output and the bundle has deployed.

QA OK.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'README.rst'
2--- README.rst 2015-08-07 10:24:11 +0000
3+++ README.rst 2015-08-12 16:25:17 +0000
4@@ -34,7 +34,7 @@
5 ------------------
6
7 Juju Quickstart is available on Ubuntu releases 12.04 LTS (precise), 14.04 LTS
8-(trusty), 15.04 (vivid) and on OS X (10.7 and later).
9+(trusty), 15.04 (vivid), 15.10 (wily) and on OS X (10.7 and later).
10
11 Starting from version 1.5.0, Juju Quickstart only supports Juju >= 1.18.1.
12
13
14=== modified file 'quickstart/app.py'
15--- quickstart/app.py 2015-06-05 13:07:47 +0000
16+++ quickstart/app.py 2015-08-12 16:25:17 +0000
17@@ -683,10 +683,28 @@
18
19 print('requesting a deployment of {} with the following services:\n'
20 ' {}'.format(bundle, services))
21- # XXX frankban 2015-02-26: use new bundle format if the GUI server is
22- # capable of handling bundle deployments with the API version 4.
23- yaml = bundle.serialize_legacy()
24- version = 3
25+ version = bundle.version
26+
27+ if version > 3:
28+ # The bundle uses a new-style syntax. Check whether the currently used
29+ # GUI server is capable of handling new-style bundles.
30+ revision = settings.MINIMUM_REVISIONS_FOR_V4_BUNDLES[charm_ref.series]
31+ if jujugui.is_promulgated(charm_ref) and charm_ref.revision < revision:
32+ logging.warn(
33+ 'new style bundle syntax is not supported by currently '
34+ 'deployed Juju GUI charm ({}): trying to deploy the bundle '
35+ 'using old syntax'.format(charm_ref))
36+ # Pretend the bundle is a legacy one. Note that this might not work
37+ # depending on whether new style unit placement is used in the
38+ # bundle contents.
39+ yaml = bundle.serialize_legacy()
40+ version = 3
41+ else:
42+ yaml = bundle.serialize()
43+ else:
44+ # The bundle uses the legacy syntax.
45+ yaml = bundle.serialize_legacy()
46+
47 # XXX frankban 2015-02-26: find and implement a better way to increase the
48 # bundle deployments count.
49 bundle_id = None if ref is None else ref.charmworld_id
50
51=== modified file 'quickstart/models/bundles.py'
52--- quickstart/models/bundles.py 2015-05-13 08:46:33 +0000
53+++ quickstart/models/bundles.py 2015-08-12 16:25:17 +0000
54@@ -63,7 +63,7 @@
55 class Bundle(object):
56 """Store information about a charm store bundle entity"""
57
58- def __init__(self, data, reference=None):
59+ def __init__(self, data, reference=None, version=4):
60 """Initialize the bundle.
61
62 The data argument is the bundle YAML decoded content.
63@@ -72,6 +72,7 @@
64 """
65 self.data = data
66 self.reference = reference
67+ self.version = version
68
69 def __str__(self):
70 """Return the byte string representation of this bundle."""
71@@ -118,7 +119,7 @@
72
73
74 def from_source(source, name=None):
75- """Return a bundle YAML encoded string and id from the given source.
76+ """Return a bundle instance from the given source.
77
78 The source argument is a string, and can be provided as:
79
80@@ -167,16 +168,18 @@
81 is_remote = source.startswith('http://') or source.startswith('https://')
82 if has_extension and not is_remote:
83 # The source refers to a local file.
84- data = _parse_and_flatten_yaml(_retrieve_from_file(source), name)
85- return Bundle(data)
86+ content = _retrieve_from_file(source)
87+ data, version = _parse_and_flatten_yaml(content, name)
88+ return Bundle(data, version=version)
89
90 try:
91 reference = references.Reference.from_jujucharms_url(source)
92 except ValueError:
93 if is_remote:
94 # The source is an arbitrary URL to a YAML/JSON content.
95- data = _parse_and_flatten_yaml(_retrieve_from_url(source), name)
96- return Bundle(data)
97+ content = _retrieve_from_url(source)
98+ data, version = _parse_and_flatten_yaml(content, name)
99+ return Bundle(data, version=version)
100 # No other options are available.
101 raise
102
103@@ -214,7 +217,8 @@
104 Note that charmworld URLs always represent a bundle.
105
106 Return a Bundle instance which includes the retrieved data and the bundle
107- reference corresponding to the given charmworld URL.
108+ reference corresponding to the given charmworld URL. The returned bundle
109+ is retrieved from the charm store and always uses version 4 syntax.
110 Raise a ValueError if the provided URL is not valid, or if the bundle
111 content is not valid.
112 Raise a IOError if a problem is encountered while fetching the YAML
113@@ -325,8 +329,8 @@
114
115 The content is provided as a YAML encoded string and can be either a new
116 style flat bundle or a legacy bundle format.
117- In both cases, the returned YAML decoded data represents a new style
118- bundle (API version 4).
119+ In both cases, the returned YAML decoded data represents a single flat
120+ bundle. The version is returned as an integer number.
121
122 Raise a ValueError if:
123 - the bundle YAML contents are not parsable by YAML;
124@@ -338,17 +342,18 @@
125 """
126 data = charmstore.load_bundle_yaml(content)
127 _ensure_is_dict(data)
128+ version = 4
129 if is_legacy_bundle(data):
130 data = _flatten_data(data, name)
131+ version = 3
132 validate(data)
133- return data
134+ return data, version
135
136
137 def _flatten_data(data, name):
138 """Retrieve the bundle content from data for a specific bundle name.
139
140- The returned YAML decoded data represents a new style bundle
141- (API version 4).
142+ The returned YAML decoded data represents a single flat bundle.
143
144 Raise a ValueError if:
145 - the YAML data is not properly structured;
146
147=== modified file 'quickstart/settings.py'
148--- quickstart/settings.py 2015-06-17 14:02:03 +0000
149+++ quickstart/settings.py 2015-08-12 16:25:17 +0000
150@@ -37,8 +37,8 @@
151 # temporary connection/charm store errors.
152 # Keep this list sorted by release date (older first).
153 DEFAULT_CHARM_URLS = collections.OrderedDict((
154- ('precise', 'cs:precise/juju-gui-117'),
155- ('trusty', 'cs:trusty/juju-gui-32'),
156+ ('precise', 'cs:precise/juju-gui-121'),
157+ ('trusty', 'cs:trusty/juju-gui-38'),
158 ))
159
160 # The quickstart app short description.
161@@ -61,7 +61,8 @@
162
163 # The possible values for the environments.yaml default-series field.
164 JUJU_DEFAULT_SERIES = (
165- 'precise', 'quantal', 'raring', 'saucy', 'trusty', 'utopic', 'vivid')
166+ 'precise', 'quantal', 'raring', 'saucy', 'trusty', 'utopic', 'vivid',
167+ 'wily')
168
169 # Retrieve the current juju-core home.
170 JUJU_HOME = os.getenv('JUJU_HOME', '~/.juju')
171@@ -96,3 +97,8 @@
172 # deployments. Assume not listed series to always support uncommitted state.
173 MINIMUM_REVISIONS_FOR_UNCOMMITTED_BUNDLES = collections.defaultdict(
174 lambda: 0, {'precise': 115, 'trusty': 28})
175+
176+# The minimum Juju GUI charm revision supporting bundle syntax version 4.
177+# Assume not listed series to always support v4 syntax.
178+MINIMUM_REVISIONS_FOR_V4_BUNDLES = collections.defaultdict(
179+ lambda: 0, {'precise': 121, 'trusty': 38})
180
181=== modified file 'quickstart/tests/models/test_bundles.py'
182--- quickstart/tests/models/test_bundles.py 2015-05-13 08:46:33 +0000
183+++ quickstart/tests/models/test_bundles.py 2015-08-12 16:25:17 +0000
184@@ -44,9 +44,19 @@
185 self.bundle_data, reference=self.reference)
186
187 def test_attributes(self):
188- # The bundle data and the optional reference are stored as attributes.
189+ # The bundle data, the optional reference and the default version are
190+ # stored as attributes.
191 self.assertEqual(self.bundle_data, self.bundle.data)
192 self.assertEqual(self.reference, self.bundle.reference)
193+ self.assertEqual(4, self.bundle.version)
194+
195+ def test_version_and_no_reference(self):
196+ # The bundle syntax version can be provided as an argument. The bundle
197+ # reference is optional.
198+ bundle = bundles.Bundle(self.bundle_data, version=3)
199+ self.assertEqual(self.bundle_data, bundle.data)
200+ self.assertIsNone(bundle.reference)
201+ self.assertEqual(3, bundle.version)
202
203 def test_string(self):
204 # The bundle correctly represents itself as a string.
205@@ -144,6 +154,7 @@
206 bundle = bundles.from_source('bundle:mediawiki/single')
207 self.assertEqual(self.bundle_data, bundle.data)
208 self.assertEqual('cs:bundle/mediawiki-single', bundle.reference.id())
209+ self.assertEqual(4, bundle.version)
210 # The charmworld id is properly set when passing charmworld URLs.
211 # XXX frankban 2015-03-09: remove this check once we get rid of the
212 # charmworld id concept.
213@@ -184,6 +195,7 @@
214 bundle = bundles.from_source('bundle:mediawiki/bundle1')
215 self.assertEqual(self.bundle_data, bundle.data)
216 self.assertEqual('cs:bundle/mediawiki', bundle.reference.id())
217+ self.assertEqual(4, bundle.version)
218 # The charmworld id is properly set when passing charmworld URLs.
219 # XXX frankban 2015-03-09: remove this check once we get rid of the
220 # charmworld id concept.
221@@ -334,6 +346,7 @@
222 bundle = bundles.from_source('django')
223 self.assertEqual(self.bundle_data, bundle.data)
224 self.assertEqual('cs:bundle/django', bundle.reference.id())
225+ self.assertEqual(4, bundle.version)
226 mock_urlread.assert_called_once_with(
227 settings.CHARMSTORE_API + 'bundle/django/archive/bundle.yaml')
228
229@@ -402,6 +415,7 @@
230 bundle = bundles.from_source(path)
231 self.assertEqual(self.bundle_data, bundle.data)
232 self.assertIsNone(bundle.reference)
233+ self.assertEqual(4, bundle.version)
234
235 def test_local_file_legacy_bundle(self):
236 # A bundle instance can be created from a local file source including
237@@ -419,6 +433,7 @@
238 bundle = bundles.from_source(path)
239 self.assertEqual(legacy_bundle_data['bundle'], bundle.data)
240 self.assertIsNone(bundle.reference)
241+ self.assertEqual(3, bundle.version)
242
243 def test_local_file_not_found(self):
244 # An IOError is raised if a local file source cannot be found.
245@@ -485,6 +500,7 @@
246 bundle = bundles.from_source('https://1.2.3.4')
247 self.assertEqual(self.bundle_data, bundle.data)
248 self.assertIsNone(bundle.reference)
249+ self.assertEqual(4, bundle.version)
250 mock_urlread.assert_called_once_with('https://1.2.3.4')
251
252 def test_remote_url_legacy_bundle(self):
253@@ -496,6 +512,7 @@
254 bundle = bundles.from_source('https://1.2.3.4:8000', 'bundle2')
255 self.assertEqual(self.legacy_bundle_data['bundle2'], bundle.data)
256 self.assertIsNone(bundle.reference)
257+ self.assertEqual(3, bundle.version)
258 mock_urlread.assert_called_once_with('https://1.2.3.4:8000')
259
260 def test_remote_url_not_reachable(self):
261
262=== modified file 'quickstart/tests/test_app.py'
263--- quickstart/tests/test_app.py 2015-06-05 13:07:47 +0000
264+++ quickstart/tests/test_app.py 2015-08-12 16:25:17 +0000
265@@ -1759,10 +1759,8 @@
266 env, uncommitted = mock.Mock(), False
267 token = app.deploy_bundle(env, self.bundle, uncommitted, self.charm)
268 self.assertIsNone(token)
269- # For the time being, the bundle version 3 is deployed by default.
270- expected_yaml = yaml.safe_dump({'bundle': self.bundle_data})
271 env.deploy_bundle.assert_called_once_with(
272- expected_yaml, 3, bundle_id=None)
273+ yaml.safe_dump(self.bundle_data), 4, bundle_id=None)
274 self.assertFalse(env.set_changes.called)
275 self.assertFalse(env.close.called)
276 self.assertEqual(2, mock_print.call_count)
277@@ -1775,6 +1773,38 @@
278 'use the GUI to check the bundle deployment progress'),
279 ])
280
281+ def test_legacy_bundle_deployment(self, mock_print):
282+ # A bundle with legacy v3 bundle syntax is successfully deployed.
283+ env, uncommitted = mock.Mock(), False
284+ bundle = bundles.Bundle(self.bundle_data, version=3)
285+ token = app.deploy_bundle(env, bundle, uncommitted, self.charm)
286+ self.assertIsNone(token)
287+ expected_yaml = yaml.safe_dump({'bundle': self.bundle_data})
288+ env.deploy_bundle.assert_called_once_with(
289+ expected_yaml, 3, bundle_id=None)
290+ self.assertFalse(env.set_changes.called)
291+ self.assertFalse(env.close.called)
292+
293+ def test_bundle_deployment_v4_not_supported(self, mock_print):
294+ # The bundle deployment falls back to version 3 syntax if the Juju GUI
295+ # unit is not recent enough. In this case there is no guarantee that
296+ # the deployment succeeds.
297+ env, uncommitted = mock.Mock(), False
298+ charm = references.Reference.from_string('cs:trusty/juju-gui-36')
299+ expected_logs = [
300+ 'new style bundle syntax is not supported by currently deployed '
301+ 'Juju GUI charm (cs:trusty/juju-gui-36): trying to deploy the '
302+ 'bundle using old syntax'
303+ ]
304+ with helpers.assert_logs(expected_logs, level='warn'):
305+ token = app.deploy_bundle(env, self.bundle, uncommitted, charm)
306+ self.assertIsNone(token)
307+ expected_yaml = yaml.safe_dump({'bundle': self.bundle_data})
308+ env.deploy_bundle.assert_called_once_with(
309+ expected_yaml, 3, bundle_id=None)
310+ self.assertFalse(env.set_changes.called)
311+ self.assertFalse(env.close.called)
312+
313 def test_bundle_deployment_with_id(self, mock_print):
314 # If the bundle reference includes the charmworld id, it is passed when
315 # calling the GUI server API.
316@@ -1787,7 +1817,7 @@
317 bundle = bundles.Bundle(self.bundle_data, reference=ref)
318 app.deploy_bundle(env, bundle, uncommitted, self.charm)
319 env.deploy_bundle.assert_called_once_with(
320- self.bundle.serialize_legacy(), 3, bundle_id='django/single')
321+ self.bundle.serialize(), 4, bundle_id='django/single')
322
323 def test_api_error(self, mock_print):
324 # A ProgramExit is raised if an error occurs while deploying the
325
326=== modified file 'tox.ini'
327--- tox.ini 2015-08-07 10:24:11 +0000
328+++ tox.ini 2015-08-12 16:25:17 +0000
329@@ -18,7 +18,7 @@
330 # The envlist option must only include platform scenarios. In essence, those
331 # are used to test Juju Quickstart with the specific dependencies present in
332 # each supported OS platform.
333-envlist = pypi,ppa,trusty,utopic,vivid
334+envlist = pypi,ppa,trusty,vivid,wily
335
336 # Define the base requirements and commands to use when testing the program.
337
338@@ -86,16 +86,6 @@
339 PyYAML==3.10
340 urwid==1.1.1
341
342-[testenv:utopic]
343-deps =
344- {[testenv]deps}
345- # Ubuntu 14.10 (utopic) distro dependencies.
346- websocket-client==0.12.0
347- jujuclient==0.17.5
348- jujubundlelib==0.1.9
349- PyYAML==3.11
350- urwid==1.2.1
351-
352 [testenv:vivid]
353 deps =
354 {[testenv]deps}
355@@ -106,6 +96,15 @@
356 PyYAML==3.11
357 urwid==1.2.1
358
359+[testenv:wily]
360+deps =
361+ {[testenv]deps}
362+ # Ubuntu 15.10 (wily) distro dependencies.
363+ websocket-client==0.18.0
364+ jujuclient==0.50.1
365+ jujubundlelib==0.1.9
366+ PyYAML==3.11
367+ urwid==1.2.1
368
369 # Define additional tox targets to be used to validate and lint the code.
370

Subscribers

People subscribed via source and target branches