Skip to content

feat: allow external DataImportCron to manage DataSources #1260

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

Closed
wants to merge 2 commits into from

Conversation

akrejcir
Copy link
Collaborator

@akrejcir akrejcir commented Feb 4, 2025

What this PR does / why we need it:
The DataSources created by SSP can now be managed by a DataImportCron that was not created by SSP.

Which issue(s) this PR fixes:
Fixes: https://issues.redhat.com/browse/CNV-49658

Release note:

DataSources created by SSP can now be managed by external DataImportCrons.

@kubevirt-bot kubevirt-bot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. release-note Denotes a PR that will be considered when it comes time to generate release notes. labels Feb 4, 2025
Copy link

openshift-ci bot commented Feb 4, 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

@kubevirt-bot kubevirt-bot added the dco-signoff: yes Indicates the PR's author has DCO signed all their commits. label Feb 4, 2025
Copy link

sonarqubecloud bot commented Feb 4, 2025

@ksimon1
Copy link
Member

ksimon1 commented Apr 11, 2025

@sourcery-ai review

Copy link

sourcery-ai bot commented Apr 11, 2025

Reviewer's Guide by Sourcery

This pull request introduces changes to allow DataSources created by SSP to be managed by external DataImportCrons. The changes include modifications to the reconcile logic to prevent reconciliation of DataSources managed by external DataImportCrons, added tests to verify the new functionality, and error handling to prevent conflicts between DataImportCron templates and external DataImportCrons.

Sequence diagram for dataSourceAutoUpdateEnabled with external DataImportCron

sequenceDiagram
  participant DS as DataSource
  participant K8sClient as Kubernetes Client
  participant DIC as DataImportCron

  DS->>K8sClient: Get DataSource
  K8sClient-->>DS: Returns DataSource with dataImportCronLabel
  DS->>DS: Check if DataImportCron template exists
  alt DataImportCron template does not exist
    DS->>DS: externalCronExists = true
    DS->>DS: Return reconcileDataSource = false, reconcileDataImportCron = false
  else DataImportCron template exists
    DS->>DS: externalCronExists = false
  end
Loading

Sequence diagram for dataSourceAutoUpdateEnabled with DataImportCron template

sequenceDiagram
  participant DS as DataSource
  participant K8sClient as Kubernetes Client
  participant DIC as DataImportCron

  DS->>K8sClient: Get DataSource
  K8sClient-->>DS: Returns DataSource
  DS->>DS: Check if DataImportCron template exists
  alt DataImportCron template exists
    DS->>DS: Check if external DataImportCron exists
    alt external DataImportCron exists
      DS->>DS: Return error
    else external DataImportCron does not exist
      DS->>DS: Check if DataSource uses golden image PVC
      alt DataSource uses golden image PVC
        DS->>DS: Return reconcileDataSource = true, reconcileDataImportCron = false
      else DataSource does not use golden image PVC
        DS->>DS: Return reconcileDataSource = false, reconcileDataImportCron = true
      end
    end
  else DataImportCron template does not exist
    DS->>DS: Return reconcileDataSource = true, reconcileDataImportCron = false
  end
Loading

File-Level Changes

Change Details Files
Added tests to verify that DataSources are not reconciled when managed by an external DataImportCron.
  • Added a new context for testing with an external DataImportCron.
  • Created an external DataImportCron and DataSource before each test.
  • Added assertions to ensure the DataSource is not reconciled when managed by an external DataImportCron.
  • Added a test case to verify that reconciliation fails if a DataImportCron template is defined for a DataSource managed by an external DataImportCron.
tests/dataSources_test.go
Modified the reconcile logic to prevent reconciliation of DataSources managed by external DataImportCrons and to handle conflicts between DataImportCron templates and external DataImportCrons.
  • Added a new autoUpdateEnabledResult_RENAME struct to encapsulate the results of the dataSourceAutoUpdateEnabled function.
  • Modified the dataSourceAutoUpdateEnabled function to check for the existence of an external DataImportCron and prevent reconciliation if one exists.
  • Added error handling to prevent reconciliation if a DataImportCron template is defined for a DataSource managed by an external DataImportCron.
  • Modified the reconcileDataSource function to only update the DataSource spec if it is not managed by an external DataImportCron.
internal/operands/data-sources/reconcile.go
Added unit tests for the data-sources operand to verify the changes related to external DataImportCrons.
  • Added a new context for testing with an external DataImportCron.
  • Created an external DataImportCron and DataSource before each test.
  • Added a test case to verify that the DataSource is not reconciled when managed by an external DataImportCron.
  • Added a test case to verify that reconciliation fails if a DataImportCron template is defined for a DataSource managed by an external DataImportCron.
internal/operands/data-sources/reconcile_test.go

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!
  • Generate a plan of action for an issue: Comment @sourcery-ai plan on
    an issue to generate a plan of action for it.

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @akrejcir - I've reviewed your changes - here's some feedback:

Overall Comments:

  • Consider adding a comment explaining why autoUpdateEnabledResult_RENAME was renamed.
  • The logic in dataSourceAutoUpdateEnabled is complex; consider breaking it down into smaller, well-named functions to improve readability.
Here's what I looked at during the review
  • 🟡 General issues: 1 issue found
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟡 Complexity: 1 issue found
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines 326 to 332
// TODO -- rewrite comment
// If PVC exists, DataSource does not use auto-update.
// Otherwise, DataSource uses auto-update.
Copy link

Choose a reason for hiding this comment

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

suggestion: Address the 'rewrite comment' TODO.

The current comment regarding golden image PVC behavior is ambiguous. Refining this comment to clearly explain the intended logic would make the code easier to follow.

Suggested change
// TODO -- rewrite comment
// If PVC exists, DataSource does not use auto-update.
// Otherwise, DataSource uses auto-update.
// Check if a golden image PVC exists for this DataSource.
// If the golden image PVC exists, this indicates that a golden image is present,
// meaning auto-update is disabled.
// If the PVC does not exist, auto-update is enabled.

const dataImportCronLabel = "cdi.kubevirt.io/dataImportCron"

func dataSourceAutoUpdateEnabled(dataSource *cdiv1beta1.DataSource, cronByDataSource map[client.ObjectKey]*ssp.DataImportCronTemplate, request *common.Request) (bool, error) {
func dataSourceAutoUpdateEnabled(dataSource *cdiv1beta1.DataSource, cronByDataSource map[client.ObjectKey]*ssp.DataImportCronTemplate, request *common.Request) (autoUpdateEnabledResult_RENAME, error) {
Copy link

Choose a reason for hiding this comment

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

issue (complexity): Consider extracting helper functions and using early returns to simplify the dataSourceAutoUpdateEnabled function and reduce nested conditionals, improving readability and maintainability of the code.

The new changes do add complexity by mixing multiple decision points in one function. Consider extracting helper functions to isolate key decision points and flatten the nested conditionals. For example, you could extract determining if an external cron is present and the PVC & Ready condition checks into separate helper functions:

func externalCronExists(found *cdiv1beta1.DataSource, cronTemplate *ssp.DataImportCronTemplate) bool {
    if found == nil {
        return false
    }
    label, ok := found.GetLabels()[dataImportCronLabel]
    return ok && (cronTemplate == nil || cronTemplate.Name != label)
}

Then restructure dataSourceAutoUpdateEnabled using early returns:

func dataSourceAutoUpdateEnabled(dataSource *cdiv1beta1.DataSource, cronByDataSource map[client.ObjectKey]*ssp.DataImportCronTemplate, request *common.Request) (autoUpdateEnabledResult_RENAME, error) {
    objectKey := client.ObjectKeyFromObject(dataSource)
    cronTemplate, cronExists := cronByDataSource[objectKey]

    found := &cdiv1beta1.DataSource{}
    err := request.Client.Get(request.Context, objectKey, found)
    if err != nil && !errors.IsNotFound(err) {
        return autoUpdateEnabledResult_RENAME{}, err
    }
    if errors.IsNotFound(err) {
        found = nil
    }

    if externalCronExists(found, cronTemplate) {
        // Return false for both when external cron is detected.
        return autoUpdateEnabledResult_RENAME{reconcileDataSource: false, reconcileDataImportCron: false}, nil
    }

    if !cronExists {
        return autoUpdateEnabledResult_RENAME{reconcileDataSource: true, reconcileDataImportCron: false}, nil
    }

    usesGolden, err := dataSourceUsesGoldenImagePVC(found, dataSource, request)
    if err != nil {
        return autoUpdateEnabledResult_RENAME{}, err
    }
    if usesGolden {
        return autoUpdateEnabledResult_RENAME{reconcileDataSource: true, reconcileDataImportCron: false}, nil
    }

    return autoUpdateEnabledResult_RENAME{reconcileDataSource: false, reconcileDataImportCron: true}, nil
}

This refactoring isolates decision logic into well-named helpers and reduces nesting, making the flow easier to understand while preserving functionality.

@akrejcir akrejcir force-pushed the allow-external-dic branch from 89043bf to 92e6176 Compare May 13, 2025 12:31
@kubevirt-bot
Copy link
Contributor

[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 assign 0xfelix for approval. 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

@akrejcir akrejcir changed the title WIP - feat: allow external DataImportCron to manage DataSources feat: allow external DataImportCron to manage DataSources May 13, 2025
@akrejcir akrejcir marked this pull request as ready for review May 13, 2025 12:31
@kubevirt-bot kubevirt-bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label May 13, 2025
@kubevirt-bot kubevirt-bot requested a review from ksimon1 May 13, 2025 12:31
@akrejcir
Copy link
Collaborator Author

/cc @0xFelix @jcanocan @codingben

@kubevirt-bot kubevirt-bot requested a review from jcanocan May 13, 2025 12:31
Copy link

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

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

Hey @akrejcir - I've reviewed your changes and they look great!

Here's what I looked at during the review
  • 🟢 General issues: all looks good
  • 🟢 Security: all looks good
  • 🟢 Testing: all looks good
  • 🟡 Complexity: 1 issue found
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Copy link
Member

@0xFelix 0xFelix left a comment

Choose a reason for hiding this comment

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

Looks ok but functional tests are failing?

for i := range existingCrons.Items {
cron := &existingCrons.Items[i]
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
for i := range existingCrons.Items {
cron := &existingCrons.Items[i]
for i, cron := range existingCrons.Items {

Should be fine?

return false, nil
func chooseReconciliationObjects(dataSource *cdiv1beta1.DataSource, cronTemplate *ssp.DataImportCronTemplate, existingCron *cdiv1beta1.DataImportCron, request *common.Request) (reconcileObjects, error) {
if existingCron != nil {
if cronTemplate != nil && common.CheckOwnerAnnotation(existingCron, request.Instance) {
Copy link
Member

Choose a reason for hiding this comment

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

What should happen if SSP owns the existing cron but cron template is nil? Will it be removed?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes. Then the operator assumes that the existing cron was previously created from a cron template by SSP.
Here is the code that removes owned crons that don't have template:

// Remove owned DataImportCrons that are not in the 'dataImportCrons' parameter
for i := range ownedCrons {
if _, isUsed := crons[client.ObjectKeyFromObject(&ownedCrons[i])]; isUsed {
continue
}
cron := ownedCrons[i] // Make local copy
funcs = append(funcs, func(request *common.Request) (common.ReconcileResult, error) {
err := request.Client.Delete(request.Context, &cron)
if err != nil && !errors.IsNotFound(err) {
request.Logger.Error(err, fmt.Sprintf("Error deleting \"%s\": %s", cron.GetName(), err))
return common.ReconcileResult{}, err
}
return common.ReconcileResult{
Resource: &cron,
}, nil
})
}

Comment on lines 393 to 395
if externalDataSource.GetLabels() == nil {
externalDataSource.SetLabels(map[string]string{})
}
Copy link
Member

Choose a reason for hiding this comment

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

Is that really required?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I've changed the tests.

akrejcir added 2 commits May 16, 2025 17:00
Signed-off-by: Andrej Krejcir <akrejcir@redhat.com>
The DataSources created by SSP can now be managed
by a DataImportCron that was not created by SSP.

Signed-off-by: Andrej Krejcir <akrejcir@redhat.com>
@akrejcir akrejcir force-pushed the allow-external-dic branch from 92e6176 to 9390d11 Compare May 16, 2025 15:23
Copy link

Copy link

openshift-ci bot commented May 16, 2025

@akrejcir: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
ci/prow/e2e-upgrade-functests 9390d11 link true /test e2e-upgrade-functests
ci/prow/e2e-functests 9390d11 link true /test e2e-functests
ci/prow/e2e-single-node-functests 9390d11 link true /test e2e-single-node-functests

Full PR test history. Your PR dashboard.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

}] = cron
}
}

var dataSourceInfos []dataSourceInfo
for i := range d.sources {
dataSource := d.sources[i] // Make a copy
Copy link
Member

Choose a reason for hiding this comment

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

would it make sense to store directly pointer to the object in this variable, so you don't have to add & in other places?

Copy link
Collaborator Author

@akrejcir akrejcir May 19, 2025

Choose a reason for hiding this comment

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

I wanted to make a copy, because it is modified in the line below:

dataSource.Namespace = internal.GoldenImagesNamespace

It's a shallow copy. It was enough, because we don't change any pointer fields.

I think dataSource := &(d.sources[i]) would not make a copy.

@akrejcir
Copy link
Collaborator Author

When external DataImporCron is created, it does not trigger SSP reconciliation, so the operator has no chance to react to it, and give management of DataSource to CDI. I need to rethink this.

/hold

@kubevirt-bot kubevirt-bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label May 19, 2025
@akrejcir akrejcir marked this pull request as draft May 19, 2025 11:42
@kubevirt-bot kubevirt-bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label May 19, 2025
@kubevirt-bot kubevirt-bot added the needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. label Jul 3, 2025
@kubevirt-bot
Copy link
Contributor

PR needs rebase.

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@akrejcir
Copy link
Collaborator Author

akrejcir commented Jul 4, 2025

The functionality implemented in this PR would be more difficult with the upcoming heterogeneous cluster functionality.

Closing this for now, we can reopen if we resume development.

/close

@kubevirt-bot
Copy link
Contributor

@akrejcir: Closed this PR.

In response to this:

The functionality implemented in this PR would be more difficult with the upcoming heterogeneous cluster functionality.

Closing this for now, we can reopen if we resume development.

/close

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@akrejcir akrejcir deleted the allow-external-dic branch July 4, 2025 12:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dco-signoff: yes Indicates the PR's author has DCO signed all their commits. do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. needs-rebase Indicates a PR cannot be merged because it has merge conflicts with HEAD. release-note Denotes a PR that will be considered when it comes time to generate release notes. size/L
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants