Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 68 additions & 7 deletions cmd/network/verification.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ type EgressVerification struct {
Namespace string
// SkipServiceLog disables automatic service log prompting on verification failures
SkipServiceLog bool
// hiveOcmUrl is the OCM environment URL for Hive operations (Classic clusters only)
hiveOcmUrl string
}

func NewCmdValidateEgress() *cobra.Command {
Expand Down Expand Up @@ -165,6 +167,13 @@ func NewCmdValidateEgress() *cobra.Command {
# Run network verification without sending service logs on failure
osdctl network verify-egress --cluster-id my-rosa-cluster --skip-service-log

# For a classic cluster that needs automatic proxy CA-bundle retrieval,
# target staging OCM while querying Hive from production
# (Note: --hive-ocm-url only applies to Hive-backed CA-bundle lookup)
export OCM_URL=staging
ocm login
osdctl network verify-egress --cluster-id my-staging-cluster --hive-ocm-url production

# (Not recommended) Run against a specific VPC, without specifying cluster-id
<export environment variables like AWS_ACCESS_KEY_ID or use aws configure>
osdctl network verify-egress --subnet-id subnet-abcdefg123 --security-group sg-abcdefgh123 --region us-east-1`,
Expand Down Expand Up @@ -195,6 +204,7 @@ func NewCmdValidateEgress() *cobra.Command {
validateEgressCmd.Flags().StringVar(&e.KubeConfig, "kubeconfig", "", "(optional) path to kubeconfig file for pod mode (uses default kubeconfig if not specified)")
validateEgressCmd.Flags().StringVar(&e.Namespace, "namespace", "openshift-network-diagnostics", "(optional) Kubernetes namespace to run verification pods in")
validateEgressCmd.Flags().BoolVar(&e.SkipServiceLog, "skip-service-log", false, "(optional) disable automatic service log sending when verification fails")
validateEgressCmd.Flags().StringVar(&e.hiveOcmUrl, "hive-ocm-url", "", "(optional) OCM environment URL for hive operations. Aliases: 'production', 'staging', 'integration'. If not specified, uses the same OCM environment as the target cluster.")

return validateEgressCmd
}
Expand Down Expand Up @@ -434,6 +444,54 @@ func selectMostRecentCaBundleConfigMap(configMaps []corev1.ConfigMap) (string, e
return "", fmt.Errorf("%s data not found in the ConfigMap %s", caBundleConfigMapKey, foundCM.Name)
}

// getHiveClient resolves the Hive cluster and builds a Kubernetes client for it.
// It handles both single-environment and multi-environment OCM modes.
func (e *EgressVerification) getHiveClient(ctx context.Context, scheme *runtime.Scheme) (*cmv1.Cluster, client.Client, error) {
if e.hiveOcmUrl != "" {
// Multi-environment path - enables staging/integration testing
targetOCM, err := utils.CreateConnection()
if err != nil {
return nil, nil, fmt.Errorf("failed to create target OCM connection: %w", err)
}
defer targetOCM.Close()

hiveOCM, err := utils.CreateConnectionWithUrl(e.hiveOcmUrl)
if err != nil {
return nil, nil, fmt.Errorf("failed to create hive OCM connection with URL '%s': %w", e.hiveOcmUrl, err)
}
defer hiveOCM.Close()

e.log.Debug(ctx, "using multi-environment OCM: target cluster OCM and hive OCM URL '%s'", e.hiveOcmUrl)

hive, err := utils.GetHiveClusterWithConn(e.cluster.ID(), targetOCM, hiveOCM)
if err != nil {
return nil, nil, fmt.Errorf("failed to get hive cluster (OCM URL:'%s'): %w", e.hiveOcmUrl, err)
}

e.log.Debug(ctx, "assembling K8s client for %s (%s)", hive.ID(), hive.Name())
hc, err := k8s.NewWithConn(hive.ID(), client.Options{Scheme: scheme}, hiveOCM)
if err != nil {
return nil, nil, fmt.Errorf("failed to create hive k8s client (OCM URL:'%s'): %w", e.hiveOcmUrl, err)
}

return hive, hc, nil
}

// Single-environment path - backward compatible
hive, err := utils.GetHiveCluster(e.cluster.ID())
if err != nil {
return nil, nil, err
}

e.log.Debug(ctx, "assembling K8s client for %s (%s)", hive.ID(), hive.Name())
hc, err := k8s.New(hive.ID(), client.Options{Scheme: scheme})
if err != nil {
return nil, nil, err
}

return hive, hc, nil
}

func (e *EgressVerification) getCaBundleFromHive(ctx context.Context) (string, error) {
scheme := runtime.NewScheme()
if err := corev1.AddToScheme(scheme); err != nil {
Expand All @@ -444,13 +502,7 @@ func (e *EgressVerification) getCaBundleFromHive(ctx context.Context) (string, e
return "", err
}

hive, err := utils.GetHiveCluster(e.cluster.ID())
if err != nil {
return "", err
}

e.log.Debug(ctx, "assembling K8s client for %s (%s)", hive.ID(), hive.Name())
hc, err := k8s.New(hive.ID(), client.Options{Scheme: scheme})
_, hc, err := e.getHiveClient(ctx, scheme)
if err != nil {
return "", err
}
Expand Down Expand Up @@ -624,6 +676,15 @@ func (e *EgressVerification) validateInput() error {
}
}

// Validate and resolve --hive-ocm-url if provided
if e.hiveOcmUrl != "" {
resolvedUrl, err := utils.ValidateAndResolveOcmUrl(e.hiveOcmUrl)
if err != nil {
return fmt.Errorf("invalid --hive-ocm-url: %w", err)
}
e.hiveOcmUrl = resolvedUrl
}

return nil
}

Expand Down
104 changes: 104 additions & 0 deletions cmd/network/verification_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"fmt"
"reflect"
"strings"
"testing"
"time"

Expand All @@ -19,6 +20,7 @@ import (
"github.com/openshift/osd-network-verifier/pkg/probes/curl"
onv "github.com/openshift/osd-network-verifier/pkg/verifier"
"github.com/openshift/osdctl/cmd/servicelog"
"github.com/openshift/osdctl/pkg/utils"
"github.com/stretchr/testify/assert"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
Expand Down Expand Up @@ -73,6 +75,46 @@ func TestEgressVerification_ValidateInput(t *testing.T) {
},
wantError: false,
},
{
name: "valid_hive_ocm_url_production",
ev: &EgressVerification{
SubnetIds: []string{"subnet-123"},
hiveOcmUrl: "production",
},
wantError: false,
},
{
name: "valid_hive_ocm_url_staging",
ev: &EgressVerification{
SubnetIds: []string{"subnet-123"},
hiveOcmUrl: "staging",
},
wantError: false,
},
{
name: "valid_hive_ocm_url_integration",
ev: &EgressVerification{
SubnetIds: []string{"subnet-123"},
hiveOcmUrl: "integration",
},
wantError: false,
},
{
name: "valid_hive_ocm_url_full_url",
ev: &EgressVerification{
SubnetIds: []string{"subnet-123"},
hiveOcmUrl: "https://api.openshift.com",
},
wantError: false,
},
{
name: "invalid_hive_ocm_url",
ev: &EgressVerification{
SubnetIds: []string{"subnet-123"},
hiveOcmUrl: "invalid-environment",
},
wantError: true,
},
}

for _, tt := range tests {
Expand All @@ -87,6 +129,68 @@ func TestEgressVerification_ValidateInput(t *testing.T) {
}
}

// TestHiveOcmUrlValidation tests the validation of --hive-ocm-url flag
func TestHiveOcmUrlValidation(t *testing.T) {
tests := []struct {
name string
hiveOcmUrl string
expectErr bool
errContains string
}{
{
name: "Valid hive-ocm-url (production)",
hiveOcmUrl: "production",
expectErr: false,
},
{
name: "Valid hive-ocm-url (staging)",
hiveOcmUrl: "staging",
expectErr: false,
},
{
name: "Valid hive-ocm-url (integration)",
hiveOcmUrl: "integration",
expectErr: false,
},
{
name: "Valid hive-ocm-url (full URL)",
hiveOcmUrl: "https://api.openshift.com",
expectErr: false,
},
{
name: "Invalid hive-ocm-url",
hiveOcmUrl: "invalid-environment",
expectErr: true,
errContains: "invalid OCM_URL",
},
{
name: "Empty hive-ocm-url",
hiveOcmUrl: "",
expectErr: true,
errContains: "empty OCM URL",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// This simulates the validation that occurs in the validateInput() method
_, err := utils.ValidateAndResolveOcmUrl(tt.hiveOcmUrl)

if tt.expectErr {
if err == nil {
t.Errorf("Expected error containing '%s', but got nil", tt.errContains)
} else if !strings.Contains(err.Error(), tt.errContains) {
t.Errorf("Expected error containing '%s', but got: %v", tt.errContains, err)
}
} else {
if err != nil {
t.Errorf("Expected no error, but got: %v", err)
}
}
})
}
}

func TestEgressVerification_GetPlatform(t *testing.T) {
tests := []struct {
name string
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3589,6 +3589,7 @@ osdctl network verify-egress [flags]
--egress-timeout duration (optional) timeout for individual egress verification requests (default 5s)
--gcp-project-id string (optional) the GCP project ID to run verification for
-h, --help help for verify-egress
--hive-ocm-url string (optional) OCM environment URL for hive operations. Aliases: 'production', 'staging', 'integration'. If not specified, uses the same OCM environment as the target cluster.
--insecure-skip-tls-verify If true, the server's certificate will not be checked for validity. This will make your HTTPS connections insecure
--kubeconfig string (optional) path to kubeconfig file for pod mode (uses default kubeconfig if not specified)
--namespace string (optional) Kubernetes namespace to run verification pods in (default "openshift-network-diagnostics")
Expand Down
8 changes: 8 additions & 0 deletions docs/osdctl_network_verify-egress.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,13 @@ osdctl network verify-egress [flags]
# Run network verification without sending service logs on failure
osdctl network verify-egress --cluster-id my-rosa-cluster --skip-service-log

# For a classic cluster that needs automatic proxy CA-bundle retrieval,
# target staging OCM while querying Hive from production
# (Note: --hive-ocm-url only applies to Hive-backed CA-bundle lookup)
export OCM_URL=staging
ocm login
osdctl network verify-egress --cluster-id my-staging-cluster --hive-ocm-url production

# (Not recommended) Run against a specific VPC, without specifying cluster-id
<export environment variables like AWS_ACCESS_KEY_ID or use aws configure>
osdctl network verify-egress --subnet-id subnet-abcdefg123 --security-group sg-abcdefgh123 --region us-east-1
Expand All @@ -80,6 +87,7 @@ osdctl network verify-egress [flags]
--egress-timeout duration (optional) timeout for individual egress verification requests (default 5s)
--gcp-project-id string (optional) the GCP project ID to run verification for
-h, --help help for verify-egress
--hive-ocm-url string (optional) OCM environment URL for hive operations. Aliases: 'production', 'staging', 'integration'. If not specified, uses the same OCM environment as the target cluster.
--kubeconfig string (optional) path to kubeconfig file for pod mode (uses default kubeconfig if not specified)
--namespace string (optional) Kubernetes namespace to run verification pods in (default "openshift-network-diagnostics")
--no-tls (optional) if provided, ignore all ssl certificate validations on client-side.
Expand Down