Skip to content

Commit 05ffd85

Browse files
authored
use catalogd HTTP server instead of CatalogMetadata API (#411)
Signed-off-by: Bryce Palmer <[email protected]>
1 parent 92aacef commit 05ffd85

File tree

13 files changed

+601
-334
lines changed

13 files changed

+601
-334
lines changed

cmd/manager/main.go

+11-4
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,9 @@ package main
1818

1919
import (
2020
"flag"
21+
"net/http"
2122
"os"
23+
"time"
2224

2325
"github.com/spf13/pflag"
2426
"go.uber.org/zap/zapcore"
@@ -35,6 +37,7 @@ import (
3537
rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1"
3638

3739
operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
40+
"github.com/operator-framework/operator-controller/internal/catalogmetadata/cache"
3841
catalogclient "github.com/operator-framework/operator-controller/internal/catalogmetadata/client"
3942
"github.com/operator-framework/operator-controller/internal/controllers"
4043
"github.com/operator-framework/operator-controller/pkg/features"
@@ -56,14 +59,18 @@ func init() {
5659
}
5760

5861
func main() {
59-
var metricsAddr string
60-
var enableLeaderElection bool
61-
var probeAddr string
62+
var (
63+
metricsAddr string
64+
enableLeaderElection bool
65+
probeAddr string
66+
cachePath string
67+
)
6268
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
6369
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
6470
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
6571
"Enable leader election for controller manager. "+
6672
"Enabling this will ensure there is only one active controller manager.")
73+
flag.StringVar(&cachePath, "cache-path", "/var/cache", "The local directory path used for filesystem based caching")
6774
opts := zap.Options{
6875
Development: true,
6976
}
@@ -100,7 +107,7 @@ func main() {
100107
}
101108

102109
cl := mgr.GetClient()
103-
catalogClient := catalogclient.New(cl)
110+
catalogClient := catalogclient.New(cl, cache.NewFilesystemCache(cachePath, &http.Client{Timeout: 10 * time.Second}))
104111

105112
if err = (&controllers.OperatorReconciler{
106113
Client: cl,

config/manager/manager.yaml

+6
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ spec:
7272
image: controller:latest
7373
imagePullPolicy: IfNotPresent
7474
name: manager
75+
volumeMounts:
76+
- name: cache
77+
mountPath: /var/cache
7578
securityContext:
7679
allowPrivilegeEscalation: false
7780
capabilities:
@@ -97,3 +100,6 @@ spec:
97100
memory: 64Mi
98101
serviceAccountName: controller-manager
99102
terminationGracePeriodSeconds: 10
103+
volumes:
104+
- name: cache
105+
emptyDir: {}

go.mod

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ require (
99
github.com/onsi/ginkgo/v2 v2.12.1
1010
github.com/onsi/gomega v1.27.10
1111
github.com/operator-framework/api v0.17.4-0.20230223191600-0131a6301e42
12-
github.com/operator-framework/catalogd v0.6.0
12+
github.com/operator-framework/catalogd v0.7.0
1313
github.com/operator-framework/deppy v0.0.1
1414
github.com/operator-framework/operator-registry v1.28.0
1515
github.com/operator-framework/rukpak v0.13.0

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -702,8 +702,8 @@ github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3
702702
github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=
703703
github.com/operator-framework/api v0.17.4-0.20230223191600-0131a6301e42 h1:d/Pnr19TnmIq3zQ6ebewC+5jt5zqYbRkvYd37YZENQY=
704704
github.com/operator-framework/api v0.17.4-0.20230223191600-0131a6301e42/go.mod h1:l/cuwtPxkVUY7fzYgdust2m9tlmb8I4pOvbsUufRb24=
705-
github.com/operator-framework/catalogd v0.6.0 h1:dSZ54MVSHJ8hcoV7OCRxnk3x4O3ramlyPvvz0vsKYdk=
706-
github.com/operator-framework/catalogd v0.6.0/go.mod h1:I0n086a4a+nP1YZy742IrPaWvOlWu0Mj6qA6j4K96Vg=
705+
github.com/operator-framework/catalogd v0.7.0 h1:L0uesxq+r59rGubtxMoVtIShKn7gSSSLqxpWLfwpAaw=
706+
github.com/operator-framework/catalogd v0.7.0/go.mod h1:tVhaenJVFTHHgdJ0Pju7U4G3epeoZfUWWM1J5nPISPQ=
707707
github.com/operator-framework/deppy v0.0.1 h1:PLTtaFGwktPhKuKZkfUruTimrWpyaO3tghbsjs0uMjc=
708708
github.com/operator-framework/deppy v0.0.1/go.mod h1:EV6vnxRodSFRn2TFztfxFhMPGh5QufOhn3tpIP1Z8cc=
709709
github.com/operator-framework/operator-registry v1.28.0 h1:vtmd2WgJxkx7vuuOxW4k5Le/oo0SfonSeJVMU3rKIfk=
+150
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package cache
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io"
7+
"net/http"
8+
"os"
9+
"path/filepath"
10+
"sync"
11+
12+
catalogd "github.com/operator-framework/catalogd/api/core/v1alpha1"
13+
14+
"github.com/operator-framework/operator-controller/internal/catalogmetadata/client"
15+
)
16+
17+
var _ client.Fetcher = &filesystemCache{}
18+
19+
// NewFilesystemCache returns a client.Fetcher implementation that uses a
20+
// local filesystem to cache Catalog contents. When fetching the Catalog contents
21+
// it will:
22+
// - Check if the Catalog is cached
23+
// - IF !cached it will fetch from the catalogd HTTP server and cache the response
24+
// - IF cached it will verify the cache is up to date. If it is up to date it will return
25+
// the cached contents, if not it will fetch the new contents from the catalogd HTTP
26+
// server and update the cached contents.
27+
func NewFilesystemCache(cachePath string, client *http.Client) client.Fetcher {
28+
return &filesystemCache{
29+
cachePath: cachePath,
30+
mutex: sync.RWMutex{},
31+
client: client,
32+
cacheDataByCatalogName: map[string]cacheData{},
33+
}
34+
}
35+
36+
// cacheData holds information about a catalog
37+
// other than it's contents that is used for
38+
// making decisions on when to attempt to refresh
39+
// the cache.
40+
type cacheData struct {
41+
ResolvedRef string
42+
}
43+
44+
// FilesystemCache is a cache that
45+
// uses the local filesystem for caching
46+
// catalog contents. It will fetch catalog
47+
// contents if the catalog does not already
48+
// exist in the cache.
49+
type filesystemCache struct {
50+
mutex sync.RWMutex
51+
cachePath string
52+
client *http.Client
53+
cacheDataByCatalogName map[string]cacheData
54+
}
55+
56+
// FetchCatalogContents implements the client.Fetcher interface and
57+
// will fetch the contents for the provided Catalog from the filesystem.
58+
// If the provided Catalog has not yet been cached, it will make a GET
59+
// request to the Catalogd HTTP server to get the Catalog contents and cache
60+
// them. The cache will be updated automatically if a Catalog is noticed to
61+
// have a different resolved image reference.
62+
// The Catalog provided to this function is expected to:
63+
// - Be non-nil
64+
// - Have a non-nil Catalog.Status.ResolvedSource.Image
65+
// This ensures that we are only attempting to fetch catalog contents for Catalog
66+
// resources that have been successfully reconciled, unpacked, and are being served.
67+
// These requirements help ensure that we can rely on status conditions to determine
68+
// when to issue a request to update the cached Catalog contents.
69+
func (fsc *filesystemCache) FetchCatalogContents(ctx context.Context, catalog *catalogd.Catalog) (io.ReadCloser, error) {
70+
if catalog == nil {
71+
return nil, fmt.Errorf("error: provided catalog must be non-nil")
72+
}
73+
74+
if catalog.Status.ResolvedSource == nil {
75+
return nil, fmt.Errorf("error: catalog %q has a nil status.resolvedSource value", catalog.Name)
76+
}
77+
78+
if catalog.Status.ResolvedSource.Image == nil {
79+
return nil, fmt.Errorf("error: catalog %q has a nil status.resolvedSource.image value", catalog.Name)
80+
}
81+
82+
cacheDir := filepath.Join(fsc.cachePath, catalog.Name)
83+
cacheFilePath := filepath.Join(cacheDir, "data.json")
84+
85+
fsc.mutex.RLock()
86+
if data, ok := fsc.cacheDataByCatalogName[catalog.Name]; ok {
87+
if catalog.Status.ResolvedSource.Image.Ref == data.ResolvedRef {
88+
fsc.mutex.RUnlock()
89+
return os.Open(cacheFilePath)
90+
}
91+
}
92+
fsc.mutex.RUnlock()
93+
94+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, catalog.Status.ContentURL, nil)
95+
if err != nil {
96+
return nil, fmt.Errorf("error forming request: %s", err)
97+
}
98+
99+
resp, err := fsc.client.Do(req)
100+
if err != nil {
101+
return nil, fmt.Errorf("error performing request: %s", err)
102+
}
103+
defer resp.Body.Close()
104+
105+
if resp.StatusCode != http.StatusOK {
106+
return nil, fmt.Errorf("error: received unexpected response status code %d", resp.StatusCode)
107+
}
108+
109+
fsc.mutex.Lock()
110+
defer fsc.mutex.Unlock()
111+
112+
// make sure we only write if this info hasn't been updated
113+
// by another thread. The check here, if multiple threads are
114+
// updating this, has no way to tell if the current ref is the
115+
// newest possible ref. If another thread has already updated
116+
// this to be the same value, skip the write logic and return
117+
// the cached contents
118+
if data, ok := fsc.cacheDataByCatalogName[catalog.Name]; ok {
119+
if data.ResolvedRef == catalog.Status.ResolvedSource.Image.Ref {
120+
return os.Open(cacheFilePath)
121+
}
122+
}
123+
124+
if err = os.MkdirAll(cacheDir, os.ModePerm); err != nil {
125+
return nil, fmt.Errorf("error creating cache directory for Catalog %q: %s", catalog.Name, err)
126+
}
127+
128+
file, err := os.Create(cacheFilePath)
129+
if err != nil {
130+
return nil, fmt.Errorf("error creating cache file for Catalog %q: %s", catalog.Name, err)
131+
}
132+
133+
if _, err := io.Copy(file, resp.Body); err != nil {
134+
return nil, fmt.Errorf("error writing contents to cache file for Catalog %q: %s", catalog.Name, err)
135+
}
136+
137+
if err = file.Sync(); err != nil {
138+
return nil, fmt.Errorf("error syncing contents to cache file for Catalog %q: %s", catalog.Name, err)
139+
}
140+
141+
if _, err = file.Seek(0, 0); err != nil {
142+
return nil, fmt.Errorf("error resetting offset for cache file reader for Catalog %q: %s", catalog.Name, err)
143+
}
144+
145+
fsc.cacheDataByCatalogName[catalog.Name] = cacheData{
146+
ResolvedRef: catalog.Status.ResolvedSource.Image.Ref,
147+
}
148+
149+
return file, nil
150+
}

0 commit comments

Comments
 (0)