Skip to content

Commit d08127f

Browse files
committed
feat: Print config on connection test
For non-legacy, --test-connection dumps a user-friendly connection configuration. First, the authentication information is printed starting with type. For BASIC, username is printed. For CERT, certificate and key paths are verified and printed. In case of missing files or credentials, the connection test fails immediately. Second, tested URLs (base, Ingress, Inventory, API cast) are listed and server type (production, staging, Satellite) is determined. HTTPS proxy information is included. feat: Improve test URLs output _test_urls and _legacy_test_urls output is nicer, with clear SUCCESS/FAILURE statement. URLs are consistently listed, so is legacy fallback. With --verbose turned on, more information about requests, responses and errors are printed. The readability of the output improved drastically, with only little changes to the logging and tiny touches to the logic. The generic HTTP method logs information about the request. To make the log messages blend nicely into the connection test, introduced logging-related arguments: * To keep the output concise by default, but more helpful with --verbose, log_level suppresses HTTP details. * To match indentation with messages outside the request method, log_prefix allows to add spaces to the beginning. chore: Use return for flow control Exceptions in _(legacy_)test_urls are merely used for control-flow. Known ones are re-thrown and re-caught in test_connection, unknown ones are not caught at all. Return is more appropriate: _test_urls passes the result, test_connection decides how to handle it. feat: Test GET from Inventory Inventory is tested along with Ingress and an API ping. Hosts are listed as the most basic Inventory GET request. feat: Check connection In case of DNS failure. The DNS is queried, then a connection is established to the resolved IP. If resolving fails, a hard-coded IP is tried for production or staging. In case of either failure, DNS query for a public CloudFlare URL one.one.one.one and its IP 1.1.1.1 is tried. feat: Recognize more errors * 429 Too Many requests means the rate limit was hit. * 401 Unauthorized from gateway means the username/password is invalid. * SSLError means the key/certificate pair is invalid. * SSL: WRONG_VERSION_NUMBER in the SSLError means that HTTPS has been used to contact an HTTP server. * ConnectionTimeout and ReadTimeout may mean the connection is slow. feat: Detect proxy errors HTTPS proxy introduces several possible error cases, similar to the actual remote server connection: * proxy name resolution (DNS) error, * proxy connection error, * proxy authentication error. The proxy authentication error can only be recognized by a string in the underlying OSError: the outer exception is a plain remote server connection error. Although the proxy is used for HTTPS connection, the actual communication for the proxy itself is HTTP. Thus, specifying a HTTPS protocol for the proxy causes a specific WRONG_VERSION_NUMBER SSL error. feat: Validate URLs urlparse from Python stdlib doesn’t fail on an invalid URL. parse_url from urllib3 used by requests does though. Invalid base URL or proxy URL raises thus an uncaught exception. Card IDs: * CCT-963 Signed-off-by: Štěpán Tomsa <[email protected]>
1 parent 90ee27e commit d08127f

File tree

2 files changed

+96
-40
lines changed

2 files changed

+96
-40
lines changed

insights/client/connection.py

+44-12
Original file line numberDiff line numberDiff line change
@@ -447,11 +447,16 @@ def _test_auth_config(self):
447447
("Password", self.password, "********"),
448448
]:
449449
if not var:
450-
error = [" %s NOT SET.", desc]
451-
errors.append(error)
450+
errors.append([" %s NOT SET.", desc])
452451

453452
val = placeholder or var if var else "NOT SET"
454453
logger.info(" %s: %s", desc, val)
454+
455+
if errors:
456+
errors.append([
457+
" Check \"username\" and \"password\" in %s.",
458+
self.config.conf
459+
])
455460
elif self.authmethod == "CERT":
456461
logger.info("Authentication: identity certificate (%s)", self.authmethod)
457462

@@ -465,12 +470,22 @@ def _test_auth_config(self):
465470
exists_description = "exists"
466471
else:
467472
exists_description = "NOT FOUND"
468-
error = [" %s file %s MISSING.", desc, path]
469-
errors.append(error)
473+
errors.append([" %s file %s MISSING.", desc, path])
470474
logger.info(" %s: %s (%s)", desc, path, exists_description)
475+
476+
if errors:
477+
errors.append([
478+
" Re-register the system by running \"subscription-manager unregister\" and then "
479+
"\"subscription-manager register\"."
480+
])
471481
else:
472482
logger.info("Authentication: unknown") # Should not happen..
473-
errors.append([" Unknown authentication method %s.", self.authmethod])
483+
errors.append([" Unknown authentication method \"%s\".", self.authmethod]),
484+
errors.append([
485+
" Set \"authmethod\" in %s to \"BASIC\" for username/password login, or to \"CERT\" for authentication"
486+
" with a certificate.",
487+
self.config.conf
488+
])
474489
logger.info("")
475490

476491
if errors:
@@ -485,28 +500,45 @@ def _test_auth_config(self):
485500
def _test_url_config(self):
486501
logger.info("URL configuration:")
487502

