Repository navigation
fcli aviator: Fail fast with clear messages when commands cannot connect #1145
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
rsenden
merged 25 commits into
fortify:dev/v3.x
from
kireetivar:p/cdatla/aviator-connectivity-failure-handling
Oct 28, 2026
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
4485113
fix: Keep existing Aviator session and admin config when validation f…
kireetivar 18887fe
chore: Allow fcli exceptions to supply a safe DEBUG log description
kireetivar 8c2e7df
chore: Add Aviator connectivity failure classification
kireetivar c9a5e11
chore: Support fail-fast request policy in Aviator gRPC client
kireetivar 15c5660
feat: Fail fast with clear messages when Aviator commands cannot connect
kireetivar 17ff153
fix: Don't show credentials from the Aviator URL in messages and logs
kireetivar 5534a7c
chore: Simplify Aviator connectivity handling
kireetivar 8c13c04
chore: streamline Aviator connectivity failure handling
kireetivar efd73a1
chore: Remove the Aviator TLS proxy test and its checked-in key
kireetivar 052812e
chore: Don't attach transport causes to Aviator connectivity exceptions
kireetivar 34ff21a
fix: Show short single-line Aviator connectivity messages
kireetivar 16a68cc
chore: Put remediation before 'contact support' in Aviator gRPC errors
kireetivar ffdd743
chore: Sort imports in Aviator connectivity tests
kireetivar 5565017
chore: Clarify session replacement hook and test the inherited default
kireetivar 811b3aa
fix: Clarify Aviator connectivity failures and initialize test context
kireetivar 706b235
fix: Keep sensitive Aviator URL details out of messages and logs
kireetivar 004dafe
chore: Make Aviator diagnostic request policy explicit
kireetivar 6fe6862
chore: Document Aviator connectivity and session contracts
kireetivar 7864e14
fix: Avoid assuming local deadline expiry for Aviator timeouts
kireetivar 1dbb5b8
chore: Remove unrelated Aviator request call wrapping
kireetivar 4febc64
docs: Remove misplaced Aviator repository usage section
kireetivar 2c14112
refactor: Require an explicit Aviator unary request policy
kireetivar 27db567
fix: Bound Aviator stream initialization to 30 seconds
kireetivar 10cadf6
fix(aviator): add stream initialization timeout that cancels the RPC …
kireetivar 1e28582
docs: Restore trailing blank line in repo-usage.md
kireetivar File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
27 changes: 27 additions & 0 deletions
27
...src/main/java/com/fortify/cli/aviator/_common/exception/AviatorConnectivityException.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| /* | ||
| * Copyright 2021-2026 Open Text. | ||
| * | ||
| * The only warranties for products and services of Open Text | ||
| * and its affiliates and licensors ("Open Text") are as may | ||
| * be set forth in the express warranty statements accompanying | ||
| * such products and services. Nothing herein should be construed | ||
| * as constituting an additional warranty. Open Text shall not be | ||
| * liable for technical or editorial errors or omissions contained | ||
| * herein. The information contained herein is subject to change | ||
| * without notice. | ||
| */ | ||
| package com.fortify.cli.aviator._common.exception; | ||
|
|
||
| /** | ||
| * Aviator connection failure with a message that is safe to show. | ||
| * <p> | ||
| * The transport cause is not attached: its text can contain proxy credentials or | ||
| * host details, and fcli prints and logs the cause chain of simple exceptions. | ||
| */ | ||
| public final class AviatorConnectivityException extends AviatorSimpleException { | ||
| private static final long serialVersionUID = 1L; | ||
|
|
||
| public AviatorConnectivityException(String message) { | ||
| super(message); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
44 changes: 44 additions & 0 deletions
44
...i-aviator-common/src/main/java/com/fortify/cli/aviator/grpc/AviatorConnectionContext.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| /* | ||
| * Copyright 2021-2026 Open Text. | ||
| * | ||
| * The only warranties for products and services of Open Text | ||
| * and its affiliates and licensors ("Open Text") are as may | ||
| * be set forth in the express warranty statements accompanying | ||
| * such products and services. Nothing herein should be construed | ||
| * as constituting an additional warranty. Open Text shall not be | ||
| * liable for technical or editorial errors or omissions contained | ||
| * herein. The information contained herein is subject to change | ||
| * without notice. | ||
| */ | ||
| package com.fortify.cli.aviator.grpc; | ||
|
|
||
| import java.util.Objects; | ||
|
|
||
| import com.fortify.cli.aviator.grpc.AviatorGrpcClientHelper.AviatorConnectionPlan; | ||
|
|
||
| import io.grpc.CallOptions; | ||
|
|
||
| /** | ||
| * Endpoints retained for error messages, without proxy credentials. | ||
| * @param proxy null for a direct connection | ||
| */ | ||
| record AviatorConnectionContext(AviatorEndpoint target, AviatorEndpoint proxy) { | ||
| // Present only on fail-fast unary stubs; absence preserves the existing error mapping. | ||
| static final CallOptions.Key<AviatorConnectionContext> CALL_OPTION = CallOptions.Key.create("aviator-connectivity"); | ||
|
|
||
| AviatorConnectionContext { | ||
| Objects.requireNonNull(target, "target"); | ||
| } | ||
|
|
||
| static AviatorConnectionContext of(AviatorConnectionPlan plan) { | ||
| var target = new AviatorEndpoint(plan.target().host(), plan.effectivePort()); | ||
| var proxy = plan.proxyDescriptor() | ||
| .map(d -> new AviatorEndpoint(d.getProxyHost(), d.getProxyPort())) | ||
| .orElse(null); | ||
| return new AviatorConnectionContext(target, proxy); | ||
| } | ||
|
|
||
| boolean isProxied() { | ||
| return proxy!=null; | ||
| } | ||
| } |
124 changes: 124 additions & 0 deletions
124
...tor-common/src/main/java/com/fortify/cli/aviator/grpc/AviatorConnectivityFailureKind.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| /* | ||
| * Copyright 2021-2026 Open Text. | ||
| * | ||
| * The only warranties for products and services of Open Text | ||
| * and its affiliates and licensors ("Open Text") are as may | ||
| * be set forth in the express warranty statements accompanying | ||
| * such products and services. Nothing herein should be construed | ||
| * as constituting an additional warranty. Open Text shall not be | ||
| * liable for technical or editorial errors or omissions contained | ||
| * herein. The information contained herein is subject to change | ||
| * without notice. | ||
| */ | ||
| package com.fortify.cli.aviator.grpc; | ||
|
|
||
| import java.net.ConnectException; | ||
| import java.net.UnknownHostException; | ||
| import java.nio.channels.UnresolvedAddressException; | ||
| import java.security.cert.CertPathValidatorException; | ||
| import java.security.cert.CertificateException; | ||
| import java.util.Optional; | ||
|
|
||
| import javax.net.ssl.SSLException; | ||
|
|
||
| import org.apache.commons.lang3.exception.ExceptionUtils; | ||
|
|
||
| import io.grpc.Status; | ||
| import io.grpc.StatusRuntimeException; | ||
| import io.grpc.netty.shaded.io.netty.channel.ConnectTimeoutException; | ||
| import io.grpc.netty.shaded.io.netty.handler.proxy.HttpProxyHandler.HttpProxyConnectException; | ||
| import io.grpc.netty.shaded.io.netty.handler.proxy.ProxyConnectException; | ||
|
|
||
| /** Shared classification and messages for ordinary unary request failures. */ | ||
| enum AviatorConnectivityFailureKind { | ||
| PROXY_REJECTED, | ||
| PROXY_NEGOTIATION_FAILED, | ||
| CONNECT_TIMEOUT, | ||
| PROXY_REFUSED, | ||
| DIRECT_REFUSED, | ||
| DNS_FAILURE, | ||
| TLS_FAILURE, | ||
| UNAVAILABLE, | ||
| DEADLINE_EXCEEDED; | ||
|
|
||
| /** | ||
| * Leaves certificate failures to the fcli PKIX trust-store guidance, and application | ||
| * statuses to the existing exception mapping. | ||
| */ | ||
| static Optional<AviatorConnectivityFailureKind> classify(StatusRuntimeException e, AviatorConnectionContext context) { | ||
| var code = e.getStatus().getCode(); | ||
| if ( code!=Status.Code.UNAVAILABLE && code!=Status.Code.DEADLINE_EXCEEDED ) { | ||
|
kireetivar marked this conversation as resolved.
|
||
| return Optional.empty(); | ||
| } | ||
| if ( isCertificateFailure(e) ) { | ||
| return Optional.empty(); | ||
| } | ||
| if ( has(e, SSLException.class) ) { | ||
| return Optional.of(TLS_FAILURE); | ||
| } | ||
| return Optional.of(classifyTransport(e, context, code)); | ||
| } | ||
|
|
||
| // JDK PKIX and host name failures always carry one of these causes; other | ||
| // handshake failures, such as timeouts or protocol mismatches, are TLS_FAILURE. | ||
| private static boolean isCertificateFailure(Throwable e) { | ||
| return has(e, CertificateException.class) || has(e, CertPathValidatorException.class); | ||
| } | ||
|
|
||
| // The order is significant: all Netty connect and proxy exceptions extend ConnectException. | ||
| private static AviatorConnectivityFailureKind classifyTransport(Throwable e, AviatorConnectionContext context, Status.Code code) { | ||
| var neutral = code==Status.Code.DEADLINE_EXCEEDED ? DEADLINE_EXCEEDED : UNAVAILABLE; | ||
| if ( has(e, ProxyConnectException.class) ) { | ||
| // A JVM or GRPC_PROXY_EXP proxy can be active without an fcli proxy; fcli cannot name it. | ||
| if ( !context.isProxied() ) { return neutral; } | ||
| return has(e, HttpProxyConnectException.class) ? PROXY_REJECTED : PROXY_NEGOTIATION_FAILED; | ||
| } | ||
| if ( has(e, ConnectTimeoutException.class) ) { return CONNECT_TIMEOUT; } | ||
| // With an HTTP CONNECT proxy, the proxy is the only TCP peer of the client; | ||
| // a target refusal arrives as HttpProxyConnectException instead. | ||
| if ( isConnectionRefused(e) ) { return context.isProxied() ? PROXY_REFUSED : DIRECT_REFUSED; } | ||
| if ( has(e, UnknownHostException.class) || has(e, UnresolvedAddressException.class) ) { return DNS_FAILURE; } | ||
| return neutral; | ||
| } | ||
|
|
||
| // Java has no refusal-specific exception type. Match complete known reasons, never | ||
| // substrings that could come from a host name; unknown/localized reasons stay neutral. | ||
| private static boolean isConnectionRefused(Throwable e) { | ||
|
kireetivar marked this conversation as resolved.
|
||
| return ExceptionUtils.getThrowableList(e).stream() | ||
| .filter(ConnectException.class::isInstance) | ||
| .map(Throwable::getMessage) | ||
| .anyMatch(message -> "Connection refused".equalsIgnoreCase(message) | ||
| || "Connection refused: getsockopt".equalsIgnoreCase(message) | ||
| || "Connection refused: no further information".equalsIgnoreCase(message) | ||
| || "connect(..) failed: Connection refused".equalsIgnoreCase(message) | ||
| || "finishConnect(..) failed: Connection refused".equalsIgnoreCase(message)); | ||
| } | ||
|
|
||
| private static boolean has(Throwable e, Class<? extends Throwable> type) { | ||
| return ExceptionUtils.indexOfType(e, type) >= 0; | ||
| } | ||
|
|
||
| String formatMessage(AviatorConnectionContext context) { | ||
| var server = "Aviator server " + context.target().display(); | ||
| var proxy = context.isProxied() ? "proxy " + context.proxy().display() : null; | ||
| return switch (this) { | ||
| case PROXY_REJECTED -> "Proxy " + context.proxy().display() + " rejected the connection to " + server | ||
| + "; check the proxy credentials and access rules"; | ||
| case PROXY_NEGOTIATION_FAILED -> "Could not connect to " + server + " through " + proxy | ||
| + "; check that the proxy allows HTTPS connections to the Aviator server"; | ||
| case CONNECT_TIMEOUT -> "Timed out connecting to " + (proxy==null ? server : proxy) | ||
| + "; check network access and firewall rules"; | ||
| case PROXY_REFUSED -> "Proxy " + context.proxy().display() + " refused the connection; check the proxy host and port"; | ||
| case DIRECT_REFUSED -> server + " refused the connection; check the Aviator URL"; | ||
| case DNS_FAILURE -> proxy==null | ||
| ? "Could not resolve the host name of " + server + "; check the Aviator URL" | ||
| : "Could not resolve the host name of " + proxy + " or " + server + "; check the proxy settings and the Aviator URL"; | ||
| case TLS_FAILURE -> "Could not set up a TLS connection to " + server | ||
| + "; check that the Aviator URL uses the HTTPS port of the Aviator server"; | ||
| case UNAVAILABLE -> server + " is unavailable or could not be reached" | ||
| + "; try again later, or check the Aviator URL, network access, and proxy settings"; | ||
| case DEADLINE_EXCEEDED -> "The Aviator request timed out; try again later" | ||
| + " (the server may be busy, or network/proxy delays may be involved)"; | ||
| }; | ||
| } | ||
| } | ||
36 changes: 36 additions & 0 deletions
36
...-core/fcli-aviator-common/src/main/java/com/fortify/cli/aviator/grpc/AviatorEndpoint.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| /* | ||
| * Copyright 2021-2026 Open Text. | ||
| * | ||
| * The only warranties for products and services of Open Text | ||
| * and its affiliates and licensors ("Open Text") are as may | ||
| * be set forth in the express warranty statements accompanying | ||
| * such products and services. Nothing herein should be construed | ||
| * as constituting an additional warranty. Open Text shall not be | ||
| * liable for technical or editorial errors or omissions contained | ||
| * herein. The information contained herein is subject to change | ||
| * without notice. | ||
| */ | ||
| package com.fortify.cli.aviator.grpc; | ||
|
|
||
| /** Replaces control characters in host names before display. */ | ||
| record AviatorEndpoint(String host, int port) { | ||
| private static final String UNKNOWN_HOST = "unknown"; | ||
|
|
||
| AviatorEndpoint { | ||
| host = sanitize(host); | ||
| } | ||
|
|
||
| String display() { | ||
| var isUnbracketedIpv6 = host.contains(":") && !host.startsWith("["); | ||
| return isUnbracketedIpv6 ? "[" + host + "]:" + port : host + ":" + port; | ||
| } | ||
|
|
||
| private static String sanitize(String host) { | ||
| if ( host==null || host.isBlank() ) { | ||
| return UNKNOWN_HOST; | ||
| } | ||
| var result = new StringBuilder(host.length()); | ||
| host.codePoints().forEach(c -> result.appendCodePoint(Character.isISOControl(c) ? '?' : c)); | ||
| return result.toString(); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.