Merge pull request #934 from XiShanYongYe-Chang/resource-explore-interface

Add interface design for Resource Explore Webhook
This commit is contained in:
karmada-bot 2021-11-11 14:50:16 +08:00 committed by GitHub
commit f7f53ee13f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 434 additions and 0 deletions

View File

@ -0,0 +1,76 @@
package crdexplorer
import (
"context"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/klog/v2"
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
"github.com/karmada-io/karmada/pkg/crdexplorer/customizedexplorer"
"github.com/karmada-io/karmada/pkg/crdexplorer/defaultexplorer"
"github.com/karmada-io/karmada/pkg/util/informermanager"
)
// CustomResourceExplorer manages both default and customized webhooks to explore custom resource structure.
type CustomResourceExplorer interface {
// Start starts running the component and will never stop running until the context is closed or an error occurs.
Start(ctx context.Context) (err error)
// GetReplicas returns the desired replicas of the object as well as the requirements of each replica.
GetReplicas(object runtime.Object) (replica int32, replicaRequires *workv1alpha2.ReplicaRequirements, err error)
// GetHealthy tells if the object in healthy state.
GetHealthy(object runtime.Object) (healthy bool, err error)
// other common method
}
// NewCustomResourceExplore return a new CustomResourceExplorer object.
func NewCustomResourceExplore(kubeconfig string, informer informermanager.SingleClusterInformerManager) CustomResourceExplorer {
return &customResourceExplorerImpl{
kubeconfig: kubeconfig,
informer: informer,
}
}
type customResourceExplorerImpl struct {
kubeconfig string
informer informermanager.SingleClusterInformerManager
customizedExplorer *customizedexplorer.CustomizedExplorer
defaultExplorer *defaultexplorer.DefaultExplorer
}
// Start starts running the component and will never stop running until the context is closed or an error occurs.
func (i *customResourceExplorerImpl) Start(ctx context.Context) (err error) {
klog.Infof("Starting custom resource explorer.")
i.customizedExplorer, err = customizedexplorer.NewCustomizedExplorer(i.kubeconfig, i.informer)
if err != nil {
return
}
i.defaultExplorer = defaultexplorer.NewDefaultExplorer()
i.informer.Start()
<-ctx.Done()
klog.Infof("Stopped as stopCh closed.")
return nil
}
// GetReplicas returns the desired replicas of the object as well as the requirements of each replica.
func (i *customResourceExplorerImpl) GetReplicas(object runtime.Object) (replica int32, replicaRequires *workv1alpha2.ReplicaRequirements, err error) {
var hookMatched bool
replica, replicaRequires, hookMatched, err = i.customizedExplorer.GetReplicas(context.TODO(), configv1alpha1.ExploreReplica, object)
if hookMatched {
return
}
replica, replicaRequires, err = i.defaultExplorer.GetReplicas(object)
return
}
// GetHealthy tells if the object in healthy state.
func (i *customResourceExplorerImpl) GetHealthy(object runtime.Object) (healthy bool, err error) {
return false, nil
}

View File

@ -0,0 +1,71 @@
package configmanager
import (
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
)
// WebhookAccessor provides a common interface to get webhook configuration.
type WebhookAccessor interface {
// GetUID gets a string that uniquely identifies the webhook.
GetUID() string
// GetConfigurationName gets the name of the webhook configuration that owns this webhook.
GetConfigurationName() string
// GetClientConfig gets the webhook ClientConfig field.
GetClientConfig() admissionregistrationv1.WebhookClientConfig
// GetRules gets the webhook Rules field.
GetRules() []configv1alpha1.RuleWithOperations
// GetFailurePolicy gets the webhook FailurePolicy field.
GetFailurePolicy() *admissionregistrationv1.FailurePolicyType
// GetTimeoutSeconds gets the webhook TimeoutSeconds field.
GetTimeoutSeconds() *int32
// GetExploreReviewVersions gets the webhook ExploreReviewVersions field.
GetExploreReviewVersions() []string
}
type resourceExploringAccessor struct {
*configv1alpha1.ResourceExploringWebhook
uid string
configurationName string
}
// NewResourceExploringAccessor create an accessor for webhook.
func NewResourceExploringAccessor(uid, configurationName string, hook *configv1alpha1.ResourceExploringWebhook) WebhookAccessor {
return &resourceExploringAccessor{uid: uid, configurationName: configurationName, ResourceExploringWebhook: hook}
}
// GetUID gets a string that uniquely identifies the webhook.
func (a *resourceExploringAccessor) GetUID() string {
return a.uid
}
// GetConfigurationName gets the name of the webhook configuration that owns this webhook.
func (a *resourceExploringAccessor) GetConfigurationName() string {
return a.configurationName
}
// GetClientConfig gets the webhook ClientConfig field.
func (a *resourceExploringAccessor) GetClientConfig() admissionregistrationv1.WebhookClientConfig {
return a.ClientConfig
}
// GetRules gets the webhook Rules field.
func (a *resourceExploringAccessor) GetRules() []configv1alpha1.RuleWithOperations {
return a.Rules
}
// GetFailurePolicy gets the webhook FailurePolicy field.
func (a *resourceExploringAccessor) GetFailurePolicy() *admissionregistrationv1.FailurePolicyType {
return a.FailurePolicy
}
// GetTimeoutSeconds gets the webhook TimeoutSeconds field.
func (a *resourceExploringAccessor) GetTimeoutSeconds() *int32 {
return a.TimeoutSeconds
}
// GetExploreReviewVersions gets the webhook ExploreReviewVersions field.
func (a *resourceExploringAccessor) GetExploreReviewVersions() []string {
return a.ExploreReviewVersions
}

View File

@ -0,0 +1,94 @@
package configmanager
import (
"fmt"
"sort"
"sync/atomic"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/tools/cache"
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
"github.com/karmada-io/karmada/pkg/util/informermanager"
)
var resourceExploringWebhookConfigurationsGVR = schema.GroupVersionResource{
Group: configv1alpha1.GroupVersion.Group,
Version: configv1alpha1.GroupVersion.Version,
Resource: "resourceexploringwebhookconfigurations",
}
// ConfigManager can list dynamic webhooks.
type ConfigManager interface {
HookAccessors() []WebhookAccessor
HasSynced() bool
}
// exploreConfigManager collect the resource explore webhook configuration.
type exploreConfigManager struct {
configuration *atomic.Value
lister cache.GenericLister
hasSynced func() bool
initialConfigurationSynced *atomic.Value
}
// HookAccessors return all configured resource explore webhook.
func (m *exploreConfigManager) HookAccessors() []WebhookAccessor {
return m.configuration.Load().([]WebhookAccessor)
}
// HasSynced return true when the manager is synced with existing configuration.
func (m *exploreConfigManager) HasSynced() bool {
return false
}
// NewExploreConfigManager return a new exploreConfigManager with resourceexploringwebhookconfigurations handlers.
func NewExploreConfigManager(inform informermanager.SingleClusterInformerManager) ConfigManager {
manager := &exploreConfigManager{
configuration: &atomic.Value{},
lister: inform.Lister(resourceExploringWebhookConfigurationsGVR),
hasSynced: func() bool { return inform.IsInformerSynced(resourceExploringWebhookConfigurationsGVR) },
initialConfigurationSynced: &atomic.Value{},
}
manager.configuration.Store([]WebhookAccessor{})
manager.initialConfigurationSynced.Store(false)
configHandlers := informermanager.NewHandlerOnEvents(
func(_ interface{}) { manager.updateConfiguration() },
func(_, _ interface{}) { manager.updateConfiguration() },
func(_ interface{}) { manager.updateConfiguration() })
inform.ForResource(resourceExploringWebhookConfigurationsGVR, configHandlers)
return manager
}
func (m *exploreConfigManager) updateConfiguration() {
//configurations, err := m.lister.List(labels.Everything())
//if err != nil {
// utilruntime.HandleError(fmt.Errorf("error updating configuration: %v", err))
// return
//}
configurations := make([]configv1alpha1.ResourceExploringWebhookConfiguration, 0)
m.configuration.Store(mergeResourceExploreWebhookConfigurations(configurations))
m.initialConfigurationSynced.Store(true)
}
func mergeResourceExploreWebhookConfigurations(configurations []configv1alpha1.ResourceExploringWebhookConfiguration) []WebhookAccessor {
sort.SliceStable(configurations, func(i, j int) bool {
return configurations[i].Name < configurations[j].Name
})
accessors := make([]WebhookAccessor, len(configurations))
for _, config := range configurations {
names := map[string]int{}
for index, hook := range config.Webhooks {
uid := fmt.Sprintf("%s/%s/%d", config.Name, hook.Name, names[hook.Name])
names[hook.Name]++
accessors = append(accessors, NewResourceExploringAccessor(uid, config.Name, &config.Webhooks[index]))
}
}
return accessors
}

View File

@ -0,0 +1,56 @@
package customizedexplorer
import (
"context"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
webhookutil "k8s.io/apiserver/pkg/util/webhook"
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
"github.com/karmada-io/karmada/pkg/crdexplorer/customizedexplorer/configmanager"
"github.com/karmada-io/karmada/pkg/util/informermanager"
)
// CustomizedExplorer explore custom resource with webhook configuration.
type CustomizedExplorer struct {
hookManager configmanager.ConfigManager
configManager *webhookutil.ClientManager
}
// NewCustomizedExplorer return a new CustomizedExplorer.
func NewCustomizedExplorer(kubeconfig string, informer informermanager.SingleClusterInformerManager) (*CustomizedExplorer, error) {
cm, err := webhookutil.NewClientManager(
[]schema.GroupVersion{configv1alpha1.SchemeGroupVersion},
configv1alpha1.AddToScheme,
)
if err != nil {
return nil, err
}
authInfoResolver, err := webhookutil.NewDefaultAuthenticationInfoResolver(kubeconfig)
if err != nil {
return nil, err
}
cm.SetAuthenticationInfoResolver(authInfoResolver)
cm.SetServiceResolver(webhookutil.NewDefaultServiceResolver())
return &CustomizedExplorer{
hookManager: configmanager.NewExploreConfigManager(informer),
configManager: &cm,
}, nil
}
// GetReplicas returns the desired replicas of the object as well as the requirements of each replica.
// return matched value to indicate whether there is a matching hook.
func (e *CustomizedExplorer) GetReplicas(ctx context.Context, operation configv1alpha1.OperationType,
object runtime.Object) (replica int32, replicaRequires *workv1alpha2.ReplicaRequirements, matched bool, err error) {
return 0, nil, false, err
}
// GetHealthy tells if the object in healthy state.
// return matched value to indicate whether there is a matching hook.
func (e *CustomizedExplorer) GetHealthy(ctx context.Context, operation configv1alpha1.OperationType,
object runtime.Object) (healthy, matched bool, err error) {
return false, false, err
}

View File

@ -0,0 +1,50 @@
package defaultexplorer
import (
"fmt"
appsv1 "k8s.io/api/apps/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
configv1alpha1 "github.com/karmada-io/karmada/pkg/apis/config/v1alpha1"
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
"github.com/karmada-io/karmada/pkg/util"
)
// DefaultExplorer contains all default operation explorer factory
// for exploring common resource.
type DefaultExplorer struct {
replicaHandlers map[schema.GroupVersionKind]replicaFactory
packingHandlers map[schema.GroupVersionKind]packingFactory
healthyHandlers map[schema.GroupVersionKind]healthyFactory
}
// NewDefaultExplorer return a new DefaultExplorer.
func NewDefaultExplorer() *DefaultExplorer {
return &DefaultExplorer{
replicaHandlers: getAllDefaultReplicaExplorer(),
packingHandlers: getAllDefaultPackingExplorer(),
healthyHandlers: getAllDefaultHealthyExplorer(),
}
}
// GetReplicas returns the desired replicas of the object as well as the requirements of each replica.
func (e *DefaultExplorer) GetReplicas(object runtime.Object) (int32, *workv1alpha2.ReplicaRequirements, error) {
// judge object type, and then get correct kind.
_, exist := e.replicaHandlers[appsv1.SchemeGroupVersion.WithKind(util.DeploymentKind)]
if !exist {
return 0, &workv1alpha2.ReplicaRequirements{}, fmt.Errorf("defalut explorer for operation %s not found", configv1alpha1.ExploreReplica)
}
return e.replicaHandlers[appsv1.SchemeGroupVersion.WithKind(util.DeploymentKind)](object)
}
// GetHealthy tells if the object in healthy state.
func (e *DefaultExplorer) GetHealthy(object runtime.Object) (bool, error) {
// judge object type, and then get correct kind.
_, exist := e.healthyHandlers[appsv1.SchemeGroupVersion.WithKind(util.DeploymentKind)]
if !exist {
return false, fmt.Errorf("defalut explorer for operation %s not found", configv1alpha1.ExploreHealthy)
}
return e.healthyHandlers[appsv1.SchemeGroupVersion.WithKind(util.DeploymentKind)](object)
}

View File

@ -0,0 +1,28 @@
package defaultexplorer
import (
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/karmada-io/karmada/pkg/util"
)
// healthyFactory return default healthy factory that tells if the object in healthy state.
type healthyFactory func(object runtime.Object) (bool, error)
func getAllDefaultHealthyExplorer() map[schema.GroupVersionKind]healthyFactory {
explorers := make(map[schema.GroupVersionKind]healthyFactory)
explorers[appsv1.SchemeGroupVersion.WithKind(util.DeploymentKind)] = deployHealthyExplorer
explorers[batchv1.SchemeGroupVersion.WithKind(util.JobKind)] = jobHealthyExplorer
return explorers
}
func deployHealthyExplorer(object runtime.Object) (bool, error) {
return false, nil
}
func jobHealthyExplorer(object runtime.Object) (bool, error) {
return false, nil
}

View File

@ -0,0 +1,29 @@
package defaultexplorer
import (
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/karmada-io/karmada/pkg/util"
)
// packingFactory return default packing factory that can be used to
// retain necessary field and packing from the input object.
type packingFactory func(object runtime.Object) ([]byte, error)
func getAllDefaultPackingExplorer() map[schema.GroupVersionKind]packingFactory {
explorers := make(map[schema.GroupVersionKind]packingFactory)
explorers[appsv1.SchemeGroupVersion.WithKind(util.DeploymentKind)] = deployPackingExplorer
explorers[batchv1.SchemeGroupVersion.WithKind(util.JobKind)] = jobPackingExplorer
return explorers
}
func deployPackingExplorer(object runtime.Object) ([]byte, error) {
return nil, nil
}
func jobPackingExplorer(object runtime.Object) ([]byte, error) {
return nil, nil
}

View File

@ -0,0 +1,30 @@
package defaultexplorer
import (
appsv1 "k8s.io/api/apps/v1"
batchv1 "k8s.io/api/batch/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
workv1alpha2 "github.com/karmada-io/karmada/pkg/apis/work/v1alpha2"
"github.com/karmada-io/karmada/pkg/util"
)
// replicaFactory return default replica factory that can be used to obtain replica
// and requirements by each replica from the input object.
type replicaFactory func(object runtime.Object) (int32, *workv1alpha2.ReplicaRequirements, error)
func getAllDefaultReplicaExplorer() map[schema.GroupVersionKind]replicaFactory {
explorers := make(map[schema.GroupVersionKind]replicaFactory)
explorers[appsv1.SchemeGroupVersion.WithKind(util.DeploymentKind)] = deployReplicaExplorer
explorers[batchv1.SchemeGroupVersion.WithKind(util.JobKind)] = jobReplicaExplorer
return explorers
}
func deployReplicaExplorer(object runtime.Object) (int32, *workv1alpha2.ReplicaRequirements, error) {
return 0, &workv1alpha2.ReplicaRequirements{}, nil
}
func jobReplicaExplorer(object runtime.Object) (int32, *workv1alpha2.ReplicaRequirements, error) {
return 0, &workv1alpha2.ReplicaRequirements{}, nil
}