488-
urls = [("Base", self.base_url)]
503+
urls = [(
504+
"Base",
505+
self.base_url,
506+
[" Check \"base_url\" in %s.", self.config.conf]
507+
)]
489508
if self.proxies:
490509
for proxy_protocol, proxy_url in self.proxies.items():
491510
proxy_description = "{} proxy".format(proxy_protocol.upper())
492-
urls.append((proxy_description, proxy_url))
511+
urls.append((
512+
proxy_description,
513+
proxy_url,
514+
[
515+
" Check \"proxy\" in %s and \"%s_proxy\" environment value.",
516+
self.config.conf,
517+
proxy_protocol
518+
]
519+
))
493520

494521
errors = []
495-
for description, url in urls:
522+
for description, url, error_message in urls:
523+
url_errors = []
496524
try:
497525
parsed = urllib3.util.url.parse_url(url)
498526
except urllib3.exceptions.LocationParseError:
499527
logger.error(" %s URL: %s (INVALID!)", description, url)
500-
errors.append((" INVALID %s URL.", description))
528+
url_errors.append([" INVALID %s URL.", description])
501529
else:
502530
if not parsed.scheme:
503531
logger.error(" %s URL: %s (INCOMPLETE!)", description, url)
504-
errors.append((" Protocol MISSING in %s URL.", description))
532+
url_errors.append([" Protocol MISSING in %s URL.", description])
505533
elif not parsed.netloc:
506534
logger.error(" %s URL: %s (INCOMPLETE!)", description, url)
507-
errors.append((" Hostname MISSING in %s URL.", description))
535+
url_errors.append([" Hostname MISSING in %s URL.", description])
508536
else:
509537
logger.info(" %s URL: %s", description, url)
538+
539+
if url_errors:
540+
errors.extend(url_errors)
541+
errors.append(error_message)
510542
if not self.proxies:
511543
logger.info(" No proxy.")
512544
logger.info("")
@@ -597,7 +629,7 @@ def test_connection(self, rc=0):
597629
else:
598630
break # Cannot happen.
599631
else:
600-
logger.info(" See %s for more details." % self.config.logging_file)
632+
logger.info(" See %s or use --verbose for more details." % self.config.logging_file)
601633
logger.info("")
602634
return rc
603635

insights/tests/client/connection/test_test_connection.py

+52-28
Original file line numberDiff line numberDiff line change
@@ -850,7 +850,7 @@ def test_test_connection_print_success(test_urls, insights_connection, capsys):
850850
insights_connection.test_connection()
851851

852852
out, err = capsys.readouterr()
853-
assert out == "See {0} for more details.\n".format(LOGGING_FILE)
853+
assert out == "See {0} or use --verbose for more details.\n".format(LOGGING_FILE)
854854
assert not err
855855

856856

@@ -973,7 +973,7 @@ def test_test_connection_non_legacy_print_success(
973973
insights_connection.test_connection()
974974

975975
out, err = capsys.readouterr()
976-
assert out == "See {0} for more details.\n".format(LOGGING_FILE)
976+
assert out == "See {0} or use --verbose for more details.\n".format(LOGGING_FILE)
977977
assert not err
978978

979979

@@ -1110,7 +1110,7 @@ def test_test_connection_legacy_print_success(
11101110
insights_connection.test_connection()
11111111

11121112
out, err = capsys.readouterr()
1113-
assert out == "See {0} for more details.\n".format(LOGGING_FILE)
1113+
assert out == "See {0} or use --verbose for more details.\n".format(LOGGING_FILE)
11141114
assert not err
11151115

11161116

@@ -1127,7 +1127,7 @@ def test_test_connection_legacy_print_one_fail(
11271127
insights_connection.test_connection()
11281128

11291129
out, err = capsys.readouterr()
1130-
assert out == "{0}\nSee {1} for more details.\n".format(
1130+
assert out == "{0}\nSee {1} or use --verbose for more details.\n".format(
11311131
EXCEPTION_MESSAGE, LOGGING_FILE
11321132
)
11331133
assert not err
@@ -1196,6 +1196,12 @@ def test_test_connection_basic_auth_incomplete_credentials_log(
11961196
]
11971197
+ [(logging.ERROR, " {}.".format(error)) for error in errors]
11981198
+ [
1199+
(
1200+
logging.ERROR,
1201+
' Check "username" and "password" in {}.'.format(
1202+
insights_connection.config.conf
1203+
),
1204+
),
11991205
(logging.ERROR, ""),
12001206
]
12011207
)
@@ -1294,6 +1300,11 @@ def test_test_connection_basic_auth_incomplete_key_pair_log(
12941300
]
12951301
+ [(logging.ERROR, " {}".format(error)) for error in errors]
12961302
+ [
1303+
(
1304+
logging.ERROR,
1305+
' Re-register the system by running "subscription-manager unregister" and then '
1306+
'"subscription-manager register".',
1307+
),
12971308
(logging.ERROR, ""),
12981309
]
12991310
)
@@ -1310,15 +1321,18 @@ def test_test_connection_unknown_auth_log(insights_connection, caplog):
13101321
with caplog.at_level(logging.INFO):
13111322
insights_connection.test_connection()
13121323

1313-
messages = (
1314-
[
1315-
(logging.INFO, "Authentication: unknown"),
1316-
(logging.INFO, ""),
1317-
(logging.ERROR, "ERROR. Cannot authenticate:"),
1318-
(logging.ERROR, " Unknown authentication method INVALID."),
1319-
(logging.ERROR, ""),
1320-
]
1321-
)
1324+
messages = [
1325+
(logging.INFO, "Authentication: unknown"),
1326+
(logging.INFO, ""),
1327+
(logging.ERROR, "ERROR. Cannot authenticate:"),
1328+
(logging.ERROR, ' Unknown authentication method "INVALID".'),
1329+
(
1330+
logging.ERROR,
1331+
' Set "authmethod" in {} to "BASIC" for username/password login, or to "CERT" for authentication'
1332+
" with a certificate.".format(insights_connection.config.conf),
1333+
),
1334+
(logging.ERROR, ""),
1335+
]
13221336

13231337
record_tuples = [
13241338
("insights.client.connection", loglevel, message)
@@ -1446,17 +1460,27 @@ def test_test_connection_url_invalid_log(
14461460
insights_connection.test_connection()
14471461

14481462
errors = base_url_errors + proxy_url_errors
1449-
messages = (
1450-
[
1451-
(logging.INFO, "URL configuration:"),
1452-
(base_url_loglevel, " {}".format(base_url_line)),
1453-
(proxy_url_loglevel, " {}".format(proxy_url_line)),
1454-
(logging.INFO, ""),
1455-
(logging.ERROR, "ERROR. Invalid URL configuration:"),
1463+
messages = [
1464+
(logging.INFO, "URL configuration:"),
1465+
(base_url_loglevel, " {}".format(base_url_line)),
1466+
(proxy_url_loglevel, " {}".format(proxy_url_line)),
1467+
(logging.INFO, ""),
1468+
(logging.ERROR, "ERROR. Invalid URL configuration:"),
1469+
]
1470+
1471+
for url_errors, url_message in [
1472+
(base_url_errors, 'Check "base_url" in {}'),
1473+
(proxy_url_errors, 'Check "proxy" in {} and "https_proxy" environment value'),
1474+
]:
1475+
messages += [(logging.ERROR, " {}.".format(error)) for error in url_errors]
1476+
messages += [
1477+
(
1478+
logging.ERROR,
1479+
" {}.".format(url_message.format(insights_connection.config.conf)),
1480+
)
14561481
]
1457-
+ [(logging.ERROR, " {}.".format(error)) for error in errors]
1458-
+ [(logging.ERROR, "")]
1459-
)
1482+
1483+
messages += [(logging.ERROR, "")]
14601484

14611485
record_tuples = [
14621486
("insights.client.connection", loglevel, message)
@@ -1580,7 +1604,7 @@ def test_test_connection_urls_legacy_log(
15801604
" Testing {}".format(full_ping_url),
15811605
" SUCCESS.",
15821606
"",
1583-
" See {} for more details.".format(LOGGING_FILE),
1607+
" See {} or use --verbose for more details.".format(LOGGING_FILE),
15841608
"",
15851609
]
15861610
record_tuples = [
@@ -1641,7 +1665,7 @@ def test_test_connection_hostname_description_log(
16411665
" Testing {}".format(insights_connection.ping_url),
16421666
" SUCCESS.",
16431667
"",
1644-
" See {} for more details.".format(LOGGING_FILE),
1668+
" See {} or use --verbose for more details.".format(LOGGING_FILE),
16451669
"",
16461670
]
16471671
record_tuples = [
@@ -1690,7 +1714,7 @@ def test_test_connection_success_status_code_log(legacy_upload, status_code, cap
16901714
" Testing {}".format(connection.ping_url),
16911715
" SUCCESS.",
16921716
"",
1693-
" See {} for more details.".format(LOGGING_FILE),
1717+
" See {} or use --verbose for more details.".format(LOGGING_FILE),
16941718
"",
16951719
]
16961720
record_tuples = [
@@ -2722,7 +2746,7 @@ def test_test_connection_legacy_path_fallback_log(
27222746
(logging.INFO, " Testing https://www.example.com/"),
27232747
(logging.INFO, " SUCCESS."),
27242748
(logging.INFO, ""),
2725-
(logging.INFO, " See {} for more details.".format(LOGGING_FILE)),
2749+
(logging.INFO, " See {} or use --verbose for more details.".format(LOGGING_FILE)),
27262750
(logging.INFO, ""),
27272751
]
27282752
elif isinstance(side_effect[-1], requests.exceptions.ConnectionError):
@@ -2847,7 +2871,7 @@ def test_test_connection_legacy_path_fallback_return_value(
28472871
),
28482872
(logging.INFO, " SUCCESS."),
28492873
(logging.INFO, ""),
2850-
(logging.INFO, " See {} for more details.".format(LOGGING_FILE)),
2874+
(logging.INFO, " See {} or use --verbose for more details.".format(LOGGING_FILE)),
28512875
(logging.INFO, ""),
28522876
],
28532877
),

0 commit comments

Comments
 (0)