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(copy): introduces CopyGraphOptions with events support #145

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

Conversation

leonardochaia
Copy link
Contributor

@leonardochaia leonardochaia commented Sep 26, 2024

What this PR does / why we need it

This PR improves CopyAsync and CopyGraphAsync to include the CopyGraphOptions, inspired from oras-go.

The current implementation:

  1. adds support for CopyOptions events, like OnPreCopy, these were implemented in the form of Action<T>s

Pending items

  • Define Copy signature

Which issue(s) this PR resolves / fixes

Please check the following list

  • Does the affected code have corresponding tests, e.g. unit test, E2E test?
  • Does this change require a documentation update?
  • Does this introduce breaking changes that would require an announcement or bumping the major version?
  • Do all new files have an appropriate license header?

@leonardochaia leonardochaia changed the title feat(copy): support mounting existing descriptors from other repos feat(copy): makes copy more efficient Sep 26, 2024
@leonardochaia leonardochaia force-pushed the feat/improve-copy-performance branch from 847ecb0 to 6817504 Compare September 26, 2024 02:00
Signed-off-by: Leonardo Chaia <[email protected]>
@leonardochaia
Copy link
Contributor Author

This is a work in progress. Seems to be working running locally. Tests are failing and need more work.

@leonardochaia leonardochaia marked this pull request as ready for review October 17, 2024 18:41
@leonardochaia
Copy link
Contributor Author

Hi. I propose concurrency support is implemented in a separate PR. Otherwise this should be ready to review :+

Leo.

@leonardochaia
Copy link
Contributor Author

Example usage:

List<string> knownReferences = [];
foreach (var toReplicate in toReplicateArray)
{
    var copyOpts = new CopyOptions()
    {
        MountFrom = _ => knownReferences.ToArray(),
    };

    copyOpts.OnCopySkipped += d => this.logger.LogTrace("{Digest}: Already exists.", d.Digest);
    copyOpts.OnPreCopy += d => this.logger.LogTrace("{Digest}: Copying..", d.Digest);
    copyOpts.OnPostCopy += d => this.logger.LogTrace("{Digest}: Copied", d.Digest);
    copyOpts.OnMounted += (d, from) => this.logger.LogTrace("{Digest}: Mounted from {From}", d.Digest, from);

    await sourceRepository.CopyAsync(
        toReplicate.SourceImage,
        destinationRepository,
        toReplicate.DestinationImage,
        cancellationToken,
        copyOpts);

    knownReferences.Add(toReplicate.DestinationImage);
}

@leonardochaia
Copy link
Contributor Author

Hi all, any feedback is appreciated 🥇

Thanks!
Leo.

Copy link

codecov bot commented Oct 28, 2024

Codecov Report

Attention: Patch coverage is 93.54839% with 2 lines in your changes missing coverage. Please review.

Project coverage is 82.62%. Comparing base (3fa86d6) to head (827f62b).
Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
src/OrasProject.Oras/AsyncInvocationExtensions.cs 66.66% 0 Missing and 2 partials ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #145      +/-   ##
==========================================
+ Coverage   81.70%   82.62%   +0.92%     
==========================================
  Files          35       37       +2     
  Lines        1104     1128      +24     
  Branches      128      134       +6     
==========================================
+ Hits          902      932      +30     
+ Misses        146      139       -7     
- Partials       56       57       +1     

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

@Wwwsylvia
Copy link
Member

Hi @leonardochaia , thank you for your contribution! Sorry for the delay. We need a bit more time to review your PR, but we’ll provide feedback as soon as we can. Thank you for your understanding!

src/OrasProject.Oras/Extensions.cs Outdated Show resolved Hide resolved
src/OrasProject.Oras/Registry/IMounter.cs Outdated Show resolved Hide resolved
src/OrasProject.Oras/Registry/Remote/Repository.cs Outdated Show resolved Hide resolved
src/OrasProject.Oras/Registry/IMounter.cs Outdated Show resolved Hide resolved
src/OrasProject.Oras/Extensions.cs Outdated Show resolved Hide resolved
@Wwwsylvia
Copy link
Member

Hi. I propose concurrency support is implemented in a separate PR. Otherwise this should be ready to review :+

Do we want to move the mounting support to a separate PR as well?

@leonardochaia
Copy link
Contributor Author

Hi. I propose concurrency support is implemented in a separate PR. Otherwise this should be ready to review :+

Do we want to move the mounting support to a separate PR as well?

Hi @Wwwsylvia , thanks for the review. I'll apply the changes and report back.
We can move the mounting to a separate PR. In such case we would leave only the CopyOptions and related events on this PR, and implement the IMounter and mounting support on copy on a separate PR.

My goal is getting the mounting support when copying graphs since we are using this to replicate a bunch of images between registries and need to make it faster.

@Wwwsylvia
Copy link
Member

@leonardochaia Sounds good. Thank you!

@leonardochaia
Copy link
Contributor Author

PR simplified to only introduce the CopyGraphOptions. Next step is to update the tests to cover the events.

Separate PRs/issues to be created:

  1. Copy to support concurrency
  2. Repository to support mounting
  3. Copy to support mounting

@leonardochaia
Copy link
Contributor Author

leonardochaia commented Nov 11, 2024

Usually CancellationToken is left as the last parameter, however, that would introduce a breaking change. Are we good with introducing a breaking change?

public static async Task<Descriptor> CopyAsync(this ITarget src, string srcRef, ITarget dst, string dstRef, CopyGraphOptions? copyOptions = default, CancellationToken cancellationToken = default)
    
public static async Task CopyGraphAsync(this ITarget src, ITarget dst, Descriptor node, , CopyGraphOptions? copyOptions = default, CancellationToken cancellationToken)

EDIT: Disregard, I've added an overload to keep the CancellationToken last but also support the current signature

Signed-off-by: Leonardo Chaia <[email protected]>
@leonardochaia leonardochaia changed the title feat(copy): makes copy more efficient feat(copy): introduces CopyGraphOptions with events support Nov 11, 2024
@leonardochaia
Copy link
Contributor Author

Quick update, I've converted this PR to only include the CopyGraphOptions with events support and corresponding tests.

Next steps:

  1. Add support for mounting to Repository feat(repository): mounting support #152
  2. Add support for mounting to Copy (after this and feat(repository): mounting support #152 are merged)

@Wwwsylvia
Copy link
Member

Quick update, I've converted this PR to only include the CopyGraphOptions with events support and corresponding tests.

Next steps:

Add support for mounting to Repository #152
Add support for mounting to Copy (after this and #152 are merged)

Thank you! I just created the following issues to track:

Signed-off-by: Leonardo Chaia <[email protected]>
@leonardochaia
Copy link
Contributor Author

Build should be fixed, tests passed locally.

@Wwwsylvia thanks for creating the issues, JIC: only CopyGraphOptions was introduced in this PR. I propose we leave CopyOptions for a separate PR with the MountFrom feature, as per oras-go:

// CopyOptions contains parameters for [oras.Copy].
type CopyOptions struct {
	CopyGraphOptions
	// MapRoot maps the resolved root node to a desired root node for copy.
	// When MapRoot is provided, the descriptor resolved from the source
	// reference will be passed to MapRoot, and the mapped descriptor will be
	// used as the root node for copy.
	MapRoot func(ctx context.Context, src content.ReadOnlyStorage, root ocispec.Descriptor) (ocispec.Descriptor, error)
}

https://github.com/oras-project/oras-go/blob/bf570bcc78b3aa1bad84401e3390cc79a03e426e/copy.go#L50-L57

When CopyOptions is introduced, there should be no breaking change since it should inherit from CopyGraphOptions

Thanks!
Leo.

@Wwwsylvia
Copy link
Member

Wwwsylvia commented Nov 12, 2024

@leonardochaia The MountFrom and OnMounted options are included in CopyGraphOptions (See https://github.com/oras-project/oras-go/blob/bf570bcc78b3aa1bad84401e3390cc79a03e426e/copy.go#L108-L113).

Since oras-dotnet is still under initial development, breaking changes are acceptable if they are necessary.


Update:

only CopyGraphOptions was introduced in this PR. I propose we leave CopyOptions for a separate PR with the MountFrom feature, as per oras-go:

Oh I might have misunderstood. I'm good with leaving CopyOptions in another PR.

@leonardochaia
Copy link
Contributor Author

Hi @Wwwsylvia , sorry I meant to say MapRoot instead of MountFrom 👍

Thanks!

src/OrasProject.Oras/Extensions.cs Outdated Show resolved Hide resolved
src/OrasProject.Oras/Extensions.cs Show resolved Hide resolved
@leonardochaia
Copy link
Contributor Author

This should be good to go.

Once this is merged I'll create a separate PR to support mounting when copying 🥇

Copy link
Member

@Wwwsylvia Wwwsylvia left a comment

Choose a reason for hiding this comment

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

LGTM

src/OrasProject.Oras/Extensions.cs Outdated Show resolved Hide resolved
src/OrasProject.Oras/CopyGraphOptions.cs Outdated Show resolved Hide resolved
internal Task OnCopySkippedAsync(Descriptor descriptor)
{
CopySkipped?.Invoke(descriptor);
return CopySkippedAsync?.Invoke(descriptor) ?? Task.CompletedTask;
Copy link
Member

Choose a reason for hiding this comment

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

Do you mean InvokeAsync?

Comment on lines +57 to +61
internal Task OnPreCopyAsync(Descriptor descriptor)
{
PreCopy?.Invoke(descriptor);
return PreCopyAsync?.InvokeAsync(descriptor) ?? Task.CompletedTask;
}
Copy link
Member

Choose a reason for hiding this comment

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

In the PoC that plays with SomeEvent, we set up the tasks for the async events before invoking the sync event handler. While in this implementation we first invoke the sync event and then the async events. I think the PoC approach is more efficient. I'm wondering if we can achieve something similar with a reusable extension method.

How about something like this:

    internal static IEnumerable<Task> InvokeAsyncEvents<TEventArgs>(
        this Func<TEventArgs, Task>? eventDelegate, TEventArgs args)
    {
        if (eventDelegate == null)
        {
            return [Task.CompletedTask];
        }
        return eventDelegate.GetInvocationList()
            .Select(d => (Task?)d.DynamicInvoke(args) ?? Task.CompletedTask);
    }
    internal Task OnPreCopyAsync(Descriptor descriptor)
    {
        var tasks = PreCopyAsync.InvokeAsyncEvents(descriptor);
        PreCopy?.Invoke(descriptor);
        return Task.WhenAll(tasks);
    }

Naming is hard though...

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

Successfully merging this pull request may close these issues.

4 participants