Skip to content

Commit db07f6f

Browse files
rendre-greylingjkosimovmozesl-nokia
authored
Make parallel repository listing configurable + Porch server doesn't respond to PackageRevision queries if one of the registered git repositories fails (#282)
* Porch server doesn't respond to PackageRevision queries if one of the registered git repositories fails * Changes to pr listing * Make parallel repository listing configurable * Fixes to way context cancel is handled * Generate mocks for PackageRevision and PackageRevisionDraft and refactor tests to use it * Update tests to align with main * Refactor of test cases * Fix tests after fixing merge conflicts * Fix hanging repository test * Fixes from commits * Refactor of test cases * Merged tests together and refactored * Add missing envvars * Refactor envvar for kpt_function_test * Missed default branch for kpt_function_test * Fix fake pkgrev after rebase * rewrite mutex logic of repository caching * fix lint * Fix failing e2e tests * Added testing to background * add main mutex to map operations in dbcache * Add missed test --------- Co-authored-by: Kosimov Javlonbek <javlonbek.kosimov@nokia.com> Co-authored-by: Mozes László <laszlo.mozes@nokia.com>
1 parent 4c066b6 commit db07f6f

27 files changed

Lines changed: 2765 additions & 488 deletions

.env.template

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@
1414
#PORCH_KPT_REPO_USER=<username>
1515
#PORCH_KPT_REPO_PASSWORD=<password/access_token>
1616

17+
#PORCH_GCP_BUCKET_REF=<branch_name>
18+
#PORCH_GCP_REDIS_BUCKET_REF=<branch_name>
19+
#PORCH_GCP_HIERARCHY_REF=<branch_name>
20+
#PORCH_KPT_FUNCTION_REF=<branch_name>
21+
1722
# GCR prefix should not contain trailing "/": TestPodFunctionEvaluatorWithDistrolessImage uses kpt-fn-demo directory.
1823
# If GRC prefix environment variable is set, make run-in-kind should be run after for changes to take effect.
1924
# Format: your-gcr-mirror/kpt-fn
2025
#PORCH_GCR_PREFIX_URL=<your_gcr_registry_mirror_url>
21-
22-
# This environment variable is used in TestPodEvaluator and should only be set if the PORCH_GCR_PREFIX_URL is set.
23-
#PORCH_POD_EVAL_REF=<branch_name>

.mockery.yaml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ packages:
1010
config:
1111
dir: "test/mockery/mocks/external/{{.InterfaceDirRelative}}"
1212
outpkg: "{{.PackageName}}"
13+
SubResourceWriter:
14+
config:
15+
dir: "test/mockery/mocks/external/{{.InterfaceDirRelative}}"
16+
outpkg: "{{.PackageName}}"
17+
WithWatch:
18+
config:
19+
dir: "test/mockery/mocks/external/{{.InterfaceDirRelative}}"
20+
outpkg: "{{.PackageName}}"
1321
github.com/nephio-project/porch/pkg/cache/crcache/meta:
1422
interfaces:
1523
MetadataStore:

pkg/apiserver/apiserver.go

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,11 @@ func init() {
7474

7575
// ExtraConfig holds custom apiserver config
7676
type ExtraConfig struct {
77-
CoreAPIKubeconfigPath string
78-
GRPCRuntimeOptions engine.GRPCRuntimeOptions
79-
CacheOptions cachetypes.CacheOptions
77+
CoreAPIKubeconfigPath string
78+
GRPCRuntimeOptions engine.GRPCRuntimeOptions
79+
CacheOptions cachetypes.CacheOptions
80+
ListTimeoutPerRepository time.Duration
81+
MaxConcurrentLists int
8082
}
8183

8284
// Config defines the config for the apiserver
@@ -91,6 +93,7 @@ type PorchServer struct {
9193
coreClient client.WithWatch
9294
cache cachetypes.Cache
9395
PeriodicRepoSyncFrequency time.Duration
96+
ListTimeoutPerRepository time.Duration
9497
}
9598

9699
type completedConfig struct {
@@ -260,7 +263,15 @@ func (c completedConfig) New(ctx context.Context) (*PorchServer, error) {
260263
return nil, err
261264
}
262265

263-
porchGroup, err := porch.NewRESTStorage(Scheme, Codecs, cad, coreClient)
266+
restStorageOptions := porch.RESTStorageOptions{
267+
Scheme: Scheme,
268+
Codecs: Codecs,
269+
CaD: cad,
270+
CoreClient: coreClient,
271+
TimeoutPerRepository: c.ExtraConfig.ListTimeoutPerRepository,
272+
MaxConcurrentLists: c.ExtraConfig.MaxConcurrentLists,
273+
}
274+
porchGroup, err := restStorageOptions.NewRESTStorage()
264275
if err != nil {
265276
return nil, err
266277
}
@@ -271,6 +282,7 @@ func (c completedConfig) New(ctx context.Context) (*PorchServer, error) {
271282
cache: cacheImpl,
272283
// Set background job periodic frequency the same as repo sync frequency.
273284
PeriodicRepoSyncFrequency: c.ExtraConfig.CacheOptions.RepoSyncFrequency,
285+
ListTimeoutPerRepository: c.ExtraConfig.ListTimeoutPerRepository,
274286
}
275287

276288
// Install the groups.
@@ -282,7 +294,10 @@ func (c completedConfig) New(ctx context.Context) (*PorchServer, error) {
282294
}
283295

284296
func (s *PorchServer) Run(ctx context.Context) error {
285-
porch.RunBackground(ctx, s.coreClient, s.cache, s.PeriodicRepoSyncFrequency)
297+
porch.RunBackground(ctx, s.coreClient, s.cache,
298+
porch.WithPeriodicRepoSyncFrequency(s.PeriodicRepoSyncFrequency),
299+
porch.WithListTimeoutPerRepo(s.ListTimeoutPerRepository),
300+
)
286301

287302
// TODO: Reconsider if the existence of CERT_STORAGE_DIR was a good inidcator for webhook setup,
288303
// but for now we keep backward compatiblity

pkg/cache/crcache/cache.go

Lines changed: 69 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ package crcache
1717
import (
1818
"context"
1919
"sync"
20+
"time"
2021

2122
configapi "github.com/nephio-project/porch/api/porchconfig/v1alpha1"
2223
"github.com/nephio-project/porch/pkg/cache/crcache/meta"
@@ -25,13 +26,15 @@ import (
2526
"github.com/nephio-project/porch/pkg/repository"
2627
"go.opentelemetry.io/otel"
2728
"go.opentelemetry.io/otel/trace"
29+
"k8s.io/klog/v2"
2830
)
2931

3032
var tracer = otel.Tracer("crcache")
3133

3234
type Cache struct {
33-
mutex sync.Mutex
3435
repositories map[repository.RepositoryKey]*cachedRepository
36+
mainLock *sync.RWMutex
37+
locks map[repository.RepositoryKey]*sync.Mutex
3538
metadataStore meta.MetadataStore
3639
options cachetypes.CacheOptions
3740
}
@@ -41,40 +44,48 @@ var _ cachetypes.Cache = &Cache{}
4144
func (c *Cache) OpenRepository(ctx context.Context, repositorySpec *configapi.Repository) (repository.Repository, error) {
4245
ctx, span := tracer.Start(ctx, "Cache::OpenRepository", trace.WithAttributes())
4346
defer span.End()
47+
start := time.Now()
48+
defer func() { klog.V(4).Infof("Cache::OpenRepository (%s) took %s", repositorySpec.Name, time.Since(start)) }()
4449

4550
key, err := externalrepo.RepositoryKey(repositorySpec)
4651
if err != nil {
4752
return nil, err
4853
}
4954

50-
c.mutex.Lock()
51-
defer c.mutex.Unlock()
55+
lock := c.getOrInsertLock(key)
56+
lock.Lock()
57+
defer lock.Unlock()
5258

53-
if cachedRepo := c.repositories[key]; cachedRepo != nil {
59+
c.mainLock.RLock()
60+
if repo, ok := c.repositories[key]; ok && repo != nil {
61+
c.mainLock.RUnlock()
5462
// Test if credentials are okay for the cached repo and update the status accordingly
55-
if _, err := externalrepo.CreateRepositoryImpl(ctx, repositorySpec, c.options.ExternalRepoOptions); err == nil {
56-
return cachedRepo, err
57-
} else {
58-
// If there is an error from the background refresh goroutine, return it.
59-
if err := cachedRepo.getRefreshError(); err == nil {
60-
return cachedRepo, nil
61-
}
63+
if _, err := externalrepo.CreateRepositoryImpl(ctx, repositorySpec, c.options.ExternalRepoOptions); err != nil {
64+
return nil, err
6265
}
63-
return nil, err
66+
// If there is an error from the background refresh goroutine, return it.
67+
if err := repo.getRefreshError(); err != nil {
68+
return nil, err
69+
}
70+
return repo, nil
6471
}
72+
c.mainLock.RUnlock()
6573

6674
externalRepo, err := externalrepo.CreateRepositoryImpl(ctx, repositorySpec, c.options.ExternalRepoOptions)
6775
if err != nil {
6876
return nil, err
6977
}
7078

7179
cachedRepo := newRepository(key, repositorySpec, externalRepo, c.metadataStore, c.options)
80+
81+
c.mainLock.Lock()
7282
c.repositories[key] = cachedRepo
83+
c.mainLock.Unlock()
7384

7485
return cachedRepo, nil
7586
}
7687

77-
func (c *Cache) UpdateRepository(ctx context.Context, repositorySpec *configapi.Repository) error {
88+
func (c *Cache) UpdateRepository(context.Context, *configapi.Repository) error {
7889
panic("Update on CR cached repositories is not applicable")
7990
}
8091

@@ -98,36 +109,69 @@ func (c *Cache) CloseRepository(ctx context.Context, repositorySpec *configapi.R
98109
}
99110
if otherKey == key {
100111
// do not close cached repo if it is shared
112+
klog.Infof("Not closing cached repository %q because it is shared", key)
101113
return nil
102114
}
103115
}
104116

105-
var repository *cachedRepository
106-
{
107-
c.mutex.Lock()
108-
if r, ok := c.repositories[key]; ok {
109-
delete(c.repositories, key)
110-
repository = r
111-
}
112-
c.mutex.Unlock()
113-
}
117+
lock := c.getOrInsertLock(key)
118+
lock.Lock()
119+
defer lock.Unlock()
120+
121+
c.mainLock.RLock()
122+
repo, ok := c.repositories[key]
123+
c.mainLock.RUnlock()
114124

115-
if repository != nil {
116-
return repository.Close(ctx)
125+
if ok {
126+
c.mainLock.Lock()
127+
delete(c.locks, key)
128+
delete(c.repositories, key)
129+
c.mainLock.Unlock()
130+
131+
if repo != nil {
132+
return repo.Close(ctx)
133+
} else {
134+
klog.Warningf("cached repository with key %q had stored value nil", key)
135+
}
117136
} else {
118-
return nil
137+
c.mainLock.Lock()
138+
delete(c.locks, key)
139+
c.mainLock.Unlock()
119140
}
141+
142+
return nil
120143
}
121144

122145
func (c *Cache) GetRepositories() []*configapi.Repository {
123146
repoSlice := []*configapi.Repository{}
124147

148+
c.mainLock.RLock()
149+
defer c.mainLock.RUnlock()
125150
for _, repo := range c.repositories {
126151
repoSlice = append(repoSlice, repo.repoSpec)
127152
}
153+
128154
return repoSlice
129155
}
130156

131157
func (c *Cache) GetRepository(repoKey repository.RepositoryKey) repository.Repository {
158+
c.mainLock.RLock()
159+
defer c.mainLock.RUnlock()
132160
return c.repositories[repoKey]
133161
}
162+
163+
func (c *Cache) getOrInsertLock(key repository.RepositoryKey) *sync.Mutex {
164+
c.mainLock.RLock()
165+
if lock, exists := c.locks[key]; exists {
166+
c.mainLock.RUnlock()
167+
return lock
168+
}
169+
c.mainLock.RUnlock()
170+
171+
c.mainLock.Lock()
172+
lock := &sync.Mutex{}
173+
c.locks[key] = lock
174+
c.mainLock.Unlock()
175+
176+
return lock
177+
}

pkg/cache/crcache/cache_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import (
1919
"fmt"
2020
"os"
2121
"path/filepath"
22+
"sync"
2223
"testing"
2324
"time"
2425

@@ -246,7 +247,9 @@ func openRepositoryFromArchive(t *testing.T, ctx context.Context, testPath, name
246247
metadataStore := createMetadataStoreFromArchive(t, fmt.Sprintf("%s-metadata.yaml", name), name)
247248

248249
cache := &Cache{
249-
repositories: make(map[repository.RepositoryKey]*cachedRepository),
250+
repositories: map[repository.RepositoryKey]*cachedRepository{},
251+
locks: map[repository.RepositoryKey]*sync.Mutex{},
252+
mainLock: &sync.RWMutex{},
250253
metadataStore: metadataStore,
251254
options: cachetypes.CacheOptions{
252255
ExternalRepoOptions: externalrepotypes.ExternalRepoOptions{

pkg/cache/crcache/crcachefactory.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package crcache
1616

1717
import (
1818
"context"
19+
"sync"
1920

2021
"github.com/nephio-project/porch/pkg/cache/crcache/meta"
2122
cachetypes "github.com/nephio-project/porch/pkg/cache/types"
@@ -29,7 +30,9 @@ type CrCacheFactory struct {
2930

3031
func (f *CrCacheFactory) NewCache(_ context.Context, options cachetypes.CacheOptions) (cachetypes.Cache, error) {
3132
return &Cache{
32-
repositories: make(map[repository.RepositoryKey]*cachedRepository),
33+
repositories: map[repository.RepositoryKey]*cachedRepository{},
34+
locks: map[repository.RepositoryKey]*sync.Mutex{},
35+
mainLock: &sync.RWMutex{},
3336
metadataStore: meta.NewCrdMetadataStore(options.CoreClient),
3437
options: options,
3538
}, nil

pkg/cache/crcache/repository.go

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ func (r *cachedRepository) getRefreshError() error {
144144
}
145145

146146
func (r *cachedRepository) getPackageRevisions(ctx context.Context, filter repository.ListPackageRevisionFilter, forceRefresh bool) ([]repository.PackageRevision, error) {
147-
147+
klog.Infof("Cache::OpenRepository(%s) fetching packages", r.Key())
148148
_, packageRevisions, err := r.getCachedPackages(ctx, forceRefresh)
149149
if err != nil {
150150
return nil, err
@@ -490,22 +490,24 @@ func (r *cachedRepository) Close(ctx context.Context) error {
490490

491491
// pollForever will continue polling until the signal channel is closed or the ctx is done.
492492
func (r *cachedRepository) pollForever(ctx context.Context, repoSyncFrequency time.Duration) {
493+
ticker := time.NewTicker(repoSyncFrequency)
494+
defer ticker.Stop()
493495
for {
494496
select {
495497
case <-ctx.Done():
496-
klog.V(2).Infof("repo %+v: exiting repository poller, because context is done: %v", r.Key(), ctx.Err())
498+
klog.Infof("repo %+v: exiting repository poller, because context is done: %v", r.Key(), ctx.Err())
497499
return
498-
default:
500+
case <-ticker.C:
499501
r.pollOnce(ctx)
500-
time.Sleep(repoSyncFrequency)
502+
ticker.Reset(repoSyncFrequency)
501503
}
502504
}
503505
}
504506

505507
func (r *cachedRepository) pollOnce(ctx context.Context) {
506508
start := time.Now()
507509
klog.Infof("repo %+v: poll started", r.Key())
508-
defer func() { klog.Infof("repo %+v: poll finished in %f secs", r.Key(), time.Since(start).Seconds()) }()
510+
defer func() { klog.Infof("repo %+v: poll finished in %s", r.Key(), time.Since(start)) }()
509511
ctx, span := tracer.Start(ctx, "[START]::Repository::pollOnce", trace.WithAttributes())
510512
defer span.End()
511513

0 commit comments

Comments
 (0)