Merge lp:~frankban/juju-deployer/improve-guiserver-feedback into lp:juju-deployer

Proposed by Francesco Banconi
Status: Merged
Merged at revision: 94
Proposed branch: lp:~frankban/juju-deployer/improve-guiserver-feedback
Merge into: lp:juju-deployer
Diff against target: 151 lines (+102/-4)
2 files modified
deployer/guiserver.py (+31/-1)
deployer/tests/test_guiserver.py (+71/-3)
To merge this branch: bzr merge lp:~frankban/juju-deployer/improve-guiserver-feedback
Reviewer Review Type Date Requested Status
Kapil Thangavelu Approve
Review via email: mp+203915@code.launchpad.net

Commit message

Improve GUI server feedback handling

Description of the change

Improve how feedback errors are propagated from the deployer to the GUI server.

To post a comment you must log in.
Revision history for this message
Kapil Thangavelu (hazmat) wrote :

looks good, merged.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'deployer/guiserver.py'
2--- deployer/guiserver.py 2013-08-29 16:35:45 +0000
3+++ deployer/guiserver.py 2014-01-30 10:51:34 +0000
4@@ -23,6 +23,36 @@
5 JUJU_HOME = '/var/lib/juju-gui/juju-home'
6
7
8+class DeploymentError(Exception):
9+ """One or more errors occurred during the deployment preparation."""
10+
11+ def __init__(self, errors):
12+ self.errors = errors
13+ super(DeploymentError, self).__init__(errors)
14+
15+ def __str__(self):
16+ return '\n'.join(self.errors)
17+
18+
19+class GUIDeployment(Deployment):
20+ """Handle bundle deployments requested by the GUI server."""
21+
22+ def __init__(self, name, data):
23+ super(GUIDeployment, self).__init__(name, data, [])
24+
25+ def _handle_feedback(self, feedback):
26+ """Raise a DeploymentError if the given feedback includes errors.
27+
28+ The GUI server will catch and report failures propagating them through
29+ the WebSocket connection to the client.
30+ """
31+ for message in feedback.get_warnings():
32+ self.log.warning(message)
33+ if feedback.has_errors:
34+ # Errors are logged by the GUI server.
35+ raise DeploymentError(feedback.get_errors())
36+
37+
38 def _validate(env, bundle):
39 """Bundle validation logic, used by both validate and import_bundle.
40
41@@ -55,7 +85,7 @@
42 def import_bundle(apiurl, password, name, bundle, options):
43 """Import a bundle."""
44 env = GUIEnvironment(apiurl, password)
45- deployment = Deployment(name, bundle, [])
46+ deployment = GUIDeployment(name, bundle)
47 importer = Importer(env, deployment, options)
48 env.connect()
49 # The Importer tries to retrieve the Juju home from the JUJU_HOME
50
51=== modified file 'deployer/tests/test_guiserver.py'
52--- deployer/tests/test_guiserver.py 2014-01-23 14:14:28 +0000
53+++ deployer/tests/test_guiserver.py 2014-01-30 10:51:34 +0000
54@@ -14,11 +14,53 @@
55 cli,
56 guiserver,
57 )
58-from deployer.deployment import Deployment
59-
60+from deployer.feedback import Feedback
61 from deployer.tests.base import TEST_OFFLINE
62
63
64+class TestDeploymentError(unittest.TestCase):
65+
66+ def test_error(self):
67+ # A single error is properly stored and represented.
68+ exception = guiserver.DeploymentError(['bad wolf'])
69+ self.assertEqual(['bad wolf'], exception.errors)
70+ self.assertEqual('bad wolf', str(exception))
71+
72+ def test_multiple_errors(self):
73+ # Multiple deployment errors are properly stored and represented.
74+ errors = ['error 1', 'error 2']
75+ exception = guiserver.DeploymentError(errors)
76+ self.assertEqual(errors, exception.errors)
77+ self.assertEqual('error 1\nerror 2', str(exception))
78+
79+
80+class TestGUIDeployment(unittest.TestCase):
81+
82+ def setUp(self):
83+ # Set up a GUIDeployment instance and a Feedback object.
84+ self.deployment = guiserver.GUIDeployment('mybundle', 'mydata')
85+ self.feedback = Feedback()
86+
87+ def test_valid_deployment(self):
88+ # If the bundle is well formed, the deployment proceeds normally.
89+ self.assertIsNone(self.deployment._handle_feedback(self.feedback))
90+
91+ def test_warnings(self):
92+ # Warning messages are properly logged.
93+ self.feedback.warn('we are the Borg')
94+ with mock.patch.object(self.deployment, 'log') as mock_log:
95+ self.deployment._handle_feedback(self.feedback)
96+ mock_log.warning.assert_called_once_with('we are the Borg')
97+
98+ def test_errors(self):
99+ # A DeploymentError is raised if errors are found in the bundle.
100+ self.feedback.error('error 1')
101+ self.feedback.error('error 2')
102+ with self.assertRaises(guiserver.DeploymentError) as cm:
103+ self.deployment._handle_feedback(self.feedback)
104+ self.assertEqual(['error 1', 'error 2'], cm.exception.errors)
105+
106+
107 class DeployerFunctionsTestMixin(object):
108 """Base set up for the functions that make use of the juju-deployer."""
109
110@@ -149,7 +191,7 @@
111 # The first argument passed to the importer is the environment.
112 self.assertIs(mock_environment(), env)
113 # The second argument is the deployment object.
114- self.assertIsInstance(deployment, Deployment)
115+ self.assertIsInstance(deployment, guiserver.GUIDeployment)
116 self.assertEqual(self.name, deployment.name)
117 self.assertEqual(self.bundle, deployment.data)
118 # The third and last argument is the options object.
119@@ -207,6 +249,32 @@
120 mock.call.close(),
121 ], any_order=True)
122
123+ def test_deployment_errors(self, mock_environment):
124+ # A DeploymentError is raised if the deployment fails.
125+ bundle = {
126+ 'services': {
127+ 'wordpress': {
128+ 'charm': 'cs:precise/wordpress-20',
129+ 'options': {'no-such-option': 42}, # Invalid options.
130+ },
131+ 'mysql': {
132+ 'charm': 'cs:precise/mysql-28',
133+ 'options': {'bad': 'wolf'}, # Invalid options.
134+ },
135+ },
136+ }
137+ self.addCleanup(self.cleanup_series_path)
138+ with self.patch_juju_home():
139+ with self.assertRaises(guiserver.DeploymentError) as cm:
140+ guiserver.import_bundle(
141+ self.apiurl, self.password, self.name, bundle,
142+ self.options)
143+ expected_errors = set([
144+ 'Invalid config charm cs:precise/wordpress-20 no-such-option=42',
145+ 'Invalid config charm cs:precise/mysql-28 bad=wolf',
146+ ])
147+ self.assertEqual(expected_errors, set(cm.exception.errors))
148+
149 def test_options(self, mock_environment):
150 # Ensure the default options are sane for us.
151 expected = argparse.Namespace(

Subscribers

People subscribed via source and target branches