Skip to content

Commit cdd8368

Browse files
committed
kcp cluster is reached
1 parent 5e5444d commit cdd8368

File tree

10 files changed

+253
-52
lines changed

10 files changed

+253
-52
lines changed

cmd/gateway.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ var gatewayCmd = &cobra.Command{
3434

3535
ctrl.SetLogger(log.Logr())
3636

37-
gatewayInstance, err := manager.NewGateway(ctx, log, appCfg)
37+
gatewayInstance, err := manager.NewGateway(ctx, log, appCfg, defaultCfg)
3838
if err != nil {
3939
log.Fatal().Err(err).Msg("Failed to create gateway")
4040
}

common/auth/metadata_injector.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -401,13 +401,26 @@ func (m *MetadataInjector) determineHost(originalHost, hostOverride string) stri
401401
return hostOverride
402402
}
403403

404+
// Check if this is an APIExport virtual workspace that needs to preserve its path
405+
if strings.Contains(originalHost, "/services/apiexport/") {
406+
m.log.Info().
407+
Str("originalHost", originalHost).
408+
Msg("*** PRESERVING APIExport virtual workspace path for GraphQL gateway routing ***")
409+
return originalHost
410+
}
411+
412+
// DEBUG: Log when we're about to strip the path
413+
m.log.Info().
414+
Str("originalHost", originalHost).
415+
Msg("*** NOT an APIExport URL, will strip virtual workspace path ***")
416+
404417
// For normal workspaces, ensure we use a clean host by stripping any virtual workspace paths
405418
cleanedHost := stripVirtualWorkspacePath(originalHost)
406419
if cleanedHost != originalHost {
407420
m.log.Info().
408421
Str("originalHost", originalHost).
409422
Str("cleanedHost", cleanedHost).
410-
Msg("cleaned virtual workspace path from host for normal workspace")
423+
Msg("cleaned virtual workspace path from kubeconfig host for normal workspace")
411424
}
412425
return cleanedHost
413426
}
@@ -423,6 +436,21 @@ func (m *MetadataInjector) determineKCPHost(kubeconfigHost, override, clusterPat
423436
return override
424437
}
425438

439+
// Check if this is an APIExport virtual workspace that needs to preserve its path
440+
if strings.Contains(kubeconfigHost, "/services/apiexport/") {
441+
m.log.Info().
442+
Str("clusterPath", clusterPath).
443+
Str("originalHost", kubeconfigHost).
444+
Msg("*** PRESERVING APIExport virtual workspace path for KCP metadata injection ***")
445+
return kubeconfigHost
446+
}
447+
448+
// DEBUG: Log when we're about to strip the path
449+
m.log.Info().
450+
Str("clusterPath", clusterPath).
451+
Str("originalHost", kubeconfigHost).
452+
Msg("*** NOT an APIExport URL, will strip virtual workspace path ***")
453+
426454
// For normal workspaces, ensure we use a clean KCP host by stripping any virtual workspace paths
427455
host := stripVirtualWorkspacePath(kubeconfigHost)
428456
if host != kubeconfigHost {

gateway/manager/manager.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"net/http"
77

88
"github.com/pkg/errors"
9+
openmfpconfig "github.com/platform-mesh/golang-commons/config"
910
"github.com/platform-mesh/golang-commons/logger"
1011
"k8s.io/client-go/rest"
1112

@@ -23,13 +24,13 @@ type Service struct {
2324
}
2425

2526
// NewGateway creates a new domain-driven Gateway instance
26-
func NewGateway(ctx context.Context, log *logger.Logger, appCfg appConfig.Config) (*Service, error) {
27+
func NewGateway(ctx context.Context, log *logger.Logger, appCfg appConfig.Config, defaultCfg *openmfpconfig.CommonServiceConfig) (*Service, error) {
2728
// Create round tripper factory
2829
roundTripperFactory := targetcluster.RoundTripperFactory(func(adminRT http.RoundTripper, tlsConfig rest.TLSClientConfig) http.RoundTripper {
2930
return roundtripper.New(log, appCfg, adminRT, roundtripper.NewUnauthorizedRoundTripper())
3031
})
3132

32-
clusterRegistry := targetcluster.NewClusterRegistry(log, appCfg, roundTripperFactory)
33+
clusterRegistry := targetcluster.NewClusterRegistry(log, appCfg, roundTripperFactory, defaultCfg.EnableHTTP2)
3334

3435
schemaWatcher, err := watcher.NewFileWatcher(log, clusterRegistry)
3536
if err != nil {

gateway/manager/targetcluster/cluster.go

Lines changed: 38 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"github.com/go-openapi/spec"
1111
"github.com/platform-mesh/golang-commons/logger"
1212
"k8s.io/client-go/rest"
13+
ctrl "sigs.k8s.io/controller-runtime"
1314
"sigs.k8s.io/controller-runtime/pkg/client"
1415

1516
"github.com/platform-mesh/kubernetes-graphql-gateway/common/auth"
@@ -64,6 +65,7 @@ func NewTargetCluster(
6465
log *logger.Logger,
6566
appCfg appConfig.Config,
6667
roundTripperFactory func(http.RoundTripper, rest.TLSClientConfig) http.RoundTripper,
68+
enableHTTP2 bool,
6769
) (*TargetCluster, error) {
6870
fileData, err := readSchemaFile(schemaFilePath)
6971
if err != nil {
@@ -77,7 +79,7 @@ func NewTargetCluster(
7779
}
7880

7981
// Connect to cluster - use metadata if available, otherwise fall back to standard config
80-
if err := cluster.connect(appCfg, fileData.ClusterMetadata, roundTripperFactory); err != nil {
82+
if err := cluster.connect(appCfg, fileData.ClusterMetadata, roundTripperFactory, enableHTTP2); err != nil {
8183
return nil, fmt.Errorf("failed to connect to cluster: %w", err)
8284
}
8385

@@ -110,7 +112,7 @@ func (tc *TargetCluster) loadSchemaFromFile(schemaFilePath string) error {
110112
}
111113

112114
// connect establishes connection to the target cluster
113-
func (tc *TargetCluster) connect(appCfg appConfig.Config, metadata *ClusterMetadata, roundTripperFactory func(http.RoundTripper, rest.TLSClientConfig) http.RoundTripper) error {
115+
func (tc *TargetCluster) connect(appCfg appConfig.Config, metadata *ClusterMetadata, roundTripperFactory func(http.RoundTripper, rest.TLSClientConfig) http.RoundTripper, enableHTTP2 bool) error {
114116
// All clusters now use metadata from schema files to get kubeconfig
115117
if metadata == nil {
116118
return fmt.Errorf("cluster %s requires cluster metadata in schema file", tc.name)
@@ -120,14 +122,37 @@ func (tc *TargetCluster) connect(appCfg appConfig.Config, metadata *ClusterMetad
120122
Str("cluster", tc.name).
121123
Str("host", metadata.Host).
122124
Bool("isVirtualWorkspace", strings.HasPrefix(tc.name, tc.appCfg.Url.VirtualWorkspacePrefix)).
125+
Bool("enableHTTP2", enableHTTP2).
123126
Msg("Using cluster metadata from schema file for connection")
124127

125128
var err error
126-
tc.restCfg, err = buildConfigFromMetadata(metadata, tc.log)
127-
if err != nil {
128-
return fmt.Errorf("failed to build config from metadata: %w", err)
129+
130+
// Use the same configuration approach as the Listener for consistency
131+
// This ensures we have the same authentication and connection setup
132+
tc.log.Debug().Msg("Using ctrl.GetConfigOrDie() approach for consistency with Listener")
133+
tc.restCfg = ctrl.GetConfigOrDie()
134+
135+
// Override the host with our cluster-specific metadata host
136+
tc.restCfg.Host = metadata.Host
137+
138+
// For KCP connections, use insecure TLS to avoid certificate issues
139+
// This is safe for internal KCP communication within the same cluster
140+
tc.restCfg.TLSClientConfig.Insecure = true
141+
tc.restCfg.TLSClientConfig.CAFile = ""
142+
tc.restCfg.TLSClientConfig.CAData = nil
143+
144+
// Force HTTP/1.1 to avoid HTTP/2 stream errors with KCP
145+
if !enableHTTP2 {
146+
tc.restCfg.TLSClientConfig.NextProtos = []string{"http/1.1"}
147+
tc.log.Debug().Msg("Disabled HTTP/2 for cluster connection")
129148
}
130149

150+
tc.log.Debug().
151+
Str("host", metadata.Host).
152+
Bool("enableHTTP2", enableHTTP2).
153+
Strs("nextProtos", tc.restCfg.TLSClientConfig.NextProtos).
154+
Msg("Configured cluster connection using Listener approach")
155+
131156
if roundTripperFactory != nil {
132157
tc.restCfg.Wrap(func(rt http.RoundTripper) http.RoundTripper {
133158
return roundTripperFactory(rt, tc.restCfg.TLSClientConfig)
@@ -145,7 +170,7 @@ func (tc *TargetCluster) connect(appCfg appConfig.Config, metadata *ClusterMetad
145170
}
146171

147172
// buildConfigFromMetadata creates rest.Config from cluster metadata
148-
func buildConfigFromMetadata(metadata *ClusterMetadata, log *logger.Logger) (*rest.Config, error) {
173+
func buildConfigFromMetadata(metadata *ClusterMetadata, log *logger.Logger, enableHTTP2 bool) (*rest.Config, error) {
149174
var authType, token, kubeconfig, certData, keyData, caData string
150175

151176
if metadata.Auth != nil {
@@ -166,10 +191,17 @@ func buildConfigFromMetadata(metadata *ClusterMetadata, log *logger.Logger) (*re
166191
return nil, err
167192
}
168193

194+
// Apply HTTP/2 configuration to match Listener behavior
195+
if !enableHTTP2 {
196+
log.Debug().Msg("disabling HTTP/2 for cluster connection")
197+
config.TLSClientConfig.NextProtos = []string{"http/1.1"}
198+
}
199+
169200
log.Debug().
170201
Str("host", metadata.Host).
171202
Str("authType", authType).
172203
Bool("hasCA", caData != "").
204+
Bool("enableHTTP2", enableHTTP2).
173205
Msg("configured cluster from metadata")
174206

175207
return config, nil

gateway/manager/targetcluster/export_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import (
99

1010
// BuildConfigFromMetadata exposes the internal buildConfigFromMetadata function for testing
1111
func BuildConfigFromMetadata(metadata *ClusterMetadata, log *logger.Logger) (*rest.Config, error) {
12-
return buildConfigFromMetadata(metadata, log)
12+
return buildConfigFromMetadata(metadata, log, true) // Default to HTTP/2 enabled for tests
1313
}
1414

1515
// NewTestTargetCluster creates a TargetCluster with the specified name for testing

gateway/manager/targetcluster/registry.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,19 +32,22 @@ type ClusterRegistry struct {
3232
log *logger.Logger
3333
appCfg appConfig.Config
3434
roundTripperFactory RoundTripperFactory
35+
enableHTTP2 bool
3536
}
3637

3738
// NewClusterRegistry creates a new cluster registry
3839
func NewClusterRegistry(
3940
log *logger.Logger,
4041
appCfg appConfig.Config,
4142
roundTripperFactory RoundTripperFactory,
43+
enableHTTP2 bool,
4244
) *ClusterRegistry {
4345
return &ClusterRegistry{
4446
clusters: make(map[string]*TargetCluster),
4547
log: log,
4648
appCfg: appCfg,
4749
roundTripperFactory: roundTripperFactory,
50+
enableHTTP2: enableHTTP2,
4851
}
4952
}
5053

@@ -62,7 +65,7 @@ func (cr *ClusterRegistry) LoadCluster(schemaFilePath string) error {
6265
Msg("Loading target cluster")
6366

6467
// Create or update cluster
65-
cluster, err := NewTargetCluster(name, schemaFilePath, cr.log, cr.appCfg, cr.roundTripperFactory)
68+
cluster, err := NewTargetCluster(name, schemaFilePath, cr.log, cr.appCfg, cr.roundTripperFactory, cr.enableHTTP2)
6669
if err != nil {
6770
return fmt.Errorf("failed to create target cluster %s: %w", name, err)
6871
}

go.mod

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,26 @@ module github.com/platform-mesh/kubernetes-graphql-gateway
22

33
go 1.24.3
44

5-
// this PR introduces newer version of graphiQL that supports headers
6-
// https://github.com/graphql-go/handler/pull/93
7-
replace github.com/graphql-go/handler => github.com/vertex451/handler v0.0.0-20250124125145-ed328e3cf42a
5+
replace (
6+
// this PR introduces newer version of graphiQL that supports headers
7+
// https://github.com/graphql-go/handler/pull/93
8+
github.com/graphql-go/handler => github.com/vertex451/handler v0.0.0-20250124125145-ed328e3cf42a
9+
10+
// Pin golang.org/x/net to match multicluster-provider (prevents HTTP/2 incompatibility)
11+
golang.org/x/net => golang.org/x/net v0.40.0
12+
13+
// Pin Kubernetes versions to match main branch (prevents KCP front proxy HTTP/2 panics)
14+
k8s.io/api => k8s.io/api v0.33.3
15+
k8s.io/apiextensions-apiserver => k8s.io/apiextensions-apiserver v0.33.3
16+
k8s.io/apimachinery => k8s.io/apimachinery v0.33.3
17+
k8s.io/apiserver => k8s.io/apiserver v0.33.3
18+
k8s.io/client-go => k8s.io/client-go v0.33.3
19+
k8s.io/component-base => k8s.io/component-base v0.33.3
20+
k8s.io/kube-openapi => k8s.io/kube-openapi v0.0.0-20250701173324-9bd5c66d9911
21+
22+
// Pin controller-runtime to compatible version with k8s v0.33.3
23+
sigs.k8s.io/controller-runtime => sigs.k8s.io/controller-runtime v0.21.0
24+
)
825

926
require (
1027
github.com/fsnotify/fsnotify v1.9.0
@@ -31,12 +48,12 @@ require (
3148
golang.org/x/exp v0.0.0-20250819193227-8b4c13bb791b
3249
golang.org/x/text v0.29.0
3350
gopkg.in/yaml.v3 v3.0.1
34-
k8s.io/api v0.34.0
35-
k8s.io/apiextensions-apiserver v0.34.0
36-
k8s.io/apimachinery v0.34.0
37-
k8s.io/client-go v0.34.0
38-
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b
39-
sigs.k8s.io/controller-runtime v0.22.0
51+
k8s.io/api v0.33.3
52+
k8s.io/apiextensions-apiserver v0.33.3
53+
k8s.io/apimachinery v0.33.3
54+
k8s.io/client-go v0.33.3
55+
k8s.io/kube-openapi v0.0.0-20250701173324-9bd5c66d9911
56+
sigs.k8s.io/controller-runtime v0.22.1
4057
sigs.k8s.io/multicluster-runtime v0.21.0-alpha.9
4158
)
4259

@@ -130,6 +147,6 @@ require (
130147
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect
131148
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
132149
sigs.k8s.io/randfill v1.0.0 // indirect
133-
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
150+
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
134151
sigs.k8s.io/yaml v1.6.0 // indirect
135152
)

0 commit comments

Comments
 (0)