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

actions: refactor to move template and manifest rendering in a helper #1563

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

ykaliuta
Copy link
Contributor

@ykaliuta ykaliuta commented Jan 24, 2025

Jira: https://issues.redhat.com/browse/RHOAIENG-15921

  • actions: abstract caching functionality into cacher

The caching code for all caching Actions is pretty much the same

  1. hash the key
  2. if the hash is the same as the stored one, return stored resource
  3. otherwise render the resource
  4. store the key and the resource
  5. return the resource

Abstract this logic into Cacher type which is supposed to be
included as a composition in on the higher levels.

The core engine is implemented as Render method which accepts actual
renderer and return boolean value in addition to the resources
indicating if it performed rendering. It supposed to be used in
resource rendering actions for accounting.

Possible usage supposed to be

type Action struct {
        cacher.Cacher
        // action data
}

type ActionOpts func(*Action)
func WithCache() ActionOpts {
        return func(a *Action) {
                a.Cacher.SetKeyFn(types.Hash) // or own
        }
}

func (s *Action) run(ctx context.Context, rr *types.ReconciliationRequest) error {
        //...
        res, acted, err := a.Cacher.Render(ctx, s.render, rr)
        if acted { /* may be do something */ }
        // use resources
}

func (s *Action) render(ctx context.Context, rr *types.ReconciliationRequest) (any, error) {
        result := make([]unstructured.Unstructured)
        // make some resources
        return result, nil
}

func NewAction(opts ...ActionOpts) actions.Fn {
        for _, opt := range opts {
                opt(&action)
        }

        return action.run
}

with reconciler creation

func (s *componentHandler) NewComponentReconciler(ctx context.Context, mgr ctrl.Manager) error {
        _, err := reconciler.ReconcilerFor(
                mgr,
                &componentApi.Type{},
        ).
                // ...
                WithAction(foobar.NewAction(
                        foobar.WithCache(),
                ))
}

Pay attention to signature of render() and call to Cacher.Render()
from run().

The code is taken pretty much from the existing kustomize rendering
Action.

Copy CachingKeyFn type. In future it can be extented to accept
context for possible debug logging.

Signed-off-by: Yauheni Kaliuta ykaliuta@redhat.com

  • actions: abstract resource caching into resourcecacher

Make an abstract wrapper on the raw cacher.Cacher to work on actions
whose Renderer produces resources ([]unstructured.Unstructured) to
be recorded into ReconciliationRequest.Manifests and later
deployed (deleted) to the cluster.

Performs common tasks:

  • invalidate caching if devflags enabled;
  • account resources if they were generated (and skip cached ones);
  • add resources to the ReconciliationRequest.

!!! The user should define Name() method for proper accounting.

At this moment this is the only part which interested in the origin
of resources (cached or not) and it's supposed to be used in actions
whose final goal is to add resources to the request (which is done
by this helper), so change signature of its Render()
method (comparing to cacher.Cacher) to return only error.

The code is basically copied from manifest/template rendering
actions run() method.

Usage (added Name() and changed run() comparing to cacher.Cacher):

type Action struct {
        resourcecacher.ResourceCacher
        // action data
}

type ActionOpts func(*Action)
func WithCache() ActionOpts {
        return func(a *Action) {
                a.ResourceCacher.SetKeyFn(types.Hash) // or own
        }
}

func (*Action) Name() string {
        return "CoolActionName"
}

func (s *Action) run(ctx context.Context, rr *types.ReconciliationRequest) error {
        //...
        return a.ResourceCacher.Render(ctx, s.render, rr)
}

func (s *Action) render(ctx context.Context, rr *types.ReconciliationRequest) (any, error) {
        result := make([]unstructured.Unstructured)
        // make some resources
        return result, nil
}

func NewAction(opts ...ActionOpts) actions.Fn {
        for _, opt := range opts {
                opt(&action)
        }

        return action.run
}

