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
=== modified file 'landscape/configuration.py'
--- landscape/configuration.py 2015-03-04 14:22:17 +0000
+++ landscape/configuration.py 2015-03-10 16:53:34 +0000
@@ -640,28 +640,32 @@
640 raise SystemExit640 raise SystemExit
641641
642642
643def register(config, reactor, connector_factory=RemoteBrokerConnector,643def register(config, reactor=None, connector_factory=RemoteBrokerConnector,
644 got_connection=got_connection, max_retries=14, results=None):644 got_connection=got_connection, max_retries=14, on_error=None,
645 results=None):
645 """Instruct the Landscape Broker to register the client.646 """Instruct the Landscape Broker to register the client.
646647
647 The broker will be instructed to reload its configuration and then to648 The broker will be instructed to reload its configuration and then to
648 attempt a registration.649 attempt a registration.
649650
650 @param reactor: The reactor to use. Please only pass reactor when you651 @param reactor: The reactor to use. This parameter is optional because
651 have totally mangled everything with mocker. Otherwise bad things652 the client charm does not pass it.
652 will happen.653 @param connector_factory: A callable that accepts a reactor and a
654 configuration object and returns a new remote broker connection. Used
655 primarily for dependency injection.
656 @param got_connection: The handler to trigger when the remote broker
657 connects. Used primarily for dependency injection.
653 @param max_retries: The number of times to retry connecting to the658 @param max_retries: The number of times to retry connecting to the
654 landscape client service. The delay between retries is calculated659 landscape client service. The delay between retries is calculated
655 by Twisted and increases geometrically. The default of 14 results in660 by Twisted and increases geometrically.
656 a total wait time of about 70 seconds.661 @param on_error: A callable that will be passed a non-zero positive
657662 integer argument in the case that some error occurs. This is a legacy
658 initialDelay = 0.05663 API provided for use by the client charm.
659 factor = 1.62664 @param results: This parameter provides a mechanism to pre-seed the result
660 maxDelay = 30665 of registering. Used for testing.
661 max_retries = 14
662
663 0.05 * (1 - 1.62 ** 14) / (1 - 1.62) = 69 seconds
664 """666 """
667 if reactor is None:
668 reactor = LandscapeReactor()
665 if results is None:669 if results is None:
666 results = []670 results = []
667 add_result = results.append671 add_result = results.append
@@ -675,7 +679,13 @@
675679
676 assert len(results) == 1, "We expect exactly one result."680 assert len(results) == 1, "We expect exactly one result."
677 # Results will be things like "success" or "ssl-error".681 # Results will be things like "success" or "ssl-error".
678 return results[0]682 result = results[0]
683
684 # If there was an error and the caller requested that errors be reported
685 # to the on_error callable, then do so.
686 if result != "success" and on_error is not None:
687 on_error(1)
688 return result
679689
680690
681def report_registration_outcome(what_happened, print=print):691def report_registration_outcome(what_happened, print=print):
682692
=== modified file 'landscape/tests/test_configuration.py'
--- landscape/tests/test_configuration.py 2015-03-04 14:22:17 +0000
+++ landscape/tests/test_configuration.py 2015-03-10 16:53:34 +0000
@@ -10,10 +10,7 @@
10from twisted.internet.defer import succeed10from twisted.internet.defer import succeed
1111
12from landscape.broker.registration import InvalidCredentialsError12from landscape.broker.registration import InvalidCredentialsError
13from landscape.tests.helpers import FakeBrokerServiceHelper
14from landscape.broker.tests.helpers import RemoteBrokerHelper13from landscape.broker.tests.helpers import RemoteBrokerHelper
15from landscape.lib.amp import MethodCallError
16from landscape.lib.fetch import HTTPCodeError, PyCurlError
17from landscape.configuration import (14from landscape.configuration import (
18 print_text, LandscapeSetupScript, LandscapeSetupConfiguration,15 print_text, LandscapeSetupScript, LandscapeSetupConfiguration,
19 register, setup, main, setup_init_script_and_start_client,16 register, setup, main, setup_init_script_and_start_client,
@@ -21,7 +18,10 @@
21 ImportOptionError, store_public_key_data,18 ImportOptionError, store_public_key_data,
22 bootstrap_tree, got_connection, success, failure, exchange_failure,19 bootstrap_tree, got_connection, success, failure, exchange_failure,
23 handle_registration_errors, done, got_error, report_registration_outcome)20 handle_registration_errors, done, got_error, report_registration_outcome)
21from landscape.lib.amp import MethodCallError
22from landscape.lib.fetch import HTTPCodeError, PyCurlError
24from landscape.sysvconfig import SysVConfig, ProcessError23from landscape.sysvconfig import SysVConfig, ProcessError
24from landscape.tests.helpers import FakeBrokerServiceHelper
25from landscape.tests.helpers import LandscapeTest, EnvironSaverHelper25from landscape.tests.helpers import LandscapeTest, EnvironSaverHelper
26from landscape.tests.mocker import ANY, MATCH, CONTAINS, expect26from landscape.tests.mocker import ANY, MATCH, CONTAINS, expect
2727
@@ -2010,6 +2010,32 @@
2010 # We ask for retries because networks aren't reliable.2010 # We ask for retries because networks aren't reliable.
2011 self.assertEqual(99, connector.max_retries)2011 self.assertEqual(99, connector.max_retries)
20122012
2013 def test_register_without_reactor(self):
2014 """If no reactor is passed, a LandscapeReactor will be instantiated.
2015
2016 This behaviour is exclusively for compatability with the client charm
2017 which does not pass in a reactor.
2018 """
2019
2020 def connector_factory(reactor, config):
2021 return FauxConnector(reactor, self.config)
2022
2023 reactor_mock = self.mocker.replace(
2024 "landscape.reactor.LandscapeReactor", passthrough=False)
2025 # The mock acts as both the constructor...
2026 reactor_mock()
2027 self.mocker.result(reactor_mock)
2028 # ...and the constructed reactor itself.
2029 reactor_mock.run()
2030 self.mocker.replay()
2031
2032 # We pre-seed a success because no actual result will be generated.
2033 register(self.config, connector_factory=connector_factory,
2034 results=["success"])
2035 # The reactor mock being run is what this test asserts, which is
2036 # verified by the test infrastructure, so there are no assertions
2037 # here.
2038
2013 def test_got_connection(self):2039 def test_got_connection(self):
2014 """got_connection() adds deferreds and callbacks."""2040 """got_connection() adds deferreds and callbacks."""
20152041
@@ -2088,6 +2114,43 @@
2088 'handle_registration_errors',2114 'handle_registration_errors',
2089 faux_remote.register_deferred.errbacks[0].__name__)2115 faux_remote.register_deferred.errbacks[0].__name__)
20902116
2117 def test_register_with_on_error_and_an_error(self):
2118 """A caller-provided on_error callable will be called if errors occur.
2119
2120 The on_error parameter is provided for the client charm which calls
2121 register() directly and provides on_error as a keyword argument.
2122 """
2123 def faux_got_connection(add_result, remote, connector, reactor):
2124 add_result("something bad")
2125
2126 on_error_was_called = []
2127
2128 def on_error(status):
2129 # A positive number is provided for the status.
2130 self.assertGreater(status, 0)
2131 on_error_was_called.append(True)
2132
2133 self.reactor.call_later(1, self.reactor.stop)
2134 register(self.config, reactor=self.reactor, on_error=on_error,
2135 got_connection=faux_got_connection)
2136 self.assertTrue(on_error_was_called)
2137
2138 def test_register_with_on_error_and_no_error(self):
2139 """A caller-provided on_error callable will not be called if no error.
2140 """
2141 def faux_got_connection(add_result, remote, connector, reactor):
2142 add_result("success")
2143
2144 on_error_was_called = []
2145
2146 def on_error(status):
2147 on_error_was_called.append(True)
2148
2149 self.reactor.call_later(1, self.reactor.stop)
2150 register(self.config, reactor=self.reactor, on_error=on_error,
2151 got_connection=faux_got_connection)
2152 self.assertFalse(on_error_was_called)
2153
2091 def test_register_happy_path(self):2154 def test_register_happy_path(self):
2092 """A successful result provokes no exceptions."""2155 """A successful result provokes no exceptions."""
2093 def faux_got_connection(add_result, remote, connector, reactor):2156 def faux_got_connection(add_result, remote, connector, reactor):

Subscribers

People subscribed via source and target branches

to all changes: