Merge lp:~bac/charms/precise/juju-gui/increment-deployments into lp:~juju-gui/charms/precise/juju-gui/trunk

Proposed by Brad Crittenden
Status: Merged
Merged at revision: 132
Proposed branch: lp:~bac/charms/precise/juju-gui/increment-deployments
Merge into: lp:~juju-gui/charms/precise/juju-gui/trunk
Diff against target: 794 lines (+315/-43)
15 files modified
config/guiserver.conf.template (+1/-0)
hooks/backend.py (+1/-1)
hooks/utils.py (+5/-3)
server/guiserver/apps.py (+2/-1)
server/guiserver/bundles/__init__.py (+8/-2)
server/guiserver/bundles/base.py (+19/-7)
server/guiserver/bundles/utils.py (+43/-0)
server/guiserver/bundles/views.py (+15/-7)
server/guiserver/manage.py (+4/-0)
server/guiserver/tests/bundles/test_base.py (+95/-14)
server/guiserver/tests/bundles/test_utils.py (+79/-0)
server/guiserver/tests/bundles/test_views.py (+28/-1)
server/runserver.py (+1/-0)
tests/test_backends.py (+5/-3)
tests/test_utils.py (+9/-4)
To merge this branch: bzr merge lp:~bac/charms/precise/juju-gui/increment-deployments
Reviewer Review Type Date Requested Status
charmers Pending
Review via email: mp+195317@code.launchpad.net

Description of the change

After deploying a bundle increment counter.

The guiserver will make a GET request to the deployment counter incrementer
URL for the bundle. This required accepting the bundle ID in the
Deployer/Import path.

Note neither the GUI nor quickstart have been updated to pass this value yet.

The charmworldurl is also now passed to the server via the command line.

QA: just deploy your favorite bundle and see that it works as it should. You
can observe the bundle page in charmworld and see the counts remain at 0.

https://codereview.appspot.com/26740043/

To post a comment you must log in.
Revision history for this message
Brad Crittenden (bac) wrote :

Reviewers: mp+195317_code.launchpad.net,

Message:
Please take a look.

Description:
After deploying a bundle increment counter.

The guiserver will make a GET request to the deployment counter
incrementer
URL for the bundle. This required accepting the bundle ID in the
Deployer/Import path.

Note neither the GUI nor quickstart have been updated to pass this value
yet.

The charmworldurl is also now passed to the server via the command line.

QA: just deploy your favorite bundle and see that it works as it should.
  You
can observe the bundle page in charmworld and see the counts remain at
0.

https://code.launchpad.net/~bac/charms/precise/juju-gui/increment-deployments/+merge/195317

(do not edit description out of merge proposal)

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

Affected files (+217, -41 lines):
   A [revision details]
   M config/guiserver.conf.template
   M hooks/backend.py
   M hooks/utils.py
   M server/guiserver/apps.py
   M server/guiserver/bundles/__init__.py
   M server/guiserver/bundles/base.py
   M server/guiserver/bundles/utils.py
   M server/guiserver/bundles/views.py
   M server/guiserver/manage.py
   M server/guiserver/tests/bundles/test_base.py
   M server/guiserver/tests/bundles/test_utils.py
   M server/guiserver/tests/bundles/test_views.py
   M server/runserver.py
   M tests/test_backends.py
   M tests/test_utils.py

Revision history for this message
Francesco Banconi (frankban) wrote :
Download full text (6.5 KiB)

This branch looks very nice Brad.
LGTM with changes. I added a lot of comments below, but they are mostly
minors/nice to have, except for some documentation requests, code
removal requests, and a possible string encoding problem. I will QA this
code when the changes are pushed later. Thank you!

https://codereview.appspot.com/26740043/diff/1/server/guiserver/apps.py
File server/guiserver/apps.py (right):

https://codereview.appspot.com/26740043/diff/1/server/guiserver/apps.py#newcode43
server/guiserver/apps.py:43: options.charmworldurl)
It would be nice to expose the charmworld URL also in the
/gui-server-info InfoHandler. That's just a nice to have for another
card I guess.

https://codereview.appspot.com/26740043/diff/1/server/guiserver/bundles/__init__.py
File server/guiserver/bundles/__init__.py (right):

https://codereview.appspot.com/26740043/diff/1/server/guiserver/bundles/__init__.py#newcode99
server/guiserver/bundles/__init__.py:99: },
In the other parts of this docstring, we aligned the closing brace of
the value with the key. <shrug>

https://codereview.appspot.com/26740043/diff/1/server/guiserver/bundles/__init__.py#newcode105
server/guiserver/bundles/__init__.py:105: parameter is optional in the
case YAML includes only one bundle.
Please document that the BundleID is completely optional, what that
value means, what it is used for and how it looks like (e.g. I guess it
must not start with "bundle:" because that's a bundle URL, but better
not to guess).

https://codereview.appspot.com/26740043/diff/1/server/guiserver/bundles/base.py
File server/guiserver/bundles/base.py (right):

https://codereview.appspot.com/26740043/diff/1/server/guiserver/bundles/base.py#newcode99
server/guiserver/bundles/base.py:99: self.bundle_ids = {}
Is bundle_ids really required? ISTM it's not really used.

https://codereview.appspot.com/26740043/diff/1/server/guiserver/bundles/base.py#newcode125
server/guiserver/bundles/base.py:125: def import_bundle(self, user,
name, bundle, bundle_id, test_callback=None):
It would be nice to make bundle_id explicitly optional (i.e.
bundle_id=None here).

https://codereview.appspot.com/26740043/diff/1/server/guiserver/bundles/base.py#newcode159
server/guiserver/bundles/base.py:159: self.bundle_ids[deployment_id] =
bundle_id
AFAICT these two lines can be removed as well.

https://codereview.appspot.com/26740043/diff/1/server/guiserver/bundles/base.py#newcode190
server/guiserver/bundles/base.py:190: # Increment the Charmworld
deployment count upon successful
The count is increased also if the deployment failed. It seems ok to me,
but we should fix the comment.

https://codereview.appspot.com/26740043/diff/1/server/guiserver/bundles/base.py#newcode192
server/guiserver/bundles/base.py:192: if bundle_id is not None:
Nice and simple.

https://codereview.appspot.com/26740043/diff/1/server/guiserver/bundles/utils.py
File server/guiserver/bundles/utils.py (right):

https://codereview.appspot.com/26740043/diff/1/server/guiserver/bundles/utils.py#newcode25
server/guiserver/bundles/utils.py:25: from tornado.httpclient import
AsyncHTTPClient
Very very minor: could you please move this import after the "from
tornado import..." one? O...

Read more...

135. By Brad Crittenden

Clean up tests and code from review

Revision history for this message
Brad Crittenden (bac) wrote :
Download full text (5.7 KiB)

Please take a look.

https://codereview.appspot.com/26740043/diff/1/server/guiserver/apps.py
File server/guiserver/apps.py (right):

https://codereview.appspot.com/26740043/diff/1/server/guiserver/apps.py#newcode43
server/guiserver/apps.py:43: options.charmworldurl)
Good idea but I'd rather not do it in this branch.

https://codereview.appspot.com/26740043/diff/1/server/guiserver/bundles/__init__.py
File server/guiserver/bundles/__init__.py (right):

