Merge lp:~ack/landscape-client/max-pending-computers-message into lp:~landscape/landscape-client/trunk

Proposed by Alberto Donato
Status: Merged
Approved by: Alberto Donato
Approved revision: 948
Merged at revision: 935
Proposed branch: lp:~ack/landscape-client/max-pending-computers-message
Merge into: lp:~landscape/landscape-client/trunk
Diff against target: 608 lines (+194/-83)
6 files modified
debian/changelog (+6/-0)
landscape/__init__.py (+10/-9)
landscape/broker/registration.py (+10/-9)
landscape/broker/tests/test_registration.py (+61/-12)
landscape/configuration.py (+45/-33)
landscape/tests/test_configuration.py (+62/-20)
To merge this branch: bzr merge lp:~ack/landscape-client/max-pending-computers-message
Reviewer Review Type Date Requested Status
Chad Smith Approve
🤖 Landscape Builder test results Approve
Free Ekanayaka (community) Approve
Review via email: mp+317961@code.launchpad.net

Commit message

This adds support in the client to handle a new error condition when trying to register a client, when there are too many pending computers already.
To do that, the client API version has been bumped, since the server needs to know if the client knows about this new error code.

Description of the change

This adds support in the client to handle a new error condition when trying to register a client, when there are too many pending computers already.
To do that, the client API version has been bumped, since the server needs to know if the client knows about this new error code.
The server side of this change is lp:~ack/landscape/max-pending-computers-message (which needs this branch landed because of the version bump).

Testing instructions:

I tested the changes as follows:

 - deploy an LDS from lp:~ack/landscape/max-pending-computers-message (make stage-landscape-charm ...)
 - create a LXD and install landscape-client, configure it to point to the LDS.
 - register the client 20 times (without accepting it) to create pending computers with: landscape-client --silent
 - try to register again, get the "Invalid account name or registration key." error (current client still works as previously)
 - make package from the client branch, install those packages in the LXD
 - the error message will be "Maximum number of computers pending approval reached."

To post a comment you must log in.
Revision history for this message
🤖 Landscape Builder (landscape-builder) :
review: Abstain (executing tests)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :

Command: TRIAL_ARGS=-j4 make check
Result: Success
Revno: 946
Branch: lp:~ack/landscape-client/max-pending-computers-message
Jenkins: https://ci.lscape.net/job/latch-test-precise/867/

review: Approve (test results)
Revision history for this message
Chad Smith (chad.smith) wrote :

just a couple nits so far. Going through deploy testing now.

Revision history for this message
Chad Smith (chad.smith) :
Revision history for this message
Free Ekanayaka (free.ekanayaka) wrote :

+1 with a few nits

review: Approve
Revision history for this message
Alberto Donato (ack) :
947. By Alberto Donato

Address review comments.

Revision history for this message
🤖 Landscape Builder (landscape-builder) :
review: Abstain (executing tests)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :

Command: TRIAL_ARGS=-j4 make check
Result: Fail
Revno: 947
Branch: lp:~ack/landscape-client/max-pending-computers-message
Jenkins: https://ci.lscape.net/job/latch-test-precise/868/

review: Needs Fixing (test results)
948. By Alberto Donato

Fix test.

Revision history for this message
🤖 Landscape Builder (landscape-builder) :
review: Abstain (executing tests)
Revision history for this message
🤖 Landscape Builder (landscape-builder) wrote :

Command: TRIAL_ARGS=-j4 make check
Result: Success
Revno: 948
Branch: lp:~ack/landscape-client/max-pending-computers-message
Jenkins: https://ci.lscape.net/job/latch-test-precise/869/

