Merge lp:~benji/landscape-client/bug-1428826-restore-register-function-public-API into lp:~landscape/landscape-client/trunk

Proposed by Benji York
Status: Merged
Approved by: Benji York
Approved revision: 816
Merged at revision: 813
Proposed branch: lp:~benji/landscape-client/bug-1428826-restore-register-function-public-API
Merge into: lp:~landscape/landscape-client/trunk
Diff against target: 168 lines (+91/-18)
2 files modified
landscape/configuration.py (+25/-15)
landscape/tests/test_configuration.py (+66/-3)
To merge this branch: bzr merge lp:~benji/landscape-client/bug-1428826-restore-register-function-public-API
Reviewer Review Type Date Requested Status
Adam Collard (community) Approve
Björn Tillenius (community) Approve
Review via email: mp+252126@code.launchpad.net

Commit message

Instead of running client configuration as an independent executable
(which would have been preferable), the client charm reaches inside and
calls the register() function directly. That has resulted in a
dependency on register()'s API which was not enforced via testing or
documented in register()'s docstring.

This branch restores the API the charm depends on, adds documentation as
to its meaning and importance, and adds tests that will warn us if the
API is broken in the future.

Description of the change

Instead of running client configuration as an independent executable (which would have been preferable), the client charm reaches inside and calls the register() function directly. That has resulted in a dependency on register()'s API which was not enforced via testing or documented in register()'s docstring.

This branch restores the API the charm depends on, adds documentation as to its meaning and importance, and adds tests that will warn us if the API is broken in the future.

To post a comment you must log in.
Revision history for this message
Björn Tillenius (bjornt) wrote :

+1. I have some minor inline comment on style.

review: Approve
814. By Benji York

A few small review changes:

- tweak a test so as to record the fact that a function got called
  and then assert that it didn't instead of making it call
  self.fail() when called
- add some commentary about the charm's use of register() in an
  additional place

Revision history for this message
Benji York (benji) :
Revision history for this message
Adam Collard (adam-collard) wrote :

I don't see how this change will make the charm work

Charm code hooks/common.py ~ line 171

            register(config, on_error=error_handler.flag_error)

This branch will fall over because there is no reactor being passed, the docstring suggests it should only be passed if you're doing crazy things, but it's a positional argument (!).

review: Needs Fixing
815. By Benji York

make providing a reactor optional (for the client charm)

Revision history for this message
Adam Collard (adam-collard) :
Revision history for this message
Adam Collard (adam-collard) wrote :

Confirmed that a deployment of this branch with trunk landscape-client charm worked fine

cat > client-config.yaml <<EOF
landscape-client:
  origin: lp:~benji/landscape-client/bug-1428826-restore-register-function-public-API
  account-name: adam-collard
EOF

juju deploy landscape-client --config client-config.yaml
juju add-relation landscape-client postgresql

Minor docstring fixes inline from previous comment

review: Approve
816. By Benji York

fix docstring/comment bugs

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'landscape/configuration.py'
2--- landscape/configuration.py 2015-03-04 14:22:17 +0000
3+++ landscape/configuration.py 2015-03-10 16:53:34 +0000
4@@ -640,28 +640,32 @@
5 raise SystemExit
6
7
8-def register(config, reactor, connector_factory=RemoteBrokerConnector,
9- got_connection=got_connection, max_retries=14, results=None):
10+def register(config, reactor=None, connector_factory=RemoteBrokerConnector,
11+ got_connection=got_connection, max_retries=14, on_error=None,
12+ results=None):
13 """Instruct the Landscape Broker to register the client.
14
15 The broker will be instructed to reload its configuration and then to
16 attempt a registration.
17
18- @param reactor: The reactor to use. Please only pass reactor when you
19- have totally mangled everything with mocker. Otherwise bad things
20- will happen.
21+ @param reactor: The reactor to use. This parameter is optional because
22+ the client charm does not pass it.
23+ @param connector_factory: A callable that accepts a reactor and a
24+ configuration object and returns a new remote broker connection. Used
25+ primarily for dependency injection.
26+ @param got_connection: The handler to trigger when the remote broker
27+ connects. Used primarily for dependency injection.
28 @param max_retries: The number of times to retry connecting to the
29 landscape client service. The delay between retries is calculated
30- by Twisted and increases geometrically. The default of 14 results in
31- a total wait time of about 70 seconds.
32-
33- initialDelay = 0.05
34- factor = 1.62
35- maxDelay = 30
36- max_retries = 14
37-
38- 0.05 * (1 - 1.62 ** 14) / (1 - 1.62) = 69 seconds
39+ by Twisted and increases geometrically.
40+ @param on_error: A callable that will be passed a non-zero positive
41+ integer argument in the case that some error occurs. This is a legacy
42+ API provided for use by the client charm.
43+ @param results: This parameter provides a mechanism to pre-seed the result
44+ of registering. Used for testing.
45 """
46+ if reactor is None:
47+ reactor = LandscapeReactor()
48 if results is None:
49 results = []
50 add_result = results.append
51@@ -675,7 +679,13 @@
52
53 assert len(results) == 1, "We expect exactly one result."
54 # Results will be things like "success" or "ssl-error".
55- return results[0]
56+ result = results[0]
57+
58+ # If there was an error and the caller requested that errors be reported
59+ # to the on_error callable, then do so.
60+ if result != "success" and on_error is not None:
61+ on_error(1)
62+ return result
63
64
65 def report_registration_outcome(what_happened, print=print):
66
67=== modified file 'landscape/tests/test_configuration.py'
68--- landscape/tests/test_configuration.py 2015-03-04 14:22:17 +0000
69+++ landscape/tests/test_configuration.py 2015-03-10 16:53:34 +0000
70@@ -10,10 +10,7 @@
71 from twisted.internet.defer import succeed
72
73 from landscape.broker.registration import InvalidCredentialsError
74-from landscape.tests.helpers import FakeBrokerServiceHelper
75 from landscape.broker.tests.helpers import RemoteBrokerHelper
76-from landscape.lib.amp import MethodCallError
77-from landscape.lib.fetch import HTTPCodeError, PyCurlError
78 from landscape.configuration import (
79 print_text, LandscapeSetupScript, LandscapeSetupConfiguration,
80 register, setup, main, setup_init_script_and_start_client,
81@@ -21,7 +18,10 @@
82 ImportOptionError, store_public_key_data,
83 bootstrap_tree, got_connection, success, failure, exchange_failure,
84 handle_registration_errors, done, got_error, report_registration_outcome)
85+from landscape.lib.amp import MethodCallError
86+from landscape.lib.fetch import HTTPCodeError, PyCurlError
87 from landscape.sysvconfig import SysVConfig, ProcessError
88+from landscape.tests.helpers import FakeBrokerServiceHelper
89 from landscape.tests.helpers import LandscapeTest, EnvironSaverHelper
90 from landscape.tests.mocker import ANY, MATCH, CONTAINS, expect
91
92@@ -2010,6 +2010,32 @@
93 # We ask for retries because networks aren't reliable.
94 self.assertEqual(99, connector.max_retries)
95
96+ def test_register_without_reactor(self):
97+ """If no reactor is passed, a LandscapeReactor will be instantiated.
98+
99+ This behaviour is exclusively for compatability with the client charm
100+ which does not pass in a reactor.
101+ """
102+
103+ def connector_factory(reactor, config):
104+ return FauxConnector(reactor, self.config)
105+
106+ reactor_mock = self.mocker.replace(
107+ "landscape.reactor.LandscapeReactor", passthrough=False)
108+ # The mock acts as both the constructor...
109+ reactor_mock()
110+ self.mocker.result(reactor_mock)
111+ # ...and the constructed reactor itself.
112+ reactor_mock.run()
113+ self.mocker.replay()
114+
115+ # We pre-seed a success because no actual result will be generated.
116+ register(self.config, connector_factory=connector_factory,
117+ results=["success"])
118+ # The reactor mock being run is what this test asserts, which is
119+ # verified by the test infrastructure, so there are no assertions
120+ # here.
121+
122 def test_got_connection(self):
123 """got_connection() adds deferreds and callbacks."""
124
125@@ -2088,6 +2114,43 @@
126 'handle_registration_errors',
127 faux_remote.register_deferred.errbacks[0].__name__)
128
129+ def test_register_with_on_error_and_an_error(self):
130+ """A caller-provided on_error callable will be called if errors occur.
131+
132+ The on_error parameter is provided for the client charm which calls
133+ register() directly and provides on_error as a keyword argument.
134+ """
135+ def faux_got_connection(add_result, remote, connector, reactor):
136+ add_result("something bad")
137+
138+ on_error_was_called = []
139+
140+ def on_error(status):
141+ # A positive number is provided for the status.
142+ self.assertGreater(status, 0)
143+ on_error_was_called.append(True)
144+
145+ self.reactor.call_later(1, self.reactor.stop)
146+ register(self.config, reactor=self.reactor, on_error=on_error,
147+ got_connection=faux_got_connection)
148+ self.assertTrue(on_error_was_called)
149+
150+ def test_register_with_on_error_and_no_error(self):
151+ """A caller-provided on_error callable will not be called if no error.
152+ """
153+ def faux_got_connection(add_result, remote, connector, reactor):
154+ add_result("success")
155+
156+ on_error_was_called = []
157+
158+ def on_error(status):
159+ on_error_was_called.append(True)
160+
161+ self.reactor.call_later(1, self.reactor.stop)
162+ register(self.config, reactor=self.reactor, on_error=on_error,
163+ got_connection=faux_got_connection)
164+ self.assertFalse(on_error_was_called)
165+
166 def test_register_happy_path(self):
167 """A successful result provokes no exceptions."""
168 def faux_got_connection(add_result, remote, connector, reactor):

Subscribers

People subscribed via source and target branches

to all changes: