From 5dfe4cf407d0ae27249435693d97ac2710262267 Mon Sep 17 00:00:00 2001 From: Dan Cech Date: Mon, 8 Apr 2024 11:42:12 -0400 Subject: [PATCH] Storage: Watch tests (#85496) * basic watch tests working * refactor to read previous event within poller * add watch test files * cleanup * watch tests passing * debug cleanup * special handling for canceled context * cleanup * fix wire * fix sqlite_sequence cleanup issue * move watch tests to integration, wait for provisioned dashboards * handle context deadline exceeded and eof errors * add comment about sleep --- pkg/server/wire.go | 2 + .../apiserver/storage/entity/storage.go | 193 ++++- .../storage/entity/test/requestinfo.go | 166 ++++ .../storage/entity/test/watch_test.go | 351 ++++++++ pkg/services/sqlstore/migrator/migrator.go | 4 +- .../sqlstore/migrator/sqlite_dialect.go | 6 +- pkg/services/sqlstore/sqlstore.go | 2 +- pkg/services/store/entity/dummy/fake_store.go | 2 +- pkg/services/store/entity/entity.pb.go | 756 ++++++++++-------- pkg/services/store/entity/entity.proto | 25 +- pkg/services/store/entity/entity_grpc.pb.go | 35 +- .../entity/sqlstash/sql_storage_server.go | 615 +++++++++----- .../api/dashboards/api_dashboards_test.go | 4 + 13 files changed, 1579 insertions(+), 582 deletions(-) create mode 100644 pkg/services/apiserver/storage/entity/test/requestinfo.go create mode 100644 pkg/services/apiserver/storage/entity/test/watch_test.go diff --git a/pkg/server/wire.go b/pkg/server/wire.go index c61ebbd413d..4f0c3323d43 100644 --- a/pkg/server/wire.go +++ b/pkg/server/wire.go @@ -132,6 +132,7 @@ import ( "github.com/grafana/grafana/pkg/services/star/starimpl" "github.com/grafana/grafana/pkg/services/stats/statsimpl" "github.com/grafana/grafana/pkg/services/store" + entityStore "github.com/grafana/grafana/pkg/services/store/entity" entityDB "github.com/grafana/grafana/pkg/services/store/entity/db" "github.com/grafana/grafana/pkg/services/store/entity/db/dbimpl" "github.com/grafana/grafana/pkg/services/store/entity/sqlstash" @@ -336,6 +337,7 @@ var wireBasicSet = wire.NewSet( dbimpl.ProvideEntityDB, wire.Bind(new(entityDB.EntityDBInterface), new(*dbimpl.EntityDB)), sqlstash.ProvideSQLEntityServer, + wire.Bind(new(entityStore.EntityStoreServer), new(sqlstash.SqlEntityServer)), resolver.ProvideEntityReferenceResolver, teamimpl.ProvideService, teamapi.ProvideTeamAPI, diff --git a/pkg/services/apiserver/storage/entity/storage.go b/pkg/services/apiserver/storage/entity/storage.go index f3e4a9fcc1a..d971f3a9b2a 100644 --- a/pkg/services/apiserver/storage/entity/storage.go +++ b/pkg/services/apiserver/storage/entity/storage.go @@ -10,7 +10,6 @@ import ( "errors" "fmt" "io" - "log" "reflect" "strconv" @@ -28,6 +27,7 @@ import ( "k8s.io/apiserver/pkg/storage" "k8s.io/apiserver/pkg/storage/storagebackend" "k8s.io/apiserver/pkg/storage/storagebackend/factory" + "k8s.io/klog/v2" entityStore "github.com/grafana/grafana/pkg/services/store/entity" ) @@ -171,21 +171,13 @@ func (s *Storage) Watch(ctx context.Context, key string, opts storage.ListOption if opts.Predicate.Field != nil { // check for metadata.name field selector - if v, ok := opts.Predicate.Field.RequiresExactMatch("metadata.name"); ok { - if k.Name != "" && k.Name != v { - return nil, apierrors.NewBadRequest("name field selector does not match key") - } - + if v, ok := opts.Predicate.Field.RequiresExactMatch("metadata.name"); ok && k.Name == "" { // just watch the specific key if we have a name field selector k.Name = v } // check for metadata.namespace field selector - if v, ok := opts.Predicate.Field.RequiresExactMatch("metadata.namespace"); ok { - if k.Namespace != "" && k.Namespace != v { - return nil, apierrors.NewBadRequest("namespace field selector does not match key") - } - + if v, ok := opts.Predicate.Field.RequiresExactMatch("metadata.namespace"); ok && k.Namespace == "" { // just watch the specific namespace if we have a namespace field selector k.Namespace = v } @@ -209,12 +201,30 @@ func (s *Storage) Watch(ctx context.Context, key string, opts storage.ListOption } req := &entityStore.EntityWatchRequest{ + Action: entityStore.EntityWatchRequest_START, Key: []string{ k.String(), }, - Labels: map[string]string{}, - WithBody: true, - WithStatus: true, + Labels: map[string]string{}, + WithBody: true, + WithStatus: true, + SendInitialEvents: false, + AllowWatchBookmarks: opts.Predicate.AllowWatchBookmarks, + } + + if opts.ResourceVersion != "" { + rv, err := strconv.ParseInt(opts.ResourceVersion, 10, 64) + if err != nil { + return nil, apierrors.NewBadRequest(fmt.Sprintf("invalid resource version: %s", opts.ResourceVersion)) + } + + req.Since = rv + } + + if opts.SendInitialEvents == nil && req.Since == 0 { + req.SendInitialEvents = true + } else if opts.SendInitialEvents != nil { + req.SendInitialEvents = *opts.SendInitialEvents } if requirements.Folder != nil { @@ -233,24 +243,35 @@ func (s *Storage) Watch(ctx context.Context, key string, opts storage.ListOption } } - if opts.ResourceVersion != "" { - rv, err := strconv.ParseInt(opts.ResourceVersion, 10, 64) - if err != nil { - return nil, apierrors.NewBadRequest(fmt.Sprintf("invalid resource version: %s", opts.ResourceVersion)) + client, err := s.store.Watch(ctx) + if err != nil { + // if the context was canceled, just return a new empty watch + if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) || errors.Is(err, io.EOF) { + return watch.NewEmptyWatch(), nil } - req.Since = rv + return nil, err } - result, err := s.store.Watch(ctx, req) + err = client.Send(req) if err != nil { + err2 := client.CloseSend() + if err2 != nil { + klog.Errorf("watch close failed: %s\n", err2) + } + + // if the context was canceled, just return a new empty watch + if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) || errors.Is(err, io.EOF) { + return watch.NewEmptyWatch(), nil + } + return nil, err } reporter := apierrors.NewClientErrorReporter(500, "WATCH", "") decoder := &Decoder{ - client: result, + client: client, newFunc: s.newFunc, opts: opts, codec: s.codec, @@ -613,49 +634,148 @@ type Decoder struct { } func (d *Decoder) Decode() (action watch.EventType, object runtime.Object, err error) { +decode: for { + err := d.client.Context().Err() + if err != nil { + klog.Errorf("client: context error: %s\n", err) + return watch.Error, nil, err + } + resp, err := d.client.Recv() if errors.Is(err, io.EOF) { - log.Printf("watch is done") return watch.Error, nil, err } if grpcStatus.Code(err) == grpcCodes.Canceled { - log.Printf("watch was canceled") return watch.Error, nil, err } if err != nil { - log.Printf("error receiving result: %s", err) + klog.Errorf("client: error receiving result: %s", err) return watch.Error, nil, err } + if resp.Entity == nil { + klog.Errorf("client: received nil entity\n") + continue decode + } + obj := d.newFunc() - err = entityToResource(resp.Entity, obj, d.codec) - if err != nil { - log.Printf("error decoding entity: %s", err) - return watch.Error, nil, err + if resp.Entity.Action == entityStore.Entity_BOOKMARK { + // here k8s expects an empty object with just resource version and k8s.io/initial-events-end annotation + accessor, err := meta.Accessor(obj) + if err != nil { + klog.Errorf("error getting object accessor: %s", err) + return watch.Error, nil, err + } + + accessor.SetResourceVersion(fmt.Sprintf("%d", resp.Entity.ResourceVersion)) + accessor.SetAnnotations(map[string]string{"k8s.io/initial-events-end": "true"}) + return watch.Bookmark, obj, nil } - // apply any predicates not handled in storage - matches, err := d.opts.Predicate.Matches(obj) + err = entityToResource(resp.Entity, obj, d.codec) if err != nil { - log.Printf("error matching object: %s", err) + klog.Errorf("error decoding entity: %s", err) return watch.Error, nil, err } - if !matches { - continue - } var watchAction watch.EventType switch resp.Entity.Action { case entityStore.Entity_CREATED: + // apply any predicates not handled in storage + matches, err := d.opts.Predicate.Matches(obj) + if err != nil { + klog.Errorf("error matching object: %s", err) + return watch.Error, nil, err + } + if !matches { + continue decode + } + watchAction = watch.Added case entityStore.Entity_UPDATED: watchAction = watch.Modified + + // apply any predicates not handled in storage + matches, err := d.opts.Predicate.Matches(obj) + if err != nil { + klog.Errorf("error matching object: %s", err) + return watch.Error, nil, err + } + + // if we have a previous object, check if it matches + prevMatches := false + prevObj := d.newFunc() + if resp.Previous != nil { + err = entityToResource(resp.Previous, prevObj, d.codec) + if err != nil { + klog.Errorf("error decoding entity: %s", err) + return watch.Error, nil, err + } + + // apply any predicates not handled in storage + prevMatches, err = d.opts.Predicate.Matches(prevObj) + if err != nil { + klog.Errorf("error matching object: %s", err) + return watch.Error, nil, err + } + } + + if !matches { + if !prevMatches { + continue decode + } + + // if the object didn't match, send a Deleted event + watchAction = watch.Deleted + + // here k8s expects the previous object but with the new resource version + obj = prevObj + + accessor, err := meta.Accessor(obj) + if err != nil { + klog.Errorf("error getting object accessor: %s", err) + return watch.Error, nil, err + } + + accessor.SetResourceVersion(fmt.Sprintf("%d", resp.Entity.ResourceVersion)) + } else if !prevMatches { + // if the object didn't previously match, send an Added event + watchAction = watch.Added + } case entityStore.Entity_DELETED: watchAction = watch.Deleted + + // if we have a previous object, return that in the deleted event + if resp.Previous != nil { + err = entityToResource(resp.Previous, obj, d.codec) + if err != nil { + klog.Errorf("error decoding entity: %s", err) + return watch.Error, nil, err + } + + // here k8s expects the previous object but with the new resource version + accessor, err := meta.Accessor(obj) + if err != nil { + klog.Errorf("error getting object accessor: %s", err) + return watch.Error, nil, err + } + + accessor.SetResourceVersion(fmt.Sprintf("%d", resp.Entity.ResourceVersion)) + } + + // apply any predicates not handled in storage + matches, err := d.opts.Predicate.Matches(obj) + if err != nil { + klog.Errorf("error matching object: %s", err) + return watch.Error, nil, err + } + if !matches { + continue decode + } default: watchAction = watch.Error } @@ -665,7 +785,10 @@ func (d *Decoder) Decode() (action watch.EventType, object runtime.Object, err e } func (d *Decoder) Close() { - _ = d.client.CloseSend() + err := d.client.CloseSend() + if err != nil { + klog.Errorf("error closing watch stream: %s", err) + } } var _ watch.Decoder = (*Decoder)(nil) diff --git a/pkg/services/apiserver/storage/entity/test/requestinfo.go b/pkg/services/apiserver/storage/entity/test/requestinfo.go new file mode 100644 index 00000000000..9b19aa7f906 --- /dev/null +++ b/pkg/services/apiserver/storage/entity/test/requestinfo.go @@ -0,0 +1,166 @@ +package test + +import ( + "context" + "fmt" + "strings" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/storage" + + "github.com/grafana/grafana/pkg/infra/appcontext" + "github.com/grafana/grafana/pkg/services/user" +) + +var _ storage.Interface = &RequestInfoWrapper{} + +type RequestInfoWrapper struct { + store storage.Interface + gr schema.GroupResource +} + +func (r *RequestInfoWrapper) setRequestInfo(ctx context.Context, key string) (context.Context, error) { + pkey, err := convertToParsedKey(key) + if err != nil { + return nil, err + } + + ctx = appcontext.WithUser(ctx, &user.SignedInUser{ + Login: "admin", + UserID: 1, + OrgID: 1, + }) + + return request.WithRequestInfo(ctx, &request.RequestInfo{ + APIGroup: pkey.Group, + APIVersion: "v1", + Resource: pkey.Resource, + Subresource: "", + Namespace: pkey.Namespace, + Name: pkey.Name, + Parts: strings.Split(key, "/"), + IsResourceRequest: true, + }), nil +} + +func (r *RequestInfoWrapper) Create(ctx context.Context, key string, obj runtime.Object, out runtime.Object, ttl uint64) error { + ctx, err := r.setRequestInfo(ctx, key) + if err != nil { + return err + } + + return r.store.Create(ctx, key, obj, out, ttl) +} + +func (r *RequestInfoWrapper) Delete(ctx context.Context, key string, out runtime.Object, preconditions *storage.Preconditions, validateDeletion storage.ValidateObjectFunc, cachedExistingObject runtime.Object) error { + ctx, err := r.setRequestInfo(ctx, key) + if err != nil { + return err + } + + return r.store.Delete(ctx, key, out, preconditions, validateDeletion, cachedExistingObject) +} + +func (r *RequestInfoWrapper) Watch(ctx context.Context, key string, opts storage.ListOptions) (watch.Interface, error) { + ctx, err := r.setRequestInfo(ctx, key) + if err != nil { + return nil, err + } + + return r.store.Watch(ctx, key, opts) +} + +func (r *RequestInfoWrapper) Get(ctx context.Context, key string, opts storage.GetOptions, objPtr runtime.Object) error { + ctx, err := r.setRequestInfo(ctx, key) + if err != nil { + return err + } + + return r.store.Get(ctx, key, opts, objPtr) +} + +func (r *RequestInfoWrapper) GetList(ctx context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { + ctx, err := r.setRequestInfo(ctx, key) + if err != nil { + return err + } + + return r.store.GetList(ctx, key, opts, listObj) +} + +func (r *RequestInfoWrapper) GuaranteedUpdate(ctx context.Context, key string, destination runtime.Object, ignoreNotFound bool, preconditions *storage.Preconditions, tryUpdate storage.UpdateFunc, cachedExistingObject runtime.Object) error { + ctx, err := r.setRequestInfo(ctx, key) + if err != nil { + return err + } + + return r.store.GuaranteedUpdate(ctx, key, destination, ignoreNotFound, preconditions, tryUpdate, cachedExistingObject) +} + +func (r *RequestInfoWrapper) Count(key string) (int64, error) { + return r.store.Count(key) +} + +func (r *RequestInfoWrapper) Versioner() storage.Versioner { + return r.store.Versioner() +} + +func (r *RequestInfoWrapper) RequestWatchProgress(ctx context.Context) error { + return r.store.RequestWatchProgress(ctx) +} + +type Key struct { + Group string + Resource string + Namespace string + Name string +} + +func convertToParsedKey(key string) (*Key, error) { + // NOTE: the following supports the watcher tests that run against v1/pods + // Other than that, there are ambiguities in the key format that only field selector + // when set to use metadata.name can be used to bring clarity in the 3-segment case + + // Cases handled below: + // namespace scoped: + // //[]/[] + // //[] + // + // cluster scoped: + // //[] + // / + k := &Key{} + + if !strings.HasPrefix(key, "/") { + key = "/" + key + } + + parts := strings.SplitN(key, "/", 5) + if len(parts) < 2 { + return nil, fmt.Errorf("invalid key format: %s", key) + } + + k.Resource = parts[1] + if len(parts) < 3 { + return k, nil + } + + // figure out whether the key is namespace scoped or cluster scoped + if isTestNs(parts[2]) { + k.Namespace = parts[2] + if len(parts) >= 4 { + k.Name = parts[3] + } + } else { + k.Name = parts[2] + } + + return k, nil +} + +func isTestNs(part string) bool { + return strings.HasPrefix(part, "test-ns-") || strings.HasPrefix(part, "ns-") || strings.Index(part, "-ns") > 0 +} diff --git a/pkg/services/apiserver/storage/entity/test/watch_test.go b/pkg/services/apiserver/storage/entity/test/watch_test.go new file mode 100644 index 00000000000..58e6a6a4097 --- /dev/null +++ b/pkg/services/apiserver/storage/entity/test/watch_test.go @@ -0,0 +1,351 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Provenance-includes-location: https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/pkg/storage/etcd3/watcher_test.go +// Provenance-includes-license: Apache-2.0 +// Provenance-includes-copyright: The Kubernetes Authors. + +package test + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "k8s.io/apimachinery/pkg/api/apitesting" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/runtime/serializer" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apiserver/pkg/apis/example" + examplev1 "k8s.io/apiserver/pkg/apis/example/v1" + "k8s.io/apiserver/pkg/storage" + "k8s.io/apiserver/pkg/storage/storagebackend" + "k8s.io/apiserver/pkg/storage/storagebackend/factory" + storagetesting "k8s.io/apiserver/pkg/storage/testing" + + "github.com/grafana/grafana/pkg/services/apiserver/storage/entity" + "github.com/grafana/grafana/pkg/services/featuremgmt" + "github.com/grafana/grafana/pkg/services/sqlstore" + entityStore "github.com/grafana/grafana/pkg/services/store/entity" + "github.com/grafana/grafana/pkg/services/store/entity/db/dbimpl" + "github.com/grafana/grafana/pkg/services/store/entity/sqlstash" + "github.com/grafana/grafana/pkg/setting" + "github.com/grafana/grafana/pkg/tests/testinfra" + "github.com/grafana/grafana/pkg/tests/testsuite" +) + +var scheme = runtime.NewScheme() +var codecs = serializer.NewCodecFactory(scheme) + +func TestMain(m *testing.M) { + testsuite.Run(m) +} + +func createTestContext(t *testing.T) (entityStore.EntityStoreClient, factory.DestroyFunc) { + t.Helper() + + grafDir, cfgPath := testinfra.CreateGrafDir(t, testinfra.GrafanaOpts{ + EnableFeatureToggles: []string{ + featuremgmt.FlagGrpcServer, + featuremgmt.FlagUnifiedStorage, + }, + AppModeProduction: false, // required for migrations to run + GRPCServerAddress: "127.0.0.1:0", // :0 for choosing the port automatically + }) + + cfg, err := setting.NewCfgFromArgs(setting.CommandLineArgs{Config: cfgPath, HomePath: grafDir}) + assert.NoError(t, err) + + featureManager, err := featuremgmt.ProvideManagerService(cfg) + assert.NoError(t, err) + + featureToggles := featuremgmt.ProvideToggles(featureManager) + + db := sqlstore.InitTestDBWithMigration(t, nil, sqlstore.InitTestDBOpt{EnsureDefaultOrgAndUser: false}) + require.NoError(t, err) + + eDB, err := dbimpl.ProvideEntityDB(db, cfg, featureToggles) + require.NoError(t, err) + + err = eDB.Init() + require.NoError(t, err) + + store, err := sqlstash.ProvideSQLEntityServer(eDB) + require.NoError(t, err) + + client := entityStore.NewEntityStoreClientLocal(store) + + return client, func() { store.Stop() } +} + +func init() { + metav1.AddToGroupVersion(scheme, metav1.SchemeGroupVersion) + utilruntime.Must(example.AddToScheme(scheme)) + utilruntime.Must(examplev1.AddToScheme(scheme)) +} + +type setupOptions struct { + codec runtime.Codec + newFunc func() runtime.Object + newListFunc func() runtime.Object + prefix string + resourcePrefix string + groupResource schema.GroupResource +} + +type setupOption func(*setupOptions, *testing.T) + +func withDefaults(options *setupOptions, t *testing.T) { + options.codec = apitesting.TestCodec(codecs, examplev1.SchemeGroupVersion) + options.newFunc = newPod + options.newListFunc = newPodList + options.prefix = t.TempDir() + options.resourcePrefix = "/pods" + options.groupResource = schema.GroupResource{Resource: "pods"} +} + +var _ setupOption = withDefaults + +func testSetup(t *testing.T, opts ...setupOption) (context.Context, storage.Interface, factory.DestroyFunc, error) { + setupOpts := setupOptions{} + opts = append([]setupOption{withDefaults}, opts...) + for _, opt := range opts { + opt(&setupOpts, t) + } + + config := storagebackend.NewDefaultConfig(setupOpts.prefix, setupOpts.codec) + + client, destroyFunc := createTestContext(t) + + store, _, err := entity.NewStorage( + config.ForResource(setupOpts.groupResource), + setupOpts.groupResource, + client, + setupOpts.codec, + func(obj runtime.Object) (string, error) { + return storage.NamespaceKeyFunc(setupOpts.resourcePrefix, obj) + }, + setupOpts.newFunc, + setupOpts.newListFunc, + storage.DefaultNamespaceScopedAttr, + ) + if err != nil { + return nil, nil, nil, err + } + + ctx := context.Background() + + wrappedStore := &RequestInfoWrapper{ + store: store, + gr: setupOpts.groupResource, + } + + return ctx, wrappedStore, destroyFunc, nil +} + +func TestIntegrationWatch(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestWatch(ctx, t, store) +} + +func TestIntegrationClusterScopedWatch(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestClusterScopedWatch(ctx, t, store) +} + +func TestIntegrationNamespaceScopedWatch(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestNamespaceScopedWatch(ctx, t, store) +} + +func TestIntegrationDeleteTriggerWatch(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestDeleteTriggerWatch(ctx, t, store) +} + +func TestIntegrationWatchFromZero(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestWatchFromZero(ctx, t, store, nil) +} + +// TestWatchFromNonZero tests that +// - watch from non-0 should just watch changes after given version +func TestIntegrationWatchFromNonZero(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestWatchFromNonZero(ctx, t, store) +} + +/* +// TODO this times out, we need to buffer events +func TestIntegrationDelayedWatchDelivery(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestDelayedWatchDelivery(ctx, t, store) +} +*/ + +/* func TestIntegrationWatchError(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx, store, _ := testSetup(t) + storagetesting.RunTestWatchError(ctx, t, &storeWithPrefixTransformer{store}) +} */ + +func TestIntegrationWatchContextCancel(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestWatchContextCancel(ctx, t, store) +} + +func TestIntegrationWatcherTimeout(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestWatcherTimeout(ctx, t, store) +} + +func TestIntegrationWatchDeleteEventObjectHaveLatestRV(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestWatchDeleteEventObjectHaveLatestRV(ctx, t, store) +} + +// TODO: enable when we support flow control and priority fairness +/* func TestIntegrationWatchInitializationSignal(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestWatchInitializationSignal(ctx, t, store) +} */ + +/* func TestIntegrationProgressNotify(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunOptionalTestProgressNotify(ctx, t, store) +} */ + +// TestWatchDispatchBookmarkEvents makes sure that +// setting allowWatchBookmarks query param against +// etcd implementation doesn't have any effect. +func TestIntegrationWatchDispatchBookmarkEvents(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunTestWatchDispatchBookmarkEvents(ctx, t, store, false) +} + +func TestIntegrationSendInitialEventsBackwardCompatibility(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunSendInitialEventsBackwardCompatibility(ctx, t, store) +} + +// TODO this test times out +func TestIntegrationEtcdWatchSemantics(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunWatchSemantics(ctx, t, store) +} + +/* +// TODO this test times out +func TestIntegrationEtcdWatchSemanticInitialEventsExtended(t *testing.T) { + if testing.Short() { + t.Skip("skipping integration test") + } + + ctx, store, destroyFunc, err := testSetup(t) + defer destroyFunc() + assert.NoError(t, err) + storagetesting.RunWatchSemanticInitialEventsExtended(ctx, t, store) +} +*/ + +func newPod() runtime.Object { + return &example.Pod{} +} + +func newPodList() runtime.Object { + return &example.PodList{} +} diff --git a/pkg/services/sqlstore/migrator/migrator.go b/pkg/services/sqlstore/migrator/migrator.go index c58da4729b2..4cc23d2b855 100644 --- a/pkg/services/sqlstore/migrator/migrator.go +++ b/pkg/services/sqlstore/migrator/migrator.go @@ -210,7 +210,9 @@ func (mg *Migrator) run() (err error) { err := mg.InTransaction(func(sess *xorm.Session) error { err := mg.exec(m, sess) // if we get an sqlite busy/locked error, sleep 100ms and try again - if errors.Is(err, sqlite3.ErrLocked) || errors.Is(err, sqlite3.ErrBusy) { + cnt := 0 + for cnt < 3 && (errors.Is(err, sqlite3.ErrLocked) || errors.Is(err, sqlite3.ErrBusy)) { + cnt++ mg.Logger.Debug("Database locked, sleeping then retrying", "error", err, "sql", sql) time.Sleep(100 * time.Millisecond) err = mg.exec(m, sess) diff --git a/pkg/services/sqlstore/migrator/sqlite_dialect.go b/pkg/services/sqlstore/migrator/sqlite_dialect.go index c2979ea1925..2c8c71ec131 100644 --- a/pkg/services/sqlstore/migrator/sqlite_dialect.go +++ b/pkg/services/sqlstore/migrator/sqlite_dialect.go @@ -122,7 +122,11 @@ func (db *SQLite3) TruncateDBTables(engine *xorm.Engine) error { } } if _, err := sess.Exec("UPDATE sqlite_sequence SET seq = 0 WHERE name != 'dashboard_acl';"); err != nil { - return fmt.Errorf("failed to cleanup sqlite_sequence: %w", err) + // if we have not created any autoincrement columns in the database this will fail, the error is expected and we can ignore it + // we can't discriminate based on code because sqlite returns a generic error code + if err.Error() != "no such table: sqlite_sequence" { + return fmt.Errorf("failed to cleanup sqlite_sequence: %w", err) + } } return nil } diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index f46074fcefc..d1c66575964 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -151,7 +151,7 @@ func newSQLStore(cfg *setting.Cfg, engine *xorm.Engine, // Has to be done in a second phase (after initialization), since other services can register migrations during // the initialization phase. func (ss *SQLStore) Migrate(isDatabaseLockingEnabled bool) error { - if ss.dbCfg.SkipMigrations { + if ss.dbCfg.SkipMigrations || ss.migrations == nil { return nil } diff --git a/pkg/services/store/entity/dummy/fake_store.go b/pkg/services/store/entity/dummy/fake_store.go index d2b4cf5fdbb..a6d85ed6156 100644 --- a/pkg/services/store/entity/dummy/fake_store.go +++ b/pkg/services/store/entity/dummy/fake_store.go @@ -40,7 +40,7 @@ func (i fakeEntityStore) List(ctx context.Context, r *entity.EntityListRequest) return nil, fmt.Errorf("unimplemented") } -func (i fakeEntityStore) Watch(*entity.EntityWatchRequest, entity.EntityStore_WatchServer) error { +func (i fakeEntityStore) Watch(entity.EntityStore_WatchServer) error { return fmt.Errorf("unimplemented") } diff --git a/pkg/services/store/entity/entity.pb.go b/pkg/services/store/entity/entity.pb.go index c489327c122..76a04593bd1 100644 --- a/pkg/services/store/entity/entity.pb.go +++ b/pkg/services/store/entity/entity.pb.go @@ -24,11 +24,12 @@ const ( type Entity_Action int32 const ( - Entity_UNKNOWN Entity_Action = 0 - Entity_CREATED Entity_Action = 1 - Entity_UPDATED Entity_Action = 2 - Entity_DELETED Entity_Action = 3 - Entity_ERROR Entity_Action = 4 + Entity_UNKNOWN Entity_Action = 0 + Entity_CREATED Entity_Action = 1 + Entity_UPDATED Entity_Action = 2 + Entity_DELETED Entity_Action = 3 + Entity_ERROR Entity_Action = 4 + Entity_BOOKMARK Entity_Action = 5 ) // Enum value maps for Entity_Action. @@ -39,13 +40,15 @@ var ( 2: "UPDATED", 3: "DELETED", 4: "ERROR", + 5: "BOOKMARK", } Entity_Action_value = map[string]int32{ - "UNKNOWN": 0, - "CREATED": 1, - "UPDATED": 2, - "DELETED": 3, - "ERROR": 4, + "UNKNOWN": 0, + "CREATED": 1, + "UPDATED": 2, + "DELETED": 3, + "ERROR": 4, + "BOOKMARK": 5, } ) @@ -223,6 +226,52 @@ func (DeleteEntityResponse_Status) EnumDescriptor() ([]byte, []int) { return file_entity_proto_rawDescGZIP(), []int{9, 0} } +type EntityWatchRequest_WatchAction int32 + +const ( + EntityWatchRequest_START EntityWatchRequest_WatchAction = 0 + EntityWatchRequest_STOP EntityWatchRequest_WatchAction = 1 +) + +// Enum value maps for EntityWatchRequest_WatchAction. +var ( + EntityWatchRequest_WatchAction_name = map[int32]string{ + 0: "START", + 1: "STOP", + } + EntityWatchRequest_WatchAction_value = map[string]int32{ + "START": 0, + "STOP": 1, + } +) + +func (x EntityWatchRequest_WatchAction) Enum() *EntityWatchRequest_WatchAction { + p := new(EntityWatchRequest_WatchAction) + *p = x + return p +} + +func (x EntityWatchRequest_WatchAction) String() string { + return protoimpl.X.EnumStringOf(x.Descriptor(), protoreflect.EnumNumber(x)) +} + +func (EntityWatchRequest_WatchAction) Descriptor() protoreflect.EnumDescriptor { + return file_entity_proto_enumTypes[4].Descriptor() +} + +func (EntityWatchRequest_WatchAction) Type() protoreflect.EnumType { + return &file_entity_proto_enumTypes[4] +} + +func (x EntityWatchRequest_WatchAction) Number() protoreflect.EnumNumber { + return protoreflect.EnumNumber(x) +} + +// Deprecated: Use EntityWatchRequest_WatchAction.Descriptor instead. +func (EntityWatchRequest_WatchAction) EnumDescriptor() ([]byte, []int) { + return file_entity_proto_rawDescGZIP(), []int{15, 0} +} + // The canonical entity/document data -- this represents the raw bytes and storage level metadata type Entity struct { state protoimpl.MessageState @@ -1092,8 +1141,12 @@ type EntityHistoryRequest struct { // Entity identifier Key string `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` + // return the history from before this version + Before int64 `protobuf:"varint,2,opt,name=before,proto3" json:"before,omitempty"` // Maximum number of items to return Limit int64 `protobuf:"varint,3,opt,name=limit,proto3" json:"limit,omitempty"` + // guid of the entity + Guid string `protobuf:"bytes,4,opt,name=guid,proto3" json:"guid,omitempty"` // Starting from the requested page NextPageToken string `protobuf:"bytes,5,opt,name=next_page_token,json=nextPageToken,proto3" json:"next_page_token,omitempty"` // Sorting instructions `field ASC/DESC` @@ -1143,6 +1196,13 @@ func (x *EntityHistoryRequest) GetKey() string { return "" } +func (x *EntityHistoryRequest) GetBefore() int64 { + if x != nil { + return x.Before + } + return 0 +} + func (x *EntityHistoryRequest) GetLimit() int64 { if x != nil { return x.Limit @@ -1150,6 +1210,13 @@ func (x *EntityHistoryRequest) GetLimit() int64 { return 0 } +func (x *EntityHistoryRequest) GetGuid() string { + if x != nil { + return x.Guid + } + return "" +} + func (x *EntityHistoryRequest) GetNextPageToken() string { if x != nil { return x.NextPageToken @@ -1561,6 +1628,8 @@ type EntityWatchRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields + // Start or stop the watch + Action EntityWatchRequest_WatchAction `protobuf:"varint,8,opt,name=action,proto3,enum=entity.EntityWatchRequest_WatchAction" json:"action,omitempty"` // ResourceVersion of last changes. Empty will default to full history Since int64 `protobuf:"varint,1,opt,name=since,proto3" json:"since,omitempty"` // Watch specific entities @@ -1575,6 +1644,9 @@ type EntityWatchRequest struct { WithBody bool `protobuf:"varint,6,opt,name=with_body,json=withBody,proto3" json:"with_body,omitempty"` // Return the full status in each payload WithStatus bool `protobuf:"varint,7,opt,name=with_status,json=withStatus,proto3" json:"with_status,omitempty"` + // Return initial events + SendInitialEvents bool `protobuf:"varint,9,opt,name=send_initial_events,json=sendInitialEvents,proto3" json:"send_initial_events,omitempty"` + AllowWatchBookmarks bool `protobuf:"varint,10,opt,name=allow_watch_bookmarks,json=allowWatchBookmarks,proto3" json:"allow_watch_bookmarks,omitempty"` } func (x *EntityWatchRequest) Reset() { @@ -1609,6 +1681,13 @@ func (*EntityWatchRequest) Descriptor() ([]byte, []int) { return file_entity_proto_rawDescGZIP(), []int{15} } +func (x *EntityWatchRequest) GetAction() EntityWatchRequest_WatchAction { + if x != nil { + return x.Action + } + return EntityWatchRequest_START +} + func (x *EntityWatchRequest) GetSince() int64 { if x != nil { return x.Since @@ -1658,6 +1737,20 @@ func (x *EntityWatchRequest) GetWithStatus() bool { return false } +func (x *EntityWatchRequest) GetSendInitialEvents() bool { + if x != nil { + return x.SendInitialEvents + } + return false +} + +func (x *EntityWatchRequest) GetAllowWatchBookmarks() bool { + if x != nil { + return x.AllowWatchBookmarks + } + return false +} + type EntityWatchResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1667,6 +1760,8 @@ type EntityWatchResponse struct { Timestamp int64 `protobuf:"varint,1,opt,name=timestamp,proto3" json:"timestamp,omitempty"` // Entity that was created, updated, or deleted Entity *Entity `protobuf:"bytes,2,opt,name=entity,proto3" json:"entity,omitempty"` + // previous version of the entity + Previous *Entity `protobuf:"bytes,3,opt,name=previous,proto3" json:"previous,omitempty"` } func (x *EntityWatchResponse) Reset() { @@ -1715,6 +1810,13 @@ func (x *EntityWatchResponse) GetEntity() *Entity { return nil } +func (x *EntityWatchResponse) GetPrevious() *Entity { + if x != nil { + return x.Previous + } + return nil +} + type EntitySummary struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1924,7 +2026,7 @@ var File_entity_proto protoreflect.FileDescriptor var file_entity_proto_rawDesc = []byte{ 0x0a, 0x0c, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x06, - 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0x9f, 0x08, 0x0a, 0x06, 0x45, 0x6e, 0x74, 0x69, 0x74, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0xad, 0x08, 0x0a, 0x06, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, 0x64, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, @@ -1986,55 +2088,75 @@ var file_entity_proto_rawDesc = []byte{ 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, - 0x47, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, + 0x55, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x50, 0x44, 0x41, 0x54, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x03, 0x12, 0x09, 0x0a, - 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x22, 0x50, 0x0a, 0x10, 0x45, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x0a, 0x06, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x22, 0x62, 0x0a, 0x0f, 0x45, 0x6e, - 0x74, 0x69, 0x74, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, - 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x63, 0x6f, 0x64, - 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x64, - 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0c, 0x52, 0x0b, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x4a, 0x73, 0x6f, 0x6e, 0x22, 0x8e, - 0x01, 0x0a, 0x11, 0x52, 0x65, 0x61, 0x64, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x12, 0x1b, 0x0a, 0x09, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x77, 0x69, 0x74, 0x68, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x1f, - 0x0a, 0x0b, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, - 0x01, 0x28, 0x08, 0x52, 0x0a, 0x77, 0x69, 0x74, 0x68, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, - 0x3d, 0x0a, 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, - 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0xcc, - 0x01, 0x0a, 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, - 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, - 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x26, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, - 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x3b, - 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, - 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6e, - 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x20, 0x0a, 0x06, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, - 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x22, 0x68, 0x0a, - 0x13, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, + 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x04, 0x12, 0x0c, 0x0a, 0x08, 0x42, 0x4f, 0x4f, 0x4b, + 0x4d, 0x41, 0x52, 0x4b, 0x10, 0x05, 0x22, 0x50, 0x0a, 0x10, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x03, 0x52, 0x04, 0x74, 0x69, 0x6d, 0x65, 0x22, 0x62, 0x0a, 0x0f, 0x45, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x63, + 0x6f, 0x64, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x12, + 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x64, 0x65, 0x74, + 0x61, 0x69, 0x6c, 0x73, 0x5f, 0x6a, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, + 0x0b, 0x64, 0x65, 0x74, 0x61, 0x69, 0x6c, 0x73, 0x4a, 0x73, 0x6f, 0x6e, 0x22, 0x8e, 0x01, 0x0a, + 0x11, 0x52, 0x65, 0x61, 0x64, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, + 0x1b, 0x0a, 0x09, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x04, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x08, 0x77, 0x69, 0x74, 0x68, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x1f, 0x0a, 0x0b, + 0x77, 0x69, 0x74, 0x68, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x05, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0a, 0x77, 0x69, 0x74, 0x68, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x3d, 0x0a, + 0x13, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, - 0x74, 0x69, 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x29, 0x0a, 0x10, + 0x74, 0x69, 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0xcc, 0x01, 0x0a, + 0x14, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x65, + 0x72, 0x72, 0x6f, 0x72, 0x12, 0x26, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x06, + 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, + 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x20, 0x0a, 0x06, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x0b, + 0x0a, 0x07, 0x43, 0x52, 0x45, 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x22, 0x68, 0x0a, 0x13, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x26, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x70, 0x72, + 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x56, 0x65, + 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xdb, 0x01, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, + 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, + 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, 0x72, 0x72, + 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x26, 0x0a, + 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x06, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x55, + 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x22, 0x2f, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x09, 0x0a, 0x05, + 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x50, 0x44, 0x41, 0x54, + 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x43, 0x48, 0x41, 0x4e, 0x47, 0x45, + 0x44, 0x10, 0x02, 0x22, 0x52, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, 0x0a, 0x10, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xdb, 0x01, 0x0a, 0x14, 0x55, 0x70, 0x64, 0x61, + 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xda, 0x01, 0x0a, 0x14, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x17, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, @@ -2043,200 +2165,199 @@ var file_entity_proto_rawDesc = []byte{ 0x0e, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x22, 0x2f, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x09, - 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x50, 0x44, - 0x41, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x55, 0x4e, 0x43, 0x48, 0x41, 0x4e, - 0x47, 0x45, 0x44, 0x10, 0x02, 0x22, 0x52, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x45, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x29, - 0x0a, 0x10, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, - 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, - 0x75, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xda, 0x01, 0x0a, 0x14, 0x44, 0x65, - 0x6c, 0x65, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x17, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, - 0x79, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, - 0x72, 0x12, 0x26, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x0e, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, - 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x23, 0x2e, 0x65, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, - 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, - 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0x2e, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, - 0x12, 0x09, 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x44, - 0x45, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x4e, 0x4f, 0x54, 0x46, - 0x4f, 0x55, 0x4e, 0x44, 0x10, 0x02, 0x22, 0xb8, 0x01, 0x0a, 0x14, 0x45, 0x6e, 0x74, 0x69, 0x74, - 0x79, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, - 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, - 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, + 0x61, 0x74, 0x75, 0x73, 0x22, 0x2e, 0x0a, 0x06, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x09, + 0x0a, 0x05, 0x45, 0x52, 0x52, 0x4f, 0x52, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x4c, + 0x45, 0x54, 0x45, 0x44, 0x10, 0x01, 0x12, 0x0c, 0x0a, 0x08, 0x4e, 0x4f, 0x54, 0x46, 0x4f, 0x55, + 0x4e, 0x44, 0x10, 0x02, 0x22, 0xe4, 0x01, 0x0a, 0x14, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x48, + 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x16, 0x0a, 0x06, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x06, 0x62, 0x65, 0x66, 0x6f, 0x72, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x12, 0x0a, + 0x04, 0x67, 0x75, 0x69, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x67, 0x75, 0x69, + 0x64, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, + 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, + 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6f, 0x72, + 0x74, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x12, 0x1b, 0x0a, + 0x09, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x08, 0x77, 0x69, 0x74, 0x68, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x69, + 0x74, 0x68, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, + 0x0a, 0x77, 0x69, 0x74, 0x68, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x22, 0xa8, 0x01, 0x0a, 0x15, + 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, 0x08, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x08, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, + 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, + 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xa9, 0x03, 0x0a, 0x11, 0x45, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, + 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, + 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, + 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x3d, 0x0a, 0x06, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x06, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x65, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, + 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x73, + 0x6f, 0x72, 0x74, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x12, + 0x1b, 0x0a, 0x09, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x08, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x08, 0x77, 0x69, 0x74, 0x68, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x1f, 0x0a, 0x0b, + 0x77, 0x69, 0x74, 0x68, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, + 0x08, 0x52, 0x0a, 0x77, 0x69, 0x74, 0x68, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, + 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x0c, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, + 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, + 0x38, 0x01, 0x22, 0xb4, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, + 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, - 0x12, 0x0a, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, 0x04, 0x73, - 0x6f, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x62, 0x6f, 0x64, 0x79, - 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x77, 0x69, 0x74, 0x68, 0x42, 0x6f, 0x64, 0x79, - 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x77, 0x69, 0x74, 0x68, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x22, 0xa8, 0x01, 0x0a, 0x15, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x48, 0x69, 0x73, 0x74, - 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, - 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2a, 0x0a, - 0x08, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x0e, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, - 0x08, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, - 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, - 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xa9, 0x03, 0x0a, - 0x11, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, - 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, - 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, - 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, - 0x12, 0x14, 0x0a, 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, - 0x05, 0x71, 0x75, 0x65, 0x72, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, - 0x09, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1a, 0x0a, 0x08, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x04, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x0b, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, - 0x6c, 0x64, 0x65, 0x72, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64, - 0x65, 0x72, 0x12, 0x3d, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x06, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x25, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4c, 0x61, - 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, - 0x73, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x18, 0x07, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x04, 0x73, 0x6f, 0x72, 0x74, 0x12, 0x1b, 0x0a, 0x09, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x62, 0x6f, - 0x64, 0x79, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x77, 0x69, 0x74, 0x68, 0x42, 0x6f, - 0x64, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x77, 0x69, 0x74, 0x68, 0x53, 0x74, 0x61, - 0x74, 0x75, 0x73, 0x12, 0x18, 0x0a, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x0c, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x1a, 0x39, 0x0a, - 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0xb4, 0x01, 0x0a, 0x10, 0x52, 0x65, 0x66, - 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, - 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, - 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, - 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, - 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, - 0x75, 0x70, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, - 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, - 0x91, 0x01, 0x0a, 0x12, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x28, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, - 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, - 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, - 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, - 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, - 0x69, 0x6f, 0x6e, 0x22, 0xa9, 0x02, 0x0a, 0x12, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x57, 0x61, - 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x69, - 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, - 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x6b, - 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, - 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, - 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x3e, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, - 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, - 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, - 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x62, - 0x6f, 0x64, 0x79, 0x18, 0x06, 0x20, 0x01, 0x28, 0x08, 0x52, 0x08, 0x77, 0x69, 0x74, 0x68, 0x42, - 0x6f, 0x64, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x73, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x77, 0x69, 0x74, 0x68, 0x53, 0x74, - 0x61, 0x74, 0x75, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, - 0x5b, 0x0a, 0x13, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, - 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, - 0x74, 0x61, 0x6d, 0x70, 0x12, 0x26, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, - 0x74, 0x69, 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x22, 0xa2, 0x04, 0x0a, - 0x0d, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x10, - 0x0a, 0x03, 0x55, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x49, 0x44, - 0x12, 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, - 0x6b, 0x69, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, - 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x06, 0x6c, 0x61, - 0x62, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x65, 0x6e, 0x74, + 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, + 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, 0x61, + 0x63, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x70, + 0x61, 0x63, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x04, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x91, 0x01, 0x0a, 0x12, 0x45, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x28, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, + 0x0b, 0x32, 0x0e, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x12, 0x26, 0x0a, 0x0f, 0x6e, 0x65, + 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, + 0x65, 0x6e, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xf1, 0x03, + 0x0a, 0x12, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x12, 0x3e, 0x0a, 0x06, 0x61, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x08, + 0x20, 0x01, 0x28, 0x0e, 0x32, 0x26, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x06, 0x61, 0x63, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x05, 0x73, 0x69, 0x6e, 0x63, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x64, + 0x65, 0x72, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, + 0x12, 0x3e, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x26, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x2e, 0x4c, 0x61, 0x62, + 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, + 0x12, 0x1b, 0x0a, 0x09, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x62, 0x6f, 0x64, 0x79, 0x18, 0x06, 0x20, + 0x01, 0x28, 0x08, 0x52, 0x08, 0x77, 0x69, 0x74, 0x68, 0x42, 0x6f, 0x64, 0x79, 0x12, 0x1f, 0x0a, + 0x0b, 0x77, 0x69, 0x74, 0x68, 0x5f, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x07, 0x20, 0x01, + 0x28, 0x08, 0x52, 0x0a, 0x77, 0x69, 0x74, 0x68, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x2e, + 0x0a, 0x13, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x69, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x5f, 0x65, + 0x76, 0x65, 0x6e, 0x74, 0x73, 0x18, 0x09, 0x20, 0x01, 0x28, 0x08, 0x52, 0x11, 0x73, 0x65, 0x6e, + 0x64, 0x49, 0x6e, 0x69, 0x74, 0x69, 0x61, 0x6c, 0x45, 0x76, 0x65, 0x6e, 0x74, 0x73, 0x12, 0x32, + 0x0a, 0x15, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x77, 0x61, 0x74, 0x63, 0x68, 0x5f, 0x62, 0x6f, + 0x6f, 0x6b, 0x6d, 0x61, 0x72, 0x6b, 0x73, 0x18, 0x0a, 0x20, 0x01, 0x28, 0x08, 0x52, 0x13, 0x61, + 0x6c, 0x6c, 0x6f, 0x77, 0x57, 0x61, 0x74, 0x63, 0x68, 0x42, 0x6f, 0x6f, 0x6b, 0x6d, 0x61, 0x72, + 0x6b, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, + 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x22, 0x0a, + 0x0b, 0x57, 0x61, 0x74, 0x63, 0x68, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x09, 0x0a, 0x05, + 0x53, 0x54, 0x41, 0x52, 0x54, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x53, 0x54, 0x4f, 0x50, 0x10, + 0x01, 0x22, 0x87, 0x01, 0x0a, 0x13, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x57, 0x61, 0x74, 0x63, + 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x74, 0x69, 0x6d, + 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x09, 0x74, 0x69, + 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x12, 0x26, 0x0a, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, + 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x06, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, + 0x2a, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x0e, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x52, 0x08, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x22, 0xa2, 0x04, 0x0a, 0x0d, + 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x55, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x55, 0x49, 0x44, 0x12, + 0x12, 0x0a, 0x04, 0x6b, 0x69, 0x6e, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6b, + 0x69, 0x6e, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, + 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, + 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x39, 0x0a, 0x06, 0x6c, 0x61, 0x62, + 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, + 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, + 0x62, 0x65, 0x6c, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, 0x06, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, 0x04, + 0x73, 0x6c, 0x75, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6c, 0x75, 0x67, + 0x12, 0x2d, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x17, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, + 0x72, 0x72, 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x12, + 0x39, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, + 0x21, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, + 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x2d, 0x0a, 0x06, 0x6e, 0x65, + 0x73, 0x74, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, - 0x79, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, - 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x18, - 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x6f, 0x6c, 0x64, 0x65, 0x72, 0x12, 0x12, 0x0a, - 0x04, 0x73, 0x6c, 0x75, 0x67, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x73, 0x6c, 0x75, - 0x67, 0x12, 0x2d, 0x0a, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x08, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x17, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x45, 0x72, 0x72, 0x6f, 0x72, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x65, 0x72, 0x72, 0x6f, 0x72, - 0x12, 0x39, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x09, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x21, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x53, 0x75, 0x6d, 0x6d, 0x61, 0x72, 0x79, 0x2e, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, 0x6e, - 0x74, 0x72, 0x79, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x12, 0x2d, 0x0a, 0x06, 0x6e, - 0x65, 0x73, 0x74, 0x65, 0x64, 0x18, 0x0a, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x65, 0x6e, - 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x53, 0x75, 0x6d, 0x6d, 0x61, - 0x72, 0x79, 0x52, 0x06, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x12, 0x3f, 0x0a, 0x0a, 0x72, 0x65, - 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, - 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, 0x78, - 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, - 0x0a, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, - 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, - 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, - 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x39, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, - 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, - 0x01, 0x22, 0x65, 0x0a, 0x17, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, 0x78, 0x74, 0x65, 0x72, - 0x6e, 0x61, 0x6c, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, - 0x66, 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x61, - 0x6d, 0x69, 0x6c, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, - 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x64, - 0x65, 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x32, 0xda, 0x03, 0x0a, 0x0b, 0x45, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, - 0x12, 0x19, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x45, 0x6e, - 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x65, 0x6e, - 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x43, 0x0a, 0x06, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x43, - 0x72, 0x65, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, - 0x74, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x43, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x65, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, - 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, - 0x1b, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x45, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x65, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x69, - 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x07, 0x48, 0x69, - 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1c, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, - 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x19, 0x2e, 0x65, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, - 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, - 0x6e, 0x74, 0x69, 0x74, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x42, 0x0a, 0x05, 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0x1a, 0x2e, 0x65, 0x6e, 0x74, - 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, - 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x30, 0x01, 0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, - 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x67, 0x72, 0x61, 0x66, - 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x73, - 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x79, 0x52, 0x06, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x12, 0x3f, 0x0a, 0x0a, 0x72, 0x65, 0x66, + 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, + 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, 0x78, 0x74, + 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x52, 0x0a, + 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x73, 0x1a, 0x39, 0x0a, 0x0b, 0x4c, 0x61, + 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x3a, 0x02, 0x38, 0x01, 0x1a, 0x39, 0x0a, 0x0b, 0x46, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x45, + 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, + 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, + 0x22, 0x65, 0x0a, 0x17, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x45, 0x78, 0x74, 0x65, 0x72, 0x6e, + 0x61, 0x6c, 0x52, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6e, 0x63, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x66, + 0x61, 0x6d, 0x69, 0x6c, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x66, 0x61, 0x6d, + 0x69, 0x6c, 0x79, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x69, 0x64, 0x65, 0x6e, 0x74, + 0x69, 0x66, 0x69, 0x65, 0x72, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x69, 0x64, 0x65, + 0x6e, 0x74, 0x69, 0x66, 0x69, 0x65, 0x72, 0x32, 0xdc, 0x03, 0x0a, 0x0b, 0x45, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x31, 0x0a, 0x04, 0x52, 0x65, 0x61, 0x64, 0x12, + 0x19, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x52, 0x65, 0x61, 0x64, 0x45, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0e, 0x2e, 0x65, 0x6e, 0x74, + 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x12, 0x43, 0x0a, 0x06, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x1a, 0x1c, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, + 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x43, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, + 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, + 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x1b, + 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x45, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x65, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x45, 0x6e, 0x74, 0x69, 0x74, + 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x07, 0x48, 0x69, 0x73, + 0x74, 0x6f, 0x72, 0x79, 0x12, 0x1c, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x3d, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x19, 0x2e, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, + 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1a, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, 0x6e, + 0x74, 0x69, 0x74, 0x79, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x44, 0x0a, 0x05, 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, 0x1a, 0x2e, 0x65, 0x6e, 0x74, 0x69, + 0x74, 0x79, 0x2e, 0x45, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x2e, 0x45, + 0x6e, 0x74, 0x69, 0x74, 0x79, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x28, 0x01, 0x30, 0x01, 0x42, 0x36, 0x5a, 0x34, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, + 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x67, 0x72, 0x61, 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x67, 0x72, 0x61, + 0x66, 0x61, 0x6e, 0x61, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, + 0x73, 0x2f, 0x73, 0x74, 0x6f, 0x72, 0x65, 0x2f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x79, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2251,85 +2372,88 @@ func file_entity_proto_rawDescGZIP() []byte { return file_entity_proto_rawDescData } -var file_entity_proto_enumTypes = make([]protoimpl.EnumInfo, 4) +var file_entity_proto_enumTypes = make([]protoimpl.EnumInfo, 5) var file_entity_proto_msgTypes = make([]protoimpl.MessageInfo, 25) var file_entity_proto_goTypes = []interface{}{ - (Entity_Action)(0), // 0: entity.Entity.Action - (CreateEntityResponse_Status)(0), // 1: entity.CreateEntityResponse.Status - (UpdateEntityResponse_Status)(0), // 2: entity.UpdateEntityResponse.Status - (DeleteEntityResponse_Status)(0), // 3: entity.DeleteEntityResponse.Status - (*Entity)(nil), // 4: entity.Entity - (*EntityOriginInfo)(nil), // 5: entity.EntityOriginInfo - (*EntityErrorInfo)(nil), // 6: entity.EntityErrorInfo - (*ReadEntityRequest)(nil), // 7: entity.ReadEntityRequest - (*CreateEntityRequest)(nil), // 8: entity.CreateEntityRequest - (*CreateEntityResponse)(nil), // 9: entity.CreateEntityResponse - (*UpdateEntityRequest)(nil), // 10: entity.UpdateEntityRequest - (*UpdateEntityResponse)(nil), // 11: entity.UpdateEntityResponse - (*DeleteEntityRequest)(nil), // 12: entity.DeleteEntityRequest - (*DeleteEntityResponse)(nil), // 13: entity.DeleteEntityResponse - (*EntityHistoryRequest)(nil), // 14: entity.EntityHistoryRequest - (*EntityHistoryResponse)(nil), // 15: entity.EntityHistoryResponse - (*EntityListRequest)(nil), // 16: entity.EntityListRequest - (*ReferenceRequest)(nil), // 17: entity.ReferenceRequest - (*EntityListResponse)(nil), // 18: entity.EntityListResponse - (*EntityWatchRequest)(nil), // 19: entity.EntityWatchRequest - (*EntityWatchResponse)(nil), // 20: entity.EntityWatchResponse - (*EntitySummary)(nil), // 21: entity.EntitySummary - (*EntityExternalReference)(nil), // 22: entity.EntityExternalReference - nil, // 23: entity.Entity.LabelsEntry - nil, // 24: entity.Entity.FieldsEntry - nil, // 25: entity.EntityListRequest.LabelsEntry - nil, // 26: entity.EntityWatchRequest.LabelsEntry - nil, // 27: entity.EntitySummary.LabelsEntry - nil, // 28: entity.EntitySummary.FieldsEntry + (Entity_Action)(0), // 0: entity.Entity.Action + (CreateEntityResponse_Status)(0), // 1: entity.CreateEntityResponse.Status + (UpdateEntityResponse_Status)(0), // 2: entity.UpdateEntityResponse.Status + (DeleteEntityResponse_Status)(0), // 3: entity.DeleteEntityResponse.Status + (EntityWatchRequest_WatchAction)(0), // 4: entity.EntityWatchRequest.WatchAction + (*Entity)(nil), // 5: entity.Entity + (*EntityOriginInfo)(nil), // 6: entity.EntityOriginInfo + (*EntityErrorInfo)(nil), // 7: entity.EntityErrorInfo + (*ReadEntityRequest)(nil), // 8: entity.ReadEntityRequest + (*CreateEntityRequest)(nil), // 9: entity.CreateEntityRequest + (*CreateEntityResponse)(nil), // 10: entity.CreateEntityResponse + (*UpdateEntityRequest)(nil), // 11: entity.UpdateEntityRequest + (*UpdateEntityResponse)(nil), // 12: entity.UpdateEntityResponse + (*DeleteEntityRequest)(nil), // 13: entity.DeleteEntityRequest + (*DeleteEntityResponse)(nil), // 14: entity.DeleteEntityResponse + (*EntityHistoryRequest)(nil), // 15: entity.EntityHistoryRequest + (*EntityHistoryResponse)(nil), // 16: entity.EntityHistoryResponse + (*EntityListRequest)(nil), // 17: entity.EntityListRequest + (*ReferenceRequest)(nil), // 18: entity.ReferenceRequest + (*EntityListResponse)(nil), // 19: entity.EntityListResponse + (*EntityWatchRequest)(nil), // 20: entity.EntityWatchRequest + (*EntityWatchResponse)(nil), // 21: entity.EntityWatchResponse + (*EntitySummary)(nil), // 22: entity.EntitySummary + (*EntityExternalReference)(nil), // 23: entity.EntityExternalReference + nil, // 24: entity.Entity.LabelsEntry + nil, // 25: entity.Entity.FieldsEntry + nil, // 26: entity.EntityListRequest.LabelsEntry + nil, // 27: entity.EntityWatchRequest.LabelsEntry + nil, // 28: entity.EntitySummary.LabelsEntry + nil, // 29: entity.EntitySummary.FieldsEntry } var file_entity_proto_depIdxs = []int32{ - 5, // 0: entity.Entity.origin:type_name -> entity.EntityOriginInfo - 23, // 1: entity.Entity.labels:type_name -> entity.Entity.LabelsEntry - 24, // 2: entity.Entity.fields:type_name -> entity.Entity.FieldsEntry - 6, // 3: entity.Entity.errors:type_name -> entity.EntityErrorInfo + 6, // 0: entity.Entity.origin:type_name -> entity.EntityOriginInfo + 24, // 1: entity.Entity.labels:type_name -> entity.Entity.LabelsEntry + 25, // 2: entity.Entity.fields:type_name -> entity.Entity.FieldsEntry + 7, // 3: entity.Entity.errors:type_name -> entity.EntityErrorInfo 0, // 4: entity.Entity.action:type_name -> entity.Entity.Action - 4, // 5: entity.CreateEntityRequest.entity:type_name -> entity.Entity - 6, // 6: entity.CreateEntityResponse.error:type_name -> entity.EntityErrorInfo - 4, // 7: entity.CreateEntityResponse.entity:type_name -> entity.Entity + 5, // 5: entity.CreateEntityRequest.entity:type_name -> entity.Entity + 7, // 6: entity.CreateEntityResponse.error:type_name -> entity.EntityErrorInfo + 5, // 7: entity.CreateEntityResponse.entity:type_name -> entity.Entity 1, // 8: entity.CreateEntityResponse.status:type_name -> entity.CreateEntityResponse.Status - 4, // 9: entity.UpdateEntityRequest.entity:type_name -> entity.Entity - 6, // 10: entity.UpdateEntityResponse.error:type_name -> entity.EntityErrorInfo - 4, // 11: entity.UpdateEntityResponse.entity:type_name -> entity.Entity + 5, // 9: entity.UpdateEntityRequest.entity:type_name -> entity.Entity + 7, // 10: entity.UpdateEntityResponse.error:type_name -> entity.EntityErrorInfo + 5, // 11: entity.UpdateEntityResponse.entity:type_name -> entity.Entity 2, // 12: entity.UpdateEntityResponse.status:type_name -> entity.UpdateEntityResponse.Status - 6, // 13: entity.DeleteEntityResponse.error:type_name -> entity.EntityErrorInfo - 4, // 14: entity.DeleteEntityResponse.entity:type_name -> entity.Entity + 7, // 13: entity.DeleteEntityResponse.error:type_name -> entity.EntityErrorInfo + 5, // 14: entity.DeleteEntityResponse.entity:type_name -> entity.Entity 3, // 15: entity.DeleteEntityResponse.status:type_name -> entity.DeleteEntityResponse.Status - 4, // 16: entity.EntityHistoryResponse.versions:type_name -> entity.Entity - 25, // 17: entity.EntityListRequest.labels:type_name -> entity.EntityListRequest.LabelsEntry - 4, // 18: entity.EntityListResponse.results:type_name -> entity.Entity - 26, // 19: entity.EntityWatchRequest.labels:type_name -> entity.EntityWatchRequest.LabelsEntry - 4, // 20: entity.EntityWatchResponse.entity:type_name -> entity.Entity - 27, // 21: entity.EntitySummary.labels:type_name -> entity.EntitySummary.LabelsEntry - 6, // 22: entity.EntitySummary.error:type_name -> entity.EntityErrorInfo - 28, // 23: entity.EntitySummary.fields:type_name -> entity.EntitySummary.FieldsEntry - 21, // 24: entity.EntitySummary.nested:type_name -> entity.EntitySummary - 22, // 25: entity.EntitySummary.references:type_name -> entity.EntityExternalReference - 7, // 26: entity.EntityStore.Read:input_type -> entity.ReadEntityRequest - 8, // 27: entity.EntityStore.Create:input_type -> entity.CreateEntityRequest - 10, // 28: entity.EntityStore.Update:input_type -> entity.UpdateEntityRequest - 12, // 29: entity.EntityStore.Delete:input_type -> entity.DeleteEntityRequest - 14, // 30: entity.EntityStore.History:input_type -> entity.EntityHistoryRequest - 16, // 31: entity.EntityStore.List:input_type -> entity.EntityListRequest - 19, // 32: entity.EntityStore.Watch:input_type -> entity.EntityWatchRequest - 4, // 33: entity.EntityStore.Read:output_type -> entity.Entity - 9, // 34: entity.EntityStore.Create:output_type -> entity.CreateEntityResponse - 11, // 35: entity.EntityStore.Update:output_type -> entity.UpdateEntityResponse - 13, // 36: entity.EntityStore.Delete:output_type -> entity.DeleteEntityResponse - 15, // 37: entity.EntityStore.History:output_type -> entity.EntityHistoryResponse - 18, // 38: entity.EntityStore.List:output_type -> entity.EntityListResponse - 20, // 39: entity.EntityStore.Watch:output_type -> entity.EntityWatchResponse - 33, // [33:40] is the sub-list for method output_type - 26, // [26:33] is the sub-list for method input_type - 26, // [26:26] is the sub-list for extension type_name - 26, // [26:26] is the sub-list for extension extendee - 0, // [0:26] is the sub-list for field type_name + 5, // 16: entity.EntityHistoryResponse.versions:type_name -> entity.Entity + 26, // 17: entity.EntityListRequest.labels:type_name -> entity.EntityListRequest.LabelsEntry + 5, // 18: entity.EntityListResponse.results:type_name -> entity.Entity + 4, // 19: entity.EntityWatchRequest.action:type_name -> entity.EntityWatchRequest.WatchAction + 27, // 20: entity.EntityWatchRequest.labels:type_name -> entity.EntityWatchRequest.LabelsEntry + 5, // 21: entity.EntityWatchResponse.entity:type_name -> entity.Entity + 5, // 22: entity.EntityWatchResponse.previous:type_name -> entity.Entity + 28, // 23: entity.EntitySummary.labels:type_name -> entity.EntitySummary.LabelsEntry + 7, // 24: entity.EntitySummary.error:type_name -> entity.EntityErrorInfo + 29, // 25: entity.EntitySummary.fields:type_name -> entity.EntitySummary.FieldsEntry + 22, // 26: entity.EntitySummary.nested:type_name -> entity.EntitySummary + 23, // 27: entity.EntitySummary.references:type_name -> entity.EntityExternalReference + 8, // 28: entity.EntityStore.Read:input_type -> entity.ReadEntityRequest + 9, // 29: entity.EntityStore.Create:input_type -> entity.CreateEntityRequest + 11, // 30: entity.EntityStore.Update:input_type -> entity.UpdateEntityRequest + 13, // 31: entity.EntityStore.Delete:input_type -> entity.DeleteEntityRequest + 15, // 32: entity.EntityStore.History:input_type -> entity.EntityHistoryRequest + 17, // 33: entity.EntityStore.List:input_type -> entity.EntityListRequest + 20, // 34: entity.EntityStore.Watch:input_type -> entity.EntityWatchRequest + 5, // 35: entity.EntityStore.Read:output_type -> entity.Entity + 10, // 36: entity.EntityStore.Create:output_type -> entity.CreateEntityResponse + 12, // 37: entity.EntityStore.Update:output_type -> entity.UpdateEntityResponse + 14, // 38: entity.EntityStore.Delete:output_type -> entity.DeleteEntityResponse + 16, // 39: entity.EntityStore.History:output_type -> entity.EntityHistoryResponse + 19, // 40: entity.EntityStore.List:output_type -> entity.EntityListResponse + 21, // 41: entity.EntityStore.Watch:output_type -> entity.EntityWatchResponse + 35, // [35:42] is the sub-list for method output_type + 28, // [28:35] is the sub-list for method input_type + 28, // [28:28] is the sub-list for extension type_name + 28, // [28:28] is the sub-list for extension extendee + 0, // [0:28] is the sub-list for field type_name } func init() { file_entity_proto_init() } @@ -2572,7 +2696,7 @@ func file_entity_proto_init() { File: protoimpl.DescBuilder{ GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_entity_proto_rawDesc, - NumEnums: 4, + NumEnums: 5, NumMessages: 25, NumExtensions: 0, NumServices: 1, diff --git a/pkg/services/store/entity/entity.proto b/pkg/services/store/entity/entity.proto index d1afd9e3d32..d1a335475da 100644 --- a/pkg/services/store/entity/entity.proto +++ b/pkg/services/store/entity/entity.proto @@ -92,6 +92,7 @@ message Entity { UPDATED = 2; DELETED = 3; ERROR = 4; + BOOKMARK = 5; } } @@ -232,9 +233,15 @@ message EntityHistoryRequest { // Entity identifier string key = 1; + // return the history from before this version + int64 before = 2; + // Maximum number of items to return int64 limit = 3; + // guid of the entity + string guid = 4; + // Starting from the requested page string next_page_token = 5; @@ -338,6 +345,14 @@ message EntityListResponse { //----------------------------------------------- message EntityWatchRequest { + enum WatchAction { + START = 0; + STOP = 1; + } + + // Start or stop the watch + WatchAction action = 8; + // ResourceVersion of last changes. Empty will default to full history int64 since = 1; @@ -358,6 +373,11 @@ message EntityWatchRequest { // Return the full status in each payload bool with_status = 7; + + // Return initial events + bool send_initial_events = 9; + + bool allow_watch_bookmarks = 10; } message EntityWatchResponse { @@ -366,6 +386,9 @@ message EntityWatchResponse { // Entity that was created, updated, or deleted Entity entity = 2; + + // previous version of the entity + Entity previous = 3; } message EntitySummary { @@ -426,5 +449,5 @@ service EntityStore { rpc Delete(DeleteEntityRequest) returns (DeleteEntityResponse); rpc History(EntityHistoryRequest) returns (EntityHistoryResponse); rpc List(EntityListRequest) returns (EntityListResponse); - rpc Watch(EntityWatchRequest) returns (stream EntityWatchResponse); + rpc Watch(stream EntityWatchRequest) returns (stream EntityWatchResponse); } diff --git a/pkg/services/store/entity/entity_grpc.pb.go b/pkg/services/store/entity/entity_grpc.pb.go index ee85521c565..e76302c91dc 100644 --- a/pkg/services/store/entity/entity_grpc.pb.go +++ b/pkg/services/store/entity/entity_grpc.pb.go @@ -38,7 +38,7 @@ type EntityStoreClient interface { Delete(ctx context.Context, in *DeleteEntityRequest, opts ...grpc.CallOption) (*DeleteEntityResponse, error) History(ctx context.Context, in *EntityHistoryRequest, opts ...grpc.CallOption) (*EntityHistoryResponse, error) List(ctx context.Context, in *EntityListRequest, opts ...grpc.CallOption) (*EntityListResponse, error) - Watch(ctx context.Context, in *EntityWatchRequest, opts ...grpc.CallOption) (EntityStore_WatchClient, error) + Watch(ctx context.Context, opts ...grpc.CallOption) (EntityStore_WatchClient, error) } type entityStoreClient struct { @@ -103,22 +103,17 @@ func (c *entityStoreClient) List(ctx context.Context, in *EntityListRequest, opt return out, nil } -func (c *entityStoreClient) Watch(ctx context.Context, in *EntityWatchRequest, opts ...grpc.CallOption) (EntityStore_WatchClient, error) { +func (c *entityStoreClient) Watch(ctx context.Context, opts ...grpc.CallOption) (EntityStore_WatchClient, error) { stream, err := c.cc.NewStream(ctx, &EntityStore_ServiceDesc.Streams[0], EntityStore_Watch_FullMethodName, opts...) if err != nil { return nil, err } x := &entityStoreWatchClient{stream} - if err := x.ClientStream.SendMsg(in); err != nil { - return nil, err - } - if err := x.ClientStream.CloseSend(); err != nil { - return nil, err - } return x, nil } type EntityStore_WatchClient interface { + Send(*EntityWatchRequest) error Recv() (*EntityWatchResponse, error) grpc.ClientStream } @@ -127,6 +122,10 @@ type entityStoreWatchClient struct { grpc.ClientStream } +func (x *entityStoreWatchClient) Send(m *EntityWatchRequest) error { + return x.ClientStream.SendMsg(m) +} + func (x *entityStoreWatchClient) Recv() (*EntityWatchResponse, error) { m := new(EntityWatchResponse) if err := x.ClientStream.RecvMsg(m); err != nil { @@ -145,7 +144,7 @@ type EntityStoreServer interface { Delete(context.Context, *DeleteEntityRequest) (*DeleteEntityResponse, error) History(context.Context, *EntityHistoryRequest) (*EntityHistoryResponse, error) List(context.Context, *EntityListRequest) (*EntityListResponse, error) - Watch(*EntityWatchRequest, EntityStore_WatchServer) error + Watch(EntityStore_WatchServer) error } // UnimplementedEntityStoreServer should be embedded to have forward compatible implementations. @@ -170,7 +169,7 @@ func (UnimplementedEntityStoreServer) History(context.Context, *EntityHistoryReq func (UnimplementedEntityStoreServer) List(context.Context, *EntityListRequest) (*EntityListResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method List not implemented") } -func (UnimplementedEntityStoreServer) Watch(*EntityWatchRequest, EntityStore_WatchServer) error { +func (UnimplementedEntityStoreServer) Watch(EntityStore_WatchServer) error { return status.Errorf(codes.Unimplemented, "method Watch not implemented") } @@ -294,15 +293,12 @@ func _EntityStore_List_Handler(srv interface{}, ctx context.Context, dec func(in } func _EntityStore_Watch_Handler(srv interface{}, stream grpc.ServerStream) error { - m := new(EntityWatchRequest) - if err := stream.RecvMsg(m); err != nil { - return err - } - return srv.(EntityStoreServer).Watch(m, &entityStoreWatchServer{stream}) + return srv.(EntityStoreServer).Watch(&entityStoreWatchServer{stream}) } type EntityStore_WatchServer interface { Send(*EntityWatchResponse) error + Recv() (*EntityWatchRequest, error) grpc.ServerStream } @@ -314,6 +310,14 @@ func (x *entityStoreWatchServer) Send(m *EntityWatchResponse) error { return x.ServerStream.SendMsg(m) } +func (x *entityStoreWatchServer) Recv() (*EntityWatchRequest, error) { + m := new(EntityWatchRequest) + if err := x.ServerStream.RecvMsg(m); err != nil { + return nil, err + } + return m, nil +} + // EntityStore_ServiceDesc is the grpc.ServiceDesc for EntityStore service. // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) @@ -351,6 +355,7 @@ var EntityStore_ServiceDesc = grpc.ServiceDesc{ StreamName: "Watch", Handler: _EntityStore_Watch_Handler, ServerStreams: true, + ClientStreams: true, }, }, Metadata: "entity.proto", diff --git a/pkg/services/store/entity/sqlstash/sql_storage_server.go b/pkg/services/store/entity/sqlstash/sql_storage_server.go index ee9b609a4fa..7c89961dd5a 100644 --- a/pkg/services/store/entity/sqlstash/sql_storage_server.go +++ b/pkg/services/store/entity/sqlstash/sql_storage_server.go @@ -7,6 +7,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "math/rand" "slices" "strings" @@ -14,6 +15,8 @@ import ( "github.com/bwmarrin/snowflake" "github.com/google/uuid" + "google.golang.org/protobuf/proto" + folder "github.com/grafana/grafana/pkg/apis/folder/v0alpha1" "github.com/grafana/grafana/pkg/infra/appcontext" "github.com/grafana/grafana/pkg/infra/log" @@ -28,14 +31,18 @@ import ( const entityTable = "entity" const entityHistoryTable = "entity_history" -// Make sure we implement both store + admin +// Make sure we implement correct interfaces var _ entity.EntityStoreServer = &sqlEntityServer{} +var _ SqlEntityServer = &sqlEntityServer{} + +func ProvideSQLEntityServer(db db.EntityDBInterface /*, cfg *setting.Cfg */) (SqlEntityServer, error) { + ctx, cancel := context.WithCancel(context.Background()) -func ProvideSQLEntityServer(db db.EntityDBInterface /*, cfg *setting.Cfg */) (entity.EntityStoreServer, error) { entityServer := &sqlEntityServer{ - db: db, - log: log.New("sql-entity-server"), - ctx: context.Background(), + db: db, + log: log.New("sql-entity-server"), + ctx: ctx, + cancel: cancel, } if err := prometheus.Register(NewStorageMetrics()); err != nil { @@ -45,14 +52,23 @@ func ProvideSQLEntityServer(db db.EntityDBInterface /*, cfg *setting.Cfg */) (en return entityServer, nil } +type SqlEntityServer interface { + entity.EntityStoreServer + + Init() error + Stop() +} + type sqlEntityServer struct { log log.Logger db db.EntityDBInterface // needed to keep xorm engine in scope sess *session.SessionDB dialect migrator.Dialect snowflake *snowflake.Node - broadcaster Broadcaster[*entity.Entity] + broadcaster Broadcaster[*entity.EntityWatchResponse] ctx context.Context + cancel context.CancelFunc + stream chan *entity.EntityWatchResponse } func (s *sqlEntityServer) Init() error { @@ -89,7 +105,9 @@ func (s *sqlEntityServer) Init() error { } // set up the broadcaster - s.broadcaster, err = NewBroadcaster(s.ctx, func(stream chan *entity.Entity) error { + s.broadcaster, err = NewBroadcaster(s.ctx, func(stream chan *entity.EntityWatchResponse) error { + s.stream = stream + // start the poller go s.poller(stream) @@ -102,6 +120,10 @@ func (s *sqlEntityServer) Init() error { return nil } +func (s *sqlEntityServer) Stop() { + s.cancel() +} + type FieldSelectRequest interface { GetWithBody() bool GetWithStatus() bool @@ -146,7 +168,7 @@ func (s *sqlEntityServer) getReadSelect(r FieldSelectRequest) (string, error) { return "SELECT " + strings.Join(quotedFields, ","), nil } -func rowToEntity(rows *sql.Rows, r FieldSelectRequest) (*entity.Entity, error) { +func readEntity(rows *sql.Rows, r FieldSelectRequest) (*entity.Entity, error) { raw := &entity.Entity{ Origin: &entity.EntityOriginInfo{}, } @@ -261,7 +283,7 @@ func (s *sqlEntityServer) read(ctx context.Context, tx session.SessionQuerier, r return &entity.Entity{}, nil } - return rowToEntity(rows, r) + return readEntity(rows, r) } //nolint:gocyclo @@ -475,6 +497,12 @@ func (s *sqlEntityServer) Create(ctx context.Context, r *entity.CreateEntityRequ rsp.Status = entity.CreateEntityResponse_ERROR } + evt := &entity.EntityWatchResponse{ + Timestamp: time.Now().UnixMilli(), + Entity: rsp.Entity, + } + s.stream <- evt + return rsp, err } @@ -506,8 +534,11 @@ func (s *sqlEntityServer) Update(ctx context.Context, r *entity.UpdateEntityRequ Status: entity.UpdateEntityResponse_UPDATED, // Will be changed if not true } - err := s.sess.WithTransaction(ctx, func(tx *session.SessionTx) error { - current, err := s.read(ctx, tx, &entity.ReadEntityRequest{ + var previous *entity.Entity + var err error + + err = s.sess.WithTransaction(ctx, func(tx *session.SessionTx) error { + previous, err = s.read(ctx, tx, &entity.ReadEntityRequest{ Key: r.Entity.Key, WithBody: true, WithStatus: true, @@ -517,58 +548,60 @@ func (s *sqlEntityServer) Update(ctx context.Context, r *entity.UpdateEntityRequ } // Optimistic locking - if r.PreviousVersion > 0 && r.PreviousVersion != current.ResourceVersion { + if r.PreviousVersion > 0 && r.PreviousVersion != previous.ResourceVersion { StorageServerMetrics.OptimisticLockFailed.WithLabelValues("update").Inc() return fmt.Errorf("optimistic lock failed") } // if we didn't find an existing entity - if current.Guid == "" { + if previous.Guid == "" { return fmt.Errorf("entity not found") } - rsp.Entity.Guid = current.Guid + rsp.Entity.Guid = previous.Guid // Clear the refs if _, err := tx.Exec(ctx, "DELETE FROM entity_ref WHERE guid=?", rsp.Entity.Guid); err != nil { return err } + updated := proto.Clone(previous).(*entity.Entity) + if r.Entity.GroupVersion != "" { - current.GroupVersion = r.Entity.GroupVersion + updated.GroupVersion = r.Entity.GroupVersion } if r.Entity.Folder != "" { - current.Folder = r.Entity.Folder + updated.Folder = r.Entity.Folder } if r.Entity.Slug != "" { - current.Slug = r.Entity.Slug + updated.Slug = r.Entity.Slug } if r.Entity.Body != nil { - current.Body = r.Entity.Body - current.Size = int64(len(current.Body)) + updated.Body = r.Entity.Body + updated.Size = int64(len(updated.Body)) } if r.Entity.Meta != nil { - current.Meta = r.Entity.Meta + updated.Meta = r.Entity.Meta } if r.Entity.Status != nil { - current.Status = r.Entity.Status + updated.Status = r.Entity.Status } - etag := createContentsHash(current.Body, current.Meta, current.Status) - current.ETag = etag + etag := createContentsHash(updated.Body, updated.Meta, updated.Status) + updated.ETag = etag - current.UpdatedAt = updatedAt - current.UpdatedBy = updatedBy + updated.UpdatedAt = updatedAt + updated.UpdatedBy = updatedBy if r.Entity.Title != "" { - current.Title = r.Entity.Title + updated.Title = r.Entity.Title } if r.Entity.Description != "" { - current.Description = r.Entity.Description + updated.Description = r.Entity.Description } labels, err := json.Marshal(r.Entity.Labels) @@ -576,80 +609,80 @@ func (s *sqlEntityServer) Update(ctx context.Context, r *entity.UpdateEntityRequ s.log.Error("error marshalling labels", "msg", err.Error()) return err } - current.Labels = r.Entity.Labels + updated.Labels = r.Entity.Labels fields, err := json.Marshal(r.Entity.Fields) if err != nil { s.log.Error("error marshalling fields", "msg", err.Error()) return err } - current.Fields = r.Entity.Fields + updated.Fields = r.Entity.Fields errors, err := json.Marshal(r.Entity.Errors) if err != nil { s.log.Error("error marshalling errors", "msg", err.Error()) return err } - current.Errors = r.Entity.Errors + updated.Errors = r.Entity.Errors - if current.Origin == nil { - current.Origin = &entity.EntityOriginInfo{} + if updated.Origin == nil { + updated.Origin = &entity.EntityOriginInfo{} } if r.Entity.Origin != nil { if r.Entity.Origin.Source != "" { - current.Origin.Source = r.Entity.Origin.Source + updated.Origin.Source = r.Entity.Origin.Source } if r.Entity.Origin.Key != "" { - current.Origin.Key = r.Entity.Origin.Key + updated.Origin.Key = r.Entity.Origin.Key } if r.Entity.Origin.Time > 0 { - current.Origin.Time = r.Entity.Origin.Time + updated.Origin.Time = r.Entity.Origin.Time } } // Set the comment on this write if r.Entity.Message != "" { - current.Message = r.Entity.Message + updated.Message = r.Entity.Message } // Update resource version - current.ResourceVersion = s.snowflake.Generate().Int64() + updated.ResourceVersion = s.snowflake.Generate().Int64() - current.Action = entity.Entity_UPDATED + updated.Action = entity.Entity_UPDATED values := map[string]any{ // below are only set in history table - "guid": current.Guid, - "key": current.Key, - "namespace": current.Namespace, - "group": current.Group, - "resource": current.Resource, - "name": current.Name, - "created_at": current.CreatedAt, - "created_by": current.CreatedBy, + "guid": updated.Guid, + "key": updated.Key, + "namespace": updated.Namespace, + "group": updated.Group, + "resource": updated.Resource, + "name": updated.Name, + "created_at": updated.CreatedAt, + "created_by": updated.CreatedBy, // below are updated - "group_version": current.GroupVersion, - "folder": current.Folder, - "slug": current.Slug, - "updated_at": current.UpdatedAt, - "updated_by": current.UpdatedBy, - "body": current.Body, - "meta": current.Meta, - "status": current.Status, - "size": current.Size, - "etag": current.ETag, - "resource_version": current.ResourceVersion, - "title": current.Title, - "description": current.Description, + "group_version": updated.GroupVersion, + "folder": updated.Folder, + "slug": updated.Slug, + "updated_at": updated.UpdatedAt, + "updated_by": updated.UpdatedBy, + "body": updated.Body, + "meta": updated.Meta, + "status": updated.Status, + "size": updated.Size, + "etag": updated.ETag, + "resource_version": updated.ResourceVersion, + "title": updated.Title, + "description": updated.Description, "labels": labels, "fields": fields, "errors": errors, - "origin": current.Origin.Source, - "origin_key": current.Origin.Key, - "origin_ts": current.Origin.Time, - "message": current.Message, - "action": current.Action, + "origin": updated.Origin.Source, + "origin_key": updated.Origin.Key, + "origin_ts": updated.Origin.Time, + "message": updated.Message, + "action": updated.Action, } // 1. Add the `entity_history` values @@ -676,7 +709,7 @@ func (s *sqlEntityServer) Update(ctx context.Context, r *entity.UpdateEntityRequ entityTable, values, map[string]any{ - "guid": current.Guid, + "guid": updated.Guid, }, ) if err != nil { @@ -684,11 +717,11 @@ func (s *sqlEntityServer) Update(ctx context.Context, r *entity.UpdateEntityRequ return err } - switch current.Group { + switch updated.Group { case folder.GROUP: - switch current.Resource { + switch updated.Resource { case folder.RESOURCE: - err = s.updateFolderTree(ctx, tx, current.Namespace) + err = s.updateFolderTree(ctx, tx, updated.Namespace) if err != nil { s.log.Error("error updating folder tree", "msg", err.Error()) return err @@ -696,15 +729,23 @@ func (s *sqlEntityServer) Update(ctx context.Context, r *entity.UpdateEntityRequ } } - rsp.Entity = current + rsp.Entity = updated - return s.setLabels(ctx, tx, current.Guid, current.Labels) + return s.setLabels(ctx, tx, updated.Guid, updated.Labels) }) if err != nil { s.log.Error("error updating entity", "msg", err.Error()) rsp.Status = entity.UpdateEntityResponse_ERROR } + evt := &entity.EntityWatchResponse{ + Timestamp: time.Now().UnixMilli(), + Entity: rsp.Entity, + Previous: previous, + } + + s.stream <- evt + return rsp, err } @@ -746,9 +787,12 @@ func (s *sqlEntityServer) Delete(ctx context.Context, r *entity.DeleteEntityRequ rsp := &entity.DeleteEntityResponse{} + var previous *entity.Entity + var updated *entity.Entity + err := s.sess.WithTransaction(ctx, func(tx *session.SessionTx) error { var err error - rsp.Entity, err = s.Read(ctx, &entity.ReadEntityRequest{ + previous, err = s.Read(ctx, &entity.ReadEntityRequest{ Key: r.Key, WithBody: true, WithStatus: true, @@ -762,13 +806,18 @@ func (s *sqlEntityServer) Delete(ctx context.Context, r *entity.DeleteEntityRequ return err } - if r.PreviousVersion > 0 && r.PreviousVersion != rsp.Entity.ResourceVersion { + if previous.Guid == "" { + rsp.Status = entity.DeleteEntityResponse_NOTFOUND + return nil + } + + if r.PreviousVersion > 0 && r.PreviousVersion != previous.ResourceVersion { rsp.Status = entity.DeleteEntityResponse_ERROR StorageServerMetrics.OptimisticLockFailed.WithLabelValues("delete").Inc() return fmt.Errorf("optimistic lock failed") } - err = s.doDelete(ctx, tx, rsp.Entity) + updated, err = s.doDelete(ctx, tx, previous) if err != nil { rsp.Status = entity.DeleteEntityResponse_ERROR return err @@ -778,114 +827,132 @@ func (s *sqlEntityServer) Delete(ctx context.Context, r *entity.DeleteEntityRequ return nil }) + if rsp.Status == entity.DeleteEntityResponse_DELETED { + // k8s expects us to return the entity as it was before the deletion, but with the updated RV + rsp.Entity = proto.Clone(previous).(*entity.Entity) + rsp.Entity.ResourceVersion = updated.ResourceVersion + + evt := &entity.EntityWatchResponse{ + Timestamp: time.Now().UnixMilli(), + Entity: updated, + Previous: previous, + } + s.stream <- evt + } else { + rsp.Entity = previous + } + return rsp, err } -func (s *sqlEntityServer) doDelete(ctx context.Context, tx *session.SessionTx, ent *entity.Entity) error { +func (s *sqlEntityServer) doDelete(ctx context.Context, tx *session.SessionTx, ent *entity.Entity) (*entity.Entity, error) { + updated := proto.Clone(ent).(*entity.Entity) + // Update resource version - ent.ResourceVersion = s.snowflake.Generate().Int64() + updated.ResourceVersion = s.snowflake.Generate().Int64() - ent.Action = entity.Entity_DELETED + updated.Action = entity.Entity_DELETED - // Set updated at/by - ent.UpdatedAt = time.Now().UnixMilli() + // Get updated by modifier, err := appcontext.User(ctx) if err != nil { - return err + return nil, err } if modifier == nil { - return fmt.Errorf("can not find user in context") + return nil, fmt.Errorf("can not find user in context") } - ent.UpdatedBy = store.GetUserIDString(modifier) - labels, err := json.Marshal(ent.Labels) + labels, err := json.Marshal(updated.Labels) if err != nil { s.log.Error("error marshalling labels", "msg", err.Error()) - return err + return nil, err } - fields, err := json.Marshal(ent.Fields) + fields, err := json.Marshal(updated.Fields) if err != nil { s.log.Error("error marshalling fields", "msg", err.Error()) - return err + return nil, err } - errors, err := json.Marshal(ent.Errors) + errors, err := json.Marshal(updated.Errors) if err != nil { s.log.Error("error marshalling errors", "msg", err.Error()) - return err + return nil, err } - if ent.Origin == nil { - ent.Origin = &entity.EntityOriginInfo{} + if updated.Origin == nil { + updated.Origin = &entity.EntityOriginInfo{} } + updated.UpdatedAt = time.Now().UnixMilli() + updated.UpdatedBy = store.GetUserIDString(modifier) + values := map[string]any{ // below are only set in history table - "guid": ent.Guid, - "key": ent.Key, - "namespace": ent.Namespace, - "group": ent.Group, - "resource": ent.Resource, - "name": ent.Name, - "created_at": ent.CreatedAt, - "created_by": ent.CreatedBy, + "guid": updated.Guid, + "key": updated.Key, + "namespace": updated.Namespace, + "group": updated.Group, + "resource": updated.Resource, + "name": updated.Name, + "created_at": updated.CreatedAt, + "created_by": updated.CreatedBy, // below are updated - "group_version": ent.GroupVersion, - "folder": ent.Folder, - "slug": ent.Slug, - "updated_at": ent.UpdatedAt, - "updated_by": ent.UpdatedBy, - "body": ent.Body, - "meta": ent.Meta, - "status": ent.Status, - "size": ent.Size, - "etag": ent.ETag, - "resource_version": ent.ResourceVersion, - "title": ent.Title, - "description": ent.Description, + "group_version": updated.GroupVersion, + "folder": updated.Folder, + "slug": updated.Slug, + "updated_at": updated.UpdatedAt, + "updated_by": updated.UpdatedBy, + "body": updated.Body, + "meta": updated.Meta, + "status": updated.Status, + "size": updated.Size, + "etag": updated.ETag, + "resource_version": updated.ResourceVersion, + "title": updated.Title, + "description": updated.Description, "labels": labels, "fields": fields, "errors": errors, - "origin": ent.Origin.Source, - "origin_key": ent.Origin.Key, - "origin_ts": ent.Origin.Time, - "message": ent.Message, - "action": ent.Action, + "origin": updated.Origin.Source, + "origin_key": updated.Origin.Key, + "origin_ts": updated.Origin.Time, + "message": updated.Message, + "action": updated.Action, } // 1. Add the `entity_history` values if err := s.dialect.Insert(ctx, tx, entityHistoryTable, values); err != nil { s.log.Error("error inserting entity history", "msg", err.Error()) - return err + return nil, err } - _, err = tx.Exec(ctx, "DELETE FROM entity WHERE guid=?", ent.Guid) + _, err = tx.Exec(ctx, "DELETE FROM entity WHERE guid=?", updated.Guid) if err != nil { - return err + return nil, err } - _, err = tx.Exec(ctx, "DELETE FROM entity_labels WHERE guid=?", ent.Guid) + _, err = tx.Exec(ctx, "DELETE FROM entity_labels WHERE guid=?", updated.Guid) if err != nil { - return err + return nil, err } - _, err = tx.Exec(ctx, "DELETE FROM entity_ref WHERE guid=?", ent.Guid) + _, err = tx.Exec(ctx, "DELETE FROM entity_ref WHERE guid=?", updated.Guid) if err != nil { - return err + return nil, err } - switch ent.Group { + switch updated.Group { case folder.GROUP: - switch ent.Resource { + switch updated.Resource { case folder.RESOURCE: - err = s.updateFolderTree(ctx, tx, ent.Namespace) + err = s.updateFolderTree(ctx, tx, updated.Namespace) if err != nil { s.log.Error("error updating folder tree", "msg", err.Error()) - return err + return nil, err } } } - return nil + return updated, nil } func (s *sqlEntityServer) History(ctx context.Context, r *entity.EntityHistoryRequest) (*entity.EntityHistoryResponse, error) { @@ -901,19 +968,10 @@ func (s *sqlEntityServer) History(ctx context.Context, r *entity.EntityHistoryRe return nil, fmt.Errorf("missing user in context") } - if r.Key == "" { - return nil, fmt.Errorf("missing key") - } - - key, err := entity.ParseKey(r.Key) - if err != nil { - return nil, err - } - - if key.Name == "" { - return nil, fmt.Errorf("missing name") - } + return s.history(ctx, r) +} +func (s *sqlEntityServer) history(ctx context.Context, r *entity.EntityHistoryRequest) (*entity.EntityHistoryResponse, error) { var limit int64 = 100 if r.Limit > 0 && r.Limit < 100 { limit = r.Limit @@ -929,19 +987,35 @@ func (s *sqlEntityServer) History(ctx context.Context, r *entity.EntityHistoryRe fields := s.getReadFields(r) entityQuery.AddFields(fields...) - args := []any{key.Group, key.Resource} - whereclause := "(" + s.dialect.Quote("group") + "=? AND " + s.dialect.Quote("resource") + "=?" - if key.Namespace != "" { - args = append(args, key.Namespace) - whereclause += " AND " + s.dialect.Quote("namespace") + "=?" - } - if key.Name != "" { - args = append(args, key.Name) - whereclause += " AND " + s.dialect.Quote("name") + "=?" - } - whereclause += ")" + if r.Key != "" { + key, err := entity.ParseKey(r.Key) + if err != nil { + return nil, err + } - entityQuery.AddWhere(whereclause, args...) + if key.Name == "" { + return nil, fmt.Errorf("missing name") + } + + args := []any{key.Group, key.Resource} + whereclause := "(" + s.dialect.Quote("group") + "=? AND " + s.dialect.Quote("resource") + "=?" + if key.Namespace != "" { + args = append(args, key.Namespace) + whereclause += " AND " + s.dialect.Quote("namespace") + "=?" + } + args = append(args, key.Name) + whereclause += " AND " + s.dialect.Quote("name") + "=?)" + + entityQuery.AddWhere(whereclause, args...) + } else if r.Guid != "" { + entityQuery.AddWhere(s.dialect.Quote("guid")+"=?", r.Guid) + } else { + return nil, fmt.Errorf("no key or guid specified") + } + + if r.Before > 0 { + entityQuery.AddWhere(s.dialect.Quote("resource_version")+" 0 { - entityQuery.from = entityHistoryTable - entityQuery.AddWhere("resource_version > ?", r.Since) - fromZero = false - } - // TODO fix this // entityQuery.addWhere("namespace", user.OrgID) @@ -1348,7 +1426,7 @@ func (s *sqlEntityServer) watchInit(r *entity.EntityWatchRequest, w entity.Entit for _, k := range r.Key { key, err := entity.ParseKey(k) if err != nil { - return err + return lastRv, err } args = append(args, key.Group, key.Resource) @@ -1375,7 +1453,7 @@ func (s *sqlEntityServer) watchInit(r *entity.EntityWatchRequest, w entity.Entit } if len(r.Labels) > 0 { - if r.Since > 0 { + if entityQuery.from != entityTable { for labelKey, labelValue := range r.Labels { entityQuery.AddWhereJsonContainsKV("labels", labelKey, labelValue) } @@ -1422,24 +1500,23 @@ func (s *sqlEntityServer) watchInit(r *entity.EntityWatchRequest, w entity.Entit return nil } - result, err := rowToEntity(rows, r) + result, err := readEntity(rows, r) if err != nil { return err } - if result.ResourceVersion > r.Since { - r.Since = result.ResourceVersion + if result.ResourceVersion > lastRv { + lastRv = result.ResourceVersion } - if fromZero { - result.Action = entity.Entity_CREATED + resp := &entity.EntityWatchResponse{ + Timestamp: time.Now().UnixMilli(), + Entity: result, } s.log.Debug("sending init event", "guid", result.Guid, "action", result.Action, "rv", result.ResourceVersion) - err = w.Send(&entity.EntityWatchResponse{ - Timestamp: time.Now().UnixMilli(), - Entity: result, - }) + + err = w.Send(resp) if err != nil { return err } @@ -1449,29 +1526,52 @@ func (s *sqlEntityServer) watchInit(r *entity.EntityWatchRequest, w entity.Entit return nil }() if err != nil { - return err + return lastRv, err } } - return nil + // send a bookmark event + if r.AllowWatchBookmarks { + resp := &entity.EntityWatchResponse{ + Timestamp: time.Now().UnixMilli(), + Entity: &entity.Entity{ + Action: entity.Entity_BOOKMARK, + ResourceVersion: lastRv, + }, + } + err = w.Send(resp) + if err != nil { + return lastRv, err + } + } + + return lastRv, nil } -func (s *sqlEntityServer) poller(stream chan *entity.Entity) { +func (s *sqlEntityServer) poller(stream chan *entity.EntityWatchResponse) { var err error since := s.snowflake.Generate().Int64() - t := time.NewTicker(1 * time.Second) + interval := 1 * time.Second + + t := time.NewTicker(interval) defer t.Stop() - for range t.C { - since, err = s.poll(since, stream) - if err != nil { - s.log.Error("watch error", "err", err) + for { + select { + case <-s.ctx.Done(): + return + case <-t.C: + since, err = s.poll(since, stream) + if err != nil { + s.log.Error("watch error", "err", err) + } + t.Reset(interval) } } } -func (s *sqlEntityServer) poll(since int64, out chan *entity.Entity) (int64, error) { +func (s *sqlEntityServer) poll(since int64, out chan *entity.EntityWatchResponse) (int64, error) { rr := &entity.ReadEntityRequest{ WithBody: true, WithStatus: true, @@ -1503,21 +1603,50 @@ func (s *sqlEntityServer) poll(since int64, out chan *entity.Entity) (int64, err found := int64(0) for rows.Next() { + // check if the context is done + if s.ctx.Err() != nil { + hasmore = false + return nil + } + found++ if found > entityQuery.limit { return nil } - result, err := rowToEntity(rows, rr) + updated, err := readEntity(rows, rr) if err != nil { return err } - if result.ResourceVersion > since { - since = result.ResourceVersion + if updated.ResourceVersion > since { + since = updated.ResourceVersion } - s.log.Debug("sending poll result", "guid", result.Guid, "action", result.Action, "rv", result.ResourceVersion) + result := &entity.EntityWatchResponse{ + Timestamp: time.Now().UnixMilli(), + Entity: updated, + } + + if updated.Action == entity.Entity_UPDATED || updated.Action == entity.Entity_DELETED { + rr := &entity.EntityHistoryRequest{ + Guid: updated.Guid, + Before: updated.ResourceVersion, + Limit: 1, + Sort: []string{"resource_version_desc"}, + WithBody: rr.WithBody, + WithStatus: rr.WithStatus, + } + history, err := s.history(s.ctx, rr) + if err != nil { + s.log.Error("error reading previous entity", "guid", updated.Guid, "err", err) + return err + } + + result.Previous = history.Versions[0] + } + + s.log.Debug("sending poll result", "guid", updated.Guid, "action", updated.Action, "rv", updated.ResourceVersion) out <- result } @@ -1533,8 +1662,7 @@ func (s *sqlEntityServer) poll(since int64, out chan *entity.Entity) (int64, err } func watchMatches(r *entity.EntityWatchRequest, result *entity.Entity) bool { - // Resource version too old - if result.ResourceVersion <= r.Since { + if result == nil { return false } @@ -1597,42 +1725,107 @@ func (s *sqlEntityServer) watch(r *entity.EntityWatchRequest, w entity.EntitySto return err } + stop := make(chan struct{}) + since := r.Since + + go func() { + for { + r, err := w.Recv() + if errors.Is(err, io.EOF) { + s.log.Debug("watch client closed stream") + stop <- struct{}{} + return + } + if err != nil { + s.log.Error("error receiving message", "err", err) + stop <- struct{}{} + return + } + if r.Action == entity.EntityWatchRequest_STOP { + s.log.Debug("watch stop requested") + stop <- struct{}{} + return + } + // handle any other message types + s.log.Debug("watch received unexpected message", "action", r.Action) + } + }() + for { select { - // user closed the connection + // stop signal + case <-stop: + s.log.Debug("watch stopped") + return nil + // context canceled case <-w.Context().Done(): + s.log.Debug("watch context done") return nil // got a raw result from the broadcaster - case result := <-evts: - // result doesn't match our watch params, skip it - if !watchMatches(r, result) { - s.log.Debug("watch result not matched", "guid", result.Guid, "action", result.Action, "rv", result.ResourceVersion) + case result, ok := <-evts: + if !ok { + s.log.Debug("watch events closed") + return nil + } + + // Invalid result or resource version too old + if result == nil || result.Entity == nil || result.Entity.ResourceVersion <= since { break } - // remove the body and status if not requested - if !r.WithBody { - result.Body = nil - } - if !r.WithStatus { - result.Status = nil - } + since = result.Entity.ResourceVersion - // update r.Since value so we don't send earlier results again - r.Since = result.ResourceVersion - - s.log.Debug("sending watch result", "guid", result.Guid, "action", result.Action, "rv", result.ResourceVersion) - err = w.Send(&entity.EntityWatchResponse{ - Timestamp: time.Now().UnixMilli(), - Entity: result, - }) + resp, err := s.watchEvent(r, result) if err != nil { + break + } + if resp == nil { + break + } + + err = w.Send(resp) + if err != nil { + s.log.Error("error sending watch event", "err", err) return err } } } } +func (s *sqlEntityServer) watchEvent(r *entity.EntityWatchRequest, result *entity.EntityWatchResponse) (*entity.EntityWatchResponse, error) { + // if this is an update or a delete, check the current or previous version matches + if result.Previous != nil { + // if neither the previous nor the current result match our watch params, skip it + if !watchMatches(r, result.Entity) && !watchMatches(r, result.Previous) { + s.log.Debug("watch result not matched", "guid", result.Entity.Guid, "action", result.Entity.Action, "rv", result.Entity.ResourceVersion) + return nil, nil + } + } else { + // if result doesn't match our watch params, skip it + if !watchMatches(r, result.Entity) { + s.log.Debug("watch result not matched", "guid", result.Entity.Guid, "action", result.Entity.Action, "rv", result.Entity.ResourceVersion) + return nil, nil + } + } + + // remove the body and status if not requested + if !r.WithBody { + result.Entity.Body = nil + if result.Previous != nil { + result.Previous.Body = nil + } + } + if !r.WithStatus { + result.Entity.Status = nil + if result.Previous != nil { + result.Previous.Status = nil + } + } + + s.log.Debug("sending watch result", "guid", result.Entity.Guid, "action", result.Entity.Action, "rv", result.Entity.ResourceVersion) + return result, nil +} + func (s *sqlEntityServer) FindReferences(ctx context.Context, r *entity.ReferenceRequest) (*entity.EntityListResponse, error) { if err := s.Init(); err != nil { return nil, err diff --git a/pkg/tests/api/dashboards/api_dashboards_test.go b/pkg/tests/api/dashboards/api_dashboards_test.go index 9ac5da995a2..0b280eed3d3 100644 --- a/pkg/tests/api/dashboards/api_dashboards_test.go +++ b/pkg/tests/api/dashboards/api_dashboards_test.go @@ -168,6 +168,10 @@ providers: Login: "admin", }) + // give provisioner some time since we don't have a way to know when provisioning is complete + // TODO https://github.com/grafana/grafana/issues/85617 + time.Sleep(1 * time.Second) + type errorResponseBody struct { Message string `json:"message"` }