Skip to content

Commit 44e9f3f

Browse files
committed
Add backplane subcommand to do health check of VPN and proxy connectivity
Fixing golang-lint issue Refactor the HealthCheckCmd to move the logic in the Run to a separate func Revert and keep proxyURL field only Refactored code based on the review feedback Added test cases Added some more test cases to improve the coverage Move the main logic to pkg and generate mock files via mockgen Removed the default value of backplane configuration and added debug log Add debug log for cloud console Updated README.md with the healthcheck usage Add utun as for checking VPN connectivity in MacOS
1 parent 272df9a commit 44e9f3f

File tree

15 files changed

+766
-9
lines changed

15 files changed

+766
-9
lines changed

Makefile

+2
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,8 @@ mock-gen:
116116
mockgen -destination=./pkg/utils/mocks/shellCheckerMock.go -package=mocks github.com/openshift/backplane-cli/pkg/utils ShellCheckerInterface
117117
mockgen -destination=./pkg/pagerduty/mocks/clientMock.go -package=mocks github.com/openshift/backplane-cli/pkg/pagerduty PagerDutyClient
118118
mockgen -destination=./pkg/jira/mocks/jiraMock.go -package=mocks github.com/openshift/backplane-cli/pkg/jira IssueServiceInterface
119+
mockgen -destination=./pkg/healthcheck/mocks/networkMock.go -package=mocks github.com/openshift/backplane-cli/pkg/healthcheck NetworkInterface
120+
mockgen -destination=./pkg/healthcheck/mocks/httpClientMock.go -package=mocks github.com/openshift/backplane-cli/pkg/healthcheck HTTPClient
119121

120122
.PHONY: build-image
121123
build-image:

README.md

