Skip to content

Commit

Permalink
Ansible operator: Adding support for event filtering via predicates
Browse files Browse the repository at this point in the history
Event filtering will allow Ansible operator to skip reconciles that are not required

Fixes operator-framework#1968
  • Loading branch information
Rammohan authored and rammohanc committed Oct 8, 2019
1 parent b5125fa commit a82652d
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 37 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

### Added

- Added support for event filtering for ansible operator and added enableControllerWatchPredicates option for watches.yaml. ([#1968](https://github.com/operator-framework/operator-sdk/issues/1968))
- Added new `--skip-generation` flag to the `operator-sdk add api` command to support skipping generation of deepcopy and OpenAPI code and OpenAPI CRD specs. ([#1890](https://github.com/operator-framework/operator-sdk/pull/1890))
- The `operator-sdk olm-catalog gen-csv` command now produces indented JSON for the `alm-examples` annotation. ([#1793](https://github.com/operator-framework/operator-sdk/pull/1793))
- Added flag `--dep-manager` to command [`operator-sdk print-deps`](https://github.com/operator-framework/operator-sdk/blob/master/doc/sdk-cli-reference.md#print-deps) to specify the type of dependency manager file to print. The choice of dependency manager is inferred from top-level dependency manager files present if `--dep-manager` is not set. ([#1819](https://github.com/operator-framework/operator-sdk/pull/1819))
Expand Down
1 change: 1 addition & 0 deletions doc/ansible/dev/advanced_options.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Some features can be overridden per resource via an annotation on that CR. The o
| Reconcile Period | `reconcilePeriod` | time between reconcile runs for a particular CR | ansbile.operator-sdk/reconcile-period | 1m | |
| Manage Status | `manageStatus` | Allows the ansible operator to manage the conditions section of each resource's status section. | | true | |
| Watching Dependent Resources | `watchDependentResources` | Allows the ansible operator to dynamically watch resources that are created by ansible | | true | [dependent_watches.md](dependent_watches.md) |
| Enable Controller Watch Predicates | `enableControllerWatchPredicates` | Allows the ansible operator to add predicates to the controller watch | | false | [event_filtering.md](../../user/event-filtering.md) |
| Watching Cluster-Scoped Resources | `watchClusterScopedResources` | Allows the ansible operator to watch cluster-scoped resources that are created by ansible | | false | |
| Max Runner Artifacts | `maxRunnerArtifacts` | Manages the number of [artifact directories](https://ansible-runner.readthedocs.io/en/latest/intro.html#runner-artifacts-directory-hierarchy) that ansible runner will keep in the operator container for each individual resource. | ansible.operator-sdk/max-runner-artifacts | 20 | |
| Finalizer | `finalizer` | Sets a finalizer on the CR and maps a deletion event to a playbook or role | | | [finalizers.md](finalizers.md)|
Expand Down
11 changes: 6 additions & 5 deletions pkg/ansible/proxy/controllermap/controllermap.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,12 @@ type WatchMap struct {

// Contents - Contains internal data associated with each controller
type Contents struct {
Controller controller.Controller
WatchDependentResources bool
WatchClusterScopedResources bool
OwnerWatchMap *WatchMap
AnnotationWatchMap *WatchMap
Controller controller.Controller
WatchDependentResources bool
WatchClusterScopedResources bool
EnableControllerWatchPredicates bool
OwnerWatchMap *WatchMap
AnnotationWatchMap *WatchMap
}

// NewControllerMap returns a new object that contains a mapping between GVK
Expand Down
64 changes: 62 additions & 2 deletions pkg/ansible/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (
"net/http"
"strings"
"sync"
"reflect"

"github.com/operator-framework/operator-sdk/pkg/ansible/proxy/controllermap"
"github.com/operator-framework/operator-sdk/pkg/ansible/proxy/kubeconfig"
Expand All @@ -40,6 +41,8 @@ import (
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/cache"
"sigs.k8s.io/controller-runtime/pkg/handler"
"sigs.k8s.io/controller-runtime/pkg/event"
"sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/source"
)

Expand Down Expand Up @@ -210,6 +213,53 @@ func addWatchToController(owner kubeconfig.NamespacedOwnerReference, cMap *contr
awMap := contents.AnnotationWatchMap
u := &unstructured.Unstructured{}
u.SetGroupVersionKind(ownerMapping.GroupVersionKind)

//Adding dependentPredicate for avoiding reconciles when dependent objects are not changed by user (borrowed from Helm operator)
dependentPredicate := predicate.Funcs{
// We don't need to reconcile dependent resource creation events
// because dependent resources are only ever created during
// reconciliation. Another reconcile would be redundant.
CreateFunc: func(e event.CreateEvent) bool {
o := e.Object.(*unstructured.Unstructured)
log.V(1).Info("Skipping reconciliation for dependent resource creation", "name", o.GetName(), "namespace", o.GetNamespace(), "apiVersion", o.GroupVersionKind().GroupVersion(), "kind", o.GroupVersionKind().Kind)
return false
},

// Reconcile when a dependent resource is deleted so that it can be
// recreated.
DeleteFunc: func(e event.DeleteEvent) bool {
o := e.Object.(*unstructured.Unstructured)
log.V(1).Info("Reconciling due to dependent resource deletion", "name", o.GetName(), "namespace", o.GetNamespace(), "apiVersion", o.GroupVersionKind().GroupVersion(), "kind", o.GroupVersionKind().Kind)
return true
},

// Don't reconcile when a generic event is received for a dependent
GenericFunc: func(e event.GenericEvent) bool {
o := e.Object.(*unstructured.Unstructured)
log.V(1).Info("Skipping reconcile due to generic event", "name", o.GetName(), "namespace", o.GetNamespace(), "apiVersion", o.GroupVersionKind().GroupVersion(), "kind", o.GroupVersionKind().Kind)
return false
},

// Reconcile when a dependent resource is updated, so that it can
// be patched back to the resource managed by the Helm release, if
// necessary. Ignore updates that only change the status and
// resourceVersion.
UpdateFunc: func(e event.UpdateEvent) bool {
old := e.ObjectOld.(*unstructured.Unstructured).DeepCopy()
new := e.ObjectNew.(*unstructured.Unstructured).DeepCopy()

delete(old.Object, "status")
delete(new.Object, "status")
old.SetResourceVersion("")
new.SetResourceVersion("")

if reflect.DeepEqual(old.Object, new.Object) {
return false
}
log.V(1).Info("Reconciling due to dependent resource update", "name", new.GetName(), "namespace", new.GetNamespace(), "apiVersion", new.GroupVersionKind().GroupVersion(), "kind", new.GroupVersionKind().Kind)
return true
},
}
// Add a watch to controller
if contents.WatchDependentResources {
// Store watch in map
Expand All @@ -224,8 +274,13 @@ func addWatchToController(owner kubeconfig.NamespacedOwnerReference, cMap *contr

owMap.Store(resource.GroupVersionKind())
log.Info("Watching child resource", "kind", resource.GroupVersionKind(), "enqueue_kind", u.GroupVersionKind())

if contents.EnableControllerWatchPredicates {
err = contents.Controller.Watch(&source.Kind{Type: resource}, &handler.EnqueueRequestForOwner{OwnerType: u}, dependentPredicate)
} else {
err = contents.Controller.Watch(&source.Kind{Type: resource}, &handler.EnqueueRequestForOwner{OwnerType: u})
}
// Store watch in map
err := contents.Controller.Watch(&source.Kind{Type: resource}, &handler.EnqueueRequestForOwner{OwnerType: u})
if err != nil {
return err
}
Expand All @@ -238,7 +293,12 @@ func addWatchToController(owner kubeconfig.NamespacedOwnerReference, cMap *contr
awMap.Store(resource.GroupVersionKind())
typeString := fmt.Sprintf("%v.%v", owner.Kind, ownerGV.Group)
log.Info("Watching child resource", "kind", resource.GroupVersionKind(), "enqueue_annotation_type", typeString)
err = contents.Controller.Watch(&source.Kind{Type: resource}, &osdkHandler.EnqueueRequestForAnnotation{Type: typeString})

if contents.EnableControllerWatchPredicates {
err = contents.Controller.Watch(&source.Kind{Type: resource}, &osdkHandler.EnqueueRequestForAnnotation{Type: typeString}, dependentPredicate)
} else {
err = contents.Controller.Watch(&source.Kind{Type: resource}, &osdkHandler.EnqueueRequestForAnnotation{Type: typeString})
}
if err != nil {
return err
}
Expand Down
9 changes: 5 additions & 4 deletions pkg/ansible/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,10 +115,11 @@ func Run(flags *aoflags.AnsibleOperatorFlags) error {
}

cMap.Store(w.GroupVersionKind, &controllermap.Contents{Controller: *ctr,
WatchDependentResources: w.WatchDependentResources,
WatchClusterScopedResources: w.WatchClusterScopedResources,
OwnerWatchMap: controllermap.NewWatchMap(),
AnnotationWatchMap: controllermap.NewWatchMap(),
WatchDependentResources: w.WatchDependentResources,
WatchClusterScopedResources: w.WatchClusterScopedResources,
EnableControllerWatchPredicates: w.EnableControllerWatchPredicates,
OwnerWatchMap: controllermap.NewWatchMap(),
AnnotationWatchMap: controllermap.NewWatchMap(),
})
gvks = append(gvks, w.GroupVersionKind)
}
Expand Down
57 changes: 31 additions & 26 deletions pkg/ansible/watches/watches.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,16 @@ var log = logf.Log.WithName("watches")
// Watch - holds data used to create a mapping of GVK to ansible playbook or role.
// The mapping is used to compose an ansible operator.
type Watch struct {
GroupVersionKind schema.GroupVersionKind `yaml:",inline"`
Playbook string `yaml:"playbook"`
Role string `yaml:"role"`
MaxRunnerArtifacts int `yaml:"maxRunnerArtifacts"`
ReconcilePeriod time.Duration `yaml:"reconcilePeriod"`
ManageStatus bool `yaml:"manageStatus"`
WatchDependentResources bool `yaml:"watchDependentResources"`
WatchClusterScopedResources bool `yaml:"watchClusterScopedResources"`
Finalizer *Finalizer `yaml:"finalizer"`
GroupVersionKind schema.GroupVersionKind `yaml:",inline"`
Playbook string `yaml:"playbook"`
Role string `yaml:"role"`
MaxRunnerArtifacts int `yaml:"maxRunnerArtifacts"`
ReconcilePeriod time.Duration `yaml:"reconcilePeriod"`
ManageStatus bool `yaml:"manageStatus"`
WatchDependentResources bool `yaml:"watchDependentResources"`
WatchClusterScopedResources bool `yaml:"watchClusterScopedResources"`
EnableControllerWatchPredicates bool `yaml:"enableControllerWatchPredicates"`
Finalizer *Finalizer `yaml:"finalizer"`
}

// Finalizer - Expose finalizer to be used by a user.
Expand All @@ -54,29 +55,31 @@ type Finalizer struct {

// Default values for optional fields on Watch
const (
ManageStatusDefault bool = true
WatchDependentResourcesDefault bool = true
MaxRunnerArtifactsDefault int = 20
ReconcilePeriodDefault string = "0s"
ReconcilePeriodDurationDefault time.Duration = time.Duration(0)
WatchClusterScopedResourcesDefault bool = false
ManageStatusDefault bool = true
WatchDependentResourcesDefault bool = true
MaxRunnerArtifactsDefault int = 20
ReconcilePeriodDefault string = "0s"
ReconcilePeriodDurationDefault time.Duration = time.Duration(0)
WatchClusterScopedResourcesDefault bool = false
EnableControllerWatchPredicatesDefault bool = false
)

// UnmarshalYAML - implements the yaml.Unmarshaler interface for Watch
func (w *Watch) UnmarshalYAML(unmarshal func(interface{}) error) error {
// Use an alias struct to handle complex types
type alias struct {
Group string `yaml:"group"`
Version string `yaml:"version"`
Kind string `yaml:"kind"`
Playbook string `yaml:"playbook"`
Role string `yaml:"role"`
MaxRunnerArtifacts int `yaml:"maxRunnerArtifacts"`
ReconcilePeriod string `yaml:"reconcilePeriod"`
ManageStatus bool `yaml:"manageStatus"`
WatchDependentResources bool `yaml:"watchDependentResources"`
WatchClusterScopedResources bool `yaml:"watchClusterScopedResources"`
Finalizer *Finalizer `yaml:"finalizer"`
Group string `yaml:"group"`
Version string `yaml:"version"`
Kind string `yaml:"kind"`
Playbook string `yaml:"playbook"`
Role string `yaml:"role"`
MaxRunnerArtifacts int `yaml:"maxRunnerArtifacts"`
ReconcilePeriod string `yaml:"reconcilePeriod"`
ManageStatus bool `yaml:"manageStatus"`
WatchDependentResources bool `yaml:"watchDependentResources"`
WatchClusterScopedResources bool `yaml:"watchClusterScopedResources"`
EnableControllerWatchPredicates bool `yaml:"enableControllerWatchPredicates"`
Finalizer *Finalizer `yaml:"finalizer"`
}
var tmp alias

Expand All @@ -87,6 +90,7 @@ func (w *Watch) UnmarshalYAML(unmarshal func(interface{}) error) error {
tmp.MaxRunnerArtifacts = MaxRunnerArtifactsDefault
tmp.ReconcilePeriod = ReconcilePeriodDefault
tmp.WatchClusterScopedResources = WatchClusterScopedResourcesDefault
tmp.EnableControllerWatchPredicates = EnableControllerWatchPredicatesDefault

if err := unmarshal(&tmp); err != nil {
return err
Expand Down Expand Up @@ -116,6 +120,7 @@ func (w *Watch) UnmarshalYAML(unmarshal func(interface{}) error) error {
w.ManageStatus = tmp.ManageStatus
w.WatchDependentResources = tmp.WatchDependentResources
w.WatchClusterScopedResources = tmp.WatchClusterScopedResources
w.EnableControllerWatchPredicates = tmp.EnableControllerWatchPredicates
w.Finalizer = tmp.Finalizer

return nil
Expand Down

0 comments on commit a82652d

Please sign in to comment.