Signed-off-by: Yauheni Kaliuta ykaliuta@redhat.com

  • actions: use resourcecacher for manifests rendering

Implement Name() method as it is required by the ResourceCacher.

All the run() functionality now implemented by the ResourceCacher,
so just call its Render() in run().

Amend WithCache() as required by ResourceCacher to set its
CachingKeyFn and remove CachingKeyFn initialization from NewAction.

Change render() signature to return any

Signed-off-by: Yauheni Kaliuta ykaliuta@redhat.com

  • actions: use resourcecacher for template rendering

The same changes as to manifests rendering.

Signed-off-by: Yauheni Kaliuta ykaliuta@redhat.com

Description

How Has This Been Tested?

Screenshot or short clip

Merge criteria

  • You have read the contributors guide.
  • Commit messages are meaningful - have a clear and concise summary and detailed explanation of what was changed and why.
  • Pull Request contains a description of the solution, a link to the JIRA issue, and to any dependent or related Pull Request.
  • Testing instructions have been added in the PR body (for PRs involving changes that are not immediately obvious).
  • The developer has manually tested the changes and verified that the changes work

Copy link

openshift-ci bot commented Jan 24, 2025

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@ykaliuta
Copy link
Contributor Author

/cc @lburgazzoli

@openshift-ci openshift-ci bot requested a review from lburgazzoli January 24, 2025 15:58
Copy link
Contributor

This PR can't be merged just yet 😢

Please run make generate manifests api-docs and commit the changes.

For more info: https://github.com/opendatahub-io/opendatahub-operator/actions/runs/12953054333

Copy link

codecov bot commented Jan 24, 2025

Codecov Report

Attention: Patch coverage is 95.00000% with 4 lines in your changes missing coverage. Please review.

Project coverage is 20.09%. Comparing base (8340068) to head (b26704a).

Files with missing lines Patch % Lines
...ctions/render/kustomize/action_render_manifests.go 71.42% 2 Missing ⚠️
...actions/render/template/action_render_templates.go 66.66% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1563      +/-   ##
==========================================
+ Coverage   19.94%   20.09%   +0.14%     
==========================================
  Files         158      160       +2     
  Lines       10799    10800       +1     
==========================================
+ Hits         2154     2170      +16     
+ Misses       8415     8407       -8     
+ Partials      230      223       -7     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

"github.com/opendatahub-io/opendatahub-operator/v2/pkg/controller/types"
)

