blob: 038209bbff57031aab6136a7120606d56926e8c7 [file] [log] [blame]
package ds_ignorestore
import (
"context"
"sort"
"sync/atomic"
"cloud.google.com/go/datastore"
"go.skia.org/infra/go/ds"
"go.skia.org/infra/go/skerr"
"go.skia.org/infra/go/sklog"
"go.skia.org/infra/golden/go/dsutil"
"go.skia.org/infra/golden/go/ignore"
"golang.org/x/sync/errgroup"
)
// DSIgnoreStore implements the IgnoreStore interface
type DSIgnoreStore struct {
client *datastore.Client
recentKeysList *dsutil.RecentKeysList
revision int64
}
// New returns an IgnoreStore instance that is backed by Cloud Datastore.
func New(client *datastore.Client) (*DSIgnoreStore, error) {
if client == nil {
return nil, skerr.Fmt("Received nil for datastore client.")
}
containerKey := ds.NewKey(ds.HELPER_RECENT_KEYS)
containerKey.Name = "ignore:recent-keys"
store := &DSIgnoreStore{
client: client,
recentKeysList: dsutil.NewRecentKeysList(client, containerKey, dsutil.DefaultConsistencyDelta),
}
return store, nil
}
// Create implements the IgnoreStore interface.
func (c *DSIgnoreStore) Create(ignoreRule *ignore.IgnoreRule) error {
createFn := func(tx *datastore.Transaction) error {
key := dsutil.TimeSortableKey(ds.IGNORE_RULE, 0)
ignoreRule.ID = key.ID
// Add the new rule and put its key with the recently added keys.
if _, err := tx.Put(key, ignoreRule); err != nil {
return err
}
return c.recentKeysList.Add(tx, key)
}
// Run the relevant updates in a transaction.
_, err := c.client.RunInTransaction(context.TODO(), createFn)
// TODO(stephana): Look into removing the revision feature. I don't think
// this is really necessary going forward.
if err == nil {
atomic.AddInt64(&c.revision, 1)
}
return err
}
// List implements the IgnoreStore interface.
func (c *DSIgnoreStore) List() ([]*ignore.IgnoreRule, error) {
ctx := context.TODO()
var egroup errgroup.Group
var queriedKeys []*datastore.Key
egroup.Go(func() error {
// Query all entities.
query := ds.NewQuery(ds.IGNORE_RULE).KeysOnly()
var err error
queriedKeys, err = c.client.GetAll(ctx, query, nil)
return err
})
var recently *dsutil.Recently
egroup.Go(func() error {
var err error
recently, err = c.recentKeysList.GetRecent()
return err
})
if err := egroup.Wait(); err != nil {
return nil, skerr.Fmt("Error getting keys of ignore rules: %s", err)
}
// Merge the keys to get all of the current keys.
allKeys := recently.Combine(queriedKeys)
if len(allKeys) == 0 {
return []*ignore.IgnoreRule{}, nil
}
ret := make([]*ignore.IgnoreRule, len(allKeys))
if err := c.client.GetMulti(ctx, allKeys, ret); err != nil {
return nil, err
}
sort.Slice(ret, func(i, j int) bool { return ret[i].Expires.Before(ret[j].Expires) })
return ret, nil
}
// Update implements the IgnoreStore interface.
func (c *DSIgnoreStore) Update(id int64, rule *ignore.IgnoreRule) error {
ctx := context.TODO()
key := ds.NewKey(ds.IGNORE_RULE)
key.ID = id
_, err := c.client.Mutate(ctx, datastore.NewUpdate(key, rule))
if err == nil {
atomic.AddInt64(&c.revision, 1)
}
return err
}
// Delete implements the IgnoreStore interface.
func (c *DSIgnoreStore) Delete(id int64) (int, error) {
if id <= 0 {
return 0, skerr.Fmt("Given id does not exist: %d", id)
}
deleteFn := func(tx *datastore.Transaction) error {
key := ds.NewKey(ds.IGNORE_RULE)
key.ID = id
ignoreRule := &ignore.IgnoreRule{}
if err := tx.Get(key, ignoreRule); err != nil {
return err
}
if err := tx.Delete(key); err != nil {
return err
}
return c.recentKeysList.Delete(tx, key)
}
// Run the relevant updates in a transaction.
_, err := c.client.RunInTransaction(context.TODO(), deleteFn)
if err != nil {
// Don't report an error if the item did not exist.
if err == datastore.ErrNoSuchEntity {
sklog.Warningf("Could not delete ignore with id %d because it did not exist", id)
return 0, nil
}
return 0, err
}
atomic.AddInt64(&c.revision, 1)
return 1, nil
}
// Revision implements the IgnoreStore interface.
func (c *DSIgnoreStore) Revision() int64 {
return atomic.LoadInt64(&c.revision)
}
// BuildRuleMatcher implements the IgnoreStore interface.
func (c *DSIgnoreStore) BuildRuleMatcher() (ignore.RuleMatcher, error) {
return ignore.BuildRuleMatcher(c)
}