2023-09-26 16:15:15 -05:00
|
|
|
package rest
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
|
|
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
|
|
"k8s.io/apiserver/pkg/registry/rest"
|
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
2024-05-06 16:18:28 -05:00
|
|
|
_ rest.Storage = (DualWriter)(nil)
|
|
|
|
_ rest.Scoper = (DualWriter)(nil)
|
|
|
|
_ rest.TableConvertor = (DualWriter)(nil)
|
|
|
|
_ rest.CreaterUpdater = (DualWriter)(nil)
|
|
|
|
_ rest.CollectionDeleter = (DualWriter)(nil)
|
|
|
|
_ rest.GracefulDeleter = (DualWriter)(nil)
|
|
|
|
_ rest.SingularNameProvider = (DualWriter)(nil)
|
2023-09-26 16:15:15 -05:00
|
|
|
)
|
|
|
|
|
|
|
|
// Storage is a storage implementation that satisfies the same interfaces as genericregistry.Store.
|
|
|
|
type Storage interface {
|
|
|
|
rest.Storage
|
|
|
|
rest.Scoper
|
|
|
|
rest.TableConvertor
|
|
|
|
rest.SingularNameProvider
|
2023-11-14 16:07:32 -06:00
|
|
|
rest.Getter
|
2024-05-06 16:18:28 -05:00
|
|
|
// TODO: when watch is implemented, we can replace all the below with rest.StandardStorage
|
|
|
|
rest.Lister
|
|
|
|
rest.CreaterUpdater
|
|
|
|
rest.GracefulDeleter
|
|
|
|
rest.CollectionDeleter
|
2023-09-26 16:15:15 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// LegacyStorage is a storage implementation that writes to the Grafana SQL database.
|
|
|
|
type LegacyStorage interface {
|
|
|
|
rest.Storage
|
|
|
|
rest.Scoper
|
|
|
|
rest.SingularNameProvider
|
2024-05-07 08:02:30 -05:00
|
|
|
rest.CreaterUpdater
|
|
|
|
rest.Lister
|
|
|
|
rest.GracefulDeleter
|
|
|
|
rest.CollectionDeleter
|
2023-09-26 16:15:15 -05:00
|
|
|
rest.TableConvertor
|
2023-11-17 13:20:54 -06:00
|
|
|
rest.Getter
|
2023-09-26 16:15:15 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// DualWriter is a storage implementation that writes first to LegacyStorage and then to Storage.
|
|
|
|
// If writing to LegacyStorage fails, the write to Storage is skipped and the error is returned.
|
2023-11-14 16:07:32 -06:00
|
|
|
// Storage is used for all read operations. This is useful as a migration step from SQL based
|
|
|
|
// legacy storage to a more standard kubernetes backed storage interface.
|
|
|
|
//
|
|
|
|
// NOTE: Only values supported by legacy storage will be preserved in the CREATE/UPDATE commands.
|
|
|
|
// For example, annotations, labels, and managed fields may not be preserved. Everything in upstream
|
|
|
|
// storage can be recrated from the data in legacy storage.
|
2023-09-26 16:15:15 -05:00
|
|
|
//
|
|
|
|
// The LegacyStorage implementation must implement the following interfaces:
|
|
|
|
// - rest.Storage
|
|
|
|
// - rest.TableConvertor
|
|
|
|
// - rest.Scoper
|
|
|
|
// - rest.SingularNameProvider
|
|
|
|
//
|
|
|
|
// These interfaces are optional, but they all should be implemented to fully support dual writes:
|
|
|
|
// - rest.Creater
|
|
|
|
// - rest.Updater
|
|
|
|
// - rest.GracefulDeleter
|
|
|
|
// - rest.CollectionDeleter
|
2024-05-06 16:18:28 -05:00
|
|
|
|
|
|
|
type DualWriter interface {
|
2023-09-26 16:15:15 -05:00
|
|
|
Storage
|
2024-05-06 16:18:28 -05:00
|
|
|
LegacyStorage
|
2023-09-26 16:15:15 -05:00
|
|
|
}
|
|
|
|
|
2024-05-06 16:18:28 -05:00
|
|
|
type DualWriterMode int
|
|
|
|
|
2024-04-04 07:02:51 -05:00
|
|
|
const (
|
|
|
|
Mode1 DualWriterMode = iota
|
|
|
|
Mode2
|
|
|
|
Mode3
|
|
|
|
Mode4
|
|
|
|
)
|
|
|
|
|
|
|
|
var CurrentMode = Mode2
|
|
|
|
|
2024-05-06 16:18:28 -05:00
|
|
|
//TODO: make CurrentMode customisable and specific to each entity
|
|
|
|
// change DualWriter signature to get the current mode as an argument
|
2024-04-04 07:02:51 -05:00
|
|
|
|
2023-09-26 16:15:15 -05:00
|
|
|
// NewDualWriter returns a new DualWriter.
|
2024-05-06 16:18:28 -05:00
|
|
|
func NewDualWriter(legacy LegacyStorage, storage Storage) DualWriter {
|
|
|
|
return selectDualWriter(CurrentMode, legacy, storage)
|
2023-11-14 16:07:32 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
type updateWrapper struct {
|
|
|
|
upstream rest.UpdatedObjectInfo
|
|
|
|
updated runtime.Object
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns preconditions built from the updated object, if applicable.
|
|
|
|
// May return nil, or a preconditions object containing nil fields,
|
|
|
|
// if no preconditions can be determined from the updated object.
|
|
|
|
func (u *updateWrapper) Preconditions() *metav1.Preconditions {
|
|
|
|
return u.upstream.Preconditions()
|
|
|
|
}
|
2023-09-26 16:15:15 -05:00
|
|
|
|
2023-11-14 16:07:32 -06:00
|
|
|
// UpdatedObject returns the updated object, given a context and old object.
|
|
|
|
// The only time an empty oldObj should be passed in is if a "create on update" is occurring (there is no oldObj).
|
|
|
|
func (u *updateWrapper) UpdatedObject(ctx context.Context, oldObj runtime.Object) (newObj runtime.Object, err error) {
|
|
|
|
return u.updated, nil
|
2023-09-26 16:15:15 -05:00
|
|
|
}
|
2024-04-04 07:02:51 -05:00
|
|
|
|
2024-05-06 16:18:28 -05:00
|
|
|
func selectDualWriter(mode DualWriterMode, legacy LegacyStorage, storage Storage) DualWriter {
|
2024-04-04 07:02:51 -05:00
|
|
|
switch mode {
|
|
|
|
case Mode1:
|
2024-05-06 16:18:28 -05:00
|
|
|
// read and write only from legacy storage
|
2024-04-04 07:02:51 -05:00
|
|
|
return NewDualWriterMode1(legacy, storage)
|
|
|
|
case Mode2:
|
2024-05-06 16:18:28 -05:00
|
|
|
// write to both, read from storage but use legacy as backup
|
2024-04-04 07:02:51 -05:00
|
|
|
return NewDualWriterMode2(legacy, storage)
|
|
|
|
case Mode3:
|
2024-05-06 16:18:28 -05:00
|
|
|
// write to both, read from storage only
|
2024-04-04 07:02:51 -05:00
|
|
|
return NewDualWriterMode3(legacy, storage)
|
|
|
|
case Mode4:
|
2024-05-06 16:18:28 -05:00
|
|
|
// read and write only from storage
|
2024-04-04 07:02:51 -05:00
|
|
|
return NewDualWriterMode4(legacy, storage)
|
|
|
|
default:
|
2024-05-06 16:18:28 -05:00
|
|
|
return NewDualWriterMode1(legacy, storage)
|
2024-04-04 07:02:51 -05:00
|
|
|
}
|
|
|
|
}
|