type Renderer interface {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
type Renderer interface {
// Renderer defines an interface for rendering resources.
type Renderer interface {

Render(r Renderer, rr *types.ReconciliationRequest) (any, bool, error)
}

type CachingKeyFn func(rr *types.ReconciliationRequest) ([]byte, error)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
type CachingKeyFn func(rr *types.ReconciliationRequest) ([]byte, error)
// CachingKeyFn is a function type used to generate a caching key based on the reconciliation request.
type CachingKeyFn func(rr *types.ReconciliationRequest) ([]byte, error)


type CachingKeyFn func(rr *types.ReconciliationRequest) ([]byte, error)

type Cacher struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
type Cacher struct {
// Cacher provides caching capabilities for rendered resources.
type Cacher struct {

s.cachingKeyFn = key
}

func (s *Cacher) InvalidateCache() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func (s *Cacher) InvalidateCache() {
// InvalidateCache clears the cached key and resources, forcing a re-render on the next invocation.
func (s *Cacher) InvalidateCache() {


type CacherOpts func(*Cacher)

func (s *Cacher) SetKey(key CachingKeyFn) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func (s *Cacher) SetKey(key CachingKeyFn) {
// SetKey sets the caching key function for the Cacher.
func (s *Cacher) SetKey(key CachingKeyFn) {

resList := resources.UnstructuredList(resUnstructured)

if acted {
controllerName := strings.ToLower(rr.Instance.GetObjectKind().GroupVersionKind().Kind)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
controllerName := strings.ToLower(rr.Instance.GetObjectKind().GroupVersionKind().Kind)
// Track the number of rendered resources for monitoring
controllerName := strings.ToLower(rr.Instance.GetObjectKind().GroupVersionKind().Kind)

controllerName := strings.ToLower(rr.Instance.GetObjectKind().GroupVersionKind().Kind)
render.RenderedResourcesTotal.WithLabelValues(controllerName, s.Name()).Add(float64(len(resList)))

rr.Generated = true
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
rr.Generated = true
// Mark the reconciliation request as having generated resources
rr.Generated = true

Comment on lines +54 to +66
// deep copy object so changes done in the pipelines won't
// alter them
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// deep copy object so changes done in the pipelines won't
// alter them
// Append cloned resources to the reconciliation request,
// ensuring a deep copy so changes in the pipeline don't alter the original objects.

rr.Resources = append(rr.Resources, result.Clone()...)

return nil
return res, true, nil
}

func (a *Action) render(rr *types.ReconciliationRequest) ([]unstructured.Unstructured, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func (a *Action) render(rr *types.ReconciliationRequest) ([]unstructured.Unstructured, error) {
// render handles the rendering of resources using the Kustomize engine.
func (a *Action) render(rr *types.ReconciliationRequest) ([]unstructured.Unstructured, error) {


rr.Generated = true
func (a *Action) Render(_ cacher.Renderer, rr *types.ReconciliationRequest) (any, bool, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
func (a *Action) Render(_ cacher.Renderer, rr *types.ReconciliationRequest) (any, bool, error) {
// Render is a wrapper for rendering the reconciliation request using Kustomize.
// It returns the rendered resources and indicates if any resources were acted upon.
func (a *Action) Render(_ cacher.Renderer, rr *types.ReconciliationRequest) (any, bool, error) {

@ugiordan
Copy link
Contributor

ugiordan commented Jan 27, 2025

@ykaliuta, I believe the same caching abstraction can be applied to the action_render_templates as well.

VaishnaviHire pushed a commit to VaishnaviHire/opendatahub-operator that referenced this pull request Jan 28, 2025
…flux/component-updates/odh-kueue-controller-v2-17

chore(deps): update odh-kueue-controller-v2-17 to 3f5e5f0
Copy link

openshift-ci bot commented Jan 29, 2025

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please ask for approval from ugiordan. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@ykaliuta ykaliuta changed the title RFC: Caching action actions: refactor to move template and manifest rendering in a helper Jan 29, 2025
@ykaliuta ykaliuta force-pushed the caching-action branch 2 times, most recently from b3b34ca to e9e2fed Compare January 29, 2025 21:37
@ykaliuta ykaliuta marked this pull request as ready for review January 29, 2025 21:38
@openshift-ci openshift-ci bot requested review from asanzgom and CFSNM January 29, 2025 21:38
@ykaliuta ykaliuta marked this pull request as draft January 29, 2025 21:42
@ykaliuta ykaliuta force-pushed the caching-action branch 2 times, most recently from 2559e06 to 34bf0d9 Compare January 30, 2025 05:05
@ykaliuta
Copy link
Contributor Author

/test all

@ykaliuta ykaliuta marked this pull request as ready for review January 30, 2025 05:12
@openshift-ci openshift-ci bot requested review from AjayJagan and lphiri January 30, 2025 05:12
@ykaliuta
Copy link
Contributor Author

/test opendatahub-operator-e2e

The caching code for all caching Actions is pretty much the same

1) hash the key
2) if the hash is the same as the stored one, return stored resource
3) otherwise render the resource
4) store the key and the resource
5) return the resource

Abstract this logic into Cacher type which is supposed to be
included as a composition in on the higher levels.

The core engine is implemented as Render method which accepts actual
renderer and return boolean value in addition to the resources
indicating if it performed rendering. It supposed to be used in
resource rendering actions for accounting.

Possible usage supposed to be

```
type Action struct {
	cacher.Cacher
	// action data
}

type ActionOpts func(*Action)
func WithCache() ActionOpts {
	return func(a *Action) {
		a.Cacher.SetKeyFn(types.Hash) // or own
	}
}

func (s *Action) run(ctx context.Context, rr *types.ReconciliationRequest) error {
	//...
	res, acted, err := a.Cacher.Render(ctx, s.render, rr)
	if acted { /* may be do something */ }
	// use resources
}

func (s *Action) render(ctx context.Context, rr *types.ReconciliationRequest) (any, error) {
	result := make([]unstructured.Unstructured)
	// make some resources
	return result, nil
}

func NewAction(opts ...ActionOpts) actions.Fn {
	for _, opt := range opts {
		opt(&action)
	}

	return action.run
}
```

with reconciler creation

```
func (s *componentHandler) NewComponentReconciler(ctx context.Context, mgr ctrl.Manager) error {
	_, err := reconciler.ReconcilerFor(
		mgr,
		&componentApi.Type{},
	).
		// ...
		WithAction(foobar.NewAction(
			foobar.WithCache(),
		))
}
```

Pay attention to signature of render() and call to Cacher.Render()
from run().

The code is taken pretty much from the existing kustomize rendering
Action.

Copy CachingKeyFn type. In future it can be extented to accept
context for possible debug logging.

Signed-off-by: Yauheni Kaliuta <ykaliuta@redhat.com>
Make an abstract wrapper on the raw cacher.Cacher to work on actions
whose Renderer produces resources ([]unstructured.Unstructured) to
be recorded into ReconciliationRequest.Manifests and later
deployed (deleted) to the cluster.

Performs common tasks:
- invalidate caching if devflags enabled;
- account resources if they were generated (and skip cached ones);
- add resources to the ReconciliationRequest.

!!! The user should define Name() method for proper accounting.

At this moment this is the only part which interested in the origin
of resources (cached or not) and it's supposed to be used in actions
whose final goal is to add resources to the request (which is done
by this helper), so change signature of its Render()
method (comparing to cacher.Cacher) to return only error.

The code is basically copied from manifest/template rendering
actions run() method.

Usage (added Name() and changed run() comparing to cacher.Cacher):

```
type Action struct {
	resourcecacher.ResourceCacher
	// action data
}

type ActionOpts func(*Action)
func WithCache() ActionOpts {
	return func(a *Action) {
		a.ResourceCacher.SetKeyFn(types.Hash) // or own
	}
}

func (*Action) Name() string {
	return "CoolActionName"
}

func (s *Action) run(ctx context.Context, rr *types.ReconciliationRequest) error {
	//...
	return a.ResourceCacher.Render(ctx, s.render, rr)
}

func (s *Action) render(ctx context.Context, rr *types.ReconciliationRequest) (any, error) {
	result := make([]unstructured.Unstructured)
	// make some resources
	return result, nil
}

func NewAction(opts ...ActionOpts) actions.Fn {
	for _, opt := range opts {
		opt(&action)
	}

	return action.run
}
```

Signed-off-by: Yauheni Kaliuta <ykaliuta@redhat.com>
Implement Name() method as it is required by the ResourceCacher.

All the run() functionality now implemented by the ResourceCacher,
so just call its Render() in run().

Amend WithCache() as required by ResourceCacher to set its
CachingKeyFn and remove CachingKeyFn initialization from NewAction.

Change render() signature to return `any`

Signed-off-by: Yauheni Kaliuta <ykaliuta@redhat.com>
The same changes as to manifests rendering.
Just keep `data` field initialization in NewAction, used in
render().

Signed-off-by: Yauheni Kaliuta <ykaliuta@redhat.com>
@ykaliuta
Copy link
Contributor Author

@ugiordan I added some comments to the update, but I usually do not add obvious comments like /* increment i */ i++. If you think, something unclear and should be commented, I'll add.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Todo
Development

Successfully merging this pull request may close these issues.

2 participants