Skip to content

Commit 00d48c7

Browse files
authored
Improve repository reconciliation to use cron based periodic syncs and introduce one time syncs (#328)
* Introduce cronbased and runOnceAt reconciliation for db-cache * Introduce new command to allow user to run repository sync once - updated repo reg and added tests to cover sync schedule - added unit tests for runOnce feature * Update cr cache to run repo sync using cron + runOnceAt mechanism * Add a feature to change repo cr status when reconciliation is in progress * fix comment * Fix cache tests and reduce code complexity * Align logs for handleRunOnceAt function * Add unit tests for sync and git connectivity check * Add more test coverage and refactor background cache updates * Add retry mechanism for repo status update for api conflicts * Align logs for invalid spec and add test coverage for external repo connection check * Introduce next sync time in Repo CR status message * Refactor cr and db cache to use a common sync interface * Parameterize repo retry attempts and fix merge conflict * Make fakeclient common to all tests and improve code
1 parent 2c9384e commit 00d48c7

59 files changed

Lines changed: 3254 additions & 310 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

api/porchconfig/v1alpha1/config.porch.kpt.dev_repositories.yaml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ spec:
3434
- jsonPath: .spec.content
3535
name: Content
3636
type: string
37+
- jsonPath: .spec.sync.schedule
38+
name: Sync schedule
39+
type: string
3740
- jsonPath: .spec.deployment
3841
name: Deployment
3942
type: boolean
@@ -155,6 +158,21 @@ spec:
155158
required:
156159
- registry
157160
type: object
161+
sync:
162+
description: Repository sync/reconcile details
163+
properties:
164+
runOnceAt:
165+
description: Value in metav1.Time format to indicate when the
166+
repository should be synced once outside the periodic cron based
167+
reconcile loop.
168+
format: date-time
169+
type: string
170+
schedule:
171+
description: 'Cron value to indicate when the repository should
172+
be synced periodically. Example: `*/10 * * * *` to sync every
173+
10 minutes.'
174+
type: string
175+
type: object
158176
type:
159177
description: Type of the repository (i.e. git, OCI)
160178
type: string

api/porchconfig/v1alpha1/types.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
//+kubebuilder:resource:path=repositories,singular=repository
2424
//+kubebuilder:printcolumn:name="Type",type=string,JSONPath=`.spec.type`
2525
//+kubebuilder:printcolumn:name="Content",type=string,JSONPath=`.spec.content`
26+
// +kubebuilder:printcolumn:name="Sync schedule",type=string,JSONPath=`.spec.sync.schedule`
2627
//+kubebuilder:printcolumn:name="Deployment",type=boolean,JSONPath=`.spec.deployment`
2728
//+kubebuilder:printcolumn:name="Ready",type=string,JSONPath=`.status.conditions[?(@.type=='Ready')].status`
2829
//+kubebuilder:printcolumn:name="Address",type=string,JSONPath=`.spec['git','oci']['repo','registry']`
@@ -67,13 +68,21 @@ type RepositorySpec struct {
6768
// +kubebuilder:validation:XValidation:message="The 'content' field is deprecated, its only valid value is 'Package'",rule="self == '' || self == 'Package'"
6869
// +kubebuilder:default="Package"
6970
Content *RepositoryContent `json:"content,omitempty"`
70-
71+
// Repository sync/reconcile details
72+
Sync *RepositorySync `json:"sync,omitempty"`
7173
// Git repository details. Required if `type` is `git`. Ignored if `type` is not `git`.
7274
Git *GitRepository `json:"git,omitempty"`
7375
// OCI repository details. Required if `type` is `oci`. Ignored if `type` is not `oci`.
7476
Oci *OciRepository `json:"oci,omitempty"`
7577
}
7678

79+
type RepositorySync struct {
80+
// Value in metav1.Time format to indicate when the repository should be synced once outside the periodic cron based reconcile loop.
81+
RunOnceAt *metav1.Time `json:"runOnceAt,omitempty"`
82+
// Cron value to indicate when the repository should be synced periodically. Example: `*/10 * * * *` to sync every 10 minutes.
83+
Schedule string `json:"schedule,omitempty"`
84+
}
85+
7786
// GitRepository describes a Git repository.
7887
// TODO: authentication methods
7988
type GitRepository struct {
@@ -139,11 +148,12 @@ type FunctionEval struct {
139148
const (
140149
// Type of the Repository condition.
141150
RepositoryReady = "Ready"
142-
143151
// Reason for the condition is error.
144152
ReasonError = "Error"
145153
// Reason for the condition is the repository is ready.
146154
ReasonReady = "Ready"
155+
// Reason for the condition is repository reconciliation is in progress.
156+
ReasonReconciling = "Reconciling"
147157
)
148158

149159
// RepositoryStatus defines the observed state of Repository

api/porchconfig/v1alpha1/zz_generated.deepcopy.go

Lines changed: 24 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

deployments/porch/3-porch-server.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ spec:
7878
- --cert-dir=/tmp/certs
7979
- --secure-port=4443
8080
- --repo-sync-frequency=3m
81+
- --repo-operation-retry-attempts=3
8182
- --disable-validating-admissions-policy=true
8283
- --max-request-body-size=6291456 # Keep this in sync with function-runner's corresponding argument
8384
#adding livenessProbes and readinessProbes for porch server

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ require (
164164
github.com/prometheus/client_model v0.6.2 // indirect
165165
github.com/prometheus/common v0.67.2 // indirect
166166
github.com/prometheus/procfs v0.19.2 // indirect
167+
github.com/robfig/cron/v3 v3.0.1
167168
github.com/russross/blackfriday/v2 v2.1.0 // indirect
168169
github.com/sergi/go-diff v1.4.0 // indirect
169170
github.com/sirupsen/logrus v1.9.3 // indirect

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,8 @@ github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4
340340
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw=
341341
github.com/qri-io/starlib v0.5.0 h1:NlveoBAhO6mNgM7+JpM9QlHh3/3pOtOiH6iXaqSdVK0=
342342
github.com/qri-io/starlib v0.5.0/go.mod h1:FpVumyB2CMrKIrjf39fAi4uydYWVvnWEvXEOwfzZRHY=
343+
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
344+
github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
343345
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
344346
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
345347
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=

pkg/apiserver/apiserver.go

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,8 +92,9 @@ type PorchServer struct {
9292
GenericAPIServer *genericapiserver.GenericAPIServer
9393
coreClient client.WithWatch
9494
cache cachetypes.Cache
95-
PeriodicRepoSyncFrequency time.Duration
96-
ListTimeoutPerRepository time.Duration
95+
periodicRepoSyncFrequency time.Duration
96+
ListTimeoutPerRepository time.Duration
97+
repoOperationRetryAttempts int
9798
}
9899

99100
type completedConfig struct {
@@ -230,6 +231,7 @@ func (c completedConfig) New(ctx context.Context) (*PorchServer, error) {
230231
c.ExtraConfig.CacheOptions.ExternalRepoOptions.CredentialResolver = credentialResolver
231232
c.ExtraConfig.CacheOptions.ExternalRepoOptions.CaBundleResolver = caBundleResolver
232233
c.ExtraConfig.CacheOptions.ExternalRepoOptions.UserInfoProvider = userInfoProvider
234+
c.ExtraConfig.CacheOptions.ExternalRepoOptions.RepoOperationRetryAttempts = c.ExtraConfig.CacheOptions.RepoOperationRetryAttempts
233235

234236
cacheImpl, err := cache.GetCacheImpl(ctx, c.ExtraConfig.CacheOptions)
235237

@@ -255,6 +257,7 @@ func (c completedConfig) New(ctx context.Context) (*PorchServer, error) {
255257
engine.WithReferenceResolver(referenceResolver),
256258
engine.WithUserInfoProvider(userInfoProvider),
257259
engine.WithWatcherManager(watcherMgr),
260+
engine.WithRepoOperationRetryAttempts(c.ExtraConfig.CacheOptions.RepoOperationRetryAttempts),
258261
)
259262
if err != nil {
260263
return nil, err
@@ -278,8 +281,9 @@ func (c completedConfig) New(ctx context.Context) (*PorchServer, error) {
278281
coreClient: coreClient,
279282
cache: cacheImpl,
280283
// Set background job periodic frequency the same as repo sync frequency.
281-
PeriodicRepoSyncFrequency: c.ExtraConfig.CacheOptions.RepoSyncFrequency,
282-
ListTimeoutPerRepository: c.ExtraConfig.ListTimeoutPerRepository,
284+
periodicRepoSyncFrequency: c.ExtraConfig.CacheOptions.RepoSyncFrequency,
285+
ListTimeoutPerRepository: c.ExtraConfig.ListTimeoutPerRepository,
286+
repoOperationRetryAttempts: c.ExtraConfig.CacheOptions.RepoOperationRetryAttempts,
283287
}
284288

285289
// Install the groups.
@@ -292,8 +296,9 @@ func (c completedConfig) New(ctx context.Context) (*PorchServer, error) {
292296

293297
func (s *PorchServer) Run(ctx context.Context) error {
294298
porch.RunBackground(ctx, s.coreClient, s.cache,
295-
porch.WithPeriodicRepoSyncFrequency(s.PeriodicRepoSyncFrequency),
299+
porch.WithPeriodicRepoSyncFrequency(s.periodicRepoSyncFrequency),
296300
porch.WithListTimeoutPerRepo(s.ListTimeoutPerRepository),
301+
porch.WithRepoOperationRetryAttempts(s.repoOperationRetryAttempts),
297302
)
298303

299304
// TODO: Reconsider if the existence of CERT_STORAGE_DIR was a good inidcator for webhook setup,

pkg/cache/crcache/cache.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,10 +59,14 @@ func (c *Cache) OpenRepository(ctx context.Context, repositorySpec *configapi.Re
5959
c.mainLock.RLock()
6060
if repo, ok := c.repositories[key]; ok && repo != nil {
6161
c.mainLock.RUnlock()
62-
// Test if credentials are okay for the cached repo and update the status accordingly
63-
if _, err := externalrepo.CreateRepositoryImpl(ctx, repositorySpec, c.options.ExternalRepoOptions); err != nil {
62+
// Keep the spec updated in the cache.
63+
repo.repoSpec = repositorySpec
64+
// Check external repo connectivity
65+
if err := externalrepo.CheckRepositoryConnection(ctx, repositorySpec, c.options.ExternalRepoOptions); err != nil {
66+
klog.Warningf("Cache:OpenRepository: repo %+v connectivity check failed with error %q", key, err)
6467
return nil, err
6568
}
69+
klog.V(2).Infof("Cache::OpenRepository: verified repo connectivity %+v", key)
6670
// If there is an error from the background refresh goroutine, return it.
6771
if err := repo.getRefreshError(); err != nil {
6872
return nil, err

pkg/cache/crcache/cache_test.go

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ import (
2626
"github.com/google/go-cmp/cmp"
2727
api "github.com/nephio-project/porch/api/porch/v1alpha1"
2828
"github.com/nephio-project/porch/api/porchconfig/v1alpha1"
29-
3029
"github.com/nephio-project/porch/pkg/cache/crcache/meta"
3130
fakemeta "github.com/nephio-project/porch/pkg/cache/crcache/meta/fake"
3231
fakecache "github.com/nephio-project/porch/pkg/cache/fake"
@@ -35,6 +34,8 @@ import (
3534
externalrepotypes "github.com/nephio-project/porch/pkg/externalrepo/types"
3635
"github.com/nephio-project/porch/pkg/repository"
3736
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
37+
"k8s.io/apimachinery/pkg/runtime"
38+
k8sfake "sigs.k8s.io/controller-runtime/pkg/client/fake"
3839
"sigs.k8s.io/yaml"
3940
)
4041

@@ -246,21 +247,6 @@ func openRepositoryFromArchive(t *testing.T, ctx context.Context, testPath, name
246247
_, address := git.ServeGitRepository(t, tarfile, tempdir)
247248
metadataStore := createMetadataStoreFromArchive(t, fmt.Sprintf("%s-metadata.yaml", name), name)
248249

249-
cache := &Cache{
250-
repositories: map[repository.RepositoryKey]*cachedRepository{},
251-
locks: map[repository.RepositoryKey]*sync.Mutex{},
252-
mainLock: &sync.RWMutex{},
253-
metadataStore: metadataStore,
254-
options: cachetypes.CacheOptions{
255-
ExternalRepoOptions: externalrepotypes.ExternalRepoOptions{
256-
LocalDirectory: t.TempDir(),
257-
UseUserDefinedCaBundle: true,
258-
CredentialResolver: &fakecache.CredentialResolver{},
259-
},
260-
RepoSyncFrequency: 60 * time.Second,
261-
RepoPRChangeNotifier: &fakecache.ObjectNotifier{},
262-
}}
263-
264250
apiRepo := &v1alpha1.Repository{
265251
TypeMeta: metav1.TypeMeta{
266252
Kind: v1alpha1.TypeRepository.Kind,
@@ -278,6 +264,27 @@ func openRepositoryFromArchive(t *testing.T, ctx context.Context, testPath, name
278264
},
279265
},
280266
}
267+
268+
scheme := runtime.NewScheme()
269+
_ = v1alpha1.AddToScheme(scheme)
270+
271+
fakeClient := k8sfake.NewClientBuilder().WithScheme(scheme).WithObjects(apiRepo).Build()
272+
cache := &Cache{
273+
repositories: map[repository.RepositoryKey]*cachedRepository{},
274+
locks: map[repository.RepositoryKey]*sync.Mutex{},
275+
mainLock: &sync.RWMutex{},
276+
metadataStore: metadataStore,
277+
options: cachetypes.CacheOptions{
278+
ExternalRepoOptions: externalrepotypes.ExternalRepoOptions{
279+
LocalDirectory: t.TempDir(),
280+
UseUserDefinedCaBundle: true,
281+
CredentialResolver: &fakecache.CredentialResolver{},
282+
RepoOperationRetryAttempts: 3,
283+
},
284+
CoreClient: fakeClient,
285+
RepoSyncFrequency: 60 * time.Second,
286+
RepoPRChangeNotifier: &fakecache.ObjectNotifier{},
287+
}}
281288
cachedRepo, err := cache.OpenRepository(ctx, apiRepo)
282289
if err != nil {
283290
t.Fatalf("OpenRepository(%q) of %q failed; %v", address, tarfile, err)

0 commit comments

Comments
 (0)