From 07ea983edc32e3a0a816f38285ddf287fdc607ad Mon Sep 17 00:00:00 2001 From: Gergely Nagy Date: Mon, 28 Oct 2024 19:09:48 +0100 Subject: [PATCH] Provide packageRevision cache as an interface --- pkg/cache/cache.go | 211 ++-------------------------- pkg/cache/memory/cache.go | 7 +- pkg/cache/memory/cache_test.go | 6 +- pkg/cache/memory/draft.go | 4 +- pkg/cache/memory/package.go | 8 +- pkg/cache/memory/packagerevision.go | 4 +- pkg/cache/memory/repository.go | 4 +- pkg/cache/memory/util.go | 2 +- 8 files changed, 40 insertions(+), 206 deletions(-) diff --git a/pkg/cache/cache.go b/pkg/cache/cache.go index 9e194cb7..11b61595 100644 --- a/pkg/cache/cache.go +++ b/pkg/cache/cache.go @@ -1,214 +1,33 @@ -// Copyright 2022 The kpt and Nephio Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - package cache import ( "context" - "errors" - "fmt" - "path/filepath" - "sync" - "time" - kptoci "github.com/GoogleContainerTools/kpt/pkg/oci" configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" - "github.com/nephio-project/porch/pkg/git" - "github.com/nephio-project/porch/pkg/meta" - "github.com/nephio-project/porch/pkg/oci" "github.com/nephio-project/porch/pkg/repository" - "go.opentelemetry.io/otel/trace" - "k8s.io/apimachinery/pkg/watch" ) -// Cache allows us to keep state for repositories, rather than querying them every time. -// -// Cache Structure: -// /git/ -// * Caches bare git repositories in directories named based on the repository address. -// /oci/ -// * Caches oci images with further hierarchy underneath -// * We Cache image layers in /oci/layers/ (this might be obsolete with the flattened Cache) -// * We Cache flattened tar files in /oci/ (so we don't need to pull to read resources) -// * We poll the repositories (every minute) and Cache the discovered images in memory. -type Cache struct { - mutex sync.Mutex - repositories map[string]*cachedRepository - cacheDir string - credentialResolver repository.CredentialResolver - userInfoProvider repository.UserInfoProvider - metadataStore meta.MetadataStore - repoSyncFrequency time.Duration - objectNotifier objectNotifier - useGitCaBundle bool -} - -type objectNotifier interface { - NotifyPackageRevisionChange(eventType watch.EventType, obj repository.PackageRevision, objMeta meta.PackageRevisionMeta) int +type Cache interface { + OpenRepository(ctx context.Context, repositorySpec *configapi.Repository) (CachedRepository, error) + CloseRepository(repositorySpec *configapi.Repository, allRepos []configapi.Repository) error } -type CacheOptions struct { - CredentialResolver repository.CredentialResolver - UserInfoProvider repository.UserInfoProvider - MetadataStore meta.MetadataStore - ObjectNotifier objectNotifier +type CachedRepository interface { + repository.Repository + // TODO: Remove this once https://github.com/nephio-project/porch/pull/119 is merged + repository.FunctionRepository + RefreshCache(ctx context.Context) error } -func NewCache(cacheDir string, repoSyncFrequency time.Duration, useGitCaBundle bool, opts CacheOptions) *Cache { - return &Cache{ - repositories: make(map[string]*cachedRepository), - cacheDir: cacheDir, - credentialResolver: opts.CredentialResolver, - userInfoProvider: opts.UserInfoProvider, - metadataStore: opts.MetadataStore, - objectNotifier: opts.ObjectNotifier, - repoSyncFrequency: repoSyncFrequency, - useGitCaBundle: useGitCaBundle, - } +type CachedPackageRevision interface { + repository.PackageRevision } -func getCacheKey(repositorySpec *configapi.Repository) (string, error) { - switch repositoryType := repositorySpec.Spec.Type; repositoryType { - case configapi.RepositoryTypeOCI: - ociSpec := repositorySpec.Spec.Oci - if ociSpec == nil { - return "", fmt.Errorf("oci not configured") - } - return "oci://" + ociSpec.Registry, nil - - case configapi.RepositoryTypeGit: - gitSpec := repositorySpec.Spec.Git - if gitSpec == nil { - return "", errors.New("git property is required") - } - if gitSpec.Repo == "" { - return "", errors.New("git.repo property is required") - } - return fmt.Sprintf("git://%s/%s@%s/%s", gitSpec.Repo, gitSpec.Directory, repositorySpec.Namespace, repositorySpec.Name), nil - - default: - return "", fmt.Errorf("repository type %q not supported", repositoryType) - } +type CachedPackageDraft interface { + repository.PackageDraft } -func (c *Cache) OpenRepository(ctx context.Context, repositorySpec *configapi.Repository) (*cachedRepository, error) { - ctx, span := tracer.Start(ctx, "Cache::OpenRepository", trace.WithAttributes()) - defer span.End() - - key, err := getCacheKey(repositorySpec) - if err != nil { - return nil, err - } - c.mutex.Lock() - defer c.mutex.Unlock() - cachedRepo := c.repositories[key] - - switch repositoryType := repositorySpec.Spec.Type; repositoryType { - case configapi.RepositoryTypeOCI: - ociSpec := repositorySpec.Spec.Oci - if cachedRepo == nil { - cacheDir := filepath.Join(c.cacheDir, "oci") - storage, err := kptoci.NewStorage(cacheDir) - if err != nil { - return nil, err - } - - r, err := oci.OpenRepository(repositorySpec.Name, repositorySpec.Namespace, repositorySpec.Spec.Content, ociSpec, repositorySpec.Spec.Deployment, storage) - if err != nil { - return nil, err - } - cachedRepo = newRepository(key, repositorySpec, r, c.objectNotifier, c.metadataStore, c.repoSyncFrequency) - c.repositories[key] = cachedRepo - } - return cachedRepo, nil - - case configapi.RepositoryTypeGit: - gitSpec := repositorySpec.Spec.Git - if !isPackageContent(repositorySpec.Spec.Content) { - return nil, fmt.Errorf("git repository supports Package content only; got %q", string(repositorySpec.Spec.Content)) - } - if cachedRepo == nil { - var mbs git.MainBranchStrategy - if gitSpec.CreateBranch { - mbs = git.CreateIfMissing - } else { - mbs = git.ErrorIfMissing - } - - r, err := git.OpenRepository(ctx, repositorySpec.Name, repositorySpec.Namespace, gitSpec, repositorySpec.Spec.Deployment, filepath.Join(c.cacheDir, "git"), git.GitRepositoryOptions{ - CredentialResolver: c.credentialResolver, - UserInfoProvider: c.userInfoProvider, - MainBranchStrategy: mbs, - UseGitCaBundle: c.useGitCaBundle, - }) - if err != nil { - return nil, err - } - - cachedRepo = newRepository(key, repositorySpec, r, c.objectNotifier, c.metadataStore, c.repoSyncFrequency) - c.repositories[key] = cachedRepo - } else { - // If there is an error from the background refresh goroutine, return it. - if err := cachedRepo.getRefreshError(); err != nil { - return nil, err - } - } - return cachedRepo, nil - - default: - return nil, fmt.Errorf("type %q not supported", repositoryType) - } -} - -func isPackageContent(content configapi.RepositoryContent) bool { - return content == configapi.RepositoryContentPackage -} - -func (c *Cache) CloseRepository(repositorySpec *configapi.Repository, allRepos []configapi.Repository) error { - key, err := getCacheKey(repositorySpec) - if err != nil { - return err - } - - // check if repositorySpec shares the underlying cached repo with another repository - for _, r := range allRepos { - if r.Name == repositorySpec.Name && r.Namespace == repositorySpec.Namespace { - continue - } - otherKey, err := getCacheKey(&r) - if err != nil { - return err - } - if otherKey == key { - // do not close cached repo if it is shared - return nil - } - } - - var repository *cachedRepository - { - c.mutex.Lock() - if r, ok := c.repositories[key]; ok { - delete(c.repositories, key) - repository = r - } - c.mutex.Unlock() - } - - if repository != nil { - return repository.Close() - } else { - return nil - } +// Remove? +type CachedPackage interface { + repository.Package } diff --git a/pkg/cache/memory/cache.go b/pkg/cache/memory/cache.go index 9e194cb7..e1c8caeb 100644 --- a/pkg/cache/memory/cache.go +++ b/pkg/cache/memory/cache.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cache +package memory import ( "context" @@ -24,6 +24,7 @@ import ( kptoci "github.com/GoogleContainerTools/kpt/pkg/oci" configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" + "github.com/nephio-project/porch/pkg/cache" "github.com/nephio-project/porch/pkg/git" "github.com/nephio-project/porch/pkg/meta" "github.com/nephio-project/porch/pkg/oci" @@ -54,6 +55,8 @@ type Cache struct { useGitCaBundle bool } +var _ cache.Cache = &Cache{} + type objectNotifier interface { NotifyPackageRevisionChange(eventType watch.EventType, obj repository.PackageRevision, objMeta meta.PackageRevisionMeta) int } @@ -102,7 +105,7 @@ func getCacheKey(repositorySpec *configapi.Repository) (string, error) { } } -func (c *Cache) OpenRepository(ctx context.Context, repositorySpec *configapi.Repository) (*cachedRepository, error) { +func (c *Cache) OpenRepository(ctx context.Context, repositorySpec *configapi.Repository) (cache.CachedRepository, error) { ctx, span := tracer.Start(ctx, "Cache::OpenRepository", trace.WithAttributes()) defer span.End() diff --git a/pkg/cache/memory/cache_test.go b/pkg/cache/memory/cache_test.go index d663d502..3b35b4e1 100644 --- a/pkg/cache/memory/cache_test.go +++ b/pkg/cache/memory/cache_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cache +package memory import ( "context" @@ -25,6 +25,8 @@ import ( "github.com/google/go-cmp/cmp" api "github.com/nephio-project/porch/api/porch/v1alpha1" "github.com/nephio-project/porch/api/porchconfig/v1alpha1" + "github.com/nephio-project/porch/pkg/cache" + fakecache "github.com/nephio-project/porch/pkg/cache/fake" "github.com/nephio-project/porch/pkg/git" "github.com/nephio-project/porch/pkg/meta" @@ -126,7 +128,7 @@ func TestPublishedLatest(t *testing.T) { } } -func openRepositoryFromArchive(t *testing.T, ctx context.Context, testPath, name string) *cachedRepository { +func openRepositoryFromArchive(t *testing.T, ctx context.Context, testPath, name string) cache.CachedRepository { t.Helper() tempdir := t.TempDir() diff --git a/pkg/cache/memory/draft.go b/pkg/cache/memory/draft.go index cb8e5bca..aa36435d 100644 --- a/pkg/cache/memory/draft.go +++ b/pkg/cache/memory/draft.go @@ -12,11 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cache +package memory import ( "context" + "github.com/nephio-project/porch/pkg/cache" "github.com/nephio-project/porch/pkg/repository" ) @@ -26,6 +27,7 @@ type cachedDraft struct { } var _ repository.PackageDraft = &cachedDraft{} +var _ cache.CachedPackageDraft = &cachedDraft{} func (cd *cachedDraft) Close(ctx context.Context) (repository.PackageRevision, error) { if closed, err := cd.PackageDraft.Close(ctx); err != nil { diff --git a/pkg/cache/memory/package.go b/pkg/cache/memory/package.go index 027e00a8..0699583b 100644 --- a/pkg/cache/memory/package.go +++ b/pkg/cache/memory/package.go @@ -12,9 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cache +package memory -import "github.com/nephio-project/porch/pkg/repository" +import ( + "github.com/nephio-project/porch/pkg/cache" + "github.com/nephio-project/porch/pkg/repository" +) // We take advantage of the cache having a global view of all the packages // in a repository and compute the latest package revision in the cache @@ -23,6 +26,7 @@ import "github.com/nephio-project/porch/pkg/repository" // between Git and OCI. var _ repository.Package = &cachedPackage{} +var _ cache.CachedPackage = &cachedPackage{} type cachedPackage struct { repository.Package diff --git a/pkg/cache/memory/packagerevision.go b/pkg/cache/memory/packagerevision.go index 17fce423..7f0b468d 100644 --- a/pkg/cache/memory/packagerevision.go +++ b/pkg/cache/memory/packagerevision.go @@ -12,12 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cache +package memory import ( "context" "github.com/nephio-project/porch/api/porch/v1alpha1" + "github.com/nephio-project/porch/pkg/cache" "github.com/nephio-project/porch/pkg/repository" ) @@ -28,6 +29,7 @@ import ( // between Git and OCI. var _ repository.PackageRevision = &cachedPackageRevision{} +var _ cache.CachedPackageRevision = &cachedPackageRevision{} type cachedPackageRevision struct { repository.PackageRevision diff --git a/pkg/cache/memory/repository.go b/pkg/cache/memory/repository.go index 80649934..4e02573e 100644 --- a/pkg/cache/memory/repository.go +++ b/pkg/cache/memory/repository.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cache +package memory import ( "context" @@ -22,6 +22,7 @@ import ( "github.com/nephio-project/porch/api/porch/v1alpha1" configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1" + "github.com/nephio-project/porch/pkg/cache" "github.com/nephio-project/porch/pkg/git" "github.com/nephio-project/porch/pkg/meta" "github.com/nephio-project/porch/pkg/repository" @@ -42,6 +43,7 @@ var tracer = otel.Tracer("cache") // between Git and OCI. var _ repository.Repository = &cachedRepository{} +var _ cache.CachedRepository = &cachedRepository{} var _ repository.FunctionRepository = &cachedRepository{} type cachedRepository struct { diff --git a/pkg/cache/memory/util.go b/pkg/cache/memory/util.go index a8d35f13..f703e59c 100644 --- a/pkg/cache/memory/util.go +++ b/pkg/cache/memory/util.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package cache +package memory import ( "sort"