review: Approve (test results)
Revision history for this message
Chad Smith (chad.smith) :
review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'debian/changelog'
2--- debian/changelog 2017-01-25 19:23:36 +0000
3+++ debian/changelog 2017-02-24 10:09:35 +0000
4@@ -1,3 +1,9 @@
5+landscape-client (17.01+bzr946-0ubuntu0) xenial; urgency=medium
6+
7+ * New local test build
8+
9+ -- Ubuntu <alberto.donato@gmail.com> Wed, 22 Feb 2017 12:04:19 +0000
10+
11 landscape-client (17.01-0ubuntu1) UNRELEASED; urgency=medium
12
13 * New trunk build
14
15=== modified file 'landscape/__init__.py'
16--- landscape/__init__.py 2017-01-25 19:23:36 +0000
17+++ landscape/__init__.py 2017-02-24 10:09:35 +0000
18@@ -1,5 +1,5 @@
19-DEBIAN_REVISION = ""
20-UPSTREAM_VERSION = "17.01"
21+DEBIAN_REVISION = "-0ubuntu0"
22+UPSTREAM_VERSION = "17.01+bzr946"
23 VERSION = "%s%s" % (UPSTREAM_VERSION, DEBIAN_REVISION)
24
25 # The minimum server API version that all Landscape servers are known to speak
26@@ -17,17 +17,13 @@
27 #
28 # Changelog:
29 #
30-# 3.3:
31-# * Add new schema for the "registration" message, providing Juju information
32 # 3.2:
33 # * Add new "eucalyptus-info" and "eucalyptus-info-error" messages.
34+# 3.3:
35+# * Add new schema for the "registration" message, providing Juju information
36 #
37 SERVER_API = "3.3"
38
39-# XXX This is needed for backward compatibility in the server code importing
40-# the API variable. We should eventually replace it in the server code.
41-API = SERVER_API
42-
43 # The "client-api" field of outgoing messages will be set to this value, and
44 # used by the server to know which schema do the message types accepted by the
45 # client support. Bump it when the schema of an accepted message type changes
46@@ -53,4 +49,9 @@
47 # 3.7:
48 # * Server returns 402 Payment Required if the computer has no valid license.
49 #
50-CLIENT_API = "3.7"
51+# 3.8:
52+# * Handle the new "max-pending-computers" key returned by the server in the
53+# "info" field of the response payload for a fail registration, in case the
54+# account has too many pending computers.
55+#
56+CLIENT_API = "3.8"
57
58=== modified file 'landscape/broker/registration.py'
59--- landscape/broker/registration.py 2015-11-09 21:10:59 +0000
60+++ landscape/broker/registration.py 2017-02-24 10:09:35 +0000
61@@ -19,10 +19,11 @@
62 from landscape.lib.versioning import is_version_higher
63
64
65-class InvalidCredentialsError(Exception):
66+class RegistrationError(Exception):
67 """
68- Raised when an invalid account title and/or registration key
69- is used with L{RegistrationManager.register}.
70+ Raised when the registration failed because an invalid account title
71+ and/or registration key are used with RegistrationManager.register,
72+ or the server has too many pending computers already.
73 """
74
75
76@@ -116,7 +117,7 @@
77
78 @return: A L{Deferred} which will either be fired with None if
79 registration was successful or will fail with an
80- L{InvalidCredentialsError} if not.
81+ RegistrationError if not.
82 """
83 self._identity.secure_id = None
84 self._identity.insecure_id = None
85@@ -163,7 +164,7 @@
86 account_name = identity.account_name
87
88 if not account_name:
89- self._reactor.fire("registration-failed")
90+ self._reactor.fire("registration-failed", reason="unknown-account")
91 return
92
93 tags = identity.tags
94@@ -233,8 +234,8 @@
95 self._reactor.fire("resynchronize-clients")
96
97 def _handle_registration(self, message):
98- if message["info"] == "unknown-account":
99- self._reactor.fire("registration-failed")
100+ if message["info"] in ("unknown-account", "max-pending-computers"):
101+ self._reactor.fire("registration-failed", reason=message["info"])
102
103 def _handle_unknown_id(self, message):
104 id = self._identity
105@@ -279,6 +280,6 @@
106 self.deferred.callback(None)
107 self._cancel_calls()
108
109- def _failed(self):
110- self.deferred.errback(InvalidCredentialsError())
111+ def _failed(self, reason=None):
112+ self.deferred.errback(RegistrationError(reason))
113 self._cancel_calls()
114
115=== modified file 'landscape/broker/tests/test_registration.py'
116--- landscape/broker/tests/test_registration.py 2016-06-16 16:17:53 +0000
117+++ landscape/broker/tests/test_registration.py 2017-02-24 10:09:35 +0000
118@@ -3,8 +3,7 @@
119 import socket
120 import mock
121
122-from landscape.broker.registration import (
123- InvalidCredentialsError, Identity)
124+from landscape.broker.registration import RegistrationError, Identity
125 from landscape.tests.helpers import LandscapeTest
126 from landscape.broker.tests.helpers import (
127 BrokerConfigurationHelper, RegistrationHelper)
128@@ -343,16 +342,29 @@
129 self.assertMessages(self.mstore.get_pending_messages(), [])
130 handler_mock.assert_called_once_with()
131
132- def test_registration_failed_event(self):
133+ def test_registration_failed_event_unknown_account(self):
134 """
135 The deferred returned by a registration request should fail
136- with L{InvalidCredentialsError} if the server responds with a
137- failure message.
138+ if the server responds with a failure message because credentials are
139+ wrong.
140 """
141 reactor_fire_mock = self.reactor.fire = mock.Mock()
142 self.exchanger.handle_message(
143 {"type": "registration", "info": "unknown-account"})
144- reactor_fire_mock.assert_called_with("registration-failed")
145+ reactor_fire_mock.assert_called_with(
146+ "registration-failed", reason="unknown-account")
147+
148+ def test_registration_failed_event_max_pending_computers(self):
149+ """
150+ The deferred returned by a registration request should fail
151+ if the server responds with a failure message because the max number of
152+ pending computers have been reached.
153+ """
154+ reactor_fire_mock = self.reactor.fire = mock.Mock()
155+ self.exchanger.handle_message(
156+ {"type": "registration", "info": "max-pending-computers"})
157+ reactor_fire_mock.assert_called_with(
158+ "registration-failed", reason="max-pending-computers")
159
160 def test_registration_failed_event_not_fired_when_uncertain(self):
161 """
162@@ -424,17 +436,22 @@
163
164 self.assertEqual(results, [None])
165
166- def test_register_deferred_called_on_failed(self):
167+ def test_register_deferred_called_on_failed_unknown_account(self):
168+ """
169+ The registration errback is called on failures when credentials are
170+ invalid.
171+ """
172 # We don't want informational messages.
173 self.logger.setLevel(logging.WARNING)
174
175- calls = [0]
176+ calls = []
177 d = self.handler.register()
178
179 def add_call(failure):
180 exception = failure.value
181- self.assertTrue(isinstance(exception, InvalidCredentialsError))
182- calls[0] += 1
183+ self.assertTrue(isinstance(exception, RegistrationError))
184+ self.assertEqual("unknown-account", str(exception))
185+ calls.append(True)
186
187 d.addErrback(add_call)
188
189@@ -442,13 +459,45 @@
190 self.exchanger.handle_message(
191 {"type": "registration", "info": "unknown-account"})
192
193- self.assertEqual(calls, [1])
194+ self.assertEqual(calls, [True])
195
196 # Doing it again to ensure that the deferred isn't called twice.
197 self.exchanger.handle_message(
198 {"type": "registration", "info": "unknown-account"})
199
200- self.assertEqual(calls, [1])
201+ self.assertEqual(calls, [True])
202+
203+ self.assertEqual(self.logfile.getvalue(), "")
204+
205+ def test_register_deferred_called_on_failed_max_pending_computers(self):
206+ """
207+ The registration errback is called on failures when max number of
208+ pending computers has been reached.
209+ """
210+ # We don't want informational messages.
211+ self.logger.setLevel(logging.WARNING)
212+
213+ calls = []
214+ d = self.handler.register()
215+
216+ def add_call(failure):
217+ exception = failure.value
218+ self.assertTrue(isinstance(exception, RegistrationError))
219+ self.assertEqual("max-pending-computers", str(exception))
220+ calls.append(True)
221+
222+ d.addErrback(add_call)
223+
224+ self.exchanger.handle_message(
225+ {"type": "registration", "info": "max-pending-computers"})
226+
227+ self.assertEqual(calls, [True])
228+
229+ # Doing it again to ensure that the deferred isn't called twice.
230+ self.exchanger.handle_message(
231+ {"type": "registration", "info": "max-pending-computers"})
232+
233+ self.assertEqual(calls, [True])
234
235 self.assertEqual(self.logfile.getvalue(), "")
236
237
238=== modified file 'landscape/configuration.py'
239--- landscape/configuration.py 2015-11-09 21:10:59 +0000
240+++ landscape/configuration.py 2017-02-24 10:09:35 +0000
241@@ -23,7 +23,7 @@
242 from landscape.lib.fetch import fetch, FetchError
243 from landscape.lib.bootstrap import BootstrapList, BootstrapDirectory
244 from landscape.reactor import LandscapeReactor
245-from landscape.broker.registration import InvalidCredentialsError
246+from landscape.broker.registration import RegistrationError
247 from landscape.broker.config import BrokerConfiguration
248 from landscape.broker.amp import RemoteBrokerConnector
249
250@@ -104,7 +104,7 @@
251 raise ImportOptionError(
252 "Couldn't read configuration from %s." %
253 self.import_from)
254- except Exception, error:
255+ except Exception as error:
256 raise ImportOptionError(str(error))
257
258 # But real command line options have precedence.
259@@ -124,7 +124,7 @@
260 error_message = None
261 try:
262 content = fetch(url)
263- except FetchError, error:
264+ except FetchError as error:
265 error_message = str(error)
266 if error_message is not None:
267 raise ImportOptionError(
268@@ -324,9 +324,9 @@
269 proxies now. If you don't use a proxy, leave these fields empty.
270 """)
271
272- if not "http_proxy" in options:
273+ if "http_proxy" not in options:
274 self.prompt("http_proxy", "HTTP proxy URL")
275- if not "https_proxy" in options:
276+ if "https_proxy" not in options:
277 self.prompt("https_proxy", "HTTPS proxy URL")
278
279 def query_script_plugin(self):
280@@ -413,7 +413,7 @@
281 self.prompt("tags", "Tags", False)
282 if self._get_invalid_tags(self.config.tags):
283 self.show_help("Tag names may only contain alphanumeric "
284- "characters.")
285+ "characters.")
286 self.config.tags = None # Reset for the next prompt
287 else:
288 break
289@@ -588,9 +588,10 @@
290 return key_filename
291
292
293-def failure(add_result):
294+def failure(add_result, reason=None):
295 """Handle a failed communication by recording the kind of failure."""
296- add_result("failure")
297+ if reason:
298+ add_result(reason)
299
300
301 def exchange_failure(add_result, ssl_error=False):
302@@ -601,17 +602,19 @@
303 add_result("non-ssl-error")
304
305
306-def handle_registration_errors(failure, connector):
307- """Handle invalid credentials.
308+def handle_registration_errors(add_result, failure, connector):
309+ """Handle registration errors.
310
311 The connection to the broker succeeded but the registration itself
312- failed, because of invalid credentials. We need to trap the exceptions
313- so they don't stacktrace (we know what is going on), and try to cleanly
314- disconnect from the broker.
315+ failed, because of invalid credentials or excessive pending computers.
316+ We need to trap the exceptions so they don't stacktrace (we know what is
317+ going on), and try to cleanly disconnect from the broker.
318
319 Note: "results" contains a failure indication already (or will shortly)
320 since the registration-failed signal will fire."""
321- failure.trap(InvalidCredentialsError, MethodCallError)
322+ error = failure.trap(RegistrationError, MethodCallError)
323+ if error is RegistrationError:
324+ add_result(str(failure.value))
325 connector.disconnect()
326
327
328@@ -633,7 +636,8 @@
329 "exchange-failed": partial(exchange_failure, add_result)}
330 deferreds = [
331 remote.call_on_event(handlers),
332- remote.register().addErrback(handle_registration_errors, connector)]
333+ remote.register().addErrback(
334+ partial(handle_registration_errors, add_result), connector)]
335 results = gather_results(deferreds)
336 results.addCallback(done, connector, reactor)
337 return results
338@@ -646,8 +650,8 @@
339
340
341 def register(config, reactor=None, connector_factory=RemoteBrokerConnector,
342- got_connection=got_connection, max_retries=14, on_error=None,
343- results=None):
344+ got_connection=got_connection, max_retries=14, on_error=None,
345+ results=None):
346 """Instruct the Landscape Broker to register the client.
347
348 The broker will be instructed to reload its configuration and then to
349@@ -694,24 +698,32 @@
350
351
352 def report_registration_outcome(what_happened, print=print):
353- """Report the registrtion interaction outcome to the user in human-readable
354+ """Report the registration interaction outcome to the user in human-readable
355 form.
356 """
357- if what_happened == "success":
358- print("System successfully registered.")
359- elif what_happened == "failure":
360- print("Invalid account name or registration key.", file=sys.stderr)
361- elif what_happened == "ssl-error":
362- print("\nThe server's SSL information is incorrect, or fails "
363- "signature verification!\n"
364- "If the server is using a self-signed certificate, "
365- "please ensure you supply it with the --ssl-public-key "
366- "parameter.", file=sys.stderr)
367- elif what_happened == "non-ssl-error":
368- print("\nWe were unable to contact the server.\n"
369- "Your internet connection may be down. "
370- "The landscape client will continue to try and contact "
371- "the server periodically.", file=sys.stderr)
372+ messages = {
373+ "success": "System successfully registered.",
374+ "unknown-account": "Invalid account name or registration key.",
375+ "max-pending-computers": (
376+ "Maximum number of computers pending approval reached. ",
377+ "Login to your Landscape server account page to manage "
378+ "pending computer approvals."),
379+ "ssl-error": (
380+ "\nThe server's SSL information is incorrect, or fails "
381+ "signature verification!\n"
382+ "If the server is using a self-signed certificate, "
383+ "please ensure you supply it with the --ssl-public-key "
384+ "parameter."),
385+ "non-ssl-error": (
386+ "\nWe were unable to contact the server.\n"
387+ "Your internet connection may be down. "
388+ "The landscape client will continue to try and contact "
389+ "the server periodically.")
390+ }
391+ message = messages.get(what_happened)
392+ if message:
393+ fd = sys.stdout if what_happened == "success" else sys.stderr
394+ print(message, file=fd)
395
396
397 def determine_exit_code(what_happened):
398
399=== modified file 'landscape/tests/test_configuration.py'
400--- landscape/tests/test_configuration.py 2016-12-08 12:48:46 +0000
401+++ landscape/tests/test_configuration.py 2017-02-24 10:09:35 +0000
402@@ -1,5 +1,6 @@
403 from __future__ import print_function
404
405+from functools import partial
406 from ConfigParser import ConfigParser
407 from cStringIO import StringIO
408 import os
409@@ -9,7 +10,7 @@
410 import mock
411 from twisted.internet.defer import succeed, fail, Deferred
412
413-from landscape.broker.registration import InvalidCredentialsError
414+from landscape.broker.registration import RegistrationError
415 from landscape.broker.tests.helpers import RemoteBrokerHelper
416 from landscape.configuration import (
417 print_text, LandscapeSetupScript, LandscapeSetupConfiguration,
418@@ -44,6 +45,7 @@
419
420
421 class SuccessTests(unittest.TestCase):
422+
423 def test_success(self):
424 """The success handler records the success."""
425 results = []
426@@ -52,11 +54,12 @@
427
428
429 class FailureTests(unittest.TestCase):
430+
431 def test_failure(self):
432 """The failure handler records the failure and returns non-zero."""
433 results = []
434- self.assertNotEqual(0, failure(results.append))
435- self.assertEqual(["failure"], results)
436+ self.assertNotEqual(0, failure(results.append, "an-error"))
437+ self.assertEqual(["an-error"], results)
438
439
440 class ExchangeFailureTests(unittest.TestCase):
441@@ -84,7 +87,7 @@
442
443 def test_handle_registration_errors_traps(self):
444 """
445- The handle_registration_errors() function traps InvalidCredentialsError
446+ The handle_registration_errors() function traps RegistrationError
447 and MethodCallError errors.
448 """
449 class FauxFailure(object):
450@@ -94,10 +97,15 @@
451 faux_connector = FauxConnector()
452 faux_failure = FauxFailure()
453
454+ results = []
455+ add_result = results.append
456+
457 self.assertNotEqual(
458- 0, handle_registration_errors(faux_failure, faux_connector))
459+ 0,
460+ handle_registration_errors(
461+ add_result, faux_failure, faux_connector))
462 self.assertTrue(
463- [InvalidCredentialsError, MethodCallError],
464+ [RegistrationError, MethodCallError],
465 faux_failure.trapped_exceptions)
466
467 def test_handle_registration_errors_disconnects_cleanly(self):
468@@ -112,8 +120,13 @@
469 faux_connector = FauxConnector()
470 faux_failure = FauxFailure()
471
472+ results = []
473+ add_result = results.append
474+
475 self.assertNotEqual(
476- 0, handle_registration_errors(faux_failure, faux_connector))
477+ 0,
478+ handle_registration_errors(
479+ add_result, faux_failure, faux_connector))
480 self.assertTrue(faux_connector.was_disconnected)
481
482 def test_handle_registration_errors_as_errback(self):
483@@ -128,11 +141,15 @@
484
485 def i_raise(result):
486 calls.append(True)
487- return InvalidCredentialsError("Bad mojo")
488+ return RegistrationError("Bad mojo")
489+
490+ results = []
491+ add_result = results.append
492
493 deferred = Deferred()
494 deferred.addCallback(i_raise)
495- deferred.addErrback(handle_registration_errors, faux_connector)
496+ deferred.addErrback(
497+ partial(handle_registration_errors, add_result), faux_connector)
498 deferred.callback("") # This kicks off the callback chain.
499
500 self.assertEqual([True], calls)
501@@ -1126,7 +1143,8 @@
502 printed)
503
504 @mock.patch("__builtin__.raw_input", return_value="y")
505- @mock.patch("landscape.configuration.register", return_value="failure")
506+ @mock.patch(
507+ "landscape.configuration.register", return_value="unknown-account")
508 @mock.patch("landscape.configuration.setup")
509 def test_main_user_interaction_failure(
510 self, mock_setup, mock_register, mock_raw_input):
511@@ -1177,11 +1195,13 @@
512 printed)
513
514 @mock.patch("__builtin__.raw_input")
515- @mock.patch("landscape.configuration.register", return_value="failure")
516+ @mock.patch(
517+ "landscape.configuration.register", return_value="unknown-account")
518 @mock.patch("landscape.configuration.setup")
519 def test_main_user_interaction_failure_silent(
520 self, mock_setup, mock_register, mock_raw_input):
521- """A failure result is communicated to the user even with --silent.
522+ """
523+ A failure result is communicated to the user even with --silent.
524 """
525 printed = []
526
527@@ -1797,13 +1817,13 @@
528
529 def test_register_registration_error(self):
530 """
531- If we get a registration error, the register() function returns
532- "failure".
533+ If we get a registration error, the register() function returns the
534+ error from the registration response message.
535 """
536 self.reactor.call_later(0, self.reactor.fire, "registration-failed")
537
538 def fail_register():
539- return fail(InvalidCredentialsError("Nope."))
540+ return fail(RegistrationError("max-pending-computers"))
541
542 self.remote.register = fail_register
543
544@@ -1811,7 +1831,7 @@
545 result = register(
546 config=self.config, reactor=self.reactor,
547 connector_factory=connector_factory, max_retries=99)
548- self.assertEqual("failure", result)
549+ self.assertEqual("max-pending-computers", result)
550
551
552 class FauxConnection(object):
553@@ -1981,9 +2001,10 @@
554 for handler in faux_remote.handlers.values()])
555 # We include a single error handler to react to exchange errors.
556 self.assertTrue(1, len(faux_remote.register_deferred.errbacks))
557+ # the handle_registration_errors is wrapped in a partial()
558 self.assertEqual(
559 'handle_registration_errors',
560- faux_remote.register_deferred.errbacks[0].__name__)
561+ faux_remote.register_deferred.errbacks[0].func.__name__)
562
563 def test_register_with_on_error_and_an_error(self):
564 """A caller-provided on_error callable will be called if errors occur.
565@@ -2070,11 +2091,30 @@
566 self.assertIn("System successfully registered.", self.result)
567 self.assertIn(sys.stdout.name, self.output)
568
569- def test_failure_case(self):
570- report_registration_outcome("failure", print=self.record_result)
571+ def test_unknown_account_case(self):
572+ """
573+ If the unknown-account error is found, an appropriate message is
574+ returned.
575+ """
576+ report_registration_outcome(
577+ "unknown-account", print=self.record_result)
578 self.assertIn("Invalid account name or registration key.", self.result)
579 self.assertIn(sys.stderr.name, self.output)
580
581+ def test_max_pending_computers_case(self):
582+ """
583+ If the max-pending-computers error is found, an appropriate message is
584+ returned.
585+ """
586+ report_registration_outcome(
587+ "max-pending-computers", print=self.record_result)
588+ messages = (
589+ "Maximum number of computers pending approval reached. ",
590+ "Login to your Landscape server account page to manage pending "
591+ "computer approvals.")
592+ self.assertIn(messages, self.result)
593+ self.assertIn(sys.stderr.name, self.output)
594+
595 def test_ssl_error_case(self):
596 report_registration_outcome("ssl-error", print=self.record_result)
597 self.assertIn("\nThe server's SSL information is incorrect, or fails "
598@@ -2107,7 +2147,9 @@
599 When passed a failure result, the determine_exit_code function returns
600 2.
601 """
602- failure_codes = ["failure", "ssl-error", "non-ssl-error"]
603+ failure_codes = [
604+ "unknown-account", "max-computers-count", "ssl-error",
605+ "non-ssl-error"]
606 for code in failure_codes:
607 result = determine_exit_code(code)
608 self.assertEqual(2, result)

Subscribers

People subscribed via source and target branches

to all changes: