Merge pull request #2366 from ikaven1024/feature-proxy
add proxy skeleton
This commit is contained in:
commit
dd7d70ae6f
|
@ -4,13 +4,18 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
|
||||||
"github.com/spf13/pflag"
|
"github.com/spf13/pflag"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/apiserver/pkg/endpoints/openapi"
|
"k8s.io/apiserver/pkg/endpoints/openapi"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/request"
|
||||||
"k8s.io/apiserver/pkg/features"
|
"k8s.io/apiserver/pkg/features"
|
||||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||||
|
genericfilters "k8s.io/apiserver/pkg/server/filters"
|
||||||
genericoptions "k8s.io/apiserver/pkg/server/options"
|
genericoptions "k8s.io/apiserver/pkg/server/options"
|
||||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||||
"k8s.io/klog/v2"
|
"k8s.io/klog/v2"
|
||||||
|
@ -18,9 +23,14 @@ import (
|
||||||
|
|
||||||
searchscheme "github.com/karmada-io/karmada/pkg/apis/search/scheme"
|
searchscheme "github.com/karmada-io/karmada/pkg/apis/search/scheme"
|
||||||
searchv1alpha1 "github.com/karmada-io/karmada/pkg/apis/search/v1alpha1"
|
searchv1alpha1 "github.com/karmada-io/karmada/pkg/apis/search/v1alpha1"
|
||||||
|
karmadaclientset "github.com/karmada-io/karmada/pkg/generated/clientset/versioned"
|
||||||
|
informerfactory "github.com/karmada-io/karmada/pkg/generated/informers/externalversions"
|
||||||
generatedopenapi "github.com/karmada-io/karmada/pkg/generated/openapi"
|
generatedopenapi "github.com/karmada-io/karmada/pkg/generated/openapi"
|
||||||
"github.com/karmada-io/karmada/pkg/search"
|
"github.com/karmada-io/karmada/pkg/search"
|
||||||
|
"github.com/karmada-io/karmada/pkg/search/proxy"
|
||||||
"github.com/karmada-io/karmada/pkg/sharedcli/profileflag"
|
"github.com/karmada-io/karmada/pkg/sharedcli/profileflag"
|
||||||
|
"github.com/karmada-io/karmada/pkg/util/fedinformer/genericmanager"
|
||||||
|
"github.com/karmada-io/karmada/pkg/util/lifted"
|
||||||
"github.com/karmada-io/karmada/pkg/version"
|
"github.com/karmada-io/karmada/pkg/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -89,12 +99,23 @@ func (o *Options) Run(ctx context.Context) error {
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
server.GenericAPIServer.AddPostStartHookOrDie("start-karmada-informers", func(context genericapiserver.PostStartHookContext) error {
|
||||||
|
config.KarmadaSharedInformerFactory.Start(context.StopCh)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
server.GenericAPIServer.AddPostStartHookOrDie("start-karmada-search-controller", func(context genericapiserver.PostStartHookContext) error {
|
server.GenericAPIServer.AddPostStartHookOrDie("start-karmada-search-controller", func(context genericapiserver.PostStartHookContext) error {
|
||||||
// start ResourceRegistry controller
|
// start ResourceRegistry controller
|
||||||
config.Controller.Start(context.StopCh)
|
config.Controller.Start(context.StopCh)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
server.GenericAPIServer.AddPostStartHookOrDie("start-karmada-proxy-controller", func(context genericapiserver.PostStartHookContext) error {
|
||||||
|
// start ResourceRegistry controller
|
||||||
|
config.ProxyController.Start(context.StopCh)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
return server.GenericAPIServer.PrepareRun().Run(ctx.Done())
|
return server.GenericAPIServer.PrepareRun().Run(ctx.Done())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -108,6 +129,9 @@ func (o *Options) Config() (*search.Config, error) {
|
||||||
o.RecommendedOptions.Features = &genericoptions.FeatureOptions{EnableProfiling: false}
|
o.RecommendedOptions.Features = &genericoptions.FeatureOptions{EnableProfiling: false}
|
||||||
|
|
||||||
serverConfig := genericapiserver.NewRecommendedConfig(searchscheme.Codecs)
|
serverConfig := genericapiserver.NewRecommendedConfig(searchscheme.Codecs)
|
||||||
|
serverConfig.LongRunningFunc = customLongRunningRequestCheck(
|
||||||
|
sets.NewString("watch", "proxy"),
|
||||||
|
sets.NewString("attach", "exec", "proxy", "log", "portforward"))
|
||||||
serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(generatedopenapi.GetOpenAPIDefinitions, openapi.NewDefinitionNamer(searchscheme.Scheme))
|
serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(generatedopenapi.GetOpenAPIDefinitions, openapi.NewDefinitionNamer(searchscheme.Scheme))
|
||||||
serverConfig.OpenAPIConfig.Info.Title = "karmada-search"
|
serverConfig.OpenAPIConfig.Info.Title = "karmada-search"
|
||||||
if err := o.RecommendedOptions.ApplyTo(serverConfig); err != nil {
|
if err := o.RecommendedOptions.ApplyTo(serverConfig); err != nil {
|
||||||
|
@ -121,9 +145,31 @@ func (o *Options) Config() (*search.Config, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
karmadaClient := karmadaclientset.NewForConfigOrDie(serverConfig.ClientConfig)
|
||||||
|
factory := informerfactory.NewSharedInformerFactory(karmadaClient, 0)
|
||||||
|
|
||||||
|
proxyCtl, err := proxy.NewController(serverConfig.ClientConfig, genericmanager.GetInstance(), serverConfig.SharedInformerFactory, factory)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
config := &search.Config{
|
config := &search.Config{
|
||||||
GenericConfig: serverConfig,
|
GenericConfig: serverConfig,
|
||||||
Controller: ctl,
|
Controller: ctl,
|
||||||
|
ProxyController: proxyCtl,
|
||||||
|
KarmadaSharedInformerFactory: factory,
|
||||||
}
|
}
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func customLongRunningRequestCheck(longRunningVerbs, longRunningSubresources sets.String) request.LongRunningRequestCheck {
|
||||||
|
return func(r *http.Request, requestInfo *request.RequestInfo) bool {
|
||||||
|
if requestInfo.APIGroup == "search.karmada.io" && requestInfo.Resource == "proxying" {
|
||||||
|
reqClone := r.Clone(context.TODO())
|
||||||
|
// requestInfo.Parts is like [proxying foo proxy api v1 nodes]
|
||||||
|
reqClone.URL.Path = "/" + path.Join(requestInfo.Parts[3:]...)
|
||||||
|
requestInfo = lifted.NewRequestInfo(reqClone)
|
||||||
|
}
|
||||||
|
return genericfilters.BasicLongRunningRequestCheck(longRunningVerbs, longRunningSubresources)(r, requestInfo)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -34,6 +34,7 @@ func addKnownTypes(scheme *runtime.Scheme) error {
|
||||||
&ResourceRegistry{},
|
&ResourceRegistry{},
|
||||||
&ResourceRegistryList{},
|
&ResourceRegistryList{},
|
||||||
&Search{},
|
&Search{},
|
||||||
|
&Proxying{},
|
||||||
)
|
)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,3 +105,10 @@ type ResourceRegistryList struct {
|
||||||
type Search struct {
|
type Search struct {
|
||||||
metav1.TypeMeta
|
metav1.TypeMeta
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// Proxying define a flag for resource proxying that do not have actual resources.
|
||||||
|
type Proxying struct {
|
||||||
|
metav1.TypeMeta
|
||||||
|
}
|
||||||
|
|
|
@ -118,3 +118,10 @@ type ResourceRegistryList struct {
|
||||||
type Search struct {
|
type Search struct {
|
||||||
metav1.TypeMeta `json:",inline"`
|
metav1.TypeMeta `json:",inline"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
|
||||||
|
// Proxying define a flag for resource proxying that do not have actual resources.
|
||||||
|
type Proxying struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
}
|
||||||
|
|
|
@ -41,6 +41,16 @@ func RegisterConversions(s *runtime.Scheme) error {
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*Proxying)(nil), (*search.Proxying)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_v1alpha1_Proxying_To_search_Proxying(a.(*Proxying), b.(*search.Proxying), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := s.AddGeneratedConversionFunc((*search.Proxying)(nil), (*Proxying)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
|
return Convert_search_Proxying_To_v1alpha1_Proxying(a.(*search.Proxying), b.(*Proxying), scope)
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
if err := s.AddGeneratedConversionFunc((*ResourceRegistry)(nil), (*search.ResourceRegistry)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
if err := s.AddGeneratedConversionFunc((*ResourceRegistry)(nil), (*search.ResourceRegistry)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||||
return Convert_v1alpha1_ResourceRegistry_To_search_ResourceRegistry(a.(*ResourceRegistry), b.(*search.ResourceRegistry), scope)
|
return Convert_v1alpha1_ResourceRegistry_To_search_ResourceRegistry(a.(*ResourceRegistry), b.(*search.ResourceRegistry), scope)
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
|
@ -146,6 +156,24 @@ func Convert_search_OpenSearchConfig_To_v1alpha1_OpenSearchConfig(in *search.Ope
|
||||||
return autoConvert_search_OpenSearchConfig_To_v1alpha1_OpenSearchConfig(in, out, s)
|
return autoConvert_search_OpenSearchConfig_To_v1alpha1_OpenSearchConfig(in, out, s)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func autoConvert_v1alpha1_Proxying_To_search_Proxying(in *Proxying, out *search.Proxying, s conversion.Scope) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_v1alpha1_Proxying_To_search_Proxying is an autogenerated conversion function.
|
||||||
|
func Convert_v1alpha1_Proxying_To_search_Proxying(in *Proxying, out *search.Proxying, s conversion.Scope) error {
|
||||||
|
return autoConvert_v1alpha1_Proxying_To_search_Proxying(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoConvert_search_Proxying_To_v1alpha1_Proxying(in *search.Proxying, out *Proxying, s conversion.Scope) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert_search_Proxying_To_v1alpha1_Proxying is an autogenerated conversion function.
|
||||||
|
func Convert_search_Proxying_To_v1alpha1_Proxying(in *search.Proxying, out *Proxying, s conversion.Scope) error {
|
||||||
|
return autoConvert_search_Proxying_To_v1alpha1_Proxying(in, out, s)
|
||||||
|
}
|
||||||
|
|
||||||
func autoConvert_v1alpha1_ResourceRegistry_To_search_ResourceRegistry(in *ResourceRegistry, out *search.ResourceRegistry, s conversion.Scope) error {
|
func autoConvert_v1alpha1_ResourceRegistry_To_search_ResourceRegistry(in *ResourceRegistry, out *search.ResourceRegistry, s conversion.Scope) error {
|
||||||
out.ObjectMeta = in.ObjectMeta
|
out.ObjectMeta = in.ObjectMeta
|
||||||
if err := Convert_v1alpha1_ResourceRegistrySpec_To_search_ResourceRegistrySpec(&in.Spec, &out.Spec, s); err != nil {
|
if err := Convert_v1alpha1_ResourceRegistrySpec_To_search_ResourceRegistrySpec(&in.Spec, &out.Spec, s); err != nil {
|
||||||
|
|
|
@ -53,6 +53,31 @@ func (in *OpenSearchConfig) DeepCopy() *OpenSearchConfig {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Proxying) DeepCopyInto(out *Proxying) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Proxying.
|
||||||
|
func (in *Proxying) DeepCopy() *Proxying {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Proxying)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *Proxying) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *ResourceRegistry) DeepCopyInto(out *ResourceRegistry) {
|
func (in *ResourceRegistry) DeepCopyInto(out *ResourceRegistry) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
|
|
@ -42,6 +42,7 @@ func init() {
|
||||||
// Adds the list of known types to Scheme.
|
// Adds the list of known types to Scheme.
|
||||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
func addKnownTypes(scheme *runtime.Scheme) error {
|
||||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
scheme.AddKnownTypes(SchemeGroupVersion,
|
||||||
|
&Proxying{},
|
||||||
&ResourceRegistry{},
|
&ResourceRegistry{},
|
||||||
&ResourceRegistryList{},
|
&ResourceRegistryList{},
|
||||||
&Search{},
|
&Search{},
|
||||||
|
|
|
@ -53,6 +53,31 @@ func (in *OpenSearchConfig) DeepCopy() *OpenSearchConfig {
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *Proxying) DeepCopyInto(out *Proxying) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Proxying.
|
||||||
|
func (in *Proxying) DeepCopy() *Proxying {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(Proxying)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *Proxying) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *ResourceRegistry) DeepCopyInto(out *ResourceRegistry) {
|
func (in *ResourceRegistry) DeepCopyInto(out *ResourceRegistry) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
|
|
@ -71,6 +71,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
||||||
"github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1.StaticClusterWeight": schema_pkg_apis_policy_v1alpha1_StaticClusterWeight(ref),
|
"github.com/karmada-io/karmada/pkg/apis/policy/v1alpha1.StaticClusterWeight": schema_pkg_apis_policy_v1alpha1_StaticClusterWeight(ref),
|
||||||
"github.com/karmada-io/karmada/pkg/apis/search/v1alpha1.BackendStoreConfig": schema_pkg_apis_search_v1alpha1_BackendStoreConfig(ref),
|
"github.com/karmada-io/karmada/pkg/apis/search/v1alpha1.BackendStoreConfig": schema_pkg_apis_search_v1alpha1_BackendStoreConfig(ref),
|
||||||
"github.com/karmada-io/karmada/pkg/apis/search/v1alpha1.OpenSearchConfig": schema_pkg_apis_search_v1alpha1_OpenSearchConfig(ref),
|
"github.com/karmada-io/karmada/pkg/apis/search/v1alpha1.OpenSearchConfig": schema_pkg_apis_search_v1alpha1_OpenSearchConfig(ref),
|
||||||
|
"github.com/karmada-io/karmada/pkg/apis/search/v1alpha1.Proxying": schema_pkg_apis_search_v1alpha1_Proxying(ref),
|
||||||
"github.com/karmada-io/karmada/pkg/apis/search/v1alpha1.ResourceRegistry": schema_pkg_apis_search_v1alpha1_ResourceRegistry(ref),
|
"github.com/karmada-io/karmada/pkg/apis/search/v1alpha1.ResourceRegistry": schema_pkg_apis_search_v1alpha1_ResourceRegistry(ref),
|
||||||
"github.com/karmada-io/karmada/pkg/apis/search/v1alpha1.ResourceRegistryList": schema_pkg_apis_search_v1alpha1_ResourceRegistryList(ref),
|
"github.com/karmada-io/karmada/pkg/apis/search/v1alpha1.ResourceRegistryList": schema_pkg_apis_search_v1alpha1_ResourceRegistryList(ref),
|
||||||
"github.com/karmada-io/karmada/pkg/apis/search/v1alpha1.ResourceRegistrySpec": schema_pkg_apis_search_v1alpha1_ResourceRegistrySpec(ref),
|
"github.com/karmada-io/karmada/pkg/apis/search/v1alpha1.ResourceRegistrySpec": schema_pkg_apis_search_v1alpha1_ResourceRegistrySpec(ref),
|
||||||
|
@ -3063,6 +3064,33 @@ func schema_pkg_apis_search_v1alpha1_OpenSearchConfig(ref common.ReferenceCallba
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func schema_pkg_apis_search_v1alpha1_Proxying(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||||
|
return common.OpenAPIDefinition{
|
||||||
|
Schema: spec.Schema{
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "Proxying define a flag for resource proxying that do not have actual resources.",
|
||||||
|
Type: []string{"object"},
|
||||||
|
Properties: map[string]spec.Schema{
|
||||||
|
"kind": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"apiVersion": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func schema_pkg_apis_search_v1alpha1_ResourceRegistry(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
func schema_pkg_apis_search_v1alpha1_ResourceRegistry(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||||
return common.OpenAPIDefinition{
|
return common.OpenAPIDefinition{
|
||||||
Schema: spec.Schema{
|
Schema: spec.Schema{
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
package storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
genericrequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||||
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
|
"k8s.io/klog/v2"
|
||||||
|
|
||||||
|
searchapis "github.com/karmada-io/karmada/pkg/apis/search"
|
||||||
|
"github.com/karmada-io/karmada/pkg/search/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
var proxyMethods = []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"}
|
||||||
|
|
||||||
|
// ProxyingREST implements a RESTStorage for proxying resource.
|
||||||
|
type ProxyingREST struct {
|
||||||
|
ctl *proxy.Controller
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ rest.Scoper = &ProxyingREST{}
|
||||||
|
var _ rest.Storage = &ProxyingREST{}
|
||||||
|
var _ rest.Connecter = &ProxyingREST{}
|
||||||
|
|
||||||
|
// NewProxyingREST returns a RESTStorage object that will work against search.
|
||||||
|
func NewProxyingREST(ctl *proxy.Controller) *ProxyingREST {
|
||||||
|
return &ProxyingREST{
|
||||||
|
ctl: ctl,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// New return empty Proxy object.
|
||||||
|
func (r *ProxyingREST) New() runtime.Object {
|
||||||
|
return &searchapis.Proxying{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NamespaceScoped returns false because Proxy is not namespaced.
|
||||||
|
func (r *ProxyingREST) NamespaceScoped() bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectMethods returns the list of HTTP methods handled by Connect.
|
||||||
|
func (r *ProxyingREST) ConnectMethods() []string {
|
||||||
|
return proxyMethods
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewConnectOptions returns an empty options object that will be used to pass options to the Connect method.
|
||||||
|
func (r *ProxyingREST) NewConnectOptions() (runtime.Object, bool, string) {
|
||||||
|
return nil, true, ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect returns a handler for proxy.
|
||||||
|
func (r *ProxyingREST) Connect(ctx context.Context, _ string, _ runtime.Object, responder rest.Responder) (http.Handler, error) {
|
||||||
|
info, ok := genericrequest.RequestInfoFrom(ctx)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("no RequestInfo found in the context")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(info.Parts) < 2 {
|
||||||
|
return nil, fmt.Errorf("invalid requestInfo parts: %v", info.Parts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// For example, the whole request URL is /apis/search.karmada.io/v1alpha1/proxying/foo/proxy/api/v1/nodes
|
||||||
|
// info.Parts is [proxying foo proxy api v1 nodes], so proxyPath is /api/v1/nodes
|
||||||
|
proxyPath := "/" + path.Join(info.Parts[3:]...)
|
||||||
|
klog.V(4).Infof("ProxyingREST connect %v", proxyPath)
|
||||||
|
return r.ctl.Connect(ctx, proxyPath, responder)
|
||||||
|
}
|
|
@ -8,23 +8,28 @@ import (
|
||||||
|
|
||||||
searchapis "github.com/karmada-io/karmada/pkg/apis/search"
|
searchapis "github.com/karmada-io/karmada/pkg/apis/search"
|
||||||
searchscheme "github.com/karmada-io/karmada/pkg/apis/search/scheme"
|
searchscheme "github.com/karmada-io/karmada/pkg/apis/search/scheme"
|
||||||
|
informerfactory "github.com/karmada-io/karmada/pkg/generated/informers/externalversions"
|
||||||
clusterlister "github.com/karmada-io/karmada/pkg/generated/listers/cluster/v1alpha1"
|
clusterlister "github.com/karmada-io/karmada/pkg/generated/listers/cluster/v1alpha1"
|
||||||
searchstorage "github.com/karmada-io/karmada/pkg/registry/search/storage"
|
searchstorage "github.com/karmada-io/karmada/pkg/registry/search/storage"
|
||||||
|
"github.com/karmada-io/karmada/pkg/search/proxy"
|
||||||
"github.com/karmada-io/karmada/pkg/util/fedinformer/genericmanager"
|
"github.com/karmada-io/karmada/pkg/util/fedinformer/genericmanager"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ExtraConfig holds custom apiserver config
|
// ExtraConfig holds custom apiserver config
|
||||||
type ExtraConfig struct {
|
type ExtraConfig struct {
|
||||||
MultiClusterInformerManager genericmanager.MultiClusterInformerManager
|
MultiClusterInformerManager genericmanager.MultiClusterInformerManager
|
||||||
ClusterLister clusterlister.ClusterLister
|
ClusterLister clusterlister.ClusterLister
|
||||||
|
KarmadaSharedInformerFactory informerfactory.SharedInformerFactory
|
||||||
|
ProxyController *proxy.Controller
|
||||||
// Add custom config if necessary.
|
// Add custom config if necessary.
|
||||||
}
|
}
|
||||||
|
|
||||||
// Config defines the config for the APIServer.
|
// Config defines the config for the APIServer.
|
||||||
type Config struct {
|
type Config struct {
|
||||||
GenericConfig *genericapiserver.RecommendedConfig
|
GenericConfig *genericapiserver.RecommendedConfig
|
||||||
Controller *Controller
|
Controller *Controller
|
||||||
|
ProxyController *proxy.Controller
|
||||||
|
KarmadaSharedInformerFactory informerfactory.SharedInformerFactory
|
||||||
}
|
}
|
||||||
|
|
||||||
// APIServer contains state for karmada-search.
|
// APIServer contains state for karmada-search.
|
||||||
|
@ -47,8 +52,10 @@ func (cfg *Config) Complete() CompletedConfig {
|
||||||
c := completedConfig{
|
c := completedConfig{
|
||||||
cfg.GenericConfig.Complete(),
|
cfg.GenericConfig.Complete(),
|
||||||
&ExtraConfig{
|
&ExtraConfig{
|
||||||
MultiClusterInformerManager: cfg.Controller.InformerManager,
|
MultiClusterInformerManager: cfg.Controller.InformerManager,
|
||||||
ClusterLister: cfg.Controller.clusterLister,
|
ClusterLister: cfg.Controller.clusterLister,
|
||||||
|
KarmadaSharedInformerFactory: cfg.KarmadaSharedInformerFactory,
|
||||||
|
ProxyController: cfg.ProxyController,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,11 +85,13 @@ func (c completedConfig) New() (*APIServer, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
searchREST := searchstorage.NewSearchREST(c.ExtraConfig.MultiClusterInformerManager, c.ExtraConfig.ClusterLister)
|
searchREST := searchstorage.NewSearchREST(c.ExtraConfig.MultiClusterInformerManager, c.ExtraConfig.ClusterLister)
|
||||||
|
proxyingREST := searchstorage.NewProxyingREST(c.ExtraConfig.ProxyController)
|
||||||
|
|
||||||
v1alpha1search := map[string]rest.Storage{}
|
v1alpha1search := map[string]rest.Storage{}
|
||||||
v1alpha1search["resourceregistries"] = resourceRegistryStorage.ResourceRegistry
|
v1alpha1search["resourceregistries"] = resourceRegistryStorage.ResourceRegistry
|
||||||
v1alpha1search["resourceregistries/status"] = resourceRegistryStorage.Status
|
v1alpha1search["resourceregistries/status"] = resourceRegistryStorage.Status
|
||||||
v1alpha1search["search"] = searchREST
|
v1alpha1search["search"] = searchREST
|
||||||
|
v1alpha1search["proxying"] = proxyingREST
|
||||||
apiGroupInfo.VersionedResourcesStorageMap["v1alpha1"] = v1alpha1search
|
apiGroupInfo.VersionedResourcesStorageMap["v1alpha1"] = v1alpha1search
|
||||||
|
|
||||||
if err = server.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil {
|
if err = server.GenericAPIServer.InstallAPIGroup(&apiGroupInfo); err != nil {
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/karmada-io/karmada/pkg/search/proxy/store"
|
||||||
|
)
|
||||||
|
|
||||||
|
type cacheProxy struct {
|
||||||
|
store *store.MultiClusterCache
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *cacheProxy) connect(ctx context.Context) http.Handler {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newCacheProxy(store *store.MultiClusterCache) *cacheProxy {
|
||||||
|
return &cacheProxy{
|
||||||
|
store: store,
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
|
listcorev1 "k8s.io/client-go/listers/core/v1"
|
||||||
|
|
||||||
|
"github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
|
||||||
|
"github.com/karmada-io/karmada/pkg/util/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// clusterProxy proxy to member clusters
|
||||||
|
type clusterProxy struct {
|
||||||
|
secretLister listcorev1.SecretLister
|
||||||
|
}
|
||||||
|
|
||||||
|
func newClusterProxy(secretLister listcorev1.SecretLister) *clusterProxy {
|
||||||
|
return &clusterProxy{
|
||||||
|
secretLister: secretLister,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *clusterProxy) connect(ctx context.Context, cluster *v1alpha1.Cluster, proxyPath string, responder rest.Responder) (http.Handler, error) {
|
||||||
|
location, transport, err := proxy.Location(cluster.Name, cluster.Spec.APIEndpoint, cluster.Spec.ProxyURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
location.Path = path.Join(location.Path, proxyPath)
|
||||||
|
|
||||||
|
secretGetter := func(context.Context, string) (*corev1.Secret, error) {
|
||||||
|
if cluster.Spec.ImpersonatorSecretRef == nil {
|
||||||
|
return nil, fmt.Errorf("the impersonatorSecretRef of cluster %s is nil", cluster.Name)
|
||||||
|
}
|
||||||
|
return c.secretLister.Secrets(cluster.Spec.ImpersonatorSecretRef.Namespace).Get(cluster.Spec.ImpersonatorSecretRef.Name)
|
||||||
|
}
|
||||||
|
return proxy.ConnectCluster(ctx, cluster.Name, location, transport, responder, secretGetter)
|
||||||
|
}
|
|
@ -0,0 +1,150 @@
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/request"
|
||||||
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
|
"k8s.io/client-go/informers"
|
||||||
|
restclient "k8s.io/client-go/rest"
|
||||||
|
"k8s.io/client-go/tools/cache"
|
||||||
|
|
||||||
|
informerfactory "github.com/karmada-io/karmada/pkg/generated/informers/externalversions"
|
||||||
|
clusterlisters "github.com/karmada-io/karmada/pkg/generated/listers/cluster/v1alpha1"
|
||||||
|
searchlisters "github.com/karmada-io/karmada/pkg/generated/listers/search/v1alpha1"
|
||||||
|
"github.com/karmada-io/karmada/pkg/search/proxy/store"
|
||||||
|
"github.com/karmada-io/karmada/pkg/util"
|
||||||
|
"github.com/karmada-io/karmada/pkg/util/fedinformer/genericmanager"
|
||||||
|
"github.com/karmada-io/karmada/pkg/util/lifted"
|
||||||
|
)
|
||||||
|
|
||||||
|
const workKey = "key"
|
||||||
|
|
||||||
|
// Controller syncs Cluster and GlobalResource.
|
||||||
|
type Controller struct {
|
||||||
|
clusterLister clusterlisters.ClusterLister
|
||||||
|
registryLister searchlisters.ResourceRegistryLister
|
||||||
|
worker util.AsyncWorker
|
||||||
|
informerManager genericmanager.MultiClusterInformerManager
|
||||||
|
store *store.MultiClusterCache
|
||||||
|
|
||||||
|
// proxy
|
||||||
|
karmadaProxy *karmadaProxy
|
||||||
|
clusterProxy *clusterProxy
|
||||||
|
cacheProxy *cacheProxy
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewController create a controller for proxy
|
||||||
|
func NewController(restConfig *restclient.Config, informerManager genericmanager.MultiClusterInformerManager, factory informers.SharedInformerFactory, karmadaFactory informerfactory.SharedInformerFactory) (*Controller, error) {
|
||||||
|
kp, err := newKarmadaProxy(restConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
s := &store.MultiClusterCache{}
|
||||||
|
|
||||||
|
ctl := &Controller{
|
||||||
|
clusterLister: karmadaFactory.Cluster().V1alpha1().Clusters().Lister(),
|
||||||
|
registryLister: karmadaFactory.Search().V1alpha1().ResourceRegistries().Lister(),
|
||||||
|
informerManager: informerManager,
|
||||||
|
store: s,
|
||||||
|
karmadaProxy: kp,
|
||||||
|
clusterProxy: newClusterProxy(factory.Core().V1().Secrets().Lister()),
|
||||||
|
cacheProxy: newCacheProxy(s),
|
||||||
|
}
|
||||||
|
|
||||||
|
workerOptions := util.Options{
|
||||||
|
Name: "proxy-controller",
|
||||||
|
KeyFunc: nil,
|
||||||
|
ReconcileFunc: ctl.reconcile,
|
||||||
|
}
|
||||||
|
ctl.worker = util.NewAsyncWorker(workerOptions)
|
||||||
|
|
||||||
|
resourceEventHandler := cache.ResourceEventHandlerFuncs{
|
||||||
|
AddFunc: func(interface{}) {
|
||||||
|
ctl.worker.Add(workKey)
|
||||||
|
},
|
||||||
|
UpdateFunc: func(_, _ interface{}) {
|
||||||
|
ctl.worker.Add(workKey)
|
||||||
|
},
|
||||||
|
DeleteFunc: func(interface{}) {
|
||||||
|
ctl.worker.Add(workKey)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
karmadaFactory.Cluster().V1alpha1().Clusters().Informer().AddEventHandler(resourceEventHandler)
|
||||||
|
karmadaFactory.Search().V1alpha1().ResourceRegistries().Informer().AddEventHandler(resourceEventHandler)
|
||||||
|
|
||||||
|
return ctl, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start run the proxy controller
|
||||||
|
func (ctl *Controller) Start(stopCh <-chan struct{}) {
|
||||||
|
ctl.worker.Run(1, stopCh)
|
||||||
|
}
|
||||||
|
|
||||||
|
// reconcile cache
|
||||||
|
func (ctl *Controller) reconcile(util.QueueKey) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect proxy and dispatch handlers
|
||||||
|
func (ctl *Controller) Connect(ctx context.Context, proxyPath string, responder rest.Responder) (http.Handler, error) {
|
||||||
|
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
newReq := req.Clone(req.Context())
|
||||||
|
newReq.URL.Path = proxyPath
|
||||||
|
requestInfo := lifted.NewRequestInfo(newReq)
|
||||||
|
|
||||||
|
newCtx := request.WithRequestInfo(ctx, requestInfo)
|
||||||
|
newCtx = request.WithNamespace(newCtx, requestInfo.Namespace)
|
||||||
|
newReq = newReq.WithContext(newCtx)
|
||||||
|
|
||||||
|
h, err := ctl.connect(newCtx, requestInfo, proxyPath, responder)
|
||||||
|
if err != nil {
|
||||||
|
responder.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.ServeHTTP(rw, newReq)
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctl *Controller) connect(ctx context.Context, requestInfo *request.RequestInfo, path string, responder rest.Responder) (http.Handler, error) {
|
||||||
|
gvr := schema.GroupVersionResource{
|
||||||
|
Group: requestInfo.APIGroup,
|
||||||
|
Version: requestInfo.APIVersion,
|
||||||
|
Resource: requestInfo.Resource,
|
||||||
|
}
|
||||||
|
|
||||||
|
// requests will be redirected to:
|
||||||
|
// 1. karmada apiserver
|
||||||
|
// 2. cache
|
||||||
|
// 3. member clusters
|
||||||
|
// see more information from https://github.com/karmada-io/karmada/tree/master/docs/proposals/resource-aggregation-proxy#request-routing
|
||||||
|
|
||||||
|
// 1. For non-resource requests, or resources are not defined in ResourceRegistry,
|
||||||
|
// we redirect the requests to karmada apiserver.
|
||||||
|
// Usually the request are
|
||||||
|
// - api index, e.g.: `/api`, `/apis`
|
||||||
|
// - to workload created in karmada controller panel, such as deployments and services.
|
||||||
|
if !requestInfo.IsResourceRequest || !ctl.store.HasResource(gvr) {
|
||||||
|
return ctl.karmadaProxy.connect(path, responder)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. For reading requests, we redirect them to cache.
|
||||||
|
// Users call these requests to read resources in member clusters, such as pods and nodes.
|
||||||
|
if requestInfo.Subresource == "" && (requestInfo.Verb == "get" || requestInfo.Verb == "list" || requestInfo.Verb == "watch") {
|
||||||
|
return ctl.cacheProxy.connect(ctx), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. The remaining requests are:
|
||||||
|
// - writing resources.
|
||||||
|
// - or subresource requests, e.g. `pods/log`
|
||||||
|
// We firstly find the resource from cache, and get the located cluster. Then redirect the request to the cluster.
|
||||||
|
cluster, err := ctl.store.GetClusterForResource(ctx, gvr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return ctl.clusterProxy.connect(ctx, cluster, path, responder)
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"path"
|
||||||
|
|
||||||
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
|
restclient "k8s.io/client-go/rest"
|
||||||
|
|
||||||
|
"github.com/karmada-io/karmada/pkg/util/proxy"
|
||||||
|
)
|
||||||
|
|
||||||
|
// karmadaProxy is proxy for karmada control panel
|
||||||
|
type karmadaProxy struct {
|
||||||
|
proxyLocation *url.URL
|
||||||
|
proxyTransport http.RoundTripper
|
||||||
|
}
|
||||||
|
|
||||||
|
func newKarmadaProxy(restConfig *restclient.Config) (*karmadaProxy, error) {
|
||||||
|
location, err := url.Parse(restConfig.Host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
transport, err := restclient.TransportFor(restConfig)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &karmadaProxy{
|
||||||
|
proxyLocation: location,
|
||||||
|
proxyTransport: transport,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// connect to Karmada-ApiServer directly
|
||||||
|
func (p *karmadaProxy) connect(proxyPath string, responder rest.Responder) (http.Handler, error) {
|
||||||
|
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
location, transport := p.resourceLocation()
|
||||||
|
location.Path = path.Join(location.Path, proxyPath)
|
||||||
|
location.RawQuery = req.URL.RawQuery
|
||||||
|
|
||||||
|
handler := proxy.NewThrottledUpgradeAwareProxyHandler(location, transport, true, false, responder)
|
||||||
|
handler.ServeHTTP(rw, req)
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *karmadaProxy) resourceLocation() (*url.URL, http.RoundTripper) {
|
||||||
|
location := *p.proxyLocation
|
||||||
|
return &location, p.proxyTransport
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
// clusterCache caches resources for single member cluster
|
||||||
|
type clusterCache struct {
|
||||||
|
}
|
|
@ -0,0 +1,28 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
|
||||||
|
clusterv1alpha1 "github.com/karmada-io/karmada/pkg/apis/cluster/v1alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
// temporarily suppress lint: xxx is unused
|
||||||
|
// TODO: remove it after these structs are used
|
||||||
|
var _ = clusterCache{}
|
||||||
|
var _ = resourceCache{}
|
||||||
|
|
||||||
|
// MultiClusterCache caches resource from multi member clusters
|
||||||
|
type MultiClusterCache struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
// HasResource return whether resource is cached.
|
||||||
|
func (c *MultiClusterCache) HasResource(_ schema.GroupVersionResource) bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetClusterForResource returns which cluster the resource belong to.
|
||||||
|
func (c *MultiClusterCache) GetClusterForResource(ctx context.Context, gvr schema.GroupVersionResource) (*clusterv1alpha1.Cluster, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
package store
|
||||||
|
|
||||||
|
// resourceCache cache one kind resource for single member cluster
|
||||||
|
type resourceCache struct {
|
||||||
|
}
|
|
@ -0,0 +1,130 @@
|
||||||
|
package proxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"crypto/tls"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
|
||||||
|
authenticationv1 "k8s.io/api/authentication/v1"
|
||||||
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||||
|
"k8s.io/apimachinery/pkg/util/proxy"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||||
|
"k8s.io/apiserver/pkg/endpoints/request"
|
||||||
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
|
|
||||||
|
clusterapis "github.com/karmada-io/karmada/pkg/apis/cluster"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewThrottledUpgradeAwareProxyHandler creates a new proxy handler with a default flush interval. Responder is required for returning
|
||||||
|
// errors to the caller.
|
||||||
|
func NewThrottledUpgradeAwareProxyHandler(location *url.URL, transport http.RoundTripper, wrapTransport, upgradeRequired bool, responder rest.Responder) *proxy.UpgradeAwareHandler {
|
||||||
|
return proxy.NewUpgradeAwareHandler(location, transport, wrapTransport, upgradeRequired, proxy.NewErrorResponder(responder))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectCluster returns a handler for proxy cluster.
|
||||||
|
func ConnectCluster(ctx context.Context, clusterName string, location *url.URL, transport http.RoundTripper, responder rest.Responder,
|
||||||
|
impersonateSecretGetter func(context.Context, string) (*corev1.Secret, error)) (http.Handler, error) {
|
||||||
|
secret, err := impersonateSecretGetter(ctx, clusterName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
impersonateToken, err := getImpersonateToken(clusterName, secret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get impresonateToken for cluster %s: %v", clusterName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return newProxyHandler(location, transport, impersonateToken, responder)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Location returns a URL to which one can send traffic for the specified cluster.
|
||||||
|
func Location(clusterName string, apiEndpoint string, proxyURL string) (*url.URL, http.RoundTripper, error) {
|
||||||
|
location, err := constructLocation(clusterName, apiEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
transport, err := createProxyTransport(proxyURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return location, transport, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func constructLocation(clusterName string, apiEndpoint string) (*url.URL, error) {
|
||||||
|
if apiEndpoint == "" {
|
||||||
|
return nil, fmt.Errorf("API endpoint of cluster %s should not be empty", clusterName)
|
||||||
|
}
|
||||||
|
|
||||||
|
uri, err := url.Parse(apiEndpoint)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse api endpoint %s: %v", apiEndpoint, err)
|
||||||
|
}
|
||||||
|
return uri, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createProxyTransport(proxyURL string) (*http.Transport, error) {
|
||||||
|
var proxyDialerFn utilnet.DialFunc
|
||||||
|
proxyTLSClientConfig := &tls.Config{InsecureSkipVerify: true} // #nosec
|
||||||
|
trans := utilnet.SetTransportDefaults(&http.Transport{
|
||||||
|
DialContext: proxyDialerFn,
|
||||||
|
TLSClientConfig: proxyTLSClientConfig,
|
||||||
|
})
|
||||||
|
|
||||||
|
if proxyURL != "" {
|
||||||
|
u, err := url.Parse(proxyURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse url of proxy url %s: %v", proxyURL, err)
|
||||||
|
}
|
||||||
|
trans.Proxy = http.ProxyURL(u)
|
||||||
|
}
|
||||||
|
return trans, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getImpersonateToken(clusterName string, secret *corev1.Secret) (string, error) {
|
||||||
|
token, found := secret.Data[clusterapis.SecretTokenKey]
|
||||||
|
if !found {
|
||||||
|
return "", fmt.Errorf("the impresonate token of cluster %s is empty", clusterName)
|
||||||
|
}
|
||||||
|
return string(token), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func newProxyHandler(location *url.URL, transport http.RoundTripper, impersonateToken string, responder rest.Responder) (http.Handler, error) {
|
||||||
|
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||||
|
requester, exist := request.UserFrom(req.Context())
|
||||||
|
if !exist {
|
||||||
|
responsewriters.InternalError(rw, req, errors.New("no user found for request"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Header.Set(authenticationv1.ImpersonateUserHeader, requester.GetName())
|
||||||
|
for _, group := range requester.GetGroups() {
|
||||||
|
if !skipGroup(group) {
|
||||||
|
req.Header.Add(authenticationv1.ImpersonateGroupHeader, group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("Authorization", fmt.Sprintf("bearer %s", impersonateToken))
|
||||||
|
|
||||||
|
// Retain RawQuery in location because upgrading the request will use it.
|
||||||
|
// See https://github.com/karmada-io/karmada/issues/1618#issuecomment-1103793290 for more info.
|
||||||
|
location.RawQuery = req.URL.RawQuery
|
||||||
|
|
||||||
|
handler := NewThrottledUpgradeAwareProxyHandler(location, transport, true, false, responder)
|
||||||
|
handler.ServeHTTP(rw, req)
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func skipGroup(group string) bool {
|
||||||
|
switch group {
|
||||||
|
case user.AllAuthenticated, user.AllUnauthenticated:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue