Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add allowedPropagatingAPIs option for resource selection #638

Merged
merged 12 commits into from
Dec 21, 2023
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,11 @@ $(GOIMPORTS):
$(ENVTEST):
GOBIN=$(TOOLS_BIN_DIR) $(GO_INSTALL) sigs.k8s.io/controller-runtime/tools/setup-envtest $(ENVTEST_BIN) $(ENVTEST_VER)

.PHONY: help
help: ## Display this help.
@awk 'BEGIN {FS = ":.*##"; printf "\nUsage:\n make \033[36m<target>\033[0m\n"} /^[a-zA-Z_0-9-]+:.*?##/ { printf " \033[36m%-15s\033[0m %s\n", $$1, $$2 } /^##@/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 5) } ' $(MAKEFILE_LIST)


## --------------------------------------
## Linting
## --------------------------------------
Expand Down Expand Up @@ -330,4 +335,4 @@ clean-e2e-tests-v1alpha1:

.PHONY: clean-e2e-tests
clean-e2e-tests:
cd ./test/e2e && chmod +x ./stop.sh && ./stop.sh $(MEMBER_CLUSTER_COUNT)
cd ./test/e2e && chmod +x ./stop.sh && ./stop.sh $(MEMBER_CLUSTER_COUNT)
2 changes: 1 addition & 1 deletion apis/placement/v1beta1/zz_generated.deepcopy.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 8 additions & 1 deletion cmd/hubagent/options/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,11 @@ type Options struct {
// WorkPendingGracePeriod represents the grace period after a work is created/updated.
// We consider a work failed if a work's last applied condition doesn't change after period.
WorkPendingGracePeriod metav1.Duration
vasudev-bongale marked this conversation as resolved.
Show resolved Hide resolved
// SkippedPropagatingAPIs indicates comma separated resources that should be skipped for propagating.
// SkippedPropagatingAPIs indicates semicolon separated resources that should be skipped for propagating.
SkippedPropagatingAPIs string
// AllowedPropagatingAPIs indicates semicolon separated resources that should be allowed for propagating.
// This is mutually exclusive with SkippedPropagatingAPIs.
AllowedPropagatingAPIs string
// SkippedPropagatingNamespaces is a list of namespaces that will be skipped for propagating.
SkippedPropagatingNamespaces string
// HubQPS is the QPS to use while talking with hub-apiserver. Default is 20.0.
Expand Down Expand Up @@ -107,6 +110,10 @@ func (o *Options) AddFlags(flags *flag.FlagSet) {
flags.DurationVar(&o.ClusterUnhealthyThreshold.Duration, "cluster-unhealthy-threshold", 60*time.Second, "The duration for a member cluster to be in a degraded state before considered unhealthy.")
flags.DurationVar(&o.WorkPendingGracePeriod.Duration, "work-pending-grace-period", 15*time.Second,
"Specifies the grace period of allowing a manifest to be pending before marking it as failed.")
flags.StringVar(&o.AllowedPropagatingAPIs, "allowed-propagating-apis", "", "Semicolon separated resources that should be allowed for propagation. Supported formats are:\n"+
"<group> for allowing resources with a specific API group(e.g. networking.k8s.io),\n"+
"<group>/<version> for allowing resources with a specific API version(e.g. networking.k8s.io/v1beta1),\n"+
"<group>/<version>/<kind>,<kind> for allowing one or more specific resources (e.g. networking.k8s.io/v1beta1/Ingress,IngressClass) where the Kinds are case-insensitive.")
flags.StringVar(&o.SkippedPropagatingAPIs, "skipped-propagating-apis", "", "Semicolon separated resources that should be skipped from propagating in addition to the default skip list(cluster.fleet.io;policy.fleet.io;work.fleet.io). Supported formats are:\n"+
"<group> for skip resources with a specific API group(e.g. networking.k8s.io),\n"+
"<group>/<version> for skip resources with a specific API version(e.g. networking.k8s.io/v1beta1),\n"+
Expand Down
12 changes: 10 additions & 2 deletions cmd/hubagent/options/validation.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,18 @@ func (o *Options) Validate() field.ErrorList {
errs := field.ErrorList{}
newPath := field.NewPath("Options")

disabledResourceConfig := utils.NewDisabledResourceConfig()
if err := disabledResourceConfig.Parse(o.SkippedPropagatingAPIs); err != nil {
if o.AllowedPropagatingAPIs != "" && o.SkippedPropagatingAPIs != "" {
errs = append(errs, field.Invalid(newPath.Child("AllowedPropagatingAPIs"), o.AllowedPropagatingAPIs, "AllowedPropagatingAPIs and SkippedPropagatingAPIs are mutually exclusive"))
}

resourceConfig := utils.NewResourceConfig(false)
vasudev-bongale marked this conversation as resolved.
Show resolved Hide resolved
if err := resourceConfig.Parse(o.SkippedPropagatingAPIs); err != nil {
errs = append(errs, field.Invalid(newPath.Child("SkippedPropagatingAPIs"), o.SkippedPropagatingAPIs, "Invalid API string"))
}
if err := resourceConfig.Parse(o.AllowedPropagatingAPIs); err != nil {
errs = append(errs, field.Invalid(newPath.Child("AllowedPropagatingAPIs"), o.AllowedPropagatingAPIs, "Invalid API string"))
}

if o.ClusterUnhealthyThreshold.Duration <= 0 {
errs = append(errs, field.Invalid(newPath.Child("ClusterUnhealthyThreshold"), o.ClusterUnhealthyThreshold, "Must be greater than 0"))
}
Expand Down
25 changes: 14 additions & 11 deletions cmd/hubagent/workload/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,8 +105,11 @@ func SetupControllers(ctx context.Context, wg *sync.WaitGroup, mgr ctrl.Manager,
}
}

disabledResourceConfig := utils.NewDisabledResourceConfig()
if err := disabledResourceConfig.Parse(opts.SkippedPropagatingAPIs); err != nil {
resourceConfig := utils.NewResourceConfig(opts.AllowedPropagatingAPIs != "")
vasudev-bongale marked this conversation as resolved.
Show resolved Hide resolved
if err := resourceConfig.Parse(opts.AllowedPropagatingAPIs); err != nil {
return err
vasudev-bongale marked this conversation as resolved.
Show resolved Hide resolved
}
if err := resourceConfig.Parse(opts.SkippedPropagatingAPIs); err != nil {
// The program will never go here because the parameters have been checked
return err
}
Expand All @@ -128,14 +131,14 @@ func SetupControllers(ctx context.Context, wg *sync.WaitGroup, mgr ctrl.Manager,

// Set up a custom controller to reconcile cluster resource placement
crpc := &clusterresourceplacement.Reconciler{
Client: mgr.GetClient(),
Recorder: mgr.GetEventRecorderFor(crpControllerName),
RestMapper: mgr.GetRESTMapper(),
InformerManager: dynamicInformerManager,
DisabledResourceConfig: disabledResourceConfig,
SkippedNamespaces: skippedNamespaces,
Scheme: mgr.GetScheme(),
UncachedReader: mgr.GetAPIReader(),
Client: mgr.GetClient(),
Recorder: mgr.GetEventRecorderFor(crpControllerName),
RestMapper: mgr.GetRESTMapper(),
InformerManager: dynamicInformerManager,
ResourceConfig: resourceConfig,
SkippedNamespaces: skippedNamespaces,
Scheme: mgr.GetScheme(),
UncachedReader: mgr.GetAPIReader(),
}

rateLimiter := options.DefaultControllerRateLimiter(opts.RateLimiterOpts)
Expand Down Expand Up @@ -269,7 +272,7 @@ func SetupControllers(ctx context.Context, wg *sync.WaitGroup, mgr ctrl.Manager,
ResourceChangeController: resourceChangeController,
MemberClusterPlacementController: memberClusterPlacementController,
InformerManager: dynamicInformerManager,
DisabledResourceConfig: disabledResourceConfig,
ResourceConfig: resourceConfig,
SkippedNamespaces: skippedNamespaces,
ConcurrentClusterPlacementWorker: opts.ConcurrentClusterPlacementSyncs,
ConcurrentResourceChangeWorker: opts.ConcurrentResourceChangeSyncs,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ type Reconciler struct {
// It's only needed by v1beta1 APIs.
UncachedReader client.Reader

// DisabledResourceConfig contains all the api resources that we won't select.
DisabledResourceConfig *utils.DisabledResourceConfig
// ResourceConfig contains all the api resources that we won't select based on allowed or skipped propagating apis option.
ResourceConfig *utils.ResourceConfig

// SkippedNamespaces contains the namespaces that we should not propagate.
SkippedNamespaces map[string]bool
Expand Down
8 changes: 4 additions & 4 deletions pkg/controllers/clusterresourceplacement/resource_selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func (r *Reconciler) gatherSelectedResource(placement string, selectors []fleetv
Kind: selector.Kind,
}

if r.DisabledResourceConfig.IsResourceDisabled(gvk) {
if r.ResourceConfig.IsResourceDisabled(gvk) {
klog.V(2).InfoS("Skip select resource", "group version kind", gvk.String())
continue
}
Expand Down Expand Up @@ -287,9 +287,9 @@ func (r *Reconciler) fetchAllResourcesInOneNamespace(namespaceName string, place
return resources, nil
}

// shouldSelectResource returns whether a resource should be propagated
// shouldSelectResource returns whether a resource should be selected for propagation.
func (r *Reconciler) shouldSelectResource(gvr schema.GroupVersionResource) bool {
if r.DisabledResourceConfig == nil {
if r.ResourceConfig.IsEmpty() {
vasudev-bongale marked this conversation as resolved.
Show resolved Hide resolved
return true
}
gvks, err := r.RestMapper.KindsFor(gvr)
Expand All @@ -298,7 +298,7 @@ func (r *Reconciler) shouldSelectResource(gvr schema.GroupVersionResource) bool
return false
}
for _, gvk := range gvks {
if r.DisabledResourceConfig.IsResourceDisabled(gvk) {
if r.ResourceConfig.IsResourceDisabled(gvk) {
klog.V(2).InfoS("Skip watch resource", "group version kind", gvk.String())
return false
}
Expand Down
14 changes: 7 additions & 7 deletions pkg/controllers/clusterresourceplacement/suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,13 @@ var _ = BeforeSuite(func() {
Expect(err).Should(Succeed(), "failed to create manager")

reconciler := &Reconciler{
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
UncachedReader: mgr.GetAPIReader(),
Recorder: mgr.GetEventRecorderFor(controllerName),
RestMapper: mgr.GetRESTMapper(),
InformerManager: informer.NewInformerManager(dynamicClient, 5*time.Minute, ctx.Done()),
DisabledResourceConfig: utils.NewDisabledResourceConfig(),
Client: mgr.GetClient(),
Scheme: mgr.GetScheme(),
UncachedReader: mgr.GetAPIReader(),
Recorder: mgr.GetEventRecorderFor(controllerName),
RestMapper: mgr.GetRESTMapper(),
InformerManager: informer.NewInformerManager(dynamicClient, 5*time.Minute, ctx.Done()),
ResourceConfig: utils.NewResourceConfig(false),
SkippedNamespaces: map[string]bool{
"default": true,
},
Expand Down
8 changes: 4 additions & 4 deletions pkg/resourcewatcher/change_dector.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,8 @@ type ChangeDetector struct {
// InformerManager manages all the dynamic informers created by the discovery client
InformerManager informer.Manager

// DisabledResourceConfig contains all the api resources that we won't select
DisabledResourceConfig *utils.DisabledResourceConfig
// ResourceConfig contains all the api resources that we won't select based on the allowed or skipped propagating apis option.
ResourceConfig *utils.ResourceConfig

// SkippedNamespaces contains all the namespaces that we won't select
SkippedNamespaces map[string]bool
Expand Down Expand Up @@ -186,7 +186,7 @@ func (d *ChangeDetector) discoverResources(dynamicResourceEventHandler cache.Res

// gvrDisabled returns whether GroupVersionResource is disabled.
func (d *ChangeDetector) shouldWatchResource(gvr schema.GroupVersionResource) bool {
if d.DisabledResourceConfig == nil {
if d.ResourceConfig.IsEmpty() {
vasudev-bongale marked this conversation as resolved.
Show resolved Hide resolved
return true
}

Expand All @@ -196,7 +196,7 @@ func (d *ChangeDetector) shouldWatchResource(gvr schema.GroupVersionResource) bo
return false
}
for _, gvk := range gvks {
if d.DisabledResourceConfig.IsResourceDisabled(gvk) {
if d.ResourceConfig.IsResourceDisabled(gvk) {
klog.V(4).InfoS("Skip watch resource", "group version kind", gvk.String())
return false
}
Expand Down
54 changes: 39 additions & 15 deletions pkg/utils/apiresources.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,23 +44,32 @@ var (
apiGroupSepToken = ";"
)

// DisabledResourceConfig represents the configuration that identifies the API resources should not be selected.
type DisabledResourceConfig struct {
// groups holds a collection of API group, all resources under this group will be avoided.
// ResourceConfig represents the configuration that identifies the API resources that are parsed from the
// user input to either allow or disable propagating them.
type ResourceConfig struct {
zhiying-lin marked this conversation as resolved.
Show resolved Hide resolved
// groups holds a collection of API group, all resources under this group will be considered.
groups map[string]struct{}
// groupVersions holds a collection of API GroupVersion, all resource under this GroupVersion will be avoided.
// groupVersions holds a collection of API GroupVersion, all resource under this GroupVersion will be considered.
groupVersions map[schema.GroupVersion]struct{}
// groupVersionKinds holds a collection of resource that should be avoided.
// groupVersionKinds holds a collection of resource that should be considered.
groupVersionKinds map[schema.GroupVersionKind]struct{}
// isAllowList indicates whether the ResourceConfig is an allow list or not.
isAllowList bool
}

// NewDisabledResourceConfig to create DisabledResourceConfig
func NewDisabledResourceConfig() *DisabledResourceConfig {
r := &DisabledResourceConfig{
// NewResourceConfig creates an empty ResourceConfig with an allow list flag.
// if the resourceConfig is not an allowlist, we add fleet related resources
vasudev-bongale marked this conversation as resolved.
Show resolved Hide resolved
// and default built-in resources to the config.
func NewResourceConfig(isAllowList bool) *ResourceConfig {
r := &ResourceConfig{
groups: map[string]struct{}{},
groupVersions: map[schema.GroupVersion]struct{}{},
groupVersionKinds: map[schema.GroupVersionKind]struct{}{},
}
r.isAllowList = isAllowList
if r.isAllowList {
return r
}
// disable fleet related resource by default
r.DisableGroup(fleetv1alpha1.GroupVersion.Group)
r.DisableGroup(placementv1beta1.GroupVersion.Group)
Expand All @@ -77,8 +86,8 @@ func NewDisabledResourceConfig() *DisabledResourceConfig {
return r
}

// Parse parses the --avoid-selecting-apis input.
func (r *DisabledResourceConfig) Parse(c string) error {
// Parse parses the user inputs that provides apis as GVK, GV or Group.
func (r *ResourceConfig) Parse(c string) error {
// default(empty) input
if c == "" {
return nil
Expand All @@ -95,7 +104,7 @@ func (r *DisabledResourceConfig) Parse(c string) error {
}

// TODO: reduce cyclo
func (r *DisabledResourceConfig) parseSingle(token string) error {
func (r *ResourceConfig) parseSingle(token string) error {
switch strings.Count(token, "/") {
// Assume user don't want to skip the 'core'(no group name) group.
// So, it should be the case "<group>".
Expand Down Expand Up @@ -164,7 +173,17 @@ func (r *DisabledResourceConfig) parseSingle(token string) error {

// IsResourceDisabled returns whether a given GroupVersionKind is disabled.
// a gkv is disabled if its group or group version is disabled
vasudev-bongale marked this conversation as resolved.
Show resolved Hide resolved
func (r *DisabledResourceConfig) IsResourceDisabled(gvk schema.GroupVersionKind) bool {
vasudev-bongale marked this conversation as resolved.
Show resolved Hide resolved
func (r *ResourceConfig) IsResourceDisabled(gvk schema.GroupVersionKind) bool {
isConfigured := r.isResourceConfigured(gvk)
if r.isAllowList {
return !isConfigured
}
return isConfigured
}

// isResourceConfigured returns whether a given GroupVersionKind is found in the ResourceConfig.
// a gvk is configured if its group or group version is configured
vasudev-bongale marked this conversation as resolved.
Show resolved Hide resolved
func (r *ResourceConfig) isResourceConfigured(gvk schema.GroupVersionKind) bool {
if _, ok := r.groups[gvk.Group]; ok {
return true
}
Expand All @@ -181,16 +200,21 @@ func (r *DisabledResourceConfig) IsResourceDisabled(gvk schema.GroupVersionKind)
}

// DisableGroup to disable group.
func (r *DisabledResourceConfig) DisableGroup(g string) {
func (r *ResourceConfig) DisableGroup(g string) {
vasudev-bongale marked this conversation as resolved.
Show resolved Hide resolved
r.groups[g] = struct{}{}
}

// DisableGroupVersion to disable group version.
func (r *DisabledResourceConfig) DisableGroupVersion(gv schema.GroupVersion) {
func (r *ResourceConfig) DisableGroupVersion(gv schema.GroupVersion) {
vasudev-bongale marked this conversation as resolved.
Show resolved Hide resolved
r.groupVersions[gv] = struct{}{}
}

// DisableGroupVersionKind to disable GroupVersionKind.
func (r *DisabledResourceConfig) DisableGroupVersionKind(gvk schema.GroupVersionKind) {
func (r *ResourceConfig) DisableGroupVersionKind(gvk schema.GroupVersionKind) {
vasudev-bongale marked this conversation as resolved.
Show resolved Hide resolved
r.groupVersionKinds[gvk] = struct{}{}
}

// IsEmpty returns whether the ResourceConfig is empty.
func (r *ResourceConfig) IsEmpty() bool {
return len(r.groups) == 0 && len(r.groupVersions) == 0 && len(r.groupVersionKinds) == 0
}
Loading
Loading