Merge lp:~frankban/charms/precise/juju-gui/new-darwin into lp:~juju-gui/charms/precise/juju-gui/trunk

Proposed by Francesco Banconi
Status: Merged
Merged at revision: 109
Proposed branch: lp:~frankban/charms/precise/juju-gui/new-darwin
Merge into: lp:~juju-gui/charms/precise/juju-gui/trunk
Diff against target: 681 lines (+37/-468)
11 files modified
hooks/utils.py (+1/-1)
revision (+1/-1)
server/guiserver/__init__.py (+1/-1)
server/guiserver/bundles/__init__.py (+14/-13)
server/guiserver/bundles/base.py (+15/-2)
server/guiserver/bundles/blocking.py (+0/-143)
server/guiserver/tests/bundles/test_base.py (+2/-1)
server/guiserver/tests/bundles/test_blocking.py (+0/-189)
server/guiserver/tests/helpers.py (+3/-49)
server/guiserver/tests/test_utils.py (+0/-51)
server/guiserver/utils.py (+0/-17)
To merge this branch: bzr merge lp:~frankban/charms/precise/juju-gui/new-darwin
Reviewer Review Type Date Requested Status
charmers Pending
Review via email: mp+187015@code.launchpad.net

Description of the change

New juju-deployer darwin version.

Switched to the new juju-deployer version,
which includes support for deployments started
by the GUI server.

This allows us to remove the blocking/deployer-specific
code from the GUI server.

Also updated the relevant parts of the documentation.

Tests: run `make unittest` from the root of this branch.

https://codereview.appspot.com/13824045/

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

Reviewers: mp+187015_code.launchpad.net,

Message:
Please take a look.

Description:
New juju-deployer darwin version.

Switched to the new juju-deployer version,
which includes support for deployments started
by the GUI server.

This allows us to remove the blocking/deployer-specific
code from the GUI server.

Also updated the relevant parts of the documentation.

Tests: run `make unittest` from the root of this branch.

https://code.launchpad.net/~frankban/charms/precise/juju-gui/new-darwin/+merge/187015

(do not edit description out of merge proposal)

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

Affected files (+37, -466 lines):
   A [revision details]
   D deps/juju-deployer-0.2.2.tar.gz
   A deps/juju-deployer-0.2.3.tar.gz
   M hooks/utils.py
   M revision
   M server/guiserver/__init__.py
   M server/guiserver/bundles/__init__.py
   M server/guiserver/bundles/base.py
   D server/guiserver/bundles/blocking.py
   M server/guiserver/tests/bundles/test_base.py
   D server/guiserver/tests/bundles/test_blocking.py
   M server/guiserver/tests/helpers.py
   M server/guiserver/tests/test_utils.py
   M server/guiserver/utils.py

Revision history for this message
Gary Poster (gary) wrote :

LGTM. No qa, guessing bac is doing it. Thank you for getting this into
the deployer and connecting with Kapil!

https://codereview.appspot.com/13824045/

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

LGTM with minor typo. Ran the tests but after talking to Francesco did
not do QA.

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

https://codereview.appspot.com/13824045/diff/1/server/guiserver/bundles/__init__.py#newcode71
server/guiserver/bundles/__init__.py:71: The deployer.guiserver module
in the juju-deployer library is responsible of
s/of/for

https://codereview.appspot.com/13824045/

118. By Francesco Banconi

Changes as per review.

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

*** Submitted:

New juju-deployer darwin version.

Switched to the new juju-deployer version,
which includes support for deployments started
by the GUI server.

This allows us to remove the blocking/deployer-specific
code from the GUI server.

Also updated the relevant parts of the documentation.

Tests: run `make unittest` from the root of this branch.

R=gary.poster, bac
CC=
https://codereview.appspot.com/13824045

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

https://codereview.appspot.com/13824045/diff/1/server/guiserver/bundles/__init__.py#newcode71
server/guiserver/bundles/__init__.py:71: The deployer.guiserver module
in the juju-deployer library is responsible of
On 2013/09/23 14:23:17, bac wrote:
> s/of/for

Done.

https://codereview.appspot.com/13824045/

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

Thank you both for the reviews!

https://codereview.appspot.com/13824045/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== removed file 'deps/juju-deployer-0.2.2.tar.gz'
2Binary files deps/juju-deployer-0.2.2.tar.gz 2013-08-23 11:01:05 +0000 and deps/juju-deployer-0.2.2.tar.gz 1970-01-01 00:00:00 +0000 differ
3=== added file 'deps/juju-deployer-0.2.3.tar.gz'
4Binary files deps/juju-deployer-0.2.3.tar.gz 1970-01-01 00:00:00 +0000 and deps/juju-deployer-0.2.3.tar.gz 2013-09-23 14:46:10 +0000 differ
5=== modified file 'hooks/utils.py'
6--- hooks/utils.py 2013-09-12 19:58:07 +0000
7+++ hooks/utils.py 2013-09-23 14:46:10 +0000
8@@ -113,7 +113,7 @@
9 'futures-2.1.4.tar.gz',
10 'tornado-3.1.tar.gz',
11 'jujuclient-0.0.9.tar.gz',
12- 'juju-deployer-0.2.2.tar.gz',
13+ 'juju-deployer-0.2.3.tar.gz',
14 )
15 SERVER_DIR = os.path.join(CURRENT_DIR, 'server')
16
17
18=== modified file 'revision'
19--- revision 2013-09-13 08:05:51 +0000
20+++ revision 2013-09-23 14:46:10 +0000
21@@ -1,1 +1,1 @@
22-85
23+86
24
25=== modified file 'server/guiserver/__init__.py'
26--- server/guiserver/__init__.py 2013-09-12 17:02:58 +0000
27+++ server/guiserver/__init__.py 2013-09-23 14:46:10 +0000
28@@ -30,7 +30,7 @@
29 the HTTPS connection, allowing changes in the Juju environment to be propagated
30 and shown immediately by the browser. """
31
32-VERSION = (0, 2, 0)
33+VERSION = (0, 2, 1)
34
35
36 def get_version():
37
38=== modified file 'server/guiserver/bundles/__init__.py'
39--- server/guiserver/bundles/__init__.py 2013-09-13 08:28:12 +0000
40+++ server/guiserver/bundles/__init__.py 2013-09-23 14:46:10 +0000
41@@ -41,12 +41,12 @@
42 the WebSocket request/response aspects, or how incoming data is retrieved
43 or generated.
44
45- The Deployer implementation in this module uses the juju-deployer library
46- to import the provided bundle into the Juju environment. Since the
47- mentioned operations are executed in a separate process, it is safe for
48- the Deployer to interact with the blocking juju-deployer library.
49- Those blocking functions are defined in the blocking module of this
50- package, described below.
51+ The Deployer implementation in this package uses the juju-deployer
52+ library to import the provided bundle into the Juju environment. Since
53+ the mentioned operations are executed in a separate process, it is safe
54+ for the Deployer to interact with the blocking juju-deployer library.
55+ Those blocking functions are defined in the guiserver module of the
56+ juju-deployer project, described below.
57
58 Note that the Deployer is not intended to store request related data: one
59 instance is created once when the application is bootstrapped and used as
60@@ -60,23 +60,24 @@
61 views module of this package. The DeployMiddleware dispatches requests
62 and collect responses to be sent back to the API client.
63
64-The views and blocking modules are responsible of handling the request/response
65-process and of starting/scheduling bundle deployments.
66+The views module is responsible for handling the request/response process and
67+of starting/scheduling bundle deployments.
68
69 - views: as already mentioned, the functions in this module handle the
70 requests from the API client, and set up responses. Since the views have
71 access to the Deployer (described above), they can start/queue bundle
72 deployments.
73
74- - blocking: all the blocking functions interacting with the juju-deployer
75- library belong here. Specifically this module defines two functions:
76- - validate: validate a bundle based on the state of the Juju env.;
77- - import_bundle: starts the bundle deployment process.
78+The deployer.guiserver module in the juju-deployer library is responsible for
79+validating a bundle and starting a deployment. Specifically the module defines
80+two functions:
81+ - validate: validate a bundle based on the state of the Juju env.;
82+ - import_bundle: starts the bundle deployment process.
83
84 The infrastructure described above can be summarized like the following
85 (each arrow meaning "calls"):
86 - request handling: request -> DeployMiddleware -> views
87- - deployment handling: views -> Deployer -> blocking
88+ - deployment handling: views -> Deployer -> deployer.guiserver
89 - response handling: views -> response
90
91 While the DeployMiddleware parses the request data and statically validates
92
93=== modified file 'server/guiserver/bundles/base.py'
94--- server/guiserver/bundles/base.py 2013-09-13 08:03:38 +0000
95+++ server/guiserver/bundles/base.py 2013-09-23 14:46:10 +0000
96@@ -29,12 +29,12 @@
97 process,
98 ProcessPoolExecutor,
99 )
100+from deployer import guiserver as blocking
101 from tornado import gen
102 from tornado.ioloop import IOLoop
103 from tornado.util import ObjectDict
104
105 from guiserver.bundles import (
106- blocking,
107 utils,
108 views,
109 )
110@@ -49,6 +49,18 @@
111 # Juju API versions supported by the GUI server Deployer.
112 # Tests use the first API version in this list.
113 SUPPORTED_API_VERSIONS = ['go']
114+# Options used by the juju-deployer Importer instance.
115+IMPORTER_OPTIONS = ObjectDict(
116+ branch_only=False, # Avoid just updating VCS branches and exiting.
117+ deploy_delay=0, # Do not sleep between 'deploy' commands.
118+ no_local_mods=True, # Disallow deployment of locally-modified charms.
119+ overrides=None, # Do not override config options.
120+ rel_wait=60, # Wait for 1 minute before checking for relation errors.
121+ retry_count=0, # Do not retry on unit errors.
122+ timeout=45*60, # Set a 45 minutes timeout for the entire deployment.
123+ update_charms=False, # Do not update existing charm branches.
124+ watch=False, # Do not watch environment changes on console.
125+)
126
127
128 class Deployer(object):
129@@ -141,7 +153,8 @@
130 # Add the import bundle job to the run executor, and set up a callback
131 # to be called when the import process completes.
132 future = self._run_executor.submit(
133- blocking.import_bundle, self._apiurl, user.password, name, bundle)
134+ blocking.import_bundle,
135+ self._apiurl, user.password, name, bundle, IMPORTER_OPTIONS)
136 add_future(self._io_loop, future, self._import_callback, deployment_id)
137 self._futures[deployment_id] = future
138 # If a customized callback is provided, schedule it as well.
139
140=== removed file 'server/guiserver/bundles/blocking.py'
141--- server/guiserver/bundles/blocking.py 2013-08-23 12:56:19 +0000
142+++ server/guiserver/bundles/blocking.py 1970-01-01 00:00:00 +0000
143@@ -1,143 +0,0 @@
144-# This file is part of the Juju GUI, which lets users view and manage Juju
145-# environments within a graphical interface (https://launchpad.net/juju-gui).
146-# Copyright (C) 2013 Canonical Ltd.
147-#
148-# This program is free software: you can redistribute it and/or modify it under
149-# the terms of the GNU Affero General Public License version 3, as published by
150-# the Free Software Foundation.
151-#
152-# This program is distributed in the hope that it will be useful, but WITHOUT
153-# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
154-# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
155-# Affero General Public License for more details.
156-#
157-# You should have received a copy of the GNU Affero General Public License
158-# along with this program. If not, see <http://www.gnu.org/licenses/>.
159-
160-"""Blocking functions and objects for handling bundle deployments.
161-
162-The following functions and objects use the juju-deployer library to handle
163-bundle deployments. They are intended to be run in a separate process.
164-Code interacting with the juju-deployer should be stored here.
165-"""
166-
167-import os
168-
169-from deployer.action.importer import Importer
170-from deployer.deployment import Deployment
171-from deployer.env import GoEnvironment
172-from tornado import util
173-
174-from guiserver.utils import mkdir
175-
176-
177-IMPORTER_OPTIONS = util.ObjectDict(
178- branch_only=False, # Avoid just updating VCS branches and exiting.
179- deploy_delay=0, # Do not sleep between 'deploy' commands.
180- no_local_mods=True, # Disallow deployment of locally-modified charms.
181- overrides=None, # Do not override config options.
182- rel_wait=60, # Wait for 1 minute before checking for relation errors.
183- retry_count=0, # Do not retry on unit errors.
184- timeout=45*60, # Set a 45 minutes timeout for the entire deployment.
185- update_charms=False, # Do not update existing charm branches.
186- watch=False, # Do not watch environment changes on console.
187-)
188-# This value is used by the juju-deployer Importer object to store charms.
189-JUJU_HOME = '/var/lib/juju/gui-server/juju-home'
190-
191-
192-class _Environment(GoEnvironment):
193- """A Juju environment for the juju-deployer.
194-
195- Add support for deployments via the Juju API and for authenticating with
196- the provided password.
197- """
198-
199- def __init__(self, endpoint, password):
200- super(_Environment, self).__init__('go', endpoint=endpoint)
201- self._password = password
202-
203- def _get_token(self):
204- """Return the stored password.
205-
206- This method is overridden so that the juju-deployer does not try to
207- parse the environments.yaml file in order to retrieve the admin-secret.
208- """
209- return self._password
210-
211- def connect(self):
212- """Connect the API client to the Juju backend.
213-
214- This method is overridden so that a call to connect is a no-op if the
215- client is already connected.
216- """
217- if self.client is None:
218- super(_Environment, self).connect()
219-
220- def close(self):
221- """Close the API connection.
222-
223- Also set the client attribute to None after the disconnection.
224- """
225- super(_Environment, self).close()
226- self.client = None
227-
228- def deploy(
229- self, name, charm_url, config=None, constraints=None, num_units=1,
230- *args, **kwargs):
231- """Deploy a service using the API.
232-
233- Using the API in place of the command line introduces some limitations:
234- - it is not possible to use a local charm/repository;
235- - it is not possible to deploy to a specific machine.
236- """
237- self.client.deploy(
238- name, charm_url, config=config, constraints=constraints,
239- num_units=num_units)
240-
241-
242-def _validate(env, bundle):
243- """Bundle validation logic, used by both validate and import_bundle.
244-
245- This function receives a connected environment and the bundle as a YAML
246- decoded object.
247- """
248- # Retrieve the services deployed in the Juju environment.
249- env_status = env.status()
250- env_services = set(env_status['services'].keys())
251- # Retrieve the services in the bundle.
252- bundle_services = set(bundle.get('services', {}).keys())
253- # Calculate overlapping services.
254- overlapping = env_services.intersection(bundle_services)
255- if overlapping:
256- services = ', '.join(overlapping)
257- error = 'service(s) already in the environment: {}'.format(services)
258- raise ValueError(error)
259-
260-
261-def validate(apiurl, password, bundle):
262- """Validate a bundle."""
263- env = _Environment(apiurl, password)
264- env.connect()
265- try:
266- _validate(env, bundle)
267- finally:
268- env.close()
269-
270-
271-def import_bundle(apiurl, password, name, bundle):
272- """Import a bundle."""
273- env = _Environment(apiurl, password)
274- deployment = Deployment(name, bundle, [])
275- importer = Importer(env, deployment, IMPORTER_OPTIONS)
276- env.connect()
277- # The Importer tries to retrieve the Juju home from the JUJU_HOME
278- # environment variable: create a customized directory (if required) and
279- # set up the environment context for the Importer.
280- mkdir(JUJU_HOME)
281- os.environ['JUJU_HOME'] = JUJU_HOME
282- try:
283- _validate(env, bundle)
284- importer.run()
285- finally:
286- env.close()
287
288=== modified file 'server/guiserver/tests/bundles/test_base.py'
289--- server/guiserver/tests/bundles/test_base.py 2013-09-13 14:09:40 +0000
290+++ server/guiserver/tests/bundles/test_base.py 2013-09-23 14:46:10 +0000
291@@ -111,7 +111,8 @@
292 # Wait for the deployment to be completed.
293 self.wait()
294 mock_import_bundle.assert_called_once_with(
295- self.apiurl, self.user.password, 'bundle', self.bundle)
296+ self.apiurl, self.user.password, 'bundle', self.bundle,
297+ base.IMPORTER_OPTIONS)
298 mock_import_bundle.assert_called_in_a_separate_process()
299
300 def test_watch(self):
301
302=== removed file 'server/guiserver/tests/bundles/test_blocking.py'
303--- server/guiserver/tests/bundles/test_blocking.py 2013-08-23 16:06:21 +0000
304+++ server/guiserver/tests/bundles/test_blocking.py 1970-01-01 00:00:00 +0000
305@@ -1,189 +0,0 @@
306-# This file is part of the Juju GUI, which lets users view and manage Juju
307-# environments within a graphical interface (https://launchpad.net/juju-gui).
308-# Copyright (C) 2013 Canonical Ltd.
309-#
310-# This program is free software: you can redistribute it and/or modify it under
311-# the terms of the GNU Affero General Public License version 3, as published by
312-# the Free Software Foundation.
313-#
314-# This program is distributed in the hope that it will be useful, but WITHOUT
315-# ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
316-# SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
317-# Affero General Public License for more details.
318-#
319-# You should have received a copy of the GNU Affero General Public License
320-# along with this program. If not, see <http://www.gnu.org/licenses/>.
321-
322-"""Tests for the bundles support blocking functions and objects."""
323-
324-from contextlib import contextmanager
325-import os
326-import shutil
327-import tempfile
328-import unittest
329-
330-import mock
331-
332-from guiserver.bundles import blocking
333-from guiserver.tests import helpers
334-
335-
336-@mock.patch('deployer.env.go.EnvironmentClient')
337-class TestEnvironment(unittest.TestCase):
338-
339- endpoint = 'wss://api.example.com:17070'
340- password = 'Secret!'
341-
342- def setUp(self):
343- self.env = blocking._Environment(self.endpoint, self.password)
344-
345- def test_connect(self, mock_client):
346- # The environment uses the provided endpoint and password to connect
347- # to the Juju API server.
348- self.env.connect()
349- mock_client.assert_called_once_with(self.endpoint)
350- mock_client().login.assert_called_once_with(self.password)
351-
352- def test_multiple_connections(self, mock_client):
353- # The environment does not attempt a second connection if it is already
354- # connected to the API backend.
355- self.env.connect()
356- self.env.connect()
357- self.assertEqual(1, mock_client.call_count)
358-
359- def test_close(self, mock_client):
360- # The client attribute is set to None when the connection is closed.
361- self.env.connect()
362- self.env.close()
363- self.assertIsNone(self.env.client)
364-
365- def test_deploy(self, mock_client):
366- # The environment uses the API to deploy charms.
367- self.env.connect()
368- config = {'foo': 'bar'}
369- constraints = {'cpu': 4}
370- # Deploy a service: the last two arguments (force_machine and repo) are
371- # ignored.
372- self.env.deploy(
373- 'myservice', 'cs:precise/service-42', config=config,
374- constraints=constraints, num_units=2, force_machine=1, repo='/tmp')
375- mock_client().deploy.assert_called_once_with(
376- 'myservice', 'cs:precise/service-42', config=config,
377- constraints=constraints, num_units=2)
378-
379-
380-class DeployerFunctionsTestMixin(helpers.BundlesTestMixin):
381- """Base set up for the functions that make use of the juju-deployer."""
382-
383- apiurl = 'wss://api.example.com:17070'
384- password = 'Secret!'
385-
386- def setUp(self):
387- self.name, self.bundle = self.get_name_and_bundle()
388-
389- def check_environment_life(self, mock_environment):
390- """Check the calls executed on the given mock environment.
391-
392- Ensure that, in order to retrieve the list of currently deployed
393- services, the environment is instantiated, connected, env.status is
394- called and then the connection is closed.
395- """
396- mock_environment.assert_called_once_with(self.apiurl, self.password)
397- mock_env_instance = mock_environment()
398- mock_env_instance.connect.assert_called_once_with()
399- mock_env_instance.status.assert_called_once_with()
400- mock_env_instance.close.assert_called_once_with()
401-
402- @contextmanager
403- def assert_overlapping_services(self, mock_environment):
404- """Ensure a ValueError is raised in the context manager block.
405-
406- The given mock environment object is set up so that its status
407- simulates an existing service. The name of this service overlaps with
408- the name of one of the services in the bundle.
409- """
410- mock_env_instance = mock_environment()
411- mock_env_instance.status.return_value = {'services': {'mysql': {}}}
412- # Ensure a ValueError is raised by the code in the context block.
413- with self.assertRaises(ValueError) as context_manager:
414- yield
415- # The error reflects the overlapping service name.
416- error = str(context_manager.exception)
417- self.assertEqual('service(s) already in the environment: mysql', error)
418- # Even if an error occurs, the environment connection is closed.
419- mock_env_instance.close.assert_called_once_with()
420-
421-
422-@mock.patch('guiserver.bundles.blocking._Environment')
423-class TestValidate(DeployerFunctionsTestMixin, unittest.TestCase):
424-
425- def test_validation(self, mock_environment):
426- # The validation is correctly run.
427- blocking.validate(self.apiurl, self.password, self.bundle)
428- # The environment is correctly instantiated and used.
429- self.check_environment_life(mock_environment)
430-
431- def test_overlapping_services(self, mock_environment):
432- # The validation fails if the bundle includes a service name already
433- # present in the Juju environment.
434- with self.assert_overlapping_services(mock_environment):
435- blocking.validate(self.apiurl, self.password, self.bundle)
436-
437-
438-@mock.patch('guiserver.bundles.blocking._Environment')
439-class TestImportBundle(DeployerFunctionsTestMixin, unittest.TestCase):
440-
441- @contextmanager
442- def patch_juju_home(self):
443- """Patch the value used by the bundle importer as Juju home."""
444- base_dir = tempfile.mkdtemp()
445- self.addCleanup(shutil.rmtree, base_dir)
446- juju_home = os.path.join(base_dir, 'juju-home')
447- with mock.patch('guiserver.bundles.blocking.JUJU_HOME', juju_home):
448- yield juju_home
449-
450- @mock.patch('guiserver.bundles.blocking.Importer')
451- def test_importing_bundle(self, mock_importer, mock_environment):
452- # The juju-deployer importer is correctly set up and run.
453- with self.patch_juju_home():
454- blocking.import_bundle(
455- self.apiurl, self.password, self.name, self.bundle)
456- # The environment is correctly instantiated and used.
457- self.check_environment_life(mock_environment)
458- # The importer is correctly instantiated.
459- self.assertEqual(1, mock_importer.call_count)
460- importer_args = mock_importer.call_args[0]
461- self.assertEqual(3, len(importer_args))
462- env, deployment, options = importer_args
463- # The first argument passed to the importer is the environment.
464- self.assertIs(mock_environment(), env)
465- # The second argument is the deployment object.
466- self.assertIsInstance(deployment, blocking.Deployment)
467- self.assertEqual(self.name, deployment.name)
468- self.assertEqual(self.bundle, deployment.data)
469- # The third and last argument is the options object.
470- self.assertIs(blocking.IMPORTER_OPTIONS, options)
471- # The importer is started.
472- mock_importer().run.assert_called_once_with()
473-
474- def test_overlapping_services(self, mock_environment):
475- # The import fails if the bundle includes a service name already
476- # present in the Juju environment.
477- with self.assert_overlapping_services(mock_environment):
478- with self.patch_juju_home():
479- blocking.import_bundle(
480- self.apiurl, self.password, self.name, self.bundle)
481-
482- @mock.patch('guiserver.bundles.blocking.Importer')
483- def test_juju_home(self, mock_importer, mock_environment):
484- # A customized Juju home is created and used during the import process.
485- with self.patch_juju_home() as juju_home:
486- assert not os.path.isdir(juju_home), 'directory should not exist'
487- # Ensure JUJU_HOME is included in the context when the Importer
488- # instance is run.
489- run = lambda: self.assertEqual(juju_home, os.getenv('JUJU_HOME'))
490- mock_importer().run = run
491- blocking.import_bundle(
492- self.apiurl, self.password, self.name, self.bundle)
493- # The JUJU_HOME directory has been created.
494- self.assertTrue(os.path.isdir(juju_home))
495
496=== modified file 'server/guiserver/tests/helpers.py'
497--- server/guiserver/tests/helpers.py 2013-08-26 08:07:36 +0000
498+++ server/guiserver/tests/helpers.py 2013-09-23 14:46:10 +0000
499@@ -25,7 +25,6 @@
500
501 import mock
502 from tornado import websocket
503-import yaml
504
505 from guiserver import auth
506 from guiserver.bundles import base
507@@ -140,52 +139,6 @@
508 """Add helper methods for testing the GUI server bundles support."""
509
510 apiurl = 'wss://api.example.com:17070'
511- bundle = """
512- envExport:
513- series: precise
514- services:
515- wordpress:
516- charm: "cs:precise/wordpress-15"
517- num_units: 1
518- options:
519- debug: "no"
520- engine: nginx
521- tuning: single
522- "wp-content": ""
523- annotations:
524- "gui-x": 313
525- "gui-y": 51
526- mysql:
527- charm: "cs:precise/mysql-26"
528- num_units: 1
529- options:
530- "binlog-format": MIXED
531- "block-size": "5"
532- "dataset-size": "80%"
533- flavor: distro
534- "ha-bindiface": eth0
535- "ha-mcastport": "5411"
536- "max-connections": "-1"
537- "preferred-storage-engine": InnoDB
538- "query-cache-size": "-1"
539- "query-cache-type": "OFF"
540- "rbd-name": mysql1
541- "tuning-level": safest
542- vip: ""
543- vip_cidr: "24"
544- vip_iface: eth0
545- annotations:
546- "gui-x": 669.5
547- "gui-y": -33.5
548- relations:
549- - - "wordpress:db"
550- - "mysql:db"
551- """
552-
553- def get_name_and_bundle(self):
554- """Return a tuple (bundle name, contents) parsing self.bundle."""
555- all_contents = yaml.load(self.bundle)
556- return all_contents.items()[0]
557
558 def make_deployer(self, apiversion=base.SUPPORTED_API_VERSIONS[0]):
559 """Create and return a Deployer instance."""
560@@ -243,12 +196,13 @@
561 def patch_validate(self, side_effect=None):
562 """Mock the blocking validate function."""
563 mock_validate = MultiProcessMock(side_effect=side_effect)
564- return mock.patch('guiserver.bundles.blocking.validate', mock_validate)
565+ validate_path = 'guiserver.bundles.base.blocking.validate'
566+ return mock.patch(validate_path, mock_validate)
567
568 def patch_import_bundle(self, side_effect=None):
569 """Mock the blocking import_bundle function."""
570 mock_import_bundle = MultiProcessMock(side_effect=side_effect)
571- import_bundle_path = 'guiserver.bundles.blocking.import_bundle'
572+ import_bundle_path = 'guiserver.bundles.base.blocking.import_bundle'
573 return mock.patch(import_bundle_path, mock_import_bundle)
574
575
576
577=== modified file 'server/guiserver/tests/test_utils.py'
578--- server/guiserver/tests/test_utils.py 2013-08-23 15:22:19 +0000
579+++ server/guiserver/tests/test_utils.py 2013-09-23 14:46:10 +0000
580@@ -17,9 +17,6 @@
581 """Tests for the Juju GUI server utilities."""
582
583 import json
584-import os
585-import shutil
586-import tempfile
587 import unittest
588
589 import mock
590@@ -113,54 +110,6 @@
591 self.assertIsNone(utils.json_decode_dict('"not-a-dict"'))
592
593
594-class TestMkdir(unittest.TestCase):
595-
596- def setUp(self):
597- self.playground = tempfile.mkdtemp()
598- self.addCleanup(shutil.rmtree, self.playground)
599-
600- def test_create_dir(self):
601- # A directory is correctly created.
602- path = os.path.join(self.playground, 'foo')
603- utils.mkdir(path)
604- self.assertTrue(os.path.isdir(path))
605-
606- def test_intermediate_dirs(self):
607- # All intermediate directories are created.
608- path = os.path.join(self.playground, 'foo', 'bar', 'leaf')
609- utils.mkdir(path)
610- self.assertTrue(os.path.isdir(path))
611-
612- def test_expand_user(self):
613- # The ~ construction is expanded.
614- with mock.patch('os.environ', {'HOME': self.playground}):
615- utils.mkdir('~/in/my/home')
616- path = os.path.join(self.playground, 'in', 'my', 'home')
617- self.assertTrue(os.path.isdir(path))
618-
619- def test_existing_dir(self):
620- # The function exits without errors if the target directory exists.
621- path = os.path.join(self.playground, 'foo')
622- os.mkdir(path)
623- utils.mkdir(path)
624-
625- def test_existing_file(self):
626- # An OSError is raised if a file already exists in the target path.
627- path = os.path.join(self.playground, 'foo')
628- with open(path, 'w'):
629- with self.assertRaises(OSError):
630- utils.mkdir(path)
631-
632- def test_failure(self):
633- # Errors are correctly re-raised.
634- path = os.path.join(self.playground, 'foo')
635- os.chmod(self.playground, 0000)
636- self.addCleanup(os.chmod, self.playground, 0700)
637- with self.assertRaises(OSError):
638- utils.mkdir(os.path.join(path))
639- self.assertFalse(os.path.exists(path))
640-
641-
642 class TestRequestSummary(unittest.TestCase):
643
644 def test_summary(self):
645
646=== modified file 'server/guiserver/utils.py'
647--- server/guiserver/utils.py 2013-08-23 15:22:19 +0000
648+++ server/guiserver/utils.py 2013-09-23 14:46:10 +0000
649@@ -17,10 +17,8 @@
650 """Juju GUI server utility functions and classes."""
651
652 import collections
653-import errno
654 import functools
655 import logging
656-import os
657 import urlparse
658 import weakref
659
660@@ -68,21 +66,6 @@
661 return data
662
663
664-def mkdir(path):
665- """Create a leaf directory and all intermediate ones.
666-
667- Also expand ~ and ~user constructions.
668- If path exists and it's a directory, return without errors.
669- """
670- path = os.path.expanduser(path)
671- try:
672- os.makedirs(path)
673- except OSError as err:
674- # Re-raise the error if the target path exists but it is not a dir.
675- if (err.errno != errno.EEXIST) or (not os.path.isdir(path)):
676- raise
677-
678-
679 def request_summary(request):
680 """Return a string representing a summary for the given request."""
681 return '{} {} ({})'.format(request.method, request.uri, request.remote_ip)

Subscribers

People subscribed via source and target branches