Skip to content

Commit

Permalink
Provide packageRevision cache as an interface
Browse files Browse the repository at this point in the history
  • Loading branch information
nagygergo committed Oct 28, 2024
1 parent fe98290 commit 07ea983
Show file tree
Hide file tree
Showing 8 changed files with 40 additions and 206 deletions.
211 changes: 15 additions & 196 deletions pkg/cache/cache.go
Original file line number Diff line number Diff line change
@@ -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:
// <cacheDir>/git/
// * Caches bare git repositories in directories named based on the repository address.
// <cacheDir>/oci/
// * Caches oci images with further hierarchy underneath
// * We Cache image layers in <cacheDir>/oci/layers/ (this might be obsolete with the flattened Cache)
// * We Cache flattened tar files in <cacheDir>/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
}
7 changes: 5 additions & 2 deletions pkg/cache/memory/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package cache
package memory

import (
"context"
Expand All @@ -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"
Expand Down Expand Up @@ -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
}
Expand Down Expand Up @@ -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()

Expand Down
6 changes: 4 additions & 2 deletions pkg/cache/memory/cache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package cache
package memory

import (
"context"
Expand All @@ -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"
Expand Down Expand Up @@ -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()
Expand Down
4 changes: 3 additions & 1 deletion pkg/cache/memory/draft.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -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 {
Expand Down
8 changes: 6 additions & 2 deletions pkg/cache/memory/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
4 changes: 3 additions & 1 deletion pkg/cache/memory/packagerevision.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand All @@ -28,6 +29,7 @@ import (
// between Git and OCI.

var _ repository.PackageRevision = &cachedPackageRevision{}
var _ cache.CachedPackageRevision = &cachedPackageRevision{}

type cachedPackageRevision struct {
repository.PackageRevision
Expand Down
4 changes: 3 additions & 1 deletion pkg/cache/memory/repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package cache
package memory

import (
"context"
Expand All @@ -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"
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion pkg/cache/memory/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

package cache
package memory

import (
"sort"
Expand Down

0 comments on commit 07ea983

Please sign in to comment.