+54-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ To setup the PS1(prompt) for bash/zsh, please follow [these instructions](https:
6565
| `ocm backplane cloud console` | Launch the current logged in cluster's cloud provider console |
6666
| `ocm backplane cloud credentials [flags]` | Retrieve a set of temporary cloud credentials for the cluster's cloud provider |
6767
| `ocm backplane elevate <reason> -- <command>` | Elevate privileges to backplane-cluster-admin and add a reason to the api request, this reason will be stored for 20min for future usage |
68-
| `ocm backplane monitoring <prometheus/alertmanager/thanos/grafana> [flags]` | Launch the specified monitoring UI (Deprecated following v4.11 for cluster monitoring stack) |
68+
| `ocm backplane monitoring <prometheus/alertmanager/thanos/grafana> [flags]` | Launch the specified monitoring UI (Deprecated following v4.11 for cluster monitoring stack)|
6969
| `ocm backplane script describe <script> [flags]` | Describe the given backplane script |
7070
| `ocm backplane script list [flags]` | List available backplane scripts |
7171
| `ocm backplane session [flags]` | Create a new session and log into the cluster |
@@ -81,6 +81,7 @@ To setup the PS1(prompt) for bash/zsh, please follow [these instructions](https:
8181
| `ocm backplane testJob logs <job_name> [flags]` | Retrieve logs of the specified test job resource |
8282
| `ocm backplane upgrade` | Upgrade backplane-cli to the latest version |
8383
| `ocm backplane version` | Display the installed backplane-cli version |
84+
| `ocm backplane healthcheck` | Check the VPN and Proxy connectivity on the host network when experiencing isssues accessing the backplane API|
8485

8586
## Login
8687

@@ -325,6 +326,58 @@ No issue if only stdout is redirected.
325326
$ ocm-backplane elevate -n -- get secret xxx | grep xxx
326327
Please enter a reason for elevation, it will be stored in current context for 20 minutes : <here you can enter your reason>
327328
```
329+
## Backplane healthcheck
330+
The backplane health check can be used to verify VPN and proxy connectivity on the host network as a troubleshooting approach when experiencing issues accessing the backplane API.
331+
332+
### Pre-settings
333+
The end-user needs to set the VPN and Proxy check-endpoints in the local backplane configuration first:
334+
```
335+
cat ~/.config/backplane/config.json
336+
{
337+
"proxy-url": ["http://proxy1.example.com:3128", "http://proxy2.example.com:3128"],
338+
"vpn-check-endpoint": "http://your-vpn-endpoint.example.com",
339+
"proxy-check-endpoint": "http://your-proxy-endpoint.example.com"
340+
}
341+
```
342+
- `vpn-check-endpoint:` To specify this test endpoint to check if it can be accessed with the currently connected VPN.
343+
- `proxy-check-endpoint:` To specify this test endpoint to check if it can be accssed with the currently working proxy.
344+
345+
**NOTE:** The `vpn-check-endpoint` and `proxy-check-endpoint` mentioned above are just examples, the end-user can customize them as needed.
346+
347+
### How to use it
348+
- Running healthcheck by default
349+
```
350+
./ocm-backplane healthcheck
351+
Checking VPN connectivity...
352+
VPN connectivity check passed!
353+
354+
Checking proxy connectivity...
355+
Getting the working proxy URL ['http://proxy1.example.com:3128'] from local backplane configuration.
356+
Testing connectivity to the pre-defined test endpoint ['https://your-proxy-endpoint.example.com'] with the proxy.
357+
Proxy connectivity check passed!
358+
359+
Checking backplane API connectivity...
360+
Successfully connected to the backplane API!
361+
Backplane API connectivity check passed!
362+
```
363+
- Specify the healthcheck flags to run `vpn` or `proxy` check only
364+
```
365+
./ocm-backplane healthcheck --vpn
366+
Checking VPN connectivity...
367+
VPN connectivity check passed!
368+
369+
./ocm-backplane healthcheck --proxy
370+
Checking proxy connectivity...
371+
Proxy connectivity check passed!
372+
```
373+
374+
**Note:** VPN connection check is a pre-requisite (The example below demonstrates checking proxy connectivity with VPN disconnected.)
375+
```
376+
./ocm-backplane healthcheck --proxy
377+
WARN[0000] No VPN interfaces found: [tun tap ppp wg]
378+
VPN connectivity check failed: No VPN interfaces found: [tun tap ppp wg]
379+
Note: Proxy connectivity check requires VPN to be connected. Please ensure VPN is connected and try again.
380+
```
328381
329382
## Promotion/Release cycle of backplane CLI
330383
Backplane CLI has a default release cycle of every 2 weeks

cmd/ocm-backplane/cloud/console.go

+7
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,13 @@ func runConsole(cmd *cobra.Command, argv []string) (err error) {
143143
// ======== Get cloud console from backplane API ============
144144
consoleResponse, err := queryConfig.GetCloudConsole()
145145
if err != nil {
146+
// Check API connection with configured proxy
147+
if connErr := backplaneConfiguration.CheckAPIConnection(); connErr != nil {
148+
logger.Error("Cannot connect to backplane API URL, check if you need to use a proxy/VPN to access backplane:")
149+
logger.Errorf("Error: %v", connErr)
150+
logger.Info("To troubleshoot connectivity issues, please run the following command:")
151+
logger.Info("ocm-backplane health-check")
152+
}
146153
return fmt.Errorf("failed to get cloud console for cluster %v: %w", clusterID, err)
147154
}
148155

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package healthcheck
2+
3+
import (
4+
"github.com/openshift/backplane-cli/pkg/healthcheck"
5+
"github.com/spf13/cobra"
6+
)
7+
8+
var (
9+
checkVPN bool
10+
checkProxy bool
11+
)
12+
13+
// HealthCheckCmd is the command for performing health checks
14+
var HealthCheckCmd = &cobra.Command{
15+
Use: "healthcheck",
16+
Aliases: []string{"healthCheck", "health-check", "healthchecks"},
17+
Short: "Check VPN and Proxy connectivity on the localhost",
18+
Run: func(cmd *cobra.Command, args []string) {
19+
healthcheck.RunHealthCheck(checkVPN, checkProxy)(cmd, args)
20+
},
21+
}
22+
23+
func init() {
24+
HealthCheckCmd.Flags().BoolVar(&checkVPN, "vpn", false, "Check only VPN connectivity")
25+
HealthCheckCmd.Flags().BoolVar(&checkProxy, "proxy", false, "Check only Proxy connectivity")
26+
}

cmd/ocm-backplane/login/login.go

+7-3
Original file line numberDiff line numberDiff line change
@@ -305,14 +305,18 @@ func runLogin(cmd *cobra.Command, argv []string) (err error) {
305305
// Hibernating, print an error
306306
return fmt.Errorf("cluster %s is hibernating, login failed", clusterKey)
307307
}
308+
308309
// Check API connection with configured proxy
309310
if connErr := bpConfig.CheckAPIConnection(); connErr != nil {
310-
return fmt.Errorf("cannot connect to backplane API URL, check if you need to use a proxy/VPN to access backplane: %v", connErr)
311+
logger.Errorf("Cannot connect to backplane API URL, check if you need to use a proxy/VPN to access backplane: %v. To troubleshoot connectivity issues, please run the following command: ocm-backplane health-check", connErr)
312+
return fmt.Errorf("cannot connect to backplane API URL: %v", connErr)
311313
}
312314

313-
// Otherwise, return the failure
314-
return fmt.Errorf("can't login to cluster: %v", err)
315+
// Log suggestion to run connectivity health check if login fails
316+
logger.Errorf("Login failed: %v. To troubleshoot connectivity issues, please run the following command: ocm-backplane health-check", err)
317+
return fmt.Errorf("login failed: %v", err)
315318
}
319+
316320
logger.WithField("URL", bpAPIClusterURL).Debugln("Proxy")
317321

318322
logger.Debugln("Generating a new K8s cluster config file")

cmd/ocm-backplane/root.go

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import (
2727
"github.com/openshift/backplane-cli/cmd/ocm-backplane/config"
2828
"github.com/openshift/backplane-cli/cmd/ocm-backplane/console"
2929
"github.com/openshift/backplane-cli/cmd/ocm-backplane/elevate"
30+
healthcheck "github.com/openshift/backplane-cli/cmd/ocm-backplane/healthcheck"
3031
"github.com/openshift/backplane-cli/cmd/ocm-backplane/login"
3132
"github.com/openshift/backplane-cli/cmd/ocm-backplane/logout"
3233
managedjob "github.com/openshift/backplane-cli/cmd/ocm-backplane/managedJob"
@@ -80,4 +81,5 @@ func init() {
8081
rootCmd.AddCommand(upgrade.UpgradeCmd)
8182
rootCmd.AddCommand(version.VersionCmd)
8283
rootCmd.AddCommand(monitoring.MonitoringCmd)
84+
rootCmd.AddCommand(healthcheck.HealthCheckCmd)
8385
}

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ require (
2525
github.com/spf13/cobra v1.8.1
2626
github.com/spf13/pflag v1.0.5
2727
github.com/spf13/viper v1.19.0
28+
github.com/trivago/tgo v1.0.7
2829
golang.org/x/term v0.22.0
2930
gopkg.in/AlecAivazis/survey.v1 v1.8.8
3031
k8s.io/api v0.28.3
@@ -123,7 +124,6 @@ require (
123124
github.com/spf13/afero v1.11.0 // indirect
124125
github.com/spf13/cast v1.6.0 // indirect
125126
github.com/subosito/gotenv v1.6.0 // indirect
126-
github.com/trivago/tgo v1.0.7 // indirect
127127
github.com/xlab/treeprint v1.2.0 // indirect
128128
github.com/zalando/go-keyring v0.2.3 // indirect
129129
go.starlark.net v0.0.0-20230525235612-a134d8f9ddca // indirect

pkg/cli/config/config.go

+9-4
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ type BackplaneConfiguration struct {
4040
JiraBaseURL string `json:"jira-base-url"`
4141
JiraToken string `json:"jira-token"`
4242
JiraConfigForAccessRequests AccessRequestsJiraConfiguration `json:"jira-config-for-access-requests"`
43+
VPNCheckEndpoint string `json:"vpn-check-endpoint"`
44+
ProxyCheckEndpoint string `json:"proxy-check-endpoint"`
4345
}
4446

4547
const (
@@ -57,12 +59,12 @@ var JiraConfigForAccessRequestsDefaultValue = AccessRequestsJiraConfiguration{
5759
ProdProject: "OHSS",
5860
ProdIssueType: "Incident",
5961
ProjectToTransitionsNames: map[string]JiraTransitionsNamesForAccessRequests{
60-
"SDAINT": JiraTransitionsNamesForAccessRequests{
62+
"SDAINT": {
6163
OnCreation: "In Progress",
6264
OnApproval: "In Progress",
6365
OnError: "Closed",
6466
},
65-
"OHSS": JiraTransitionsNamesForAccessRequests{
67+
"OHSS": {
6668
OnCreation: "Pending Customer",
6769
OnApproval: "New",
6870
OnError: "Cancelled",
@@ -113,7 +115,6 @@ func GetBackplaneConfiguration() (bpConfig BackplaneConfiguration, err error) {
113115
}
114116

115117
if err = validateConfig(); err != nil {
116-
// FIXME: we should return instead of warning here, after the tests do not require external network access
117118
logger.Warn(err)
118119
}
119120

@@ -131,7 +132,7 @@ func GetBackplaneConfiguration() (bpConfig BackplaneConfiguration, err error) {
131132
// Warn if user has explicitly defined backplane URL via env
132133
url, ok := getBackplaneEnv(info.BackplaneURLEnvName)
133134
if ok {
134-
logger.Warn(fmt.Printf("Manual URL configuration is deprecated, please unset the environment %s", info.BackplaneURLEnvName))
135+
logger.Warn(fmt.Sprintf("Manual URL configuration is deprecated, please unset the environment %s", info.BackplaneURLEnvName))
135136
bpConfig.URL = url
136137
} else {
137138
// Fetch backplane URL from ocm env
@@ -180,6 +181,10 @@ func GetBackplaneConfiguration() (bpConfig BackplaneConfiguration, err error) {
180181
}
181182
}
182183

184+
// Load VPN and Proxy check endpoints from the local backplane configuration file
185+
bpConfig.VPNCheckEndpoint = viper.GetString("vpn-check-endpoint")
186+
bpConfig.ProxyCheckEndpoint = viper.GetString("proxy-check-endpoint")
187+
183188
return bpConfig, nil
184189
}
185190

pkg/healthcheck/check_proxy.go

+70
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package healthcheck
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
"net/url"
7+
8+
logger "github.com/sirupsen/logrus"
9+
)
10+
11+
// CheckProxyConnectivity checks the proxy connectivity
12+
func CheckProxyConnectivity(client HTTPClient) (string, error) {
13+
bpConfig, err := getConfigFunc()
14+
if err != nil {
15+
logger.Errorf("Failed to get backplane configuration: %v", err)
16+
return "", fmt.Errorf("failed to get backplane configuration: %v", err)
17+
}
18+
19+
proxyURL := bpConfig.ProxyURL
20+
if proxyURL == nil || *proxyURL == "" {
21+
errMsg := "no proxy URL configured in backplane configuration"
22+
logger.Warn(errMsg)
23+
return "", fmt.Errorf(errMsg)
24+
}
25+
26+
logger.Infof("Getting the working proxy URL ['%s'] from local backplane configuration.", *proxyURL)
27+
28+
proxy, err := url.Parse(*proxyURL)
29+
if err != nil {
30+
logger.Errorf("Invalid proxy URL: %v", err)
31+
return "", fmt.Errorf("invalid proxy URL: %v", err)
32+
}
33+
34+
httpClientWithProxy := &DefaultHTTPClient{
35+
Client: &http.Client{
36+
Transport: &http.Transport{
37+
Proxy: http.ProxyURL(proxy),
38+
},
39+
},
40+
}
41+
42+
proxyTestEndpoint, err := GetProxyTestEndpointFunc()
43+
if err != nil {
44+
logger.Errorf("Failed to get proxy test endpoint: %v", err)
45+
return "", err
46+
}
47+
48+
logger.Infof("Testing connectivity to the pre-defined test endpoint ['%s'] with the proxy.", proxyTestEndpoint)
49+
if err := testEndPointConnectivity(proxyTestEndpoint, httpClientWithProxy); err != nil {
50+
errMsg := fmt.Sprintf("Failed to access target endpoint ['%s'] with the proxy: %v", proxyTestEndpoint, err)
51+
logger.Errorf(errMsg)
52+
return "", fmt.Errorf(errMsg)
53+
}
54+
55+
return *proxyURL, nil
56+
}
57+
58+
func GetProxyTestEndpoint() (string, error) {
59+
bpConfig, err := getConfigFunc()
60+
if err != nil {
61+
logger.Errorf("Failed to get backplane configuration: %v", err)
62+
return "", fmt.Errorf("failed to get backplane configuration: %v", err)
63+
}
64+
if bpConfig.ProxyCheckEndpoint == "" {
65+
errMsg := "proxy test endpoint not configured"
66+
logger.Warn(errMsg)
67+
return "", fmt.Errorf(errMsg)
68+
}
69+
return bpConfig.ProxyCheckEndpoint, nil
70+
}

0 commit comments

Comments
 (0)