Skip to content

Commit 038505c

Browse files
committed
use catalogd HTTP server instead of CatalogMetadata API
Signed-off-by: Bryce Palmer <[email protected]>
1 parent 52a1e28 commit 038505c

File tree

13 files changed

+596
-338
lines changed

13 files changed

+596
-338
lines changed

cmd/manager/main.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package main
1818

1919
import (
2020
"flag"
21+
"net/http"
2122
"os"
2223

2324
"github.com/spf13/pflag"
@@ -35,6 +36,7 @@ import (
3536
rukpakv1alpha1 "github.com/operator-framework/rukpak/api/v1alpha1"
3637

3738
operatorsv1alpha1 "github.com/operator-framework/operator-controller/api/v1alpha1"
39+
"github.com/operator-framework/operator-controller/internal/catalogmetadata/cache"
3840
catalogclient "github.com/operator-framework/operator-controller/internal/catalogmetadata/client"
3941
"github.com/operator-framework/operator-controller/internal/controllers"
4042
"github.com/operator-framework/operator-controller/pkg/features"
@@ -56,14 +58,18 @@ func init() {
5658
}
5759

5860
func main() {
59-
var metricsAddr string
60-
var enableLeaderElection bool
61-
var probeAddr string
61+
var (
62+
metricsAddr string
63+
enableLeaderElection bool
64+
probeAddr string
65+
cachePath string
66+
)
6267
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
6368
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
6469
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
6570
"Enable leader election for controller manager. "+
6671
"Enabling this will ensure there is only one active controller manager.")
72+
flag.StringVar(&cachePath, "cache-path", "/var/cache", "The local directory path used for filesystem based caching")
6773
opts := zap.Options{
6874
Development: true,
6975
}
@@ -100,7 +106,7 @@ func main() {
100106
}
101107

102108
cl := mgr.GetClient()
103-
catalogClient := catalogclient.New(cl)
109+
catalogClient := catalogclient.New(cl, cache.NewFilesystemCache(cachePath, http.DefaultClient))
104110

105111
if err = (&controllers.OperatorReconciler{
106112
Client: cl,

config/manager/manager.yaml

Lines changed: 6 additions & 0 deletions
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

Lines changed: 1 addition & 1 deletion
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

Lines changed: 2 additions & 2 deletions
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=
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
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+
switch resp.StatusCode {
106+
case http.StatusOK:
107+
fsc.mutex.Lock()
108+
defer fsc.mutex.Unlock()
109+
110+
// make sure we only write if this info hasn't been updated
111+
// by another thread. The check here, if multiple threads are
112+
// updating this, has no way to tell if the current ref is the
113+
// newest possible ref. If another thread has already updated
114+
// this to be the same value, skip the write logic and return
115+
// the cached contents
116+
if data, ok := fsc.cacheDataByCatalogName[catalog.Name]; ok {
117+
if data.ResolvedRef == catalog.Status.ResolvedSource.Image.Ref {
118+
break
119+
}
120+
}
121+
122+
if err = os.MkdirAll(cacheDir, os.ModePerm); err != nil {
123+
return nil, fmt.Errorf("error creating cache directory for Catalog %q: %s", catalog.Name, err)
124+
}
125+
126+
file, err := os.Create(cacheFilePath)
127+
if err != nil {
128+
return nil, fmt.Errorf("error creating cache file for Catalog %q: %s", catalog.Name, err)
129+
}
130+
131+
if _, err := io.Copy(file, resp.Body); err != nil {
132+
return nil, fmt.Errorf("error writing contents to cache for Catalog %q: %s", catalog.Name, err)
133+
}
134+
135+
fsc.cacheDataByCatalogName[catalog.Name] = cacheData{
136+
ResolvedRef: catalog.Status.ResolvedSource.Image.Ref,
137+
}
138+
default:
139+
return nil, fmt.Errorf("error: received unexpected response status code %d", resp.StatusCode)
140+
}
141+
142+
return os.Open(cacheFilePath)
143+
}

0 commit comments

Comments
 (0)