Skip to content

Commit 562cf9c

Browse files
authored
refactor FBC caching (#1051)
This commit improves maintainability and extensibility of the FBC caching used to serve the GRPC API. It: - introduces a new cache package and interface, which defines primitives for checking cache integrity, building the cache, and loading the cache. - moves the existing registry.Querier to an implementation of the cache interface, called cache.JSON This refactor also resolves two outstanding issues: 1. The current code contains a bug that causes panics when certain failures occur loading the cache. 2. The current code is hardcoded to always rebuild the cache if it is unreadable or its digest doesn't contain the expected value. This commit: 1. Refactors and removes the cases the led to the panic situation 2. Adds a new flag, --cache-enforce-integrity, that causes `opm serve` to exit with a failure if the cache is unreadable or its digest doesn't contain the expected value. This is useful in production contexts when one always expects the cache to be pre-populated correctly. Signed-off-by: Joe Lanford <[email protected]> Signed-off-by: Joe Lanford <[email protected]>
1 parent 25a478d commit 562cf9c

File tree

118 files changed

+15461
-1551
lines changed

Some content is hidden

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

118 files changed

+15461
-1551
lines changed

cmd/opm/serve/serve.go

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"errors"
77
"fmt"
8+
"io"
89
"net"
910
"net/http"
1011
endpoint "net/http/pprof"
@@ -19,17 +20,18 @@ import (
1920

2021
"github.com/operator-framework/operator-registry/pkg/api"
2122
health "github.com/operator-framework/operator-registry/pkg/api/grpc_health_v1"
23+
"github.com/operator-framework/operator-registry/pkg/cache"
2224
"github.com/operator-framework/operator-registry/pkg/lib/dns"
2325
"github.com/operator-framework/operator-registry/pkg/lib/graceful"
2426
"github.com/operator-framework/operator-registry/pkg/lib/log"
25-
"github.com/operator-framework/operator-registry/pkg/registry"
2627
"github.com/operator-framework/operator-registry/pkg/server"
2728
)
2829

2930
type serve struct {
30-
configDir string
31-
cacheDir string
32-
cacheOnly bool
31+
configDir string
32+
cacheDir string
33+
cacheOnly bool
34+
cacheEnforceIntegrity bool
3335

3436
port string
3537
terminationLog string
@@ -59,15 +61,19 @@ startup. Changes made to the declarative config after the this command starts
5961
will not be reflected in the served content.
6062
`,
6163
Args: cobra.ExactArgs(1),
62-
PreRunE: func(_ *cobra.Command, args []string) error {
64+
PreRun: func(_ *cobra.Command, args []string) {
6365
s.configDir = args[0]
6466
if s.debug {
6567
logger.SetLevel(logrus.DebugLevel)
6668
}
67-
return nil
6869
},
69-
RunE: func(cmd *cobra.Command, _ []string) error {
70-
return s.run(cmd.Context())
70+
Run: func(cmd *cobra.Command, _ []string) {
71+
if !cmd.Flags().Changed("cache-enforce-integrity") {
72+
s.cacheEnforceIntegrity = s.cacheDir != "" && !s.cacheOnly
73+
}
74+
if err := s.run(cmd.Context()); err != nil {
75+
logger.Fatal(err)
76+
}
7177
},
7278
}
7379

@@ -77,6 +83,7 @@ will not be reflected in the served content.
7783
cmd.Flags().StringVar(&s.pprofAddr, "pprof-addr", "", "address of startup profiling endpoint (addr:port format)")
7884
cmd.Flags().StringVar(&s.cacheDir, "cache-dir", "", "if set, sync and persist server cache directory")
7985
cmd.Flags().BoolVar(&s.cacheOnly, "cache-only", false, "sync the serve cache and exit without serving")
86+
cmd.Flags().BoolVar(&s.cacheEnforceIntegrity, "cache-enforce-integrity", false, "exit with error if cache is not present or has been invalidated. (default: true when --cache-dir is set and --cache-only is false, false otherwise), ")
8087
return cmd
8188
}
8289

@@ -102,11 +109,38 @@ func (s *serve) run(ctx context.Context) error {
102109

103110
s.logger = s.logger.WithFields(logrus.Fields{"configs": s.configDir, "port": s.port})
104111

105-
store, err := registry.NewQuerierFromFS(os.DirFS(s.configDir), s.cacheDir)
106-
defer store.Close()
112+
if s.cacheDir == "" && s.cacheEnforceIntegrity {
113+
return fmt.Errorf("--cache-dir must be specified with --cache-enforce-integrity")
114+
}
115+
116+
if s.cacheDir == "" {
117+
s.cacheDir, err = os.MkdirTemp("", "opm-serve-cache-")
118+
if err != nil {
119+
return err
120+
}
121+
defer os.RemoveAll(s.cacheDir)
122+
}
123+
124+
store, err := cache.New(s.cacheDir)
107125
if err != nil {
108126
return err
109127
}
128+
if storeCloser, ok := store.(io.Closer); ok {
129+
defer storeCloser.Close()
130+
}
131+
if s.cacheEnforceIntegrity {
132+
if err := store.CheckIntegrity(os.DirFS(s.configDir)); err != nil {
133+
return err
134+
}
135+
if err := store.Load(); err != nil {
136+
return err
137+
}
138+
} else {
139+
if err := cache.LoadOrRebuild(store, os.DirFS(s.configDir)); err != nil {
140+
return err
141+
}
142+
}
143+
110144
if s.cacheOnly {
111145
return nil
112146
}

go.mod

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ require (
3434
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4
3535
golang.org/x/net v0.0.0-20220722155237-a158d28d115b
3636
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
37-
golang.org/x/sys v0.0.0-20220907062415-87db552b00fd
37+
golang.org/x/sys v0.0.0-20221010170243-090e33056c14
3838
google.golang.org/grpc v1.47.0
3939
google.golang.org/grpc/cmd/protoc-gen-go-grpc v0.0.0-20200709232328-d8193ee9cc3e
4040
google.golang.org/protobuf v1.28.0
@@ -94,6 +94,7 @@ require (
9494
github.com/gogo/protobuf v1.3.2 // indirect
9595
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
9696
github.com/golang/protobuf v1.5.2 // indirect
97+
github.com/golang/snappy v0.0.3 // indirect
9798
github.com/google/cel-go v0.12.4 // indirect
9899
github.com/google/gnostic v0.5.7-v3refs // indirect
99100
github.com/google/gofuzz v1.1.0 // indirect
@@ -108,7 +109,7 @@ require (
108109
github.com/josharian/intern v1.0.0 // indirect
109110
github.com/json-iterator/go v1.1.12 // indirect
110111
github.com/kardianos/osext v0.0.0-20190222173326-2bc1f35cddc0 // indirect
111-
github.com/klauspost/compress v1.11.13 // indirect
112+
github.com/klauspost/compress v1.12.3 // indirect
112113
github.com/mailru/easyjson v0.7.6 // indirect
113114
github.com/mattn/go-isatty v0.0.12 // indirect
114115
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 // indirect

go.sum

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -338,6 +338,8 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
338338
github.com/golang/snappy v0.0.0-20170215233205-553a64147049/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
339339
github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
340340
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
341+
github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA=
342+
github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
341343
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
342344
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
343345
github.com/google/cel-go v0.12.4 h1:YINKfuHZ8n72tPOqSPZBwGiDpew2CJS48mdM5W8LZQU=
@@ -468,8 +470,8 @@ github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvW
468470
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
469471
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
470472
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
471-
github.com/klauspost/compress v1.11.13 h1:eSvu8Tmq6j2psUJqJrLcWH6K3w5Dwc+qipbaA6eVEN4=
472-
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
473+
github.com/klauspost/compress v1.12.3 h1:G5AfA94pHPysR56qqrkO2pxEexdDzrpFJ6yt/VqWxVU=
474+
github.com/klauspost/compress v1.12.3/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg=
473475
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
474476
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
475477
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
@@ -942,8 +944,8 @@ golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBc
942944
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
943945
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
944946
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
945-
golang.org/x/sys v0.0.0-20220907062415-87db552b00fd h1:AZeIEzg+8RCELJYq8w+ODLVxFgLMMigSwO/ffKPEd9U=
946-
golang.org/x/sys v0.0.0-20220907062415-87db552b00fd/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
947+
golang.org/x/sys v0.0.0-20221010170243-090e33056c14 h1:k5II8e6QD8mITdi+okbbmR/cIyEbeXLBhy5Ha4nevyc=
948+
golang.org/x/sys v0.0.0-20221010170243-090e33056c14/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
947949
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
948950
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
949951
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

pkg/cache/cache.go

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package cache
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"io/fs"
8+
"os"
9+
"path/filepath"
10+
11+
"k8s.io/apimachinery/pkg/util/sets"
12+
13+
"github.com/operator-framework/operator-registry/pkg/api"
14+
"github.com/operator-framework/operator-registry/pkg/registry"
15+
)
16+
17+
type Cache interface {
18+
registry.GRPCQuery
19+
20+
CheckIntegrity(fbc fs.FS) error
21+
Build(fbc fs.FS) error
22+
Load() error
23+
}
24+
25+
func LoadOrRebuild(c Cache, fbc fs.FS) error {
26+
if err := c.CheckIntegrity(fbc); err != nil {
27+
if err := c.Build(fbc); err != nil {
28+
return err
29+
}
30+
}
31+
return c.Load()
32+
}
33+
34+
// New creates a new Cache. It chooses a cache implementation based
35+
// on the files it finds in the cache directory, with a preference for the
36+
// latest iteration of the cache implementation. It returns an error if
37+
// cacheDir exists and contains unexpected files.
38+
func New(cacheDir string) (Cache, error) {
39+
entries, err := os.ReadDir(cacheDir)
40+
if err != nil && !errors.Is(err, os.ErrNotExist) {
41+
return nil, fmt.Errorf("detect cache format: read cache directory: %v", err)
42+
}
43+
jsonCache := sets.NewString(jsonDir, jsonDigestFile)
44+
45+
found := sets.NewString()
46+
for _, e := range entries {
47+
found.Insert(e.Name())
48+
}
49+
50+
// Preference (and currently only supported) is the JSON-based cache implementation.
51+
if found.IsSuperset(jsonCache) || len(entries) == 0 {
52+
return NewJSON(cacheDir), nil
53+
}
54+
55+
// Anything else is unexpected.
56+
return nil, fmt.Errorf("cache directory has unexpected contents")
57+
}
58+
59+
func ensureEmptyDir(dir string, mode os.FileMode) error {
60+
if err := os.MkdirAll(dir, mode); err != nil {
61+
return err
62+
}
63+
entries, err := os.ReadDir(dir)
64+
if err != nil {
65+
return err
66+
}
67+
for _, entry := range entries {
68+
if err := os.RemoveAll(filepath.Join(dir, entry.Name())); err != nil {
69+
return err
70+
}
71+
}
72+
return nil
73+
}
74+
75+
func doesBundleProvide(ctx context.Context, c Cache, pkgName, chName, bundleName, group, version, kind string) (bool, error) {
76+
apiBundle, err := c.GetBundle(ctx, pkgName, chName, bundleName)
77+
if err != nil {
78+
return false, fmt.Errorf("get bundle %q: %v", bundleName, err)
79+
}
80+
for _, gvk := range apiBundle.ProvidedApis {
81+
if gvk.Group == group && gvk.Version == version && gvk.Kind == kind {
82+
return true, nil
83+
}
84+
}
85+
return false, nil
86+
}
87+
88+
type sliceBundleSender []*api.Bundle
89+
90+
func (s *sliceBundleSender) Send(b *api.Bundle) error {
91+
*s = append(*s, b)
92+
return nil
93+
}
94+
95+
func listBundles(ctx context.Context, c Cache) ([]*api.Bundle, error) {
96+
var bundleSender sliceBundleSender
97+
98+
err := c.SendBundles(ctx, &bundleSender)
99+
if err != nil {
100+
return nil, err
101+
}
102+
103+
return bundleSender, nil
104+
}

0 commit comments

Comments
 (0)