https://codereview.appspot.com/26740043/diff/1/server/guiserver/bundles/__init__.py#newcode99
server/guiserver/bundles/__init__.py:99: },
On 2013/11/15 08:48:20, frankban wrote:
> In the other parts of this docstring, we aligned the closing brace of
the value
> with the key. <shrug>

Done.

https://codereview.appspot.com/26740043/diff/1/server/guiserver/bundles/__init__.py#newcode105
server/guiserver/bundles/__init__.py:105: parameter is optional in the
case YAML includes only one bundle.
On 2013/11/15 08:48:20, frankban wrote:
> Please document that the BundleID is completely optional, what that
value means,
> what it is used for and how it looks like (e.g. I guess it must not
start with
> "bundle:" because that's a bundle URL, but better not to guess).

Done.

https://codereview.appspot.com/26740043/diff/1/server/guiserver/bundles/base.py
File server/guiserver/bundles/base.py (right):

https://codereview.appspot.com/26740043/diff/1/server/guiserver/bundles/base.py#newcode99
server/guiserver/bundles/base.py:99: self.bundle_ids = {}
Good catch. I stopped using it when I began passing it directly to
add_future.

https://codereview.appspot.com/26740043/diff/1/server/guiserver/bundles/base.py#newcode125
server/guiserver/bundles/base.py:125: def import_bundle(self, user,
name, bundle, bundle_id, test_callback=None):
I don't see the reason. It is optional in the params payload and will
get passed as None if there. import_bundle is not called through any
other path.

https://codereview.appspot.com/26740043/diff/1/server/guiserver/bundles/base.py#newcode159
server/guiserver/bundles/base.py:159: self.bundle_ids[deployment_id] =
bundle_id
On 2013/11/15 08:48:20, frankban wrote:
> AFAICT these two lines can be removed as well.

Done.

https://codereview.appspot.com/26740043/diff/1/server/guiserver/bundles/base.py#newcode190
server/guiserver/bundles/base.py:190: # Increment the Charmworld
deployment count upon successful
I'd really like to only increment on success. I'll add a check for
error is None.

https://codereview.appspot.com/26740043/diff/1/server/guiserver/bundles/utils.py
File server/guiserver/bundles/utils.py (right):

https://codereview.appspot.com/26740043/diff/1/server/guiserver/bundles/utils.py#newcode25
server/guiserver/bundles/utils.py:25: from tornado.httpclient import
AsyncHTTPClient
On 2013/11/15 08:48:20, frankban wrote:
> Very very minor: could you please move this import after the "from
tornado
> import..." one? Or just add "httpclient" to the list below?
> <shrug>

Done.

https://codereview.appspot.com/26740043/diff/1/server/guiserver/bundles/utils.py#newcode229
server/guiserver/bundles/utils.py:229: url =
'{}api/3/bundle/{}/{}'.format(charmworld_url, bundle_id, path)
On 2013/11...

Read more...

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

Just one more comment, I'll QA asap.

https://codereview.appspot.com/26740043/diff/20001/server/guiserver/bundles/base.py
File server/guiserver/bundles/base.py (right):

https://codereview.appspot.com/26740043/diff/20001/server/guiserver/bundles/base.py#newcode188
server/guiserver/bundles/base.py:188: if bundle_id is not None and error
is None:
I the future has been cancelled then error is not defined here.

https://codereview.appspot.com/26740043/

136. By Brad Crittenden

Merge from trunk

137. By Brad Crittenden

Fix success criteria for incrementing deployment counter.

138. By Brad Crittenden

lint

139. By Brad Crittenden

Fix _import_callback error and test the method.

140. By Brad Crittenden

lint

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

*** Submitted:

After deploying a bundle increment counter.

The guiserver will make a GET request to the deployment counter
incrementer
URL for the bundle. This required accepting the bundle ID in the
Deployer/Import path.

Note neither the GUI nor quickstart have been updated to pass this value
yet.

The charmworldurl is also now passed to the server via the command line.

QA: just deploy your favorite bundle and see that it works as it should.
  You
can observe the bundle page in charmworld and see the counts remain at
0.

R=frankban
CC=
https://codereview.appspot.com/26740043

https://codereview.appspot.com/26740043/diff/20001/server/guiserver/bundles/base.py
File server/guiserver/bundles/base.py (right):

https://codereview.appspot.com/26740043/diff/20001/server/guiserver/bundles/base.py#newcode188
server/guiserver/bundles/base.py:188: if bundle_id is not None and error
is None:
On 2013/11/15 14:42:03, frankban wrote:
> I the future has been cancelled then error is not defined here.

Done.

https://codereview.appspot.com/26740043/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'config/guiserver.conf.template'
--- config/guiserver.conf.template 2013-08-26 12:18:19 +0000
+++ config/guiserver.conf.template 2013-11-15 18:31:21 +0000
@@ -8,6 +8,7 @@
8 --logging="{{builtin_server_logging}}" \8 --logging="{{builtin_server_logging}}" \
9 --guiroot="{{gui_root}}" \9 --guiroot="{{gui_root}}" \
10 --sslpath="{{ssl_cert_path}}" \10 --sslpath="{{ssl_cert_path}}" \
11 --charmworldurl="{{charmworld_url}}" \
11 {{if sandbox}}12 {{if sandbox}}
12 --sandbox \13 --sandbox \
13 {{else}}14 {{else}}
1415
=== modified file 'hooks/backend.py'
--- hooks/backend.py 2013-11-13 18:01:03 +0000
+++ hooks/backend.py 2013-11-15 18:31:21 +0000
@@ -216,7 +216,7 @@
216 utils.start_builtin_server(216 utils.start_builtin_server(
217 build_dir, config['ssl-cert-path'], config['serve-tests'],217 build_dir, config['ssl-cert-path'], config['serve-tests'],
218 config['sandbox'], config['builtin-server-logging'],218 config['sandbox'], config['builtin-server-logging'],
219 not config['secure'])219 not config['secure'], config['charmworld-url'])
220220
221 def stop(self, backend):221 def stop(self, backend):
222 utils.stop_builtin_server()222 utils.stop_builtin_server()
223223
=== modified file 'hooks/utils.py'
--- hooks/utils.py 2013-11-14 19:10:28 +0000
+++ hooks/utils.py 2013-11-15 18:31:21 +0000
@@ -557,7 +557,7 @@
557557
558def write_builtin_server_startup(558def write_builtin_server_startup(
559 gui_root, ssl_cert_path, serve_tests=False, sandbox=False,559 gui_root, ssl_cert_path, serve_tests=False, sandbox=False,
560 builtin_server_logging='info', insecure=False):560 builtin_server_logging='info', insecure=False, charmworld_url=''):
561 """Generate the builtin server Upstart file."""561 """Generate the builtin server Upstart file."""
562 log('Generating the builtin server Upstart file.')562 log('Generating the builtin server Upstart file.')
563 context = {563 context = {
@@ -567,6 +567,7 @@
567 'sandbox': sandbox,567 'sandbox': sandbox,
568 'serve_tests': serve_tests,568 'serve_tests': serve_tests,
569 'ssl_cert_path': ssl_cert_path,569 'ssl_cert_path': ssl_cert_path,
570 'charmworld_url': charmworld_url,
570 }571 }
571 if not sandbox:572 if not sandbox:
572 is_legacy_juju = legacy_juju()573 is_legacy_juju = legacy_juju()
@@ -586,11 +587,12 @@
586587
587def start_builtin_server(588def start_builtin_server(
588 build_dir, ssl_cert_path, serve_tests, sandbox, builtin_server_logging,589 build_dir, ssl_cert_path, serve_tests, sandbox, builtin_server_logging,
589 insecure):590 insecure, charmworld_url):
590 """Start the builtin server."""591 """Start the builtin server."""
591 write_builtin_server_startup(592 write_builtin_server_startup(
592 build_dir, ssl_cert_path, serve_tests=serve_tests, sandbox=sandbox,593 build_dir, ssl_cert_path, serve_tests=serve_tests, sandbox=sandbox,
593 builtin_server_logging=builtin_server_logging, insecure=insecure)594 builtin_server_logging=builtin_server_logging, insecure=insecure,
595 charmworld_url=charmworld_url)
594 log('Starting the builtin server.')596 log('Starting the builtin server.')
595 with su('root'):597 with su('root'):
596 service_control(BUILTIN_SERVER, RESTART)598 service_control(BUILTIN_SERVER, RESTART)
597599
=== modified file 'server/guiserver/apps.py'
--- server/guiserver/apps.py 2013-08-26 13:10:57 +0000
+++ server/guiserver/apps.py 2013-11-15 18:31:21 +0000
@@ -39,7 +39,8 @@
39 guiroot = options.guiroot39 guiroot = options.guiroot
40 static_path = os.path.join(guiroot, 'juju-ui')40 static_path = os.path.join(guiroot, 'juju-ui')
41 # Set up the bundle deployer.41 # Set up the bundle deployer.
42 deployer = Deployer(options.apiurl, options.apiversion)42 deployer = Deployer(options.apiurl, options.apiversion,
43 options.charmworldurl)
43 # Set up handlers.44 # Set up handlers.
44 server_handlers = []45 server_handlers = []
45 if not options.sandbox:46 if not options.sandbox:
4647
=== modified file 'server/guiserver/bundles/__init__.py'
--- server/guiserver/bundles/__init__.py 2013-09-23 14:45:58 +0000
+++ server/guiserver/bundles/__init__.py 2013-11-15 18:31:21 +0000
@@ -93,13 +93,19 @@
93 'RequestId': 1,93 'RequestId': 1,
94 'Type': 'Deployer',94 'Type': 'Deployer',
95 'Request': 'Import',95 'Request': 'Import',
96 'Params': {'Name': 'bundle-name', 'YAML': 'bundles'},96 'Params': {
97 'Name': 'bundle-name',
98 'YAML': 'bundles',
99 'BundleID': 'id'
100 },
97 }101 }
98102
99In the request parameters above, the YAML field stores the YAML encoded103In the request parameters above, the YAML field stores the YAML encoded
100contents representing one or more bundles, and the Name field is the name of104contents representing one or more bundles, and the Name field is the name of
101the specific bundle (included in YAML) that must be deployed. The Name105the specific bundle (included in YAML) that must be deployed. The Name
102parameter is optional in the case YAML includes only one bundle.106parameter is optional in the case YAML includes only one bundle. The BundleID
107is optional and is used for incrementing the deployment counter in
108Charmworld.
103109
104After receiving a deployment request, the DeployMiddleware sends a response110After receiving a deployment request, the DeployMiddleware sends a response
105indicating whether or not the request has been accepted. This response is sent111indicating whether or not the request has been accepted. This response is sent
106112
=== modified file 'server/guiserver/bundles/base.py'
--- server/guiserver/bundles/base.py 2013-11-07 11:17:54 +0000
+++ server/guiserver/bundles/base.py 2013-11-15 18:31:21 +0000
@@ -69,7 +69,7 @@
69 singleton by all WebSocket requests.69 singleton by all WebSocket requests.
70 """70 """
7171
72 def __init__(self, apiurl, apiversion, io_loop=None):72 def __init__(self, apiurl, apiversion, charmworldurl=None, io_loop=None):
73 """Initialize the deployer.73 """Initialize the deployer.
7474
75 The apiurl argument is the URL of the juju-core WebSocket server.75 The apiurl argument is the URL of the juju-core WebSocket server.
@@ -77,6 +77,9 @@
77 """77 """
78 self._apiurl = apiurl78 self._apiurl = apiurl
79 self._apiversion = apiversion79 self._apiversion = apiversion
80 if charmworldurl is not None and not charmworldurl.endswith('/'):
81 charmworldurl = charmworldurl + '/'
82 self._charmworldurl = charmworldurl
80 if io_loop is None:83 if io_loop is None:
81 io_loop = IOLoop.current()84 io_loop = IOLoop.current()
82 self._io_loop = io_loop85 self._io_loop = io_loop
@@ -117,15 +120,16 @@
117 except Exception as err:120 except Exception as err:
118 raise gen.Return(str(err))121 raise gen.Return(str(err))
119122
120 def import_bundle(self, user, name, bundle, test_callback=None):123 def import_bundle(self, user, name, bundle, bundle_id, test_callback=None):
121 """Schedule a deployment bundle import process.124 """Schedule a deployment bundle import process.
122125
123 The deployment is executed in a separate process.126 The deployment is executed in a separate process.
124127
125 Three arguments are required:128 The following arguments are required:
126 - user: the current authenticated user;129 - user: the current authenticated user;
127 - name: then name of the bundle to be imported;130 - name: the name of the bundle to be imported;
128 - bundle: a YAML decoded object representing the bundle contents.131 - bundle: a YAML decoded object representing the bundle contents.
132 - bundle_id: the ID of the bundle. May be None.
129133
130 It is possible to also provide an optional test_callback that will be134 It is possible to also provide an optional test_callback that will be
131 called when the deployment is completed. Note that this functionality135 called when the deployment is completed. Note that this functionality
@@ -146,7 +150,8 @@
146 future = self._run_executor.submit(150 future = self._run_executor.submit(
147 blocking.import_bundle,151 blocking.import_bundle,
148 self._apiurl, user.password, name, bundle, IMPORTER_OPTIONS)152 self._apiurl, user.password, name, bundle, IMPORTER_OPTIONS)
149 add_future(self._io_loop, future, self._import_callback, deployment_id)153 add_future(self._io_loop, future, self._import_callback,
154 deployment_id, bundle_id)
150 self._futures[deployment_id] = future155 self._futures[deployment_id] = future
151 # If a customized callback is provided, schedule it as well.156 # If a customized callback is provided, schedule it as well.
152 if test_callback is not None:157 if test_callback is not None:
@@ -157,7 +162,7 @@
157 self._run_executor.submit(time.sleep, 1)162 self._run_executor.submit(time.sleep, 1)
158 return deployment_id163 return deployment_id
159164
160 def _import_callback(self, deployment_id, future):165 def _import_callback(self, deployment_id, bundle_id, future):
161 """Callback called when a deployment process is completed.166 """Callback called when a deployment process is completed.
162167
163 This callback, scheduled in self.import_bundle(), receives the168 This callback, scheduled in self.import_bundle(), receives the
@@ -167,17 +172,24 @@
167 if future.cancelled():172 if future.cancelled():
168 # Notify a deployment has been cancelled.173 # Notify a deployment has been cancelled.
169 self._observer.notify_cancelled(deployment_id)174 self._observer.notify_cancelled(deployment_id)
175 success = False
170 else:176 else:
171 exception = future.exception()177 exception = future.exception()
172 error = None if exception is None else str(exception)178 error = None if exception is None else str(exception)
173 # Notify a deployment completed.179 # Notify a deployment completed.
174 self._observer.notify_completed(deployment_id, error=error)180 self._observer.notify_completed(deployment_id, error=error)
181 success = (error is None)
175 # Remove the completed deployment job from the queue.182 # Remove the completed deployment job from the queue.
176 self._queue.remove(deployment_id)183 self._queue.remove(deployment_id)
177 del self._futures[deployment_id]184 del self._futures[deployment_id]
178 # Notify the new position of all remaining deployments in the queue.185 # Notify the new position of all remaining deployments in the queue.
179 for position, deploy_id in enumerate(self._queue):186 for position, deploy_id in enumerate(self._queue):
180 self._observer.notify_position(deploy_id, position)187 self._observer.notify_position(deploy_id, position)
188 # Increment the Charmworld deployment count upon successful
189 # deployment.
190 if success and bundle_id is not None:
191 utils.increment_deployment_counter(
192 bundle_id, self._charmworldurl)
181193
182 def watch(self, deployment_id):194 def watch(self, deployment_id):
183 """Start watching a deployment and return a watcher identifier.195 """Start watching a deployment and return a watcher identifier.
@@ -232,7 +244,7 @@
232244
233 This class handles the process of parsing requests from the GUI, checking245 This class handles the process of parsing requests from the GUI, checking
234 if any incoming message is a deployment request, ensuring that the request246 if any incoming message is a deployment request, ensuring that the request
235 is well formed and, if so, forwarding the requests to the bundle views.247 is well-formed and, if so, forwarding the requests to the bundle views.
236248
237 Assuming that:249 Assuming that:
238 - user is a guiserver.auth.User instance (used by this middleware in250 - user is a guiserver.auth.User instance (used by this middleware in
239251
=== modified file 'server/guiserver/bundles/utils.py'
--- server/guiserver/bundles/utils.py 2013-11-07 13:55:56 +0000
+++ server/guiserver/bundles/utils.py 2013-11-15 18:31:21 +0000
@@ -21,11 +21,13 @@
21import itertools21import itertools
22import logging22import logging
23import time23import time
24import urllib
2425
25from tornado import (26from tornado import (
26 gen,27 gen,
27 escape,28 escape,
28)29)
30from tornado.httpclient import AsyncHTTPClient
2931
30from guiserver.watchers import AsyncWatcher32from guiserver.watchers import AsyncWatcher
3133
@@ -204,3 +206,44 @@
204 logging.error('deployer: {}'.format(escape.utf8(error)))206 logging.error('deployer: {}'.format(escape.utf8(error)))
205 data['Error'] = error207 data['Error'] = error
206 return gen.Return(data)208 return gen.Return(data)
209
210
211@gen.coroutine
212def increment_deployment_counter(bundle_id, charmworld_url):
213 """Increment the deployment count in Charmworld.
214
215 If the call to Charmworld fails we log the error but don't report it.
216 This counter is a 'best effort' attempt but it will not impede our
217 deployment of the bundle.
218
219 Arguments are:
220 - bundle_id: the ID for the bundle in Charmworld.
221 - charmworld_url: the URL for charmworld, including the protocol.
222 If None, do nothing.
223
224 Returns True if the counter is successfully incremented else False.
225 """
226 if charmworld_url is None:
227 raise gen.Return(False)
228
229 if not all((isinstance(bundle_id, basestring),
230 isinstance(charmworld_url, basestring))):
231 raise gen.Return(False)
232
233 path = 'metric/deployments/increment'
234 url = u'{}api/3/bundle/{}/{}'.format(
235 charmworld_url,
236 urllib.quote(bundle_id), path)
237 logging.info('Incrementing bundle deployment count using\n{}.'.format(
238 url.encode('utf-8')))
239 client = AsyncHTTPClient()
240 # We use a GET instead of a POST since there is not request body.
241 try:
242 resp = yield client.fetch(url, callback=None)
243 except Exception as exc:
244 logging.error('Attempt to increment deployment counter failed.')
245 logging.error('URL: {}'.format(url))
246 logging.exception(exc)
247 raise gen.Return(False)
248 success = bool(resp.code == 200)
249 raise gen.Return(success)
207250
=== modified file 'server/guiserver/bundles/views.py'
--- server/guiserver/bundles/views.py 2013-11-07 11:17:54 +0000
+++ server/guiserver/bundles/views.py 2013-11-15 18:31:21 +0000
@@ -71,11 +71,15 @@
7171
7272
73def _validate_import_params(params):73def _validate_import_params(params):
74 """Parse the request data and return a (name, bundle) tuple.74 """Parse the request data and return a (name, bundle, bundle_id) tuple.
7575
76 In the tuple:76 In the tuple:
77 - name is the name of the bundle to be imported;77 - name is the name of the bundle to be imported;
78 - bundle is the YAML decoded bundle object.78 - bundle is the YAML decoded bundle object.
79 - bundle_id is the permanent id of the bundle of the form
80 ~user/basketname/version/bundlename, e.g.
81 ~jorge/mediawiki/3/mediawiki-simple. The bundle_id is optional and
82 will be None if not given.
7983
80 Raise a ValueError if data represents an invalid request.84 Raise a ValueError if data represents an invalid request.
81 """85 """
@@ -88,14 +92,17 @@
88 raise ValueError('invalid YAML contents: {}'.format(err))92 raise ValueError('invalid YAML contents: {}'.format(err))
89 name = params.get('Name')93 name = params.get('Name')
90 if name is None:94 if name is None:
91 # The Name is optional if the YAML contents contain only one bunlde.95 # The Name is optional if the YAML contents contain only one bundle.
92 if len(bundles) == 1:96 if len(bundles) == 1:
93 return bundles.items()[0]97 name = bundles.keys()[0]
94 raise ValueError('invalid data parameters: no bundle name provided')98 else:
99 raise ValueError(
100 'invalid data parameters: no bundle name provided')
95 bundle = bundles.get(name)101 bundle = bundles.get(name)
96 if bundle is None:102 if bundle is None:
97 raise ValueError('bundle {} not found'.format(name))103 raise ValueError('bundle {} not found'.format(name))
98 return name, bundle104 bundle_id = params.get('BundleID')
105 return name, bundle, bundle_id
99106
100107
101@gen.coroutine108@gen.coroutine
@@ -111,7 +118,7 @@
111 """118 """
112 # Validate the request parameters.119 # Validate the request parameters.
113 try:120 try:
114 name, bundle = _validate_import_params(request.params)121 name, bundle, bundle_id = _validate_import_params(request.params)
115 except ValueError as err:122 except ValueError as err:
116 raise response(error='invalid request: {}'.format(err))123 raise response(error='invalid request: {}'.format(err))
117 # Validate and prepare the bundle.124 # Validate and prepare the bundle.
@@ -125,7 +132,8 @@
125 if err is not None:132 if err is not None:
126 raise response(error='invalid request: {}'.format(err))133 raise response(error='invalid request: {}'.format(err))
127 # Add the bundle deployment to the Deployer queue.134 # Add the bundle deployment to the Deployer queue.
128 deployment_id = deployer.import_bundle(request.user, name, bundle)135 deployment_id = deployer.import_bundle(
136 request.user, name, bundle, bundle_id)
129 raise response({'DeploymentId': deployment_id})137 raise response({'DeploymentId': deployment_id})
130138
131139
132140
=== modified file 'server/guiserver/manage.py'
--- server/guiserver/manage.py 2013-08-26 11:27:32 +0000
+++ server/guiserver/manage.py 2013-11-15 18:31:21 +0000
@@ -116,6 +116,10 @@
116 'an in-memory backend. When this is set to True, the GUI server '116 'an in-memory backend. When this is set to True, the GUI server '
117 'does not listen to incoming WebSocket connections, and '117 'does not listen to incoming WebSocket connections, and '
118 'therefore the --apiurl and --apiversion options are ignored.')118 'therefore the --apiurl and --apiversion options are ignored.')
119 define(
120 'charmworldurl', type=str,
121 help='The URL to use for Charmworld.')
122
119 # In Tornado, parsing the options also sets up the default logger.123 # In Tornado, parsing the options also sets up the default logger.
120 parse_command_line()124 parse_command_line()
121 _validate_required('guiroot')125 _validate_required('guiroot')
122126
=== modified file 'server/guiserver/tests/bundles/test_base.py'
--- server/guiserver/tests/bundles/test_base.py 2013-11-07 17:27:20 +0000
+++ server/guiserver/tests/bundles/test_base.py 2013-11-15 18:31:21 +0000
@@ -42,6 +42,18 @@
42 raise jujuclient.EnvError({'Error': 'bad wolf'})42 raise jujuclient.EnvError({'Error': 'bad wolf'})
4343
4444
45class FakeFuture(object):
46 def __init__(self, cancelled=False, exception=None):
47 self._cancelled = cancelled
48 self._exception = exception
49
50 def cancelled(self):
51 return self._cancelled
52
53 def exception(self):
54 return self._exception
55
56
45@mock.patch('time.time', mock.Mock(return_value=42))57@mock.patch('time.time', mock.Mock(return_value=42))
46class TestDeployer(helpers.BundlesTestMixin, AsyncTestCase):58class TestDeployer(helpers.BundlesTestMixin, AsyncTestCase):
4759
@@ -108,7 +120,8 @@
108 deployer = self.make_deployer()120 deployer = self.make_deployer()
109 with self.patch_import_bundle():121 with self.patch_import_bundle():
110 deployment_id = deployer.import_bundle(122 deployment_id = deployer.import_bundle(
111 self.user, 'bundle', self.bundle, test_callback=self.stop)123 self.user, 'bundle', self.bundle, bundle_id=None,
124 test_callback=self.stop)
112 self.assertIsInstance(deployment_id, int)125 self.assertIsInstance(deployment_id, int)
113 # Wait for the deployment to be completed.126 # Wait for the deployment to be completed.
114 self.wait()127 self.wait()
@@ -118,7 +131,8 @@
118 deployer = self.make_deployer()131 deployer = self.make_deployer()
119 with self.patch_import_bundle() as mock_import_bundle:132 with self.patch_import_bundle() as mock_import_bundle:
120 deployer.import_bundle(133 deployer.import_bundle(
121 self.user, 'bundle', self.bundle, test_callback=self.stop)134 self.user, 'bundle', self.bundle, bundle_id=None,
135 test_callback=self.stop)
122 # Wait for the deployment to be completed.136 # Wait for the deployment to be completed.
123 self.wait()137 self.wait()
124 mock_import_bundle.assert_called_once_with(138 mock_import_bundle.assert_called_once_with(
@@ -140,7 +154,8 @@
140 deployer = self.make_deployer()154 deployer = self.make_deployer()
141 with self.patch_import_bundle():155 with self.patch_import_bundle():
142 deployment_id = deployer.import_bundle(156 deployment_id = deployer.import_bundle(
143 self.user, 'bundle', self.bundle, test_callback=self.stop)157 self.user, 'bundle', self.bundle, bundle_id=None,
158 test_callback=self.stop)
144 watcher_id = deployer.watch(deployment_id)159 watcher_id = deployer.watch(deployment_id)
145 self.assertIsInstance(watcher_id, int)160 self.assertIsInstance(watcher_id, int)
146 # Wait for the deployment to be completed.161 # Wait for the deployment to be completed.
@@ -157,7 +172,8 @@
157 deployer = self.make_deployer()172 deployer = self.make_deployer()
158 with self.patch_import_bundle():173 with self.patch_import_bundle():
159 deployment_id = deployer.import_bundle(174 deployment_id = deployer.import_bundle(
160 self.user, 'bundle', self.bundle, test_callback=self.stop)175 self.user, 'bundle', self.bundle, bundle_id=None,
176 test_callback=self.stop)
161 watcher_id = deployer.watch(deployment_id)177 watcher_id = deployer.watch(deployment_id)
162 # A first change is received notifying that the deployment is started.178 # A first change is received notifying that the deployment is started.
163 changes = yield deployer.next(watcher_id)179 changes = yield deployer.next(watcher_id)
@@ -178,9 +194,10 @@
178 deployer = self.make_deployer()194 deployer = self.make_deployer()
179 with self.patch_import_bundle():195 with self.patch_import_bundle():
180 deployment1 = deployer.import_bundle(196 deployment1 = deployer.import_bundle(
181 self.user, 'bundle', self.bundle)197 self.user, 'bundle', self.bundle, bundle_id=None)
182 deployment2 = deployer.import_bundle(198 deployment2 = deployer.import_bundle(
183 self.user, 'bundle', self.bundle, test_callback=self.stop)199 self.user, 'bundle', self.bundle, bundle_id=None,
200 test_callback=self.stop)
184 watcher1 = deployer.watch(deployment1)201 watcher1 = deployer.watch(deployment1)
185 watcher2 = deployer.watch(deployment2)202 watcher2 = deployer.watch(deployment2)
186 # The first deployment is started.203 # The first deployment is started.
@@ -205,7 +222,8 @@
205 deployer = self.make_deployer()222 deployer = self.make_deployer()
206 with self.patch_import_bundle(side_effect=RuntimeError('bad wolf')):223 with self.patch_import_bundle(side_effect=RuntimeError('bad wolf')):
207 deployment_id = deployer.import_bundle(224 deployment_id = deployer.import_bundle(
208 self.user, 'bundle', self.bundle, test_callback=self.stop)225 self.user, 'bundle', self.bundle, bundle_id=None,
226 test_callback=self.stop)
209 watcher_id = deployer.watch(deployment_id)227 watcher_id = deployer.watch(deployment_id)
210 # We expect two changes: the second one should include the error.228 # We expect two changes: the second one should include the error.
211 yield deployer.next(watcher_id)229 yield deployer.next(watcher_id)
@@ -222,7 +240,8 @@
222 import_bundle_path = 'guiserver.bundles.base.blocking.import_bundle'240 import_bundle_path = 'guiserver.bundles.base.blocking.import_bundle'
223 with mock.patch(import_bundle_path, import_bundle_mock):241 with mock.patch(import_bundle_path, import_bundle_mock):
224 deployer.import_bundle(242 deployer.import_bundle(
225 self.user, 'bundle', self.bundle, test_callback=self.stop)243 self.user, 'bundle', self.bundle, bundle_id=None,
244 test_callback=self.stop)
226 # Wait for the deployment to be completed.245 # Wait for the deployment to be completed.
227 self.wait()246 self.wait()
228 status = deployer.status()247 status = deployer.status()
@@ -249,9 +268,10 @@
249 # The test callback is passed to the first deployment because we268 # The test callback is passed to the first deployment because we
250 # expect the second one to be immediately cancelled.269 # expect the second one to be immediately cancelled.
251 deployer.import_bundle(270 deployer.import_bundle(
252 self.user, 'bundle', self.bundle, test_callback=self.stop)271 self.user, 'bundle', self.bundle, bundle_id=None,
272 test_callback=self.stop)
253 deployment_id = deployer.import_bundle(273 deployment_id = deployer.import_bundle(
254 self.user, 'bundle', self.bundle)274 self.user, 'bundle', self.bundle, bundle_id=None)
255 watcher_id = deployer.watch(deployment_id)275 watcher_id = deployer.watch(deployment_id)
256 self.assertIsNone(deployer.cancel(deployment_id))276 self.assertIsNone(deployer.cancel(deployment_id))
257 # We expect two changes: the second one should notify the deployment277 # We expect two changes: the second one should notify the deployment
@@ -274,7 +294,8 @@
274 deployer = self.make_deployer()294 deployer = self.make_deployer()
275 with self.patch_import_bundle():295 with self.patch_import_bundle():
276 deployment_id = deployer.import_bundle(296 deployment_id = deployer.import_bundle(
277 self.user, 'bundle', self.bundle, test_callback=self.stop)297 self.user, 'bundle', self.bundle, bundle_id=None,
298 test_callback=self.stop)
278 watcher_id = deployer.watch(deployment_id)299 watcher_id = deployer.watch(deployment_id)
279 # Assume the deployment is completed after two changes.300 # Assume the deployment is completed after two changes.
280 yield deployer.next(watcher_id)301 yield deployer.next(watcher_id)
@@ -291,7 +312,8 @@
291 deployer = self.make_deployer()312 deployer = self.make_deployer()
292 with self.patch_import_bundle() as mock_import_bundle:313 with self.patch_import_bundle() as mock_import_bundle:
293 deployment_id = deployer.import_bundle(314 deployment_id = deployer.import_bundle(
294 self.user, 'bundle', self.bundle, test_callback=self.stop)315 self.user, 'bundle', self.bundle, bundle_id=None,
316 test_callback=self.stop)
295 watcher_id = deployer.watch(deployment_id)317 watcher_id = deployer.watch(deployment_id)
296 # Wait until the deployment is started.318 # Wait until the deployment is started.
297 yield deployer.next(watcher_id)319 yield deployer.next(watcher_id)
@@ -314,9 +336,10 @@
314 deployer = self.make_deployer()336 deployer = self.make_deployer()
315 with self.patch_import_bundle():337 with self.patch_import_bundle():
316 deployment1 = deployer.import_bundle(338 deployment1 = deployer.import_bundle(
317 self.user, 'bundle', self.bundle)339 self.user, 'bundle', self.bundle, bundle_id=None)
318 deployment2 = deployer.import_bundle(340 deployment2 = deployer.import_bundle(
319 self.user, 'bundle', self.bundle, test_callback=self.stop)341 self.user, 'bundle', self.bundle, bundle_id=None,
342 test_callback=self.stop)
320 # Wait for the deployment to be completed.343 # Wait for the deployment to be completed.
321 self.wait()344 self.wait()
322 # At this point we expect two completed deployments.345 # At this point we expect two completed deployments.
@@ -326,6 +349,64 @@
326 self.assertEqual(deployment1, change1['DeploymentId'])349 self.assertEqual(deployment1, change1['DeploymentId'])
327 self.assertEqual(deployment2, change2['DeploymentId'])350 self.assertEqual(deployment2, change2['DeploymentId'])
328351
352 def test_import_callback_cancelled(self):
353 deployer = self.make_deployer()
354 deployer_id = 123
355 deployer._queue.append(deployer_id)
356 deployer._futures[deployer_id] = None
357 mock_path = 'guiserver.bundles.utils.increment_deployment_counter'
358 future = FakeFuture(True)
359 with mock.patch.object(
360 deployer._observer, 'notify_cancelled') as mock_notify:
361 with mock.patch(mock_path) as mock_incrementer:
362 deployer._import_callback(deployer_id, None, future)
363 mock_notify.assert_called_with(deployer_id)
364 self.assertFalse(mock_incrementer.called)
365
366 def test_import_callback_error(self):
367 deployer = self.make_deployer()
368 deployer_id = 123
369 deployer._queue.append(deployer_id)
370 deployer._futures[deployer_id] = None
371 mock_path = 'guiserver.bundles.utils.increment_deployment_counter'
372 future = FakeFuture(exception='aiiee')
373 with mock.patch.object(
374 deployer._observer, 'notify_completed') as mock_notify:
375 with mock.patch(mock_path) as mock_incrementer:
376 deployer._import_callback(deployer_id, None, future)
377 mock_notify.assert_called_with(deployer_id, error='aiiee')
378 self.assertFalse(mock_incrementer.called)
379
380 def test_import_callback_no_bundleid(self):
381 deployer = self.make_deployer()
382 deployer_id = 123
383 deployer._queue.append(deployer_id)
384 deployer._futures[deployer_id] = None
385 mock_path = 'guiserver.bundles.utils.increment_deployment_counter'
386 future = FakeFuture()
387 with mock.patch.object(
388 deployer._observer, 'notify_completed') as mock_notify:
389 with mock.patch(mock_path) as mock_incrementer:
390 deployer._import_callback(deployer_id, None, future)
391 mock_notify.assert_called_with(deployer_id, error=None)
392 self.assertFalse(mock_incrementer.called)
393
394 def test_import_callback_success(self):
395 deployer = self.make_deployer()
396 deployer_id = 123
397 bundle_id = '~jorge/basket/bundle'
398 deployer._charmworldurl = 'http://cw.example.com'
399 deployer._queue.append(deployer_id)
400 deployer._futures[deployer_id] = None
401 mock_path = 'guiserver.bundles.utils.increment_deployment_counter'
402 future = FakeFuture()
403 with mock.patch.object(
404 deployer._observer, 'notify_completed') as mock_notify:
405 with mock.patch(mock_path) as mock_incrementer:
406 deployer._import_callback(deployer_id, bundle_id, future)
407 mock_notify.assert_called_with(deployer_id, error=None)
408 mock_incrementer.assert_called_with(bundle_id, deployer._charmworldurl)
409
329410
330class TestDeployMiddleware(helpers.BundlesTestMixin, AsyncTestCase):411class TestDeployMiddleware(helpers.BundlesTestMixin, AsyncTestCase):
331412
332413
=== modified file 'server/guiserver/tests/bundles/test_utils.py'
--- server/guiserver/tests/bundles/test_utils.py 2013-11-07 13:55:56 +0000
+++ server/guiserver/tests/bundles/test_utils.py 2013-11-15 18:31:21 +0000
@@ -18,6 +18,7 @@
1818
19import unittest19import unittest
2020
21from concurrent.futures import Future
21import mock22import mock
22from tornado import gen23from tornado import gen
23from tornado.testing import(24from tornado.testing import(
@@ -26,6 +27,7 @@
26 gen_test,27 gen_test,
27 LogTrapTestCase,28 LogTrapTestCase,
28)29)
30import urllib
2931
30from guiserver import watchers32from guiserver import watchers
31from guiserver.bundles import utils33from guiserver.bundles import utils
@@ -422,3 +424,80 @@
422 # An error log is written when a failure response is generated.424 # An error log is written when a failure response is generated.
423 with ExpectLog('', 'deployer: an error occurred', required=True):425 with ExpectLog('', 'deployer: an error occurred', required=True):
424 utils.response(error='an error occurred')426 utils.response(error='an error occurred')
427
428
429def mock_fetch_factory(response_code, called_with=None):
430 def fetch(*args, **kwargs):
431 if called_with is not None:
432 called_with.append((args[1:], kwargs))
433
434 class FakeResponse(object):
435 pass
436
437 resp = FakeResponse()
438 resp.code = response_code
439 future = Future()
440 future.set_result(resp)
441 return future
442 return fetch
443
444
445class TestIncrementDeploymentCounter(LogTrapTestCase, AsyncTestCase):
446
447 @gen_test
448 def test_no_cw_url_returns_true(self):
449 bundle_id = '~bac/muletrain/wiki'
450 mock_path = 'tornado.httpclient.AsyncHTTPClient.fetch'
451 with mock.patch(mock_path) as mock_fetch:
452 ok = yield utils.increment_deployment_counter(bundle_id, None)
453 self.assertFalse(ok)
454 self.assertFalse(mock_fetch.called)
455
456 @gen_test
457 def test_increment_nonstring_bundle_id(self):
458 bundle_id = 4
459 cw_url = 'http://my.charmworld.example.com/'
460 mock_path = 'tornado.httpclient.AsyncHTTPClient.fetch'
461 with mock.patch(mock_path) as mock_fetch:
462 ok = yield utils.increment_deployment_counter(bundle_id, cw_url)
463 self.assertFalse(ok)
464 self.assertFalse(mock_fetch.called)
465
466 @gen_test
467 def test_increment_nonstring_cwurl(self):
468 bundle_id = u'~bac/muletrain/wiki'
469 cw_url = 7
470 mock_path = 'tornado.httpclient.AsyncHTTPClient.fetch'
471 with mock.patch(mock_path) as mock_fetch:
472 ok = yield utils.increment_deployment_counter(bundle_id, cw_url)
473 self.assertFalse(ok)
474 self.assertFalse(mock_fetch.called)
475
476 @gen_test
477 def test_increment_url_logged(self):
478 bundle_id = '~bac/muletrain/wiki'
479 cw_url = 'http://my.charmworld.example.com/'
480 url = u'{}api/3/bundle/{}/metric/deployments/increment'.format(
481 cw_url, bundle_id)
482 expected = 'Incrementing bundle.+'
483 called_with = []
484 mock_fetch = mock_fetch_factory(200, called_with)
485 with ExpectLog('', expected, required=True):
486 mock_path = 'tornado.httpclient.AsyncHTTPClient.fetch'
487 with mock.patch(mock_path, mock_fetch):
488 ok = yield utils.increment_deployment_counter(
489 bundle_id, cw_url)
490 self.assertTrue(ok)
491 called_args, called_kwargs = called_with[0]
492 self.assertEqual(url, urllib.unquote(called_args[0]))
493 self.assertEqual(dict(callback=None), called_kwargs)
494
495 @gen_test
496 def test_increment_errors(self):
497 bundle_id = '~bac/muletrain/wiki'
498 cw_url = 'http://my.charmworld.example.com/'
499 mock_path = 'tornado.httpclient.AsyncHTTPClient.fetch'
500 mock_fetch = mock_fetch_factory(404)
501 with mock.patch(mock_path, mock_fetch):
502 ok = yield utils.increment_deployment_counter(bundle_id, cw_url)
503 self.assertFalse(ok)
425504
=== modified file 'server/guiserver/tests/bundles/test_views.py'
--- server/guiserver/tests/bundles/test_views.py 2013-11-07 11:17:54 +0000
+++ server/guiserver/tests/bundles/test_views.py 2013-11-15 18:31:21 +0000
@@ -206,13 +206,38 @@
206 # Ensure the Deployer methods have been correctly called.206 # Ensure the Deployer methods have been correctly called.
207 args = (request.user, 'mybundle', {'services': {}})207 args = (request.user, 'mybundle', {'services': {}})
208 self.deployer.validate.assert_called_once_with(*args)208 self.deployer.validate.assert_called_once_with(*args)
209 args = (request.user, 'mybundle', {'services': {}}, None)
209 self.deployer.import_bundle.assert_called_once_with(*args)210 self.deployer.import_bundle.assert_called_once_with(*args)
210211
211 @gen_test212 # The following tests exercise views._validate_import_params directly.
212 def test_no_name_success(self):213 def test_no_name_success(self):
213 # The process succeeds if the bundle name is not provided but the214 # The process succeeds if the bundle name is not provided but the
214 # YAML contents include just one bundle.215 # YAML contents include just one bundle.
215 params = {'YAML': 'mybundle: {services: {}}'}216 params = {'YAML': 'mybundle: {services: {}}'}
217 results = views._validate_import_params(params)
218 expected = ('mybundle', {'services': {}}, None)
219 self.assertEqual(expected, results)
220
221 def test_id_provided(self):
222 params = {'YAML': 'mybundle: {services: {}}',
223 'BundleID': '~jorge/wiki/3/smallwiki'}
224 results = views._validate_import_params(params)
225 expected = ('mybundle', {'services': {}}, '~jorge/wiki/3/smallwiki')
226 self.assertEqual(expected, results)
227
228 def test_id_and_name_provided(self):
229 params = {'YAML': 'mybundle: {services: {}}',
230 'Name': 'mybundle',
231 'BundleID': '~jorge/wiki/3/smallwiki'}
232 results = views._validate_import_params(params)
233 expected = ('mybundle', {'services': {}}, '~jorge/wiki/3/smallwiki')
234 self.assertEqual(expected, results)
235
236 @gen_test
237 def test_id_passed_to_deployer(self):
238 params = {'YAML': 'mybundle: {services: {}}',
239 'Name': 'mybundle',
240 'BundleID': '~jorge/wiki/3/smallwiki'}
216 request = self.make_view_request(params=params)241 request = self.make_view_request(params=params)
217 # Set up the Deployer mock.242 # Set up the Deployer mock.
218 self.deployer.validate.return_value = self.make_future(None)243 self.deployer.validate.return_value = self.make_future(None)
@@ -222,6 +247,8 @@
222 # Ensure the Deployer methods have been correctly called.247 # Ensure the Deployer methods have been correctly called.
223 args = (request.user, 'mybundle', {'services': {}})248 args = (request.user, 'mybundle', {'services': {}})
224 self.deployer.validate.assert_called_once_with(*args)249 self.deployer.validate.assert_called_once_with(*args)
250 args = (request.user, 'mybundle', {'services': {}},
251 '~jorge/wiki/3/smallwiki')
225 self.deployer.import_bundle.assert_called_once_with(*args)252 self.deployer.import_bundle.assert_called_once_with(*args)
226253
227254
228255
=== modified file 'server/runserver.py'
--- server/runserver.py 2013-08-26 11:27:32 +0000
+++ server/runserver.py 2013-11-15 18:31:21 +0000
@@ -25,6 +25,7 @@
25 --insecure25 --insecure
26 --sandbox26 --sandbox
27 --logging=debug|info|warning|error27 --logging=debug|info|warning|error
28 --charmworldurl="https://manage.jujucharms.com/"
2829
29The --sslpath option is ignored if --insecure is set.30The --sslpath option is ignored if --insecure is set.
30The --apiurl and --apiversion options are ignored if --sandbox is set.31The --apiurl and --apiversion options are ignored if --sandbox is set.
3132
=== modified file 'tests/test_backends.py'
--- tests/test_backends.py 2013-11-13 18:31:14 +0000
+++ tests/test_backends.py 2013-11-15 18:31:21 +0000
@@ -202,7 +202,7 @@
202 config = {202 config = {
203 'builtin-server': True,203 'builtin-server': True,
204 'builtin-server-logging': 'info',204 'builtin-server-logging': 'info',
205 'charmworld-url': 'http://charmworld.example.com',205 'charmworld-url': 'http://charmworld.example.com/',
206 'command-log-file': self.command_log_file,206 'command-log-file': self.command_log_file,
207 'default-viewmode': 'sidebar',207 'default-viewmode': 'sidebar',
208 'ga-key': 'my-key',208 'ga-key': 'my-key',
@@ -437,7 +437,8 @@
437 mocks.start_builtin_server.assert_called_once_with(437 mocks.start_builtin_server.assert_called_once_with(
438 mocks.compute_build_dir(), self.ssl_cert_path,438 mocks.compute_build_dir(), self.ssl_cert_path,
439 config['serve-tests'], config['sandbox'],439 config['serve-tests'], config['sandbox'],
440 config['builtin-server-logging'], not config['secure'])440 config['builtin-server-logging'], not config['secure'],
441 config['charmworld-url'])
441 self.assertFalse(mocks.start_haproxy_apache.called)442 self.assertFalse(mocks.start_haproxy_apache.called)
442443
443 def test_start_go_builtin(self):444 def test_start_go_builtin(self):
@@ -455,7 +456,8 @@
455 mocks.start_builtin_server.assert_called_once_with(456 mocks.start_builtin_server.assert_called_once_with(
456 mocks.compute_build_dir(), self.ssl_cert_path,457 mocks.compute_build_dir(), self.ssl_cert_path,
457 config['serve-tests'], config['sandbox'],458 config['serve-tests'], config['sandbox'],
458 config['builtin-server-logging'], not config['secure'])459 config['builtin-server-logging'], not config['secure'],
460 config['charmworld-url'])
459 self.assertFalse(mocks.start_haproxy_apache.called)461 self.assertFalse(mocks.start_haproxy_apache.called)
460462
461 def test_stop_python_legacy(self):463 def test_stop_python_legacy(self):
462464
=== modified file 'tests/test_utils.py'
--- tests/test_utils.py 2013-11-14 19:10:28 +0000
+++ tests/test_utils.py 2013-11-15 18:31:21 +0000
@@ -833,7 +833,7 @@
833 self.run_call_count = 0833 self.run_call_count = 0
834 self.fake_zk_address = '192.168.5.26'834 self.fake_zk_address = '192.168.5.26'
835 self.build_dir = 'juju-gui/build-'835 self.build_dir = 'juju-gui/build-'
836 self.charmworld_url = 'http://charmworld.example'836 self.charmworld_url = 'http://charmworld.example.com/'
837 self.ssl_cert_path = 'ssl/cert/path'837 self.ssl_cert_path = 'ssl/cert/path'
838838
839 # Monkey patches.839 # Monkey patches.
@@ -983,7 +983,8 @@
983983
984 def test_write_builtin_server_startup(self):984 def test_write_builtin_server_startup(self):
985 write_builtin_server_startup(985 write_builtin_server_startup(
986 JUJU_GUI_DIR, self.ssl_cert_path, serve_tests=True, insecure=True)986 JUJU_GUI_DIR, self.ssl_cert_path, serve_tests=True, insecure=True,
987 charmworld_url=self.charmworld_url)
987 guiserver_conf = self.files['guiserver.conf']988 guiserver_conf = self.files['guiserver.conf']
988 self.assertIn('description "GUIServer"', guiserver_conf)989 self.assertIn('description "GUIServer"', guiserver_conf)
989 self.assertIn('--logging="info"', guiserver_conf)990 self.assertIn('--logging="info"', guiserver_conf)
@@ -993,6 +994,8 @@
993 '--testsroot="{}/test/"'.format(JUJU_GUI_DIR), guiserver_conf)994 '--testsroot="{}/test/"'.format(JUJU_GUI_DIR), guiserver_conf)
994 self.assertIn('--insecure', guiserver_conf)995 self.assertIn('--insecure', guiserver_conf)
995 self.assertNotIn('--sandbox', guiserver_conf)996 self.assertNotIn('--sandbox', guiserver_conf)
997 self.assertIn('--charmworldurl="http://charmworld.example.com/"',
998 guiserver_conf)
996999
997 def test_write_builtin_server_startup_sandbox_and_logging(self):1000 def test_write_builtin_server_startup_sandbox_and_logging(self):
998 # The upstart configuration file for the GUI server is correctly1001 # The upstart configuration file for the GUI server is correctly
@@ -1011,7 +1014,8 @@
1011 def test_start_builtin_server(self):1014 def test_start_builtin_server(self):
1012 start_builtin_server(1015 start_builtin_server(
1013 JUJU_GUI_DIR, self.ssl_cert_path, serve_tests=False, sandbox=False,1016 JUJU_GUI_DIR, self.ssl_cert_path, serve_tests=False, sandbox=False,
1014 builtin_server_logging='info', insecure=False)1017 builtin_server_logging='info', insecure=False,
1018 charmworld_url='http://charmworld.example.com/')
1015 self.assertEqual(self.svc_ctl_call_count, 1)1019 self.assertEqual(self.svc_ctl_call_count, 1)
1016 self.assertEqual(self.service_names, ['guiserver'])1020 self.assertEqual(self.service_names, ['guiserver'])
1017 self.assertEqual(self.actions, [charmhelpers.RESTART])1021 self.assertEqual(self.actions, [charmhelpers.RESTART])
@@ -1036,7 +1040,8 @@
1036 self.assertIn('readOnly: true', js_conf)1040 self.assertIn('readOnly: true', js_conf)
1037 self.assertIn("socket_url: 'wss://", js_conf)1041 self.assertIn("socket_url: 'wss://", js_conf)
1038 self.assertIn('socket_protocol: "wss"', js_conf)1042 self.assertIn('socket_protocol: "wss"', js_conf)
1039 self.assertIn('charmworldURL: "http://charmworld.example"', js_conf)1043 self.assertIn('charmworldURL: "http://charmworld.example.com/"',
1044 js_conf)
1040 self.assertIn('GA_key: "UA-123456"', js_conf)1045 self.assertIn('GA_key: "UA-123456"', js_conf)
10411046
1042 def test_write_gui_config_insecure(self):1047 def test_write_gui_config_insecure(self):

Subscribers

People subscribed via source and target branches