mirror of
https://github.com/siderolabs/image-factory.git
synced 2025-09-21 22:01:09 +02:00
Add fallback to direct asset download in case of S3 issues. Signed-off-by: Mateusz Urbanek <mateusz.urbanek@siderolabs.com>
160 lines
3.5 KiB
Go
160 lines
3.5 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
// Package cache implements an in-memory cache over schematic storage.
|
|
package cache
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
|
|
"github.com/prometheus/client_golang/prometheus"
|
|
"github.com/siderolabs/gen/optional"
|
|
"github.com/siderolabs/gen/xerrors"
|
|
"golang.org/x/sync/singleflight"
|
|
|
|
"github.com/siderolabs/image-factory/internal/schematic/storage"
|
|
)
|
|
|
|
// Options configures the storage.
|
|
type Options struct {
|
|
MetricsNamespace string
|
|
}
|
|
|
|
// Storage is a schematic storage in-memory cache.
|
|
type Storage struct {
|
|
underlying storage.Storage
|
|
|
|
metricCacheSize prometheus.Gauge
|
|
|
|
g singleflight.Group
|
|
m map[string]optional.Optional[[]byte]
|
|
mu sync.Mutex
|
|
}
|
|
|
|
// NewCache returns a new cache storage.
|
|
func NewCache(underlying storage.Storage, options Options) *Storage {
|
|
return &Storage{
|
|
underlying: underlying,
|
|
m: map[string]optional.Optional[[]byte]{},
|
|
metricCacheSize: prometheus.NewGauge(prometheus.GaugeOpts{
|
|
Name: "image_factory_schematic_cache_size",
|
|
Help: "Number of schematics in in-memory cache.",
|
|
Namespace: options.MetricsNamespace,
|
|
}),
|
|
}
|
|
}
|
|
|
|
// Check interface.
|
|
var _ storage.Storage = (*Storage)(nil)
|
|
|
|
// Head checks if the schematic exists.
|
|
func (s *Storage) Head(ctx context.Context, id string) error {
|
|
// check cache
|
|
s.mu.Lock()
|
|
v, ok := s.m[id]
|
|
s.mu.Unlock()
|
|
|
|
// cache entry is there, return immediate response
|
|
if ok {
|
|
if v.IsPresent() {
|
|
return nil
|
|
}
|
|
|
|
return xerrors.NewTaggedf[storage.ErrNotFoundTag]("schematic ID %q not found", id)
|
|
}
|
|
|
|
// cache entry is not there, use .Get to populate it
|
|
_, err := s.Get(ctx, id)
|
|
|
|
return err
|
|
}
|
|
|
|
// Get returns the schematic.
|
|
func (s *Storage) Get(ctx context.Context, id string) ([]byte, error) {
|
|
// check cache
|
|
s.mu.Lock()
|
|
v, ok := s.m[id]
|
|
s.mu.Unlock()
|
|
|
|
// cache entry is there, return immediate response
|
|
if ok {
|
|
if v.IsPresent() {
|
|
return v.ValueOrZero(), nil
|
|
}
|
|
|
|
return nil, xerrors.NewTaggedf[storage.ErrNotFoundTag]("schematic ID %q not found", id)
|
|
}
|
|
|
|
ch := s.g.DoChan(id, func() (any, error) {
|
|
data, err := s.underlying.Get(ctx, id)
|
|
if err != nil {
|
|
if xerrors.TagIs[storage.ErrNotFoundTag](err) {
|
|
s.mu.Lock()
|
|
|
|
// never overwrite a present value, as Put might have been called
|
|
if _, ok := s.m[id]; !ok {
|
|
s.m[id] = optional.None[[]byte]()
|
|
}
|
|
|
|
s.mu.Unlock()
|
|
}
|
|
|
|
return nil, err
|
|
}
|
|
|
|
s.mu.Lock()
|
|
|
|
// never overwrite a present value, as Put might have been called
|
|
if _, ok := s.m[id]; !ok {
|
|
s.m[id] = optional.Some(data)
|
|
}
|
|
|
|
s.mu.Unlock()
|
|
|
|
return data, nil
|
|
})
|
|
|
|
select {
|
|
case <-ctx.Done():
|
|
return nil, ctx.Err()
|
|
case r := <-ch:
|
|
if r.Err != nil {
|
|
return nil, r.Err
|
|
}
|
|
|
|
return r.Val.([]byte), nil //nolint:forcetypeassert,errcheck
|
|
}
|
|
}
|
|
|
|
// Put stores the schematic.
|
|
func (s *Storage) Put(ctx context.Context, id string, data []byte) error {
|
|
err := s.underlying.Put(ctx, id, data)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
s.mu.Lock()
|
|
s.m[id] = optional.Some(data)
|
|
s.mu.Unlock()
|
|
|
|
return nil
|
|
}
|
|
|
|
// Describe implements prom.Collector interface.
|
|
func (s *Storage) Describe(ch chan<- *prometheus.Desc) {
|
|
prometheus.DescribeByCollect(s, ch)
|
|
}
|
|
|
|
// Collect implements prom.Collector interface.
|
|
func (s *Storage) Collect(ch chan<- prometheus.Metric) {
|
|
s.mu.Lock()
|
|
s.metricCacheSize.Set(float64(len(s.m)))
|
|
s.mu.Unlock()
|
|
|
|
s.metricCacheSize.Collect(ch)
|
|
}
|
|
|
|
var _ prometheus.Collector = &Storage{}
|