From f481f11d3a4e59c276a2699ceb9c282e17ea1600 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Thu, 13 Jun 2024 09:11:10 +0300 Subject: [PATCH] change tree structure --- Makefile | 2 +- go.work | 2 +- pkg/storage/README.md | 4 - pkg/storage/unified/README.md | 15 + pkg/storage/unified/apistore/restoptions.go | 90 ++ pkg/storage/unified/apistore/selector.go | 89 ++ pkg/storage/unified/apistore/storage.go | 797 ++++++++++++++++++ .../unified/apistore/test/requestinfo.go | 166 ++++ .../unified/apistore/test/watch_test.go | 366 ++++++++ pkg/storage/unified/apistore/utils.go | 182 ++++ pkg/storage/unified/apistore/utils_test.go | 216 +++++ .../unified/{ => resource}/buf.gen.yaml | 4 +- pkg/storage/unified/{ => resource}/buf.yaml | 0 .../unified/{ => resource}/client_wrapper.go | 2 +- pkg/storage/unified/{ => resource}/deleted.go | 2 +- pkg/storage/unified/{ => resource}/event.go | 2 +- pkg/storage/unified/{ => resource}/go.mod | 2 +- pkg/storage/unified/{ => resource}/go.sum | 0 .../unified/{ => resource}/resource.go | 2 +- .../unified/{ => resource}/resource.pb.go | 772 ++++++++--------- .../unified/{ => resource}/resource.proto | 4 +- .../{ => resource}/resource_grpc.pb.go | 24 +- .../unified/{ => resource}/resource_test.go | 6 +- .../testdata/01_create_playlist.json | 0 .../unified/{ => resource}/validator.go | 2 +- .../{ => unified}/sqlstash/data/common.sql | 0 .../sqlstash/data/resource_get.sql | 0 .../sqlstash/data/resource_insert.sql | 0 .../{ => unified}/sqlstash/data/rv_get.sql | 0 .../{ => unified}/sqlstash/data/rv_inc.sql | 0 .../{ => unified}/sqlstash/data/rv_insert.sql | 0 .../{ => unified}/sqlstash/data/rv_lock.sql | 0 pkg/storage/{ => unified}/sqlstash/queries.go | 0 .../{ => unified}/sqlstash/queries_test.go | 8 +- .../sqlstash/sql_storage_server.go | 56 +- .../testdata/01_create_playlist.json | 0 pkg/storage/{ => unified}/sqlstash/utils.go | 6 +- .../{ => unified}/sqlstash/utils_test.go | 0 pkg/storage/{ => unified}/sqlstash/watch.go | 10 +- 39 files changed, 2376 insertions(+), 455 deletions(-) delete mode 100644 pkg/storage/README.md create mode 100644 pkg/storage/unified/README.md create mode 100644 pkg/storage/unified/apistore/restoptions.go create mode 100644 pkg/storage/unified/apistore/selector.go create mode 100644 pkg/storage/unified/apistore/storage.go create mode 100644 pkg/storage/unified/apistore/test/requestinfo.go create mode 100644 pkg/storage/unified/apistore/test/watch_test.go create mode 100644 pkg/storage/unified/apistore/utils.go create mode 100644 pkg/storage/unified/apistore/utils_test.go rename pkg/storage/unified/{ => resource}/buf.gen.yaml (69%) rename pkg/storage/unified/{ => resource}/buf.yaml (100%) rename pkg/storage/unified/{ => resource}/client_wrapper.go (98%) rename pkg/storage/unified/{ => resource}/deleted.go (98%) rename pkg/storage/unified/{ => resource}/event.go (98%) rename pkg/storage/unified/{ => resource}/go.mod (96%) rename pkg/storage/unified/{ => resource}/go.sum (100%) rename pkg/storage/unified/{ => resource}/resource.go (98%) rename pkg/storage/unified/{ => resource}/resource.pb.go (68%) rename pkg/storage/unified/{ => resource}/resource.proto (99%) rename pkg/storage/unified/{ => resource}/resource_grpc.pb.go (95%) rename pkg/storage/unified/{ => resource}/resource_test.go (84%) rename pkg/storage/{sqlstash => unified/resource}/testdata/01_create_playlist.json (100%) rename pkg/storage/unified/{ => resource}/validator.go (99%) rename pkg/storage/{ => unified}/sqlstash/data/common.sql (100%) rename pkg/storage/{ => unified}/sqlstash/data/resource_get.sql (100%) rename pkg/storage/{ => unified}/sqlstash/data/resource_insert.sql (100%) rename pkg/storage/{ => unified}/sqlstash/data/rv_get.sql (100%) rename pkg/storage/{ => unified}/sqlstash/data/rv_inc.sql (100%) rename pkg/storage/{ => unified}/sqlstash/data/rv_insert.sql (100%) rename pkg/storage/{ => unified}/sqlstash/data/rv_lock.sql (100%) rename pkg/storage/{ => unified}/sqlstash/queries.go (100%) rename pkg/storage/{ => unified}/sqlstash/queries_test.go (82%) rename pkg/storage/{ => unified}/sqlstash/sql_storage_server.go (77%) rename pkg/storage/unified/{ => sqlstash}/testdata/01_create_playlist.json (100%) rename pkg/storage/{ => unified}/sqlstash/utils.go (95%) rename pkg/storage/{ => unified}/sqlstash/utils_test.go (100%) rename pkg/storage/{ => unified}/sqlstash/watch.go (70%) diff --git a/Makefile b/Makefile index 89dbc0ad130..420c0c187fe 100644 --- a/Makefile +++ b/Makefile @@ -379,7 +379,7 @@ protobuf: ## Compile protobuf definitions # buf generate pkg/plugins/backendplugin/pluginextensionv2 --template pkg/plugins/backendplugin/pluginextensionv2/buf.gen.yaml # buf generate pkg/plugins/backendplugin/secretsmanagerplugin --template pkg/plugins/backendplugin/secretsmanagerplugin/buf.gen.yaml # buf generate pkg/services/store/entity --template pkg/services/store/entity/buf.gen.yaml - buf generate pkg/storage/unified --template pkg/storage/unified/buf.gen.yaml + buf generate pkg/storage/unified/resource --template pkg/storage/unified/resource/buf.gen.yaml .PHONY: clean clean: ## Clean up intermediate build artifacts. diff --git a/go.work b/go.work index bc3f2fe8192..74db73dc1c9 100644 --- a/go.work +++ b/go.work @@ -6,7 +6,7 @@ use ( ./pkg/apiserver ./pkg/build/wire ./pkg/promlib - ./pkg/storage/unified + ./pkg/storage/unified/resource ./pkg/util/xorm ) diff --git a/pkg/storage/README.md b/pkg/storage/README.md deleted file mode 100644 index 508a59eb7a6..00000000000 --- a/pkg/storage/README.md +++ /dev/null @@ -1,4 +0,0 @@ -This includes two packages - -api = the protobuf and common helpers useful for both client and server -server = the server support.... \ No newline at end of file diff --git a/pkg/storage/unified/README.md b/pkg/storage/unified/README.md new file mode 100644 index 00000000000..be8728f366a --- /dev/null +++ b/pkg/storage/unified/README.md @@ -0,0 +1,15 @@ +This includes three packages + +== resource + +this is a go module that can be imported into external projects + +This includes the protobuf based client+server and all the logic required to convert requests into write events. + +== apistore + +The apiserver storage.Interface that links the storage to kubernetes + +== sqlstash + +SQL based implementation of the unified storage server diff --git a/pkg/storage/unified/apistore/restoptions.go b/pkg/storage/unified/apistore/restoptions.go new file mode 100644 index 00000000000..46850585875 --- /dev/null +++ b/pkg/storage/unified/apistore/restoptions.go @@ -0,0 +1,90 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +package apistore + +import ( + "encoding/json" + "path" + "time" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/registry/generic" + "k8s.io/apiserver/pkg/storage" + "k8s.io/apiserver/pkg/storage/storagebackend" + "k8s.io/apiserver/pkg/storage/storagebackend/factory" + flowcontrolrequest "k8s.io/apiserver/pkg/util/flowcontrol/request" + "k8s.io/client-go/tools/cache" + + entityStore "github.com/grafana/grafana/pkg/services/store/entity" + "github.com/grafana/grafana/pkg/setting" +) + +var _ generic.RESTOptionsGetter = (*RESTOptionsGetter)(nil) + +type RESTOptionsGetter struct { + cfg *setting.Cfg + store entityStore.EntityStoreClient + Codec runtime.Codec +} + +func NewRESTOptionsGetter(cfg *setting.Cfg, store entityStore.EntityStoreClient, codec runtime.Codec) *RESTOptionsGetter { + return &RESTOptionsGetter{ + cfg: cfg, + store: store, + Codec: codec, + } +} + +func (f *RESTOptionsGetter) GetRESTOptions(resource schema.GroupResource) (generic.RESTOptions, error) { + // build connection string to uniquely identify the storage backend + connectionInfo, err := json.Marshal(f.cfg.SectionWithEnvOverrides("entity_api").KeysHash()) + if err != nil { + return generic.RESTOptions{}, err + } + + storageConfig := &storagebackend.ConfigForResource{ + Config: storagebackend.Config{ + Type: "custom", + Prefix: "", + Transport: storagebackend.TransportConfig{ + ServerList: []string{ + string(connectionInfo), + }, + }, + Codec: f.Codec, + EncodeVersioner: nil, + Transformer: nil, + CompactionInterval: 0, + CountMetricPollPeriod: 0, + DBMetricPollInterval: 0, + HealthcheckTimeout: 0, + ReadycheckTimeout: 0, + StorageObjectCountTracker: nil, + }, + GroupResource: resource, + } + + ret := generic.RESTOptions{ + StorageConfig: storageConfig, + Decorator: func( + config *storagebackend.ConfigForResource, + resourcePrefix string, + keyFunc func(obj runtime.Object) (string, error), + newFunc func() runtime.Object, + newListFunc func() runtime.Object, + getAttrsFunc storage.AttrFunc, + trigger storage.IndexerFuncs, + indexers *cache.Indexers, + ) (storage.Interface, factory.DestroyFunc, error) { + return NewStorage(config, resource, f.store, f.Codec, keyFunc, newFunc, newListFunc, getAttrsFunc) + }, + DeleteCollectionWorkers: 0, + EnableGarbageCollection: false, + ResourcePrefix: path.Join(storageConfig.Prefix, resource.Group, resource.Resource), + CountMetricPollPeriod: 1 * time.Second, + StorageObjectCountTracker: flowcontrolrequest.NewStorageObjectCountTracker(), + } + + return ret, nil +} diff --git a/pkg/storage/unified/apistore/selector.go b/pkg/storage/unified/apistore/selector.go new file mode 100644 index 00000000000..39e53db3375 --- /dev/null +++ b/pkg/storage/unified/apistore/selector.go @@ -0,0 +1,89 @@ +package apistore + +import ( + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/selection" + + "github.com/grafana/grafana/pkg/apimachinery/utils" +) + +const SortByKey = "grafana.app/sortBy" +const ListDeletedKey = "grafana.app/listDeleted" +const ListHistoryKey = "grafana.app/listHistory" + +type Requirements struct { + // Equals folder + Folder *string + // SortBy is a list of fields to sort by + SortBy []string + // ListDeleted is a flag to list deleted entities + ListDeleted bool + // ListHistory is a resource name to list the history of + ListHistory string + // ListOriginKeys needs to include the origin key of a given entity in order for it to be selected. + ListOriginKeys []string +} + +func ReadLabelSelectors(selector labels.Selector) (Requirements, labels.Selector, error) { + requirements := Requirements{} + newSelector := labels.NewSelector() + + if selector == nil { + return requirements, newSelector, nil + } + + labelSelectors, _ := selector.Requirements() + + for _, r := range labelSelectors { + switch r.Key() { + case utils.AnnoKeyFolder: + if (r.Operator() != selection.Equals) && (r.Operator() != selection.DoubleEquals) { + return requirements, newSelector, apierrors.NewBadRequest(utils.AnnoKeyFolder + " label selector only supports equality") + } + folder := r.Values().List()[0] + requirements.Folder = &folder + case SortByKey: + if r.Operator() != selection.In { + return requirements, newSelector, apierrors.NewBadRequest(SortByKey + " label selector only supports in") + } + requirements.SortBy = r.Values().List() + case utils.AnnoKeyOriginKey: + if r.Operator() != selection.In { + return requirements, newSelector, apierrors.NewBadRequest(SortByKey + " label selector only supports in") + } + requirements.ListOriginKeys = r.Values().List() + case ListDeletedKey: + if r.Operator() != selection.Equals { + return requirements, newSelector, apierrors.NewBadRequest(ListDeletedKey + " label selector only supports equality") + } + if len(r.Values().List()) != 1 { + return requirements, newSelector, apierrors.NewBadRequest(ListDeletedKey + " label selector only supports one value") + } + if r.Values().List()[0] != "true" && r.Values().List()[0] != "false" { + return requirements, newSelector, apierrors.NewBadRequest(ListDeletedKey + " label selector only supports true or false") + } + requirements.ListDeleted = r.Values().List()[0] == "true" + case ListHistoryKey: + if r.Operator() != selection.Equals { + return requirements, newSelector, apierrors.NewBadRequest(ListHistoryKey + " label selector only supports equality") + } + if len(r.Values().List()) != 1 { + return requirements, newSelector, apierrors.NewBadRequest(ListHistoryKey + " label selector only supports one value") + } + if r.Values().List()[0] == "" { + return requirements, newSelector, apierrors.NewBadRequest(ListHistoryKey + " label selector must not be empty") + } + requirements.ListHistory = r.Values().List()[0] + // add all unregonized label selectors to the new selector list, these will be processed by the entity store + default: + newSelector = newSelector.Add(r) + } + } + + if requirements.ListDeleted && requirements.ListHistory != "" { + return requirements, newSelector, apierrors.NewBadRequest("cannot list deleted and history at the same time") + } + + return requirements, newSelector, nil +} diff --git a/pkg/storage/unified/apistore/storage.go b/pkg/storage/unified/apistore/storage.go new file mode 100644 index 00000000000..8ae3660748d --- /dev/null +++ b/pkg/storage/unified/apistore/storage.go @@ -0,0 +1,797 @@ +// SPDX-License-Identifier: AGPL-3.0-only +// Provenance-includes-location: https://github.com/kubernetes-sigs/apiserver-runtime/blob/main/pkg/experimental/storage/filepath/jsonfile_rest.go +// Provenance-includes-license: Apache-2.0 +// Provenance-includes-copyright: The Kubernetes Authors. + +package apistore + +import ( + "context" + "errors" + "fmt" + "io" + "reflect" + "strconv" + + grpcCodes "google.golang.org/grpc/codes" + grpcStatus "google.golang.org/grpc/status" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/conversion" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/selection" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/apiserver/pkg/endpoints/request" + "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" +) + +var _ storage.Interface = (*Storage)(nil) + +// Storage implements storage.Interface and stores resources in unified storage +type Storage struct { + config *storagebackend.ConfigForResource + store entityStore.EntityStoreClient + gr schema.GroupResource + codec runtime.Codec + keyFunc func(obj runtime.Object) (string, error) + newFunc func() runtime.Object + newListFunc func() runtime.Object + getAttrsFunc storage.AttrFunc + // trigger storage.IndexerFuncs + // indexers *cache.Indexers +} + +func NewStorage( + config *storagebackend.ConfigForResource, + gr schema.GroupResource, + store entityStore.EntityStoreClient, + codec runtime.Codec, + keyFunc func(obj runtime.Object) (string, error), + newFunc func() runtime.Object, + newListFunc func() runtime.Object, + getAttrsFunc storage.AttrFunc, +) (storage.Interface, factory.DestroyFunc, error) { + return &Storage{ + config: config, + gr: gr, + codec: codec, + store: store, + keyFunc: keyFunc, + newFunc: newFunc, + newListFunc: newListFunc, + getAttrsFunc: getAttrsFunc, + }, nil, nil +} + +// Create adds a new object at a key unless it already exists. 'ttl' is time-to-live +// in seconds (0 means forever). If no error is returned and out is not nil, out will be +// set to the read value from database. +func (s *Storage) Create(ctx context.Context, key string, obj runtime.Object, out runtime.Object, ttl uint64) error { + requestInfo, ok := request.RequestInfoFrom(ctx) + if !ok { + return apierrors.NewInternalError(fmt.Errorf("could not get request info")) + } + + if err := s.Versioner().PrepareObjectForStorage(obj); err != nil { + return err + } + + e, err := resourceToEntity(obj, requestInfo, s.codec) + if err != nil { + return err + } + + req := &entityStore.CreateEntityRequest{ + Entity: e, + } + + rsp, err := s.store.Create(ctx, req) + if err != nil { + return err + } + if rsp.Status != entityStore.CreateEntityResponse_CREATED { + return fmt.Errorf("this was not a create operation... (%s)", rsp.Status.String()) + } + + err = entityToResource(rsp.Entity, out, s.codec) + if err != nil { + return apierrors.NewInternalError(err) + } + + return nil +} + +// Delete removes the specified key and returns the value that existed at that spot. +// If key didn't exist, it will return NotFound storage error. +// If 'cachedExistingObject' is non-nil, it can be used as a suggestion about the +// current version of the object to avoid read operation from storage to get it. +// However, the implementations have to retry in case suggestion is stale. +func (s *Storage) Delete(ctx context.Context, key string, out runtime.Object, preconditions *storage.Preconditions, validateDeletion storage.ValidateObjectFunc, cachedExistingObject runtime.Object) error { + requestInfo, ok := request.RequestInfoFrom(ctx) + if !ok { + return apierrors.NewInternalError(fmt.Errorf("could not get request info")) + } + + k := &entityStore.Key{ + Group: requestInfo.APIGroup, + Resource: requestInfo.Resource, + Namespace: requestInfo.Namespace, + Name: requestInfo.Name, + Subresource: requestInfo.Subresource, + } + + previousVersion := int64(0) + if preconditions != nil && preconditions.ResourceVersion != nil { + previousVersion, _ = strconv.ParseInt(*preconditions.ResourceVersion, 10, 64) + } + + rsp, err := s.store.Delete(ctx, &entityStore.DeleteEntityRequest{ + Key: k.String(), + PreviousVersion: previousVersion, + }) + if err != nil { + return err + } + + err = entityToResource(rsp.Entity, out, s.codec) + if err != nil { + return apierrors.NewInternalError(err) + } + + return nil +} + +// Watch begins watching the specified key. Events are decoded into API objects, +// and any items selected by 'p' are sent down to returned watch.Interface. +// resourceVersion may be used to specify what version to begin watching, +// which should be the current resourceVersion, and no longer rv+1 +// (e.g. reconnecting without missing any updates). +// If resource version is "0", this interface will get current object at given key +// and send it in an "ADDED" event, before watch starts. +func (s *Storage) Watch(ctx context.Context, key string, opts storage.ListOptions) (watch.Interface, error) { + requestInfo, ok := request.RequestInfoFrom(ctx) + if !ok { + return nil, apierrors.NewInternalError(fmt.Errorf("could not get request info")) + } + + k := &entityStore.Key{ + Group: requestInfo.APIGroup, + Resource: requestInfo.Resource, + Namespace: requestInfo.Namespace, + Name: requestInfo.Name, + Subresource: requestInfo.Subresource, + } + + if opts.Predicate.Field != nil { + // check for metadata.name field selector + 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 && k.Namespace == "" { + // just watch the specific namespace if we have a namespace field selector + k.Namespace = v + } + } + + // translate grafana.app/* label selectors into field requirements + requirements, newSelector, err := ReadLabelSelectors(opts.Predicate.Label) + if err != nil { + return nil, err + } + + // Update the selector to remove the unneeded requirements + opts.Predicate.Label = newSelector + + // if we got a listHistory label selector, watch the specified resource + if requirements.ListHistory != "" { + if k.Name != "" && k.Name != requirements.ListHistory { + return nil, apierrors.NewBadRequest("name field selector does not match listHistory") + } + k.Name = requirements.ListHistory + } + + req := &entityStore.EntityWatchRequest{ + Action: entityStore.EntityWatchRequest_START, + Key: []string{ + k.String(), + }, + 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 { + req.Folder = *requirements.Folder + } + + // translate "equals" label selectors to storage label conditions + labelRequirements, selectable := opts.Predicate.Label.Requirements() + if !selectable { + return nil, apierrors.NewBadRequest("label selector is not selectable") + } + + for _, r := range labelRequirements { + if r.Operator() == selection.Equals { + req.Labels[r.Key()] = r.Values().List()[0] + } + } + + 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 + } + + return nil, err + } + + 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: client, + newFunc: s.newFunc, + opts: opts, + codec: s.codec, + } + + w := watch.NewStreamWatcher(decoder, reporter) + + return w, nil +} + +// Get unmarshals object found at key into objPtr. On a not found error, will either +// return a zero object of the requested type, or an error, depending on 'opts.ignoreNotFound'. +// Treats empty responses and nil response nodes exactly like a not found error. +// The returned contents may be delayed, but it is guaranteed that they will +// match 'opts.ResourceVersion' according 'opts.ResourceVersionMatch'. +func (s *Storage) Get(ctx context.Context, key string, opts storage.GetOptions, objPtr runtime.Object) error { + requestInfo, ok := request.RequestInfoFrom(ctx) + if !ok { + return apierrors.NewInternalError(fmt.Errorf("could not get request info")) + } + + k := &entityStore.Key{ + Group: requestInfo.APIGroup, + Resource: requestInfo.Resource, + Namespace: requestInfo.Namespace, + Name: requestInfo.Name, + Subresource: requestInfo.Subresource, + } + + resourceVersion := int64(0) + var err error + if opts.ResourceVersion != "" { + resourceVersion, err = strconv.ParseInt(opts.ResourceVersion, 10, 64) + if err != nil { + return apierrors.NewBadRequest(fmt.Sprintf("invalid resource version: %s", opts.ResourceVersion)) + } + } + + rsp, err := s.store.Read(ctx, &entityStore.ReadEntityRequest{ + Key: k.String(), + WithBody: true, + WithStatus: true, + ResourceVersion: resourceVersion, + }) + if err != nil { + return err + } + + if rsp.Key == "" { + if opts.IgnoreNotFound { + return nil + } + + return apierrors.NewNotFound(s.gr, k.Name) + } + + err = entityToResource(rsp, objPtr, s.codec) + if err != nil { + return apierrors.NewInternalError(err) + } + + return nil +} + +// GetList unmarshalls objects found at key into a *List api object (an object +// that satisfies runtime.IsList definition). +// If 'opts.Recursive' is false, 'key' is used as an exact match. If `opts.Recursive' +// is true, 'key' is used as a prefix. +// The returned contents may be delayed, but it is guaranteed that they will +// match 'opts.ResourceVersion' according 'opts.ResourceVersionMatch'. +func (s *Storage) GetList(ctx context.Context, key string, opts storage.ListOptions, listObj runtime.Object) error { + requestInfo, ok := request.RequestInfoFrom(ctx) + if !ok { + return apierrors.NewInternalError(fmt.Errorf("could not get request info")) + } + + k := &entityStore.Key{ + Group: requestInfo.APIGroup, + Resource: requestInfo.Resource, + Namespace: requestInfo.Namespace, + Name: requestInfo.Name, + Subresource: requestInfo.Subresource, + } + + listPtr, err := meta.GetItemsPtr(listObj) + if err != nil { + return err + } + v, err := conversion.EnforcePtr(listPtr) + if err != nil { + return err + } + + // translate grafana.app/* label selectors into field requirements + requirements, newSelector, err := ReadLabelSelectors(opts.Predicate.Label) + if err != nil { + return err + } + + // Update the selector to remove the unneeded requirements + opts.Predicate.Label = newSelector + + if requirements.ListHistory != "" { + k.Name = requirements.ListHistory + + req := &entityStore.EntityHistoryRequest{ + Key: k.String(), + WithBody: true, + WithStatus: true, + NextPageToken: opts.Predicate.Continue, + Limit: opts.Predicate.Limit, + Sort: requirements.SortBy, + } + + rsp, err := s.store.History(ctx, req) + if err != nil { + return apierrors.NewInternalError(err) + } + + for _, r := range rsp.Versions { + res := s.newFunc() + + err := entityToResource(r, res, s.codec) + if err != nil { + return apierrors.NewInternalError(err) + } + + // apply any predicates not handled in storage + matches, err := opts.Predicate.Matches(res) + if err != nil { + return apierrors.NewInternalError(err) + } + if !matches { + continue + } + + v.Set(reflect.Append(v, reflect.ValueOf(res).Elem())) + } + + listAccessor, err := meta.ListAccessor(listObj) + if err != nil { + return err + } + + if rsp.NextPageToken != "" { + listAccessor.SetContinue(rsp.NextPageToken) + } + + listAccessor.SetResourceVersion(strconv.FormatInt(rsp.ResourceVersion, 10)) + + return nil + } + + req := &entityStore.EntityListRequest{ + Key: []string{ + k.String(), + }, + WithBody: true, + WithStatus: true, + NextPageToken: opts.Predicate.Continue, + Limit: opts.Predicate.Limit, + Labels: map[string]string{}, + } + + if requirements.Folder != nil { + req.Folder = *requirements.Folder + } + if len(requirements.SortBy) > 0 { + req.Sort = requirements.SortBy + } + if len(requirements.ListOriginKeys) > 0 { + req.OriginKeys = requirements.ListOriginKeys + } + if requirements.ListDeleted { + req.Deleted = true + } + + // translate "equals" label selectors to storage label conditions + labelRequirements, selectable := opts.Predicate.Label.Requirements() + if !selectable { + return apierrors.NewBadRequest("label selector is not selectable") + } + + for _, r := range labelRequirements { + if r.Operator() == selection.Equals { + req.Labels[r.Key()] = r.Values().List()[0] + } + } + + rsp, err := s.store.List(ctx, req) + if err != nil { + return apierrors.NewInternalError(err) + } + + for _, r := range rsp.Results { + res := s.newFunc() + + err := entityToResource(r, res, s.codec) + if err != nil { + return apierrors.NewInternalError(err) + } + + // apply any predicates not handled in storage + matches, err := opts.Predicate.Matches(res) + if err != nil { + return apierrors.NewInternalError(err) + } + if !matches { + continue + } + + v.Set(reflect.Append(v, reflect.ValueOf(res).Elem())) + } + + listAccessor, err := meta.ListAccessor(listObj) + if err != nil { + return err + } + + if rsp.NextPageToken != "" { + listAccessor.SetContinue(rsp.NextPageToken) + } + + listAccessor.SetResourceVersion(strconv.FormatInt(rsp.ResourceVersion, 10)) + + return nil +} + +// GuaranteedUpdate keeps calling 'tryUpdate()' to update key 'key' (of type 'destination') +// retrying the update until success if there is index conflict. +// Note that object passed to tryUpdate may change across invocations of tryUpdate() if +// other writers are simultaneously updating it, so tryUpdate() needs to take into account +// the current contents of the object when deciding how the update object should look. +// If the key doesn't exist, it will return NotFound storage error if ignoreNotFound=false +// else `destination` will be set to the zero value of it's type. +// If the eventual successful invocation of `tryUpdate` returns an output with the same serialized +// contents as the input, it won't perform any update, but instead set `destination` to an object with those +// contents. +// If 'cachedExistingObject' is non-nil, it can be used as a suggestion about the +// current version of the object to avoid read operation from storage to get it. +// However, the implementations have to retry in case suggestion is stale. +func (s *Storage) GuaranteedUpdate( + ctx context.Context, + key string, + destination runtime.Object, + ignoreNotFound bool, + preconditions *storage.Preconditions, + tryUpdate storage.UpdateFunc, + cachedExistingObject runtime.Object, +) error { + requestInfo, ok := request.RequestInfoFrom(ctx) + if !ok { + return apierrors.NewInternalError(fmt.Errorf("could not get request info")) + } + + k := &entityStore.Key{ + Group: requestInfo.APIGroup, + Resource: requestInfo.Resource, + Namespace: requestInfo.Namespace, + Name: requestInfo.Name, + Subresource: requestInfo.Subresource, + } + + getErr := s.Get(ctx, k.String(), storage.GetOptions{}, destination) + if getErr != nil { + if ignoreNotFound && apierrors.IsNotFound(getErr) { + // destination is already set to zero value + // we'll create the resource + } else { + return getErr + } + } + + accessor, err := meta.Accessor(destination) + if err != nil { + return err + } + previousVersion, _ := strconv.ParseInt(accessor.GetResourceVersion(), 10, 64) + if preconditions != nil && preconditions.ResourceVersion != nil { + previousVersion, _ = strconv.ParseInt(*preconditions.ResourceVersion, 10, 64) + } + + res := &storage.ResponseMeta{} + updatedObj, _, err := tryUpdate(destination, *res) + if err != nil { + var statusErr *apierrors.StatusError + if errors.As(err, &statusErr) { + // For now, forbidden may come from a mutation handler + if statusErr.ErrStatus.Reason == metav1.StatusReasonForbidden { + return statusErr + } + } + + return apierrors.NewInternalError(fmt.Errorf("could not successfully update object. key=%s, err=%s", k.String(), err.Error())) + } + + e, err := resourceToEntity(updatedObj, requestInfo, s.codec) + if err != nil { + return err + } + + // if we have a non-nil getErr, then we've ignored a not found error + if getErr != nil { + // object does not exist, create it + req := &entityStore.CreateEntityRequest{ + Entity: e, + } + + rsp, err := s.store.Create(ctx, req) + if err != nil { + return err + } + + err = entityToResource(rsp.Entity, destination, s.codec) + if err != nil { + return apierrors.NewInternalError(err) + } + + return nil + } + + // update the existing object + req := &entityStore.UpdateEntityRequest{ + Entity: e, + PreviousVersion: previousVersion, + } + + rsp, err := s.store.Update(ctx, req) + if err != nil { + return err // continue??? + } + + if rsp.Status == entityStore.UpdateEntityResponse_UNCHANGED { + return nil // destination is already set + } + + err = entityToResource(rsp.Entity, destination, s.codec) + if err != nil { + return apierrors.NewInternalError(err) + } + + return nil +} + +// Count returns number of different entries under the key (generally being path prefix). +func (s *Storage) Count(key string) (int64, error) { + return 0, nil +} + +func (s *Storage) Versioner() storage.Versioner { + return &storage.APIObjectVersioner{} +} + +func (s *Storage) RequestWatchProgress(ctx context.Context) error { + return nil +} + +type Decoder struct { + client entityStore.EntityStore_WatchClient + newFunc func() runtime.Object + opts storage.ListOptions + codec runtime.Codec +} + +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) { + return watch.Error, nil, err + } + + if grpcStatus.Code(err) == grpcCodes.Canceled { + return watch.Error, nil, err + } + + if err != nil { + 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() + + 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 + } + + err = entityToResource(resp.Entity, obj, d.codec) + if err != nil { + klog.Errorf("error decoding entity: %s", err) + return watch.Error, nil, err + } + + 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 + } + + return watchAction, obj, nil + } +} + +func (d *Decoder) Close() { + err := d.client.CloseSend() + if err != nil { + klog.Errorf("error closing watch stream: %s", err) + } +} + +var _ watch.Decoder = (*Decoder)(nil) diff --git a/pkg/storage/unified/apistore/test/requestinfo.go b/pkg/storage/unified/apistore/test/requestinfo.go new file mode 100644 index 00000000000..9b19aa7f906 --- /dev/null +++ b/pkg/storage/unified/apistore/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/storage/unified/apistore/test/watch_test.go b/pkg/storage/unified/apistore/test/watch_test.go new file mode 100644 index 00000000000..83ef2a03dce --- /dev/null +++ b/pkg/storage/unified/apistore/test/watch_test.go @@ -0,0 +1,366 @@ +// 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/grafana/grafana/pkg/infra/tracing" + "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, nil) + require.NoError(t, err) + + err = eDB.Init() + require.NoError(t, err) + + traceConfig, err := tracing.ParseTracingConfig(cfg) + require.NoError(t, err) + tracer, err := tracing.ProvideService(traceConfig) + require.NoError(t, err) + store, err := sqlstash.ProvideSQLEntityServer(eDB, tracer) + 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") + } + t.Skip("In maintenance") + + 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") + } + t.Skip("In maintenance") + + 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") + } + t.Skip("In maintenance") + + 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") + } + t.Skip("In maintenance") + + 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") + } + t.Skip("In maintenance") + + 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") + } + t.Skip("In maintenance") + + 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") + } + t.Skip("In maintenance") + + 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") + } + t.Skip("In maintenance") + + 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") + } + t.Skip("In maintenance") + + 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") + } + t.Skip("In maintenance") + + 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/storage/unified/apistore/utils.go b/pkg/storage/unified/apistore/utils.go new file mode 100644 index 00000000000..1e9f1ff49f5 --- /dev/null +++ b/pkg/storage/unified/apistore/utils.go @@ -0,0 +1,182 @@ +package apistore + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + "strconv" + "time" + + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apimachinery/pkg/types" + "k8s.io/apiserver/pkg/endpoints/request" + + "github.com/grafana/grafana/pkg/apimachinery/utils" + entityStore "github.com/grafana/grafana/pkg/services/store/entity" +) + +func entityToResource(rsp *entityStore.Entity, res runtime.Object, codec runtime.Codec) error { + var err error + + // Read the body first -- it includes old resourceVersion! + if len(rsp.Body) > 0 { + decoded, _, err := codec.Decode(rsp.Body, &schema.GroupVersionKind{Group: rsp.Group, Version: rsp.GroupVersion}, res) + if err != nil { + return err + } + res = decoded + } + + metaAccessor, err := meta.Accessor(res) + if err != nil { + return err + } + + if len(rsp.Meta) > 0 { + err = json.Unmarshal(rsp.Meta, res) + if err != nil { + return err + } + } + + metaAccessor.SetName(rsp.Name) + metaAccessor.SetNamespace(rsp.Namespace) + metaAccessor.SetUID(types.UID(rsp.Guid)) + metaAccessor.SetResourceVersion(fmt.Sprintf("%d", rsp.ResourceVersion)) + metaAccessor.SetCreationTimestamp(metav1.Unix(rsp.CreatedAt/1000, rsp.CreatedAt%1000*1000000)) + + grafanaAccessor, err := utils.MetaAccessor(metaAccessor) + if err != nil { + return err + } + + if rsp.Folder != "" { + grafanaAccessor.SetFolder(rsp.Folder) + } + if rsp.CreatedBy != "" { + grafanaAccessor.SetCreatedBy(rsp.CreatedBy) + } + if rsp.UpdatedBy != "" { + grafanaAccessor.SetUpdatedBy(rsp.UpdatedBy) + } + if rsp.UpdatedAt != 0 { + updatedAt := time.UnixMilli(rsp.UpdatedAt).UTC() + grafanaAccessor.SetUpdatedTimestamp(&updatedAt) + } + grafanaAccessor.SetSlug(rsp.Slug) + + if rsp.Origin != nil { + originTime := time.UnixMilli(rsp.Origin.Time).UTC() + grafanaAccessor.SetOriginInfo(&utils.ResourceOriginInfo{ + Name: rsp.Origin.Source, + Key: rsp.Origin.Key, + // Path: rsp.Origin.Path, + Timestamp: &originTime, + }) + } + + if len(rsp.Labels) > 0 { + metaAccessor.SetLabels(rsp.Labels) + } + + // TODO fields? + + if len(rsp.Status) > 0 { + status := reflect.ValueOf(res).Elem().FieldByName("Status") + if status != (reflect.Value{}) && status.CanSet() { + err = json.Unmarshal(rsp.Status, status.Addr().Interface()) + if err != nil { + return err + } + } + } + + return nil +} + +func resourceToEntity(res runtime.Object, requestInfo *request.RequestInfo, codec runtime.Codec) (*entityStore.Entity, error) { + metaAccessor, err := meta.Accessor(res) + if err != nil { + return nil, err + } + + grafanaAccessor, err := utils.MetaAccessor(metaAccessor) + if err != nil { + return nil, err + } + rv, _ := strconv.ParseInt(metaAccessor.GetResourceVersion(), 10, 64) + + k := &entityStore.Key{ + Group: requestInfo.APIGroup, + Resource: requestInfo.Resource, + Namespace: requestInfo.Namespace, + Name: metaAccessor.GetName(), + Subresource: requestInfo.Subresource, + } + + rsp := &entityStore.Entity{ + Group: k.Group, + GroupVersion: requestInfo.APIVersion, + Resource: k.Resource, + Subresource: k.Subresource, + Namespace: k.Namespace, + Key: k.String(), + Name: k.Name, + Guid: string(metaAccessor.GetUID()), + ResourceVersion: rv, + Folder: grafanaAccessor.GetFolder(), + CreatedAt: metaAccessor.GetCreationTimestamp().Time.UnixMilli(), + CreatedBy: grafanaAccessor.GetCreatedBy(), + UpdatedBy: grafanaAccessor.GetUpdatedBy(), + Slug: grafanaAccessor.GetSlug(), + Title: grafanaAccessor.FindTitle(metaAccessor.GetName()), + Origin: &entityStore.EntityOriginInfo{ + Source: grafanaAccessor.GetOriginName(), + Key: grafanaAccessor.GetOriginKey(), + // Path: grafanaAccessor.GetOriginPath(), + }, + Labels: metaAccessor.GetLabels(), + } + + t, err := grafanaAccessor.GetUpdatedTimestamp() + if err != nil { + return nil, err + } + if t != nil { + rsp.UpdatedAt = t.UnixMilli() + } + + t, err = grafanaAccessor.GetOriginTimestamp() + if err != nil { + return nil, err + } + if t != nil { + rsp.Origin.Time = t.UnixMilli() + } + + rsp.Meta, err = json.Marshal(meta.AsPartialObjectMetadata(metaAccessor)) + if err != nil { + return nil, err + } + + var buf bytes.Buffer + err = codec.Encode(res, &buf) + if err != nil { + return nil, err + } + rsp.Body = buf.Bytes() + + status := reflect.ValueOf(res).Elem().FieldByName("Status") + if status != (reflect.Value{}) { + rsp.Status, err = json.Marshal(status.Interface()) + if err != nil { + return nil, err + } + } + + return rsp, nil +} diff --git a/pkg/storage/unified/apistore/utils_test.go b/pkg/storage/unified/apistore/utils_test.go new file mode 100644 index 00000000000..b4bb8e14617 --- /dev/null +++ b/pkg/storage/unified/apistore/utils_test.go @@ -0,0 +1,216 @@ +package apistore + +import ( + "fmt" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apiserver/pkg/endpoints/request" + + "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1" + entityStore "github.com/grafana/grafana/pkg/services/store/entity" +) + +func TestResourceToEntity(t *testing.T) { + createdAt := metav1.Now() + createdAtStr := createdAt.UTC().Format(time.RFC3339) + + // truncate here because RFC3339 doesn't support millisecond precision + // consider updating accessor to use RFC3339Nano to encode timestamps + updatedAt := createdAt.Add(time.Hour).Truncate(time.Second) + updatedAtStr := updatedAt.UTC().Format(time.RFC3339) + + Scheme := runtime.NewScheme() + Scheme.AddKnownTypes(v0alpha1.PlaylistResourceInfo.GroupVersion(), &v0alpha1.Playlist{}) + Codecs := serializer.NewCodecFactory(Scheme) + + testCases := []struct { + requestInfo *request.RequestInfo + resource runtime.Object + codec runtime.Codec + expectedKey string + expectedGroupVersion string + expectedName string + expectedNamespace string + expectedTitle string + expectedGuid string + expectedVersion string + expectedFolder string + expectedCreatedAt int64 + expectedUpdatedAt int64 + expectedCreatedBy string + expectedUpdatedBy string + expectedSlug string + expectedOrigin *entityStore.EntityOriginInfo + expectedLabels map[string]string + expectedMeta []byte + expectedBody []byte + }{ + { + requestInfo: &request.RequestInfo{ + APIGroup: "playlist.grafana.app", + APIVersion: "v0alpha1", + Resource: "playlists", + Namespace: "default", + Name: "test-name", + }, + resource: &v0alpha1.Playlist{ + ObjectMeta: metav1.ObjectMeta{ + CreationTimestamp: createdAt, + Labels: map[string]string{"label1": "value1", "label2": "value2"}, + Name: "test-name", + ResourceVersion: "1", + UID: "test-uid", + Annotations: map[string]string{ + "grafana.app/createdBy": "test-created-by", + "grafana.app/updatedBy": "test-updated-by", + "grafana.app/updatedTimestamp": updatedAtStr, + "grafana.app/folder": "test-folder", + "grafana.app/slug": "test-slug", + }, + }, + Spec: v0alpha1.Spec{ + Title: "A playlist", + Interval: "5m", + Items: []v0alpha1.Item{ + {Type: v0alpha1.ItemTypeDashboardByTag, Value: "panel-tests"}, + {Type: v0alpha1.ItemTypeDashboardByUid, Value: "vmie2cmWz"}, + }, + }, + }, + expectedKey: "/playlist.grafana.app/playlists/namespaces/default/test-name", + expectedGroupVersion: "v0alpha1", + expectedName: "test-name", + expectedNamespace: "default", + expectedTitle: "A playlist", + expectedGuid: "test-uid", + expectedVersion: "1", + expectedFolder: "test-folder", + expectedCreatedAt: createdAt.UnixMilli(), + expectedUpdatedAt: updatedAt.UnixMilli(), + expectedCreatedBy: "test-created-by", + expectedUpdatedBy: "test-updated-by", + expectedSlug: "test-slug", + expectedOrigin: &entityStore.EntityOriginInfo{Source: "", Key: ""}, + expectedLabels: map[string]string{"label1": "value1", "label2": "value2"}, + expectedMeta: []byte(fmt.Sprintf(`{"metadata":{"name":"test-name","uid":"test-uid","resourceVersion":"1","creationTimestamp":%q,"labels":{"label1":"value1","label2":"value2"},"annotations":{"grafana.app/createdBy":"test-created-by","grafana.app/folder":"test-folder","grafana.app/slug":"test-slug","grafana.app/updatedBy":"test-updated-by","grafana.app/updatedTimestamp":%q}}}`, createdAtStr, updatedAtStr)), + expectedBody: []byte(fmt.Sprintf(`{"kind":"Playlist","apiVersion":"playlist.grafana.app/v0alpha1","metadata":{"name":"test-name","uid":"test-uid","resourceVersion":"1","creationTimestamp":%q,"labels":{"label1":"value1","label2":"value2"},"annotations":{"grafana.app/createdBy":"test-created-by","grafana.app/folder":"test-folder","grafana.app/slug":"test-slug","grafana.app/updatedBy":"test-updated-by","grafana.app/updatedTimestamp":%q}},"spec":{"title":"A playlist","interval":"5m","items":[{"type":"dashboard_by_tag","value":"panel-tests"},{"type":"dashboard_by_uid","value":"vmie2cmWz"}]}}`, createdAtStr, updatedAtStr)), + }, + } + + for _, tc := range testCases { + t.Run(tc.resource.GetObjectKind().GroupVersionKind().Kind+" to entity conversion should succeed", func(t *testing.T) { + entity, err := resourceToEntity(tc.resource, tc.requestInfo, Codecs.LegacyCodec(v0alpha1.PlaylistResourceInfo.GroupVersion())) + require.NoError(t, err) + assert.Equal(t, tc.expectedKey, entity.Key) + assert.Equal(t, tc.expectedName, entity.Name) + assert.Equal(t, tc.expectedNamespace, entity.Namespace) + assert.Equal(t, tc.expectedTitle, entity.Title) + assert.Equal(t, tc.expectedGroupVersion, entity.GroupVersion) + assert.Equal(t, tc.expectedName, entity.Name) + assert.Equal(t, tc.expectedGuid, entity.Guid) + assert.Equal(t, tc.expectedFolder, entity.Folder) + assert.Equal(t, tc.expectedCreatedAt, entity.CreatedAt) + assert.Equal(t, tc.expectedUpdatedAt, entity.UpdatedAt) + assert.Equal(t, tc.expectedCreatedBy, entity.CreatedBy) + assert.Equal(t, tc.expectedUpdatedBy, entity.UpdatedBy) + assert.Equal(t, tc.expectedSlug, entity.Slug) + assert.Equal(t, tc.expectedOrigin, entity.Origin) + assert.Equal(t, tc.expectedLabels, entity.Labels) + assert.Equal(t, tc.expectedMeta, entity.Meta) + assert.Equal(t, tc.expectedBody, entity.Body[:len(entity.Body)-1]) // remove trailing newline + }) + } +} + +func TestEntityToResource(t *testing.T) { + createdAt := metav1.Now() + createdAtStr := createdAt.UTC().Format(time.RFC3339) + + updatedAt := createdAt.Add(time.Hour) + updatedAtStr := updatedAt.UTC().Format(time.RFC3339) + + Scheme := runtime.NewScheme() + Scheme.AddKnownTypes(v0alpha1.PlaylistResourceInfo.GroupVersion(), &v0alpha1.Playlist{}) + Codecs := serializer.NewCodecFactory(Scheme) + + testCases := []struct { + entity *entityStore.Entity + codec runtime.Codec + expectedApiVersion string + expectedCreationTimestamp metav1.Time + expectedLabels map[string]string + expectedName string + expectedResourceVersion string + expectedUid string + expectedTitle string + expectedAnnotations map[string]string + expectedSpec any + }{ + { + entity: &entityStore.Entity{ + Key: "/playlist.grafana.app/playlists/namespaces/default/test-uid", + GroupVersion: "v0alpha1", + Name: "test-uid", + Title: "A playlist", + Guid: "test-guid", + Folder: "test-folder", + CreatedBy: "test-created-by", + CreatedAt: createdAt.UnixMilli(), + UpdatedAt: updatedAt.UnixMilli(), + UpdatedBy: "test-updated-by", + Slug: "test-slug", + Origin: &entityStore.EntityOriginInfo{}, + Labels: map[string]string{"label1": "value1", "label2": "value2"}, + Meta: []byte(fmt.Sprintf(`{"metadata":{"name":"test-name","uid":"test-uid","resourceVersion":"1","creationTimestamp":%q,"labels":{"label1":"value1","label2":"value2"},"annotations":{"grafana.app/createdBy":"test-created-by","grafana.app/folder":"test-folder","grafana.app/slug":"test-slug","grafana.app/updatedTimestamp":%q,"grafana.app/updatedBy":"test-updated-by"}}}`, createdAtStr, updatedAtStr)), + Body: []byte(fmt.Sprintf(`{"kind":"Playlist","apiVersion":"playlist.grafana.app/v0alpha1","metadata":{"name":"test-name","uid":"test-uid","resourceVersion":"1","creationTimestamp":%q,"labels":{"label1":"value1","label2":"value2"},"annotations":{"grafana.app/createdBy":"test-created-by","grafana.app/folder":"test-folder","grafana.app/slug":"test-slug","grafana.app/updatedBy":"test-updated-by","grafana.app/updatedTimestamp":%q}},"spec":{"title":"A playlist","interval":"5m","items":[{"type":"dashboard_by_tag","value":"panel-tests"},{"type":"dashboard_by_uid","value":"vmie2cmWz"}]}}`, createdAtStr, updatedAtStr)), + ResourceVersion: 1, + Action: entityStore.Entity_CREATED, + }, + codec: runtime.Codec(nil), + expectedApiVersion: "playlist.grafana.app/v0alpha1", + expectedCreationTimestamp: createdAt, + expectedLabels: map[string]string{"label1": "value1", "label2": "value2"}, + expectedName: "test-uid", + expectedTitle: "test-name", + expectedResourceVersion: "1", + expectedUid: "test-guid", + expectedAnnotations: map[string]string{ + "grafana.app/createdBy": "test-created-by", + "grafana.app/folder": "test-folder", + "grafana.app/slug": "test-slug", + "grafana.app/updatedBy": "test-updated-by", + "grafana.app/updatedTimestamp": updatedAtStr, + }, + expectedSpec: v0alpha1.Spec{ + Title: "A playlist", + Interval: "5m", + Items: []v0alpha1.Item{ + {Type: v0alpha1.ItemTypeDashboardByTag, Value: "panel-tests"}, + {Type: v0alpha1.ItemTypeDashboardByUid, Value: "vmie2cmWz"}, + }, + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.entity.Key+" to resource conversion should succeed", func(t *testing.T) { + var p v0alpha1.Playlist + err := entityToResource(tc.entity, &p, Codecs.LegacyCodec(v0alpha1.PlaylistResourceInfo.GroupVersion())) + require.NoError(t, err) + assert.Equal(t, tc.expectedApiVersion, p.TypeMeta.APIVersion) + assert.Equal(t, tc.expectedCreationTimestamp.Unix(), p.ObjectMeta.CreationTimestamp.Unix()) + assert.Equal(t, tc.expectedLabels, p.ObjectMeta.Labels) + assert.Equal(t, tc.expectedName, p.ObjectMeta.Name) + assert.Equal(t, tc.expectedResourceVersion, p.ObjectMeta.ResourceVersion) + assert.Equal(t, tc.expectedUid, string(p.ObjectMeta.UID)) + assert.Equal(t, tc.expectedAnnotations, p.ObjectMeta.Annotations) + assert.Equal(t, tc.expectedSpec, p.Spec) + }) + } +} diff --git a/pkg/storage/unified/buf.gen.yaml b/pkg/storage/unified/resource/buf.gen.yaml similarity index 69% rename from pkg/storage/unified/buf.gen.yaml rename to pkg/storage/unified/resource/buf.gen.yaml index d8ed501cf57..f0d8393e9ac 100644 --- a/pkg/storage/unified/buf.gen.yaml +++ b/pkg/storage/unified/resource/buf.gen.yaml @@ -1,10 +1,10 @@ version: v1 plugins: - plugin: go - out: pkg/storage/unified + out: pkg/storage/unified/resource opt: paths=source_relative - plugin: go-grpc - out: pkg/storage/unified + out: pkg/storage/unified/resource opt: - paths=source_relative - require_unimplemented_servers=false diff --git a/pkg/storage/unified/buf.yaml b/pkg/storage/unified/resource/buf.yaml similarity index 100% rename from pkg/storage/unified/buf.yaml rename to pkg/storage/unified/resource/buf.yaml diff --git a/pkg/storage/unified/client_wrapper.go b/pkg/storage/unified/resource/client_wrapper.go similarity index 98% rename from pkg/storage/unified/client_wrapper.go rename to pkg/storage/unified/resource/client_wrapper.go index 06a4e2caacb..7eef6c144f7 100644 --- a/pkg/storage/unified/client_wrapper.go +++ b/pkg/storage/unified/resource/client_wrapper.go @@ -1,4 +1,4 @@ -package unified +package resource import ( "github.com/fullstorydev/grpchan" diff --git a/pkg/storage/unified/deleted.go b/pkg/storage/unified/resource/deleted.go similarity index 98% rename from pkg/storage/unified/deleted.go rename to pkg/storage/unified/resource/deleted.go index e1fc78ae664..8a943060335 100644 --- a/pkg/storage/unified/deleted.go +++ b/pkg/storage/unified/resource/deleted.go @@ -1,4 +1,4 @@ -package unified +package resource import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" diff --git a/pkg/storage/unified/event.go b/pkg/storage/unified/resource/event.go similarity index 98% rename from pkg/storage/unified/event.go rename to pkg/storage/unified/resource/event.go index 356153805b1..793196fcd56 100644 --- a/pkg/storage/unified/event.go +++ b/pkg/storage/unified/resource/event.go @@ -1,4 +1,4 @@ -package unified +package resource import ( "encoding/json" diff --git a/pkg/storage/unified/go.mod b/pkg/storage/unified/resource/go.mod similarity index 96% rename from pkg/storage/unified/go.mod rename to pkg/storage/unified/resource/go.mod index fa54af5209d..8aef4fffeed 100644 --- a/pkg/storage/unified/go.mod +++ b/pkg/storage/unified/resource/go.mod @@ -1,4 +1,4 @@ -module github.com/grafana/grafana/pkg/storage/unified +module github.com/grafana/grafana/pkg/storage/unified/resource go 1.21.10 diff --git a/pkg/storage/unified/go.sum b/pkg/storage/unified/resource/go.sum similarity index 100% rename from pkg/storage/unified/go.sum rename to pkg/storage/unified/resource/go.sum diff --git a/pkg/storage/unified/resource.go b/pkg/storage/unified/resource/resource.go similarity index 98% rename from pkg/storage/unified/resource.go rename to pkg/storage/unified/resource/resource.go index 8a2c07949b4..a30a4ad27a2 100644 --- a/pkg/storage/unified/resource.go +++ b/pkg/storage/unified/resource/resource.go @@ -1,4 +1,4 @@ -package unified +package resource import ( "bytes" diff --git a/pkg/storage/unified/resource.pb.go b/pkg/storage/unified/resource/resource.pb.go similarity index 68% rename from pkg/storage/unified/resource.pb.go rename to pkg/storage/unified/resource/resource.pb.go index 9d4d1c93580..b62e26b7819 100644 --- a/pkg/storage/unified/resource.pb.go +++ b/pkg/storage/unified/resource/resource.pb.go @@ -4,7 +4,7 @@ // protoc (unknown) // source: resource.proto -package unified +package resource import ( protoreflect "google.golang.org/protobuf/reflect/protoreflect" @@ -322,7 +322,7 @@ type ResourceWrapper struct { // Full kubernetes json bytes (although the resource version may not be accurate) Value []byte `protobuf:"bytes,2,opt,name=value,proto3" json:"value,omitempty"` // Operation - Operation ResourceOperation `protobuf:"varint,3,opt,name=operation,proto3,enum=unified.ResourceOperation" json:"operation,omitempty"` + Operation ResourceOperation `protobuf:"varint,3,opt,name=operation,proto3,enum=resource.ResourceOperation" json:"operation,omitempty"` // The resource has an attached blob HasBlob bool `protobuf:"varint,4,opt,name=has_blob,json=hasBlob,proto3" json:"has_blob,omitempty"` } @@ -396,7 +396,7 @@ type ResourceMeta struct { // The resource version ResourceVersion int64 `protobuf:"varint,1,opt,name=resource_version,json=resourceVersion,proto3" json:"resource_version,omitempty"` // The optional commit message - Operation ResourceOperation `protobuf:"varint,2,opt,name=operation,proto3,enum=unified.ResourceOperation" json:"operation,omitempty"` + Operation ResourceOperation `protobuf:"varint,2,opt,name=operation,proto3,enum=resource.ResourceOperation" json:"operation,omitempty"` // Size of the full resource body Size int32 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"` // Hash for the resource @@ -1407,7 +1407,7 @@ type Sort struct { unknownFields protoimpl.UnknownFields Field string `protobuf:"bytes,1,opt,name=field,proto3" json:"field,omitempty"` - Order Sort_Order `protobuf:"varint,2,opt,name=order,proto3,enum=unified.Sort_Order" json:"order,omitempty"` + Order Sort_Order `protobuf:"varint,2,opt,name=order,proto3,enum=resource.Sort_Order" json:"order,omitempty"` } func (x *Sort) Reset() { @@ -2274,7 +2274,7 @@ type HealthCheckResponse struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - Status HealthCheckResponse_ServingStatus `protobuf:"varint,1,opt,name=status,proto3,enum=unified.HealthCheckResponse_ServingStatus" json:"status,omitempty"` + Status HealthCheckResponse_ServingStatus `protobuf:"varint,1,opt,name=status,proto3,enum=resource.HealthCheckResponse_ServingStatus" json:"status,omitempty"` } func (x *HealthCheckResponse) Reset() { @@ -2320,249 +2320,251 @@ var File_resource_proto protoreflect.FileDescriptor var file_resource_proto_rawDesc = []byte{ 0x0a, 0x0e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x12, 0x07, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x22, 0x9c, 0x01, 0x0a, 0x0b, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, 0x6d, - 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 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, 0x01, 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, 0x12, 0x29, 0x0a, + 0x12, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x22, 0x9c, 0x01, 0x0a, 0x0b, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x12, 0x1c, 0x0a, 0x09, 0x6e, 0x61, + 0x6d, 0x65, 0x73, 0x70, 0x61, 0x63, 0x65, 0x18, 0x02, 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, 0x01, 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, 0x12, 0x29, + 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, + 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xa8, 0x01, 0x0a, 0x0f, 0x52, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, - 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, - 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0xa7, 0x01, 0x0a, 0x0f, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x12, 0x29, 0x0a, 0x10, - 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x38, 0x0a, - 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0e, - 0x32, 0x1a, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x6f, 0x70, - 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x68, 0x61, 0x73, 0x5f, 0x62, - 0x6c, 0x6f, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x61, 0x73, 0x42, 0x6c, - 0x6f, 0x62, 0x22, 0xf1, 0x01, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, - 0x65, 0x74, 0x61, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, - 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x38, - 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x0e, 0x32, 0x1a, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, 0x6f, - 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x12, 0x0a, 0x04, - 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, - 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x05, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x0a, 0x0b, 0x6f, 0x62, - 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, 0x28, 0x0c, 0x52, - 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x19, 0x0a, 0x08, 0x68, - 0x61, 0x73, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, - 0x61, 0x73, 0x42, 0x6c, 0x6f, 0x62, 0x22, 0x69, 0x0a, 0x08, 0x42, 0x6c, 0x6f, 0x62, 0x49, 0x6e, - 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x21, 0x0a, 0x0c, 0x63, 0x6f, - 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x12, 0x0a, - 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, - 0x68, 0x22, 0x6c, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x75, 0x6c, - 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 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, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x63, - 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x22, - 0x7c, 0x0a, 0x08, 0x4c, 0x69, 0x6e, 0x6b, 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x21, 0x0a, 0x0c, 0x63, - 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, - 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, - 0x61, 0x6c, 0x75, 0x65, 0x22, 0x37, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x0b, - 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, 0x0a, 0x06, 0x55, - 0x50, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x4b, 0x45, 0x45, 0x50, 0x10, - 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x45, 0x4d, 0x4f, 0x56, 0x45, 0x10, 0x03, 0x22, 0x8e, 0x01, - 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, - 0x26, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x75, - 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, - 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, - 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a, - 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, - 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x25, 0x0a, 0x04, 0x62, 0x6c, 0x6f, 0x62, 0x18, - 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, - 0x4c, 0x69, 0x6e, 0x6b, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x04, 0x62, 0x6c, 0x6f, 0x62, 0x22, 0x6a, - 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x2d, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x15, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 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, 0x22, 0x8e, 0x01, 0x0a, 0x0d, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x75, 0x6e, 0x69, 0x66, - 0x69, 0x65, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, - 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, - 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, - 0x73, 0x61, 0x67, 0x65, 0x12, 0x25, 0x0a, 0x04, 0x62, 0x6c, 0x6f, 0x62, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x4c, 0x69, 0x6e, - 0x6b, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x04, 0x62, 0x6c, 0x6f, 0x62, 0x22, 0x6a, 0x0a, 0x0e, 0x55, - 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, - 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, - 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 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, 0x22, 0x49, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, - 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, - 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, - 0x69, 0x64, 0x22, 0x6a, 0x0a, 0x0e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, - 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x53, - 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x73, 0x74, 0x61, - 0x74, 0x75, 0x73, 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, 0x22, 0x5d, - 0x0a, 0x12, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x14, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1f, 0x0a, 0x0b, - 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x18, 0x02, 0x20, 0x01, 0x28, - 0x08, 0x52, 0x0a, 0x69, 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x22, 0xa0, 0x01, - 0x0a, 0x13, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, - 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x73, 0x74, - 0x61, 0x74, 0x75, 0x73, 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, - 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, - 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x75, 0x72, - 0x6c, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x6c, 0x6f, 0x62, 0x55, 0x72, 0x6c, - 0x22, 0x38, 0x0a, 0x0e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x12, 0x26, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, - 0x14, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x7d, 0x0a, 0x0f, 0x47, 0x65, - 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, - 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, - 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x25, 0x0a, 0x04, - 0x69, 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x11, 0x2e, 0x75, 0x6e, 0x69, - 0x66, 0x69, 0x65, 0x64, 0x2e, 0x42, 0x6c, 0x6f, 0x62, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, + 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x39, + 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, + 0x0e, 0x32, 0x1b, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x09, + 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x19, 0x0a, 0x08, 0x68, 0x61, 0x73, + 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, 0x52, 0x07, 0x68, 0x61, 0x73, + 0x42, 0x6c, 0x6f, 0x62, 0x22, 0xf2, 0x01, 0x0a, 0x0c, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x29, 0x0a, 0x10, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, + 0x0f, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, + 0x12, 0x39, 0x0a, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0e, 0x32, 0x1b, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, + 0x52, 0x09, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x73, + 0x69, 0x7a, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, + 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, + 0x61, 0x73, 0x68, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x05, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x1f, 0x0a, + 0x0b, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x6d, 0x65, 0x74, 0x61, 0x18, 0x06, 0x20, 0x01, + 0x28, 0x0c, 0x52, 0x0a, 0x6f, 0x62, 0x6a, 0x65, 0x63, 0x74, 0x4d, 0x65, 0x74, 0x61, 0x12, 0x19, + 0x0a, 0x08, 0x68, 0x61, 0x73, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x18, 0x07, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x07, 0x68, 0x61, 0x73, 0x42, 0x6c, 0x6f, 0x62, 0x22, 0x69, 0x0a, 0x08, 0x42, 0x6c, 0x6f, + 0x62, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, 0x7a, + 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x12, 0x21, 0x0a, + 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, 0x65, + 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, 0x68, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, + 0x68, 0x61, 0x73, 0x68, 0x22, 0x6c, 0x0a, 0x0c, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, + 0x73, 0x75, 0x6c, 0x74, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 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, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, + 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x12, + 0x0a, 0x04, 0x63, 0x6f, 0x64, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x05, 0x52, 0x04, 0x63, 0x6f, + 0x64, 0x65, 0x22, 0x7c, 0x0a, 0x08, 0x4c, 0x69, 0x6e, 0x6b, 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x21, + 0x0a, 0x0c, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x63, 0x6f, 0x6e, 0x74, 0x65, 0x6e, 0x74, 0x54, 0x79, 0x70, + 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, + 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x37, 0x0a, 0x06, 0x41, 0x63, 0x74, 0x69, 0x6f, + 0x6e, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0a, + 0x0a, 0x06, 0x55, 0x50, 0x4c, 0x4f, 0x41, 0x44, 0x10, 0x01, 0x12, 0x08, 0x0a, 0x04, 0x4b, 0x45, + 0x45, 0x50, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x52, 0x45, 0x4d, 0x4f, 0x56, 0x45, 0x10, 0x03, + 0x22, 0x90, 0x01, 0x0a, 0x0d, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x26, 0x0a, 0x04, 0x62, + 0x6c, 0x6f, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x04, 0x62, + 0x6c, 0x6f, 0x62, 0x22, 0x6b, 0x0a, 0x0e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 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, + 0x22, 0x90, 0x01, 0x0a, 0x0d, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, + 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, + 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x12, 0x26, 0x0a, 0x04, 0x62, + 0x6c, 0x6f, 0x62, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x6e, 0x6b, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x04, 0x62, + 0x6c, 0x6f, 0x62, 0x22, 0x6b, 0x0a, 0x0e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 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, + 0x22, 0x4a, 0x0a, 0x0d, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x69, + 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x69, 0x64, 0x22, 0x6b, 0x0a, 0x0e, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, + 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, + 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 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, 0x22, 0x5e, 0x0a, 0x12, 0x47, 0x65, 0x74, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1f, 0x0a, 0x0b, 0x69, 0x67, 0x6e, 0x6f, + 0x72, 0x65, 0x5f, 0x62, 0x6c, 0x6f, 0x62, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0a, 0x69, + 0x67, 0x6e, 0x6f, 0x72, 0x65, 0x42, 0x6c, 0x6f, 0x62, 0x22, 0xa1, 0x01, 0x0a, 0x13, 0x47, 0x65, + 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x2e, 0x0a, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, + 0x73, 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, 0x14, 0x0a, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, + 0x75, 0x65, 0x12, 0x19, 0x0a, 0x08, 0x62, 0x6c, 0x6f, 0x62, 0x5f, 0x75, 0x72, 0x6c, 0x18, 0x04, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x62, 0x6c, 0x6f, 0x62, 0x55, 0x72, 0x6c, 0x22, 0x39, 0x0a, + 0x0e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, + 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x22, 0x7f, 0x0a, 0x0f, 0x47, 0x65, 0x74, 0x42, + 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2e, 0x0a, 0x06, 0x73, + 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x16, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x65, 0x73, + 0x75, 0x6c, 0x74, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x26, 0x0a, 0x04, 0x69, + 0x6e, 0x66, 0x6f, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x12, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x42, 0x6c, 0x6f, 0x62, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x04, 0x69, 0x6e, 0x66, 0x6f, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x53, 0x0a, 0x0b, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6f, 0x70, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, - 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x63, + 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x06, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x73, 0x22, 0x64, 0x0a, 0x04, 0x53, 0x6f, 0x72, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x29, 0x0a, 0x05, - 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x13, 0x2e, 0x75, 0x6e, - 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x53, 0x6f, 0x72, 0x74, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, - 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x1a, 0x0a, 0x05, 0x4f, 0x72, 0x64, 0x65, 0x72, - 0x12, 0x07, 0x0a, 0x03, 0x41, 0x53, 0x43, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x45, 0x53, - 0x43, 0x10, 0x01, 0x22, 0xbf, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x26, 0x0a, 0x03, 0x6b, 0x65, 0x79, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, - 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x12, 0x2c, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, - 0x0b, 0x32, 0x14, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x52, 0x65, 0x71, 0x75, - 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, - 0x2c, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x14, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x52, 0x65, 0x71, 0x75, 0x69, 0x72, - 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 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, 0x22, 0x88, 0x01, 0x0a, 0x0b, 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, 0x2e, 0x0a, - 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, - 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x74, - 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x21, 0x0a, - 0x04, 0x73, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0d, 0x2e, 0x75, 0x6e, - 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x53, 0x6f, 0x72, 0x74, 0x52, 0x04, 0x73, 0x6f, 0x72, 0x74, - 0x22, 0xc3, 0x01, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x2e, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x18, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, - 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, 0x12, 0x30, 0x0a, 0x14, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, - 0x67, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, - 0x28, 0x03, 0x52, 0x12, 0x72, 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x74, 0x65, - 0x6d, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xe0, 0x01, 0x0a, 0x0c, 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, 0x26, 0x0a, - 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x75, 0x6e, 0x69, - 0x66, 0x69, 0x65, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, - 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2e, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, - 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, - 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 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, 0x04, 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, 0x05, - 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, 0x22, 0x99, 0x01, 0x0a, 0x0d, 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, 0x34, 0x0a, 0x08, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x75, 0x6e, - 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, 0x72, - 0x61, 0x70, 0x70, 0x65, 0x72, 0x52, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, - 0x34, 0x0a, 0x08, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, - 0x0b, 0x32, 0x18, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x6f, - 0x75, 0x72, 0x63, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x52, 0x08, 0x70, 0x72, 0x65, - 0x76, 0x69, 0x6f, 0x75, 0x73, 0x22, 0x99, 0x01, 0x0a, 0x0e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, - 0x79, 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, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x12, 0x2a, 0x0a, 0x05, + 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x14, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x6f, 0x72, 0x74, 0x2e, 0x4f, 0x72, 0x64, 0x65, + 0x72, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x1a, 0x0a, 0x05, 0x4f, 0x72, 0x64, 0x65, + 0x72, 0x12, 0x07, 0x0a, 0x03, 0x41, 0x53, 0x43, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x44, 0x45, + 0x53, 0x43, 0x10, 0x01, 0x22, 0xc2, 0x01, 0x0a, 0x0b, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, 0x74, + 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x03, 0x52, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x2d, 0x0a, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x04, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, + 0x65, 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, + 0x6c, 0x73, 0x12, 0x2d, 0x0a, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, 0x73, 0x18, 0x05, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, + 0x71, 0x75, 0x69, 0x72, 0x65, 0x6d, 0x65, 0x6e, 0x74, 0x52, 0x06, 0x66, 0x69, 0x65, 0x6c, 0x64, + 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, 0x22, 0x8a, 0x01, 0x0a, 0x0b, 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, 0x2f, 0x0a, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x02, 0x20, 0x01, + 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, + 0x73, 0x74, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, + 0x6e, 0x73, 0x12, 0x22, 0x0a, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x0e, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x53, 0x6f, 0x72, 0x74, + 0x52, 0x04, 0x73, 0x6f, 0x72, 0x74, 0x22, 0xc4, 0x01, 0x0a, 0x0c, 0x4c, 0x69, 0x73, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2f, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, + 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, + 0x72, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 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, 0x14, 0x0a, 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x05, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x26, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, - 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x21, - 0x0a, 0x0c, 0x73, 0x68, 0x6f, 0x77, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x04, - 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x73, 0x68, 0x6f, 0x77, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, - 0x64, 0x22, 0x91, 0x01, 0x0a, 0x0f, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2b, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, - 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x52, - 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x05, 0x69, 0x74, 0x65, - 0x6d, 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, 0x8d, 0x01, 0x0a, 0x0d, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, - 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, 0x26, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, - 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x52, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x16, 0x0a, + 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, 0x12, 0x30, 0x0a, 0x14, 0x72, + 0x65, 0x6d, 0x61, 0x69, 0x6e, 0x69, 0x6e, 0x67, 0x5f, 0x69, 0x74, 0x65, 0x6d, 0x5f, 0x63, 0x6f, + 0x75, 0x6e, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x12, 0x72, 0x65, 0x6d, 0x61, 0x69, + 0x6e, 0x69, 0x6e, 0x67, 0x49, 0x74, 0x65, 0x6d, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x22, 0xe2, 0x01, + 0x0a, 0x0c, 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, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x2f, 0x0a, + 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x4f, 0x70, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 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, 0x04, 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, 0x05, 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, 0x22, 0x9b, 0x01, 0x0a, 0x0d, 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, 0x35, 0x0a, 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, + 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x52, + 0x08, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x35, 0x0a, 0x08, 0x70, 0x72, 0x65, + 0x76, 0x69, 0x6f, 0x75, 0x73, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x57, + 0x72, 0x61, 0x70, 0x70, 0x65, 0x72, 0x52, 0x08, 0x70, 0x72, 0x65, 0x76, 0x69, 0x6f, 0x75, 0x73, + 0x22, 0x9a, 0x01, 0x0a, 0x0e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 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, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x68, + 0x6f, 0x77, 0x5f, 0x64, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x08, + 0x52, 0x0b, 0x73, 0x68, 0x6f, 0x77, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x64, 0x22, 0x92, 0x01, + 0x0a, 0x0f, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, + 0x65, 0x12, 0x2c, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 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, 0x8e, 0x01, 0x0a, 0x0d, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 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, 0x27, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, + 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x6f, + 0x72, 0x69, 0x67, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, 0x72, 0x69, + 0x67, 0x69, 0x6e, 0x22, 0xe5, 0x01, 0x0a, 0x12, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x27, 0x0a, 0x03, 0x6b, 0x65, + 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, 0x03, + 0x6b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, + 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, 0x0a, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x6f, - 0x72, 0x69, 0x67, 0x69, 0x6e, 0x22, 0xe4, 0x01, 0x0a, 0x12, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, - 0x63, 0x65, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x26, 0x0a, 0x03, - 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x14, 0x2e, 0x75, 0x6e, 0x69, 0x66, - 0x69, 0x65, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4b, 0x65, 0x79, 0x52, - 0x03, 0x6b, 0x65, 0x79, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x0c, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x69, 0x7a, 0x65, 0x12, 0x23, 0x0a, 0x0d, 0x72, 0x65, 0x73, - 0x6f, 0x75, 0x72, 0x63, 0x65, 0x5f, 0x68, 0x61, 0x73, 0x68, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, - 0x52, 0x0c, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x48, 0x61, 0x73, 0x68, 0x12, 0x16, - 0x0a, 0x06, 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x6f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, - 0x73, 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1c, - 0x0a, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, - 0x03, 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x96, 0x01, 0x0a, - 0x0e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, - 0x31, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, - 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, + 0x72, 0x69, 0x67, 0x69, 0x6e, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x61, 0x74, 0x68, 0x18, 0x05, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x70, 0x61, 0x74, 0x68, 0x12, 0x12, 0x0a, 0x04, 0x68, 0x61, 0x73, + 0x68, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x68, 0x61, 0x73, 0x68, 0x12, 0x1c, 0x0a, + 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, + 0x52, 0x09, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x22, 0x97, 0x01, 0x0a, 0x0e, + 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x32, + 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 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, @@ -2572,66 +2574,68 @@ var file_resource_proto_rawDesc = []byte{ 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x2e, 0x0a, 0x12, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x18, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x73, 0x65, - 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0xaa, 0x01, 0x0a, 0x13, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, - 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x42, 0x0a, - 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2a, 0x2e, - 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, - 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, 0x76, - 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, - 0x73, 0x22, 0x4f, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, - 0x0b, 0x0a, 0x07, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, - 0x4e, 0x4f, 0x54, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x13, 0x0a, - 0x0f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, - 0x10, 0x03, 0x2a, 0x55, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, 0x70, - 0x65, 0x72, 0x61, 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, 0x0c, 0x0a, 0x08, 0x42, - 0x4f, 0x4f, 0x4b, 0x4d, 0x41, 0x52, 0x4b, 0x10, 0x04, 0x32, 0xf8, 0x04, 0x0a, 0x0d, 0x52, 0x65, - 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x48, 0x0a, 0x0b, 0x47, - 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1b, 0x2e, 0x75, 0x6e, 0x69, - 0x66, 0x69, 0x65, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, - 0x64, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x65, 0x73, - 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x12, - 0x16, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, - 0x64, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x12, 0x39, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x75, 0x6e, 0x69, - 0x66, 0x69, 0x65, 0x64, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, - 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x39, 0x0a, 0x06, 0x44, - 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x16, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, - 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, - 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, - 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x14, - 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x4c, - 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x38, 0x0a, 0x05, 0x57, - 0x61, 0x74, 0x63, 0x68, 0x12, 0x15, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x57, - 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x75, 0x6e, - 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x30, 0x01, 0x12, 0x3c, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, - 0x12, 0x17, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, - 0x6f, 0x62, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x75, 0x6e, 0x69, 0x66, - 0x69, 0x65, 0x64, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, - 0x6e, 0x73, 0x65, 0x12, 0x3c, 0x0a, 0x07, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x17, - 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, - 0x64, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, - 0x65, 0x12, 0x39, 0x0a, 0x06, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x12, 0x16, 0x2e, 0x75, 0x6e, - 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x71, 0x75, - 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2e, 0x4f, 0x72, - 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x46, 0x0a, 0x09, - 0x49, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x1b, 0x2e, 0x75, 0x6e, 0x69, 0x66, - 0x69, 0x65, 0x64, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1c, 0x2e, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, - 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, - 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x30, 0x5a, 0x2e, 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, 0x74, 0x6f, 0x72, 0x61, 0x67, 0x65, 0x2f, 0x75, - 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x72, 0x76, 0x69, 0x63, 0x65, 0x22, 0xab, 0x01, 0x0a, 0x13, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, + 0x43, 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x43, 0x0a, + 0x06, 0x73, 0x74, 0x61, 0x74, 0x75, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x2b, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, + 0x68, 0x65, 0x63, 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x52, 0x06, 0x73, 0x74, 0x61, 0x74, + 0x75, 0x73, 0x22, 0x4f, 0x0a, 0x0d, 0x53, 0x65, 0x72, 0x76, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x61, + 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, + 0x12, 0x0b, 0x0a, 0x07, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0f, 0x0a, + 0x0b, 0x4e, 0x4f, 0x54, 0x5f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x4e, 0x47, 0x10, 0x02, 0x12, 0x13, + 0x0a, 0x0f, 0x53, 0x45, 0x52, 0x56, 0x49, 0x43, 0x45, 0x5f, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, + 0x4e, 0x10, 0x03, 0x2a, 0x55, 0x0a, 0x11, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x4f, + 0x70, 0x65, 0x72, 0x61, 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, 0x0c, 0x0a, 0x08, + 0x42, 0x4f, 0x4f, 0x4b, 0x4d, 0x41, 0x52, 0x4b, 0x10, 0x04, 0x32, 0x8c, 0x05, 0x0a, 0x0d, 0x52, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x53, 0x74, 0x6f, 0x72, 0x65, 0x12, 0x4a, 0x0a, 0x0b, + 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x12, 0x1c, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x43, 0x72, 0x65, 0x61, + 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x72, + 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x43, 0x72, 0x65, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x12, + 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, + 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, + 0x72, 0x63, 0x65, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x12, 0x17, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x35, 0x0a, 0x04, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x15, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, + 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3a, 0x0a, 0x05, 0x57, 0x61, 0x74, 0x63, 0x68, 0x12, + 0x16, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x2e, 0x57, 0x61, 0x74, 0x63, 0x68, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x30, 0x01, 0x12, 0x3e, 0x0a, 0x07, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x12, 0x18, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x2e, 0x47, 0x65, 0x74, 0x42, 0x6c, 0x6f, 0x62, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x3e, 0x0a, 0x07, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x12, 0x18, 0x2e, + 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, + 0x63, 0x65, 0x2e, 0x48, 0x69, 0x73, 0x74, 0x6f, 0x72, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, + 0x73, 0x65, 0x12, 0x3b, 0x0a, 0x06, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x12, 0x17, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x72, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, + 0x2e, 0x4f, 0x72, 0x69, 0x67, 0x69, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x48, 0x0a, 0x09, 0x49, 0x73, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x79, 0x12, 0x1c, 0x2e, 0x72, + 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, + 0x65, 0x63, 0x6b, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x72, 0x65, 0x73, + 0x6f, 0x75, 0x72, 0x63, 0x65, 0x2e, 0x48, 0x65, 0x61, 0x6c, 0x74, 0x68, 0x43, 0x68, 0x65, 0x63, + 0x6b, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x39, 0x5a, 0x37, 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, 0x74, 0x6f, 0x72, + 0x61, 0x67, 0x65, 0x2f, 0x75, 0x6e, 0x69, 0x66, 0x69, 0x65, 0x64, 0x2f, 0x72, 0x65, 0x73, 0x6f, + 0x75, 0x72, 0x63, 0x65, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( @@ -2649,94 +2653,94 @@ func file_resource_proto_rawDescGZIP() []byte { var file_resource_proto_enumTypes = make([]protoimpl.EnumInfo, 4) var file_resource_proto_msgTypes = make([]protoimpl.MessageInfo, 30) var file_resource_proto_goTypes = []interface{}{ - (ResourceOperation)(0), // 0: unified.ResourceOperation - (LinkBlob_Action)(0), // 1: unified.LinkBlob.Action - (Sort_Order)(0), // 2: unified.Sort.Order - (HealthCheckResponse_ServingStatus)(0), // 3: unified.HealthCheckResponse.ServingStatus - (*ResourceKey)(nil), // 4: unified.ResourceKey - (*ResourceWrapper)(nil), // 5: unified.ResourceWrapper - (*ResourceMeta)(nil), // 6: unified.ResourceMeta - (*BlobInfo)(nil), // 7: unified.BlobInfo - (*StatusResult)(nil), // 8: unified.StatusResult - (*LinkBlob)(nil), // 9: unified.LinkBlob - (*CreateRequest)(nil), // 10: unified.CreateRequest - (*CreateResponse)(nil), // 11: unified.CreateResponse - (*UpdateRequest)(nil), // 12: unified.UpdateRequest - (*UpdateResponse)(nil), // 13: unified.UpdateResponse - (*DeleteRequest)(nil), // 14: unified.DeleteRequest - (*DeleteResponse)(nil), // 15: unified.DeleteResponse - (*GetResourceRequest)(nil), // 16: unified.GetResourceRequest - (*GetResourceResponse)(nil), // 17: unified.GetResourceResponse - (*GetBlobRequest)(nil), // 18: unified.GetBlobRequest - (*GetBlobResponse)(nil), // 19: unified.GetBlobResponse - (*Requirement)(nil), // 20: unified.Requirement - (*Sort)(nil), // 21: unified.Sort - (*ListOptions)(nil), // 22: unified.ListOptions - (*ListRequest)(nil), // 23: unified.ListRequest - (*ListResponse)(nil), // 24: unified.ListResponse - (*WatchRequest)(nil), // 25: unified.WatchRequest - (*WatchResponse)(nil), // 26: unified.WatchResponse - (*HistoryRequest)(nil), // 27: unified.HistoryRequest - (*HistoryResponse)(nil), // 28: unified.HistoryResponse - (*OriginRequest)(nil), // 29: unified.OriginRequest - (*ResourceOriginInfo)(nil), // 30: unified.ResourceOriginInfo - (*OriginResponse)(nil), // 31: unified.OriginResponse - (*HealthCheckRequest)(nil), // 32: unified.HealthCheckRequest - (*HealthCheckResponse)(nil), // 33: unified.HealthCheckResponse + (ResourceOperation)(0), // 0: resource.ResourceOperation + (LinkBlob_Action)(0), // 1: resource.LinkBlob.Action + (Sort_Order)(0), // 2: resource.Sort.Order + (HealthCheckResponse_ServingStatus)(0), // 3: resource.HealthCheckResponse.ServingStatus + (*ResourceKey)(nil), // 4: resource.ResourceKey + (*ResourceWrapper)(nil), // 5: resource.ResourceWrapper + (*ResourceMeta)(nil), // 6: resource.ResourceMeta + (*BlobInfo)(nil), // 7: resource.BlobInfo + (*StatusResult)(nil), // 8: resource.StatusResult + (*LinkBlob)(nil), // 9: resource.LinkBlob + (*CreateRequest)(nil), // 10: resource.CreateRequest + (*CreateResponse)(nil), // 11: resource.CreateResponse + (*UpdateRequest)(nil), // 12: resource.UpdateRequest + (*UpdateResponse)(nil), // 13: resource.UpdateResponse + (*DeleteRequest)(nil), // 14: resource.DeleteRequest + (*DeleteResponse)(nil), // 15: resource.DeleteResponse + (*GetResourceRequest)(nil), // 16: resource.GetResourceRequest + (*GetResourceResponse)(nil), // 17: resource.GetResourceResponse + (*GetBlobRequest)(nil), // 18: resource.GetBlobRequest + (*GetBlobResponse)(nil), // 19: resource.GetBlobResponse + (*Requirement)(nil), // 20: resource.Requirement + (*Sort)(nil), // 21: resource.Sort + (*ListOptions)(nil), // 22: resource.ListOptions + (*ListRequest)(nil), // 23: resource.ListRequest + (*ListResponse)(nil), // 24: resource.ListResponse + (*WatchRequest)(nil), // 25: resource.WatchRequest + (*WatchResponse)(nil), // 26: resource.WatchResponse + (*HistoryRequest)(nil), // 27: resource.HistoryRequest + (*HistoryResponse)(nil), // 28: resource.HistoryResponse + (*OriginRequest)(nil), // 29: resource.OriginRequest + (*ResourceOriginInfo)(nil), // 30: resource.ResourceOriginInfo + (*OriginResponse)(nil), // 31: resource.OriginResponse + (*HealthCheckRequest)(nil), // 32: resource.HealthCheckRequest + (*HealthCheckResponse)(nil), // 33: resource.HealthCheckResponse } var file_resource_proto_depIdxs = []int32{ - 0, // 0: unified.ResourceWrapper.operation:type_name -> unified.ResourceOperation - 0, // 1: unified.ResourceMeta.operation:type_name -> unified.ResourceOperation - 4, // 2: unified.CreateRequest.key:type_name -> unified.ResourceKey - 9, // 3: unified.CreateRequest.blob:type_name -> unified.LinkBlob - 8, // 4: unified.CreateResponse.status:type_name -> unified.StatusResult - 4, // 5: unified.UpdateRequest.key:type_name -> unified.ResourceKey - 9, // 6: unified.UpdateRequest.blob:type_name -> unified.LinkBlob - 8, // 7: unified.UpdateResponse.status:type_name -> unified.StatusResult - 4, // 8: unified.DeleteRequest.key:type_name -> unified.ResourceKey - 8, // 9: unified.DeleteResponse.status:type_name -> unified.StatusResult - 4, // 10: unified.GetResourceRequest.key:type_name -> unified.ResourceKey - 8, // 11: unified.GetResourceResponse.status:type_name -> unified.StatusResult - 4, // 12: unified.GetBlobRequest.key:type_name -> unified.ResourceKey - 8, // 13: unified.GetBlobResponse.status:type_name -> unified.StatusResult - 7, // 14: unified.GetBlobResponse.info:type_name -> unified.BlobInfo - 2, // 15: unified.Sort.order:type_name -> unified.Sort.Order - 4, // 16: unified.ListOptions.key:type_name -> unified.ResourceKey - 20, // 17: unified.ListOptions.labels:type_name -> unified.Requirement - 20, // 18: unified.ListOptions.fields:type_name -> unified.Requirement - 22, // 19: unified.ListRequest.options:type_name -> unified.ListOptions - 21, // 20: unified.ListRequest.sort:type_name -> unified.Sort - 5, // 21: unified.ListResponse.items:type_name -> unified.ResourceWrapper - 4, // 22: unified.WatchRequest.key:type_name -> unified.ResourceKey - 22, // 23: unified.WatchRequest.options:type_name -> unified.ListOptions - 5, // 24: unified.WatchResponse.resource:type_name -> unified.ResourceWrapper - 5, // 25: unified.WatchResponse.previous:type_name -> unified.ResourceWrapper - 4, // 26: unified.HistoryRequest.key:type_name -> unified.ResourceKey - 6, // 27: unified.HistoryResponse.items:type_name -> unified.ResourceMeta - 4, // 28: unified.OriginRequest.key:type_name -> unified.ResourceKey - 4, // 29: unified.ResourceOriginInfo.key:type_name -> unified.ResourceKey - 30, // 30: unified.OriginResponse.items:type_name -> unified.ResourceOriginInfo - 3, // 31: unified.HealthCheckResponse.status:type_name -> unified.HealthCheckResponse.ServingStatus - 16, // 32: unified.ResourceStore.GetResource:input_type -> unified.GetResourceRequest - 10, // 33: unified.ResourceStore.Create:input_type -> unified.CreateRequest - 12, // 34: unified.ResourceStore.Update:input_type -> unified.UpdateRequest - 14, // 35: unified.ResourceStore.Delete:input_type -> unified.DeleteRequest - 23, // 36: unified.ResourceStore.List:input_type -> unified.ListRequest - 25, // 37: unified.ResourceStore.Watch:input_type -> unified.WatchRequest - 18, // 38: unified.ResourceStore.GetBlob:input_type -> unified.GetBlobRequest - 27, // 39: unified.ResourceStore.History:input_type -> unified.HistoryRequest - 29, // 40: unified.ResourceStore.Origin:input_type -> unified.OriginRequest - 32, // 41: unified.ResourceStore.IsHealthy:input_type -> unified.HealthCheckRequest - 17, // 42: unified.ResourceStore.GetResource:output_type -> unified.GetResourceResponse - 11, // 43: unified.ResourceStore.Create:output_type -> unified.CreateResponse - 13, // 44: unified.ResourceStore.Update:output_type -> unified.UpdateResponse - 15, // 45: unified.ResourceStore.Delete:output_type -> unified.DeleteResponse - 24, // 46: unified.ResourceStore.List:output_type -> unified.ListResponse - 26, // 47: unified.ResourceStore.Watch:output_type -> unified.WatchResponse - 19, // 48: unified.ResourceStore.GetBlob:output_type -> unified.GetBlobResponse - 28, // 49: unified.ResourceStore.History:output_type -> unified.HistoryResponse - 31, // 50: unified.ResourceStore.Origin:output_type -> unified.OriginResponse - 33, // 51: unified.ResourceStore.IsHealthy:output_type -> unified.HealthCheckResponse + 0, // 0: resource.ResourceWrapper.operation:type_name -> resource.ResourceOperation + 0, // 1: resource.ResourceMeta.operation:type_name -> resource.ResourceOperation + 4, // 2: resource.CreateRequest.key:type_name -> resource.ResourceKey + 9, // 3: resource.CreateRequest.blob:type_name -> resource.LinkBlob + 8, // 4: resource.CreateResponse.status:type_name -> resource.StatusResult + 4, // 5: resource.UpdateRequest.key:type_name -> resource.ResourceKey + 9, // 6: resource.UpdateRequest.blob:type_name -> resource.LinkBlob + 8, // 7: resource.UpdateResponse.status:type_name -> resource.StatusResult + 4, // 8: resource.DeleteRequest.key:type_name -> resource.ResourceKey + 8, // 9: resource.DeleteResponse.status:type_name -> resource.StatusResult + 4, // 10: resource.GetResourceRequest.key:type_name -> resource.ResourceKey + 8, // 11: resource.GetResourceResponse.status:type_name -> resource.StatusResult + 4, // 12: resource.GetBlobRequest.key:type_name -> resource.ResourceKey + 8, // 13: resource.GetBlobResponse.status:type_name -> resource.StatusResult + 7, // 14: resource.GetBlobResponse.info:type_name -> resource.BlobInfo + 2, // 15: resource.Sort.order:type_name -> resource.Sort.Order + 4, // 16: resource.ListOptions.key:type_name -> resource.ResourceKey + 20, // 17: resource.ListOptions.labels:type_name -> resource.Requirement + 20, // 18: resource.ListOptions.fields:type_name -> resource.Requirement + 22, // 19: resource.ListRequest.options:type_name -> resource.ListOptions + 21, // 20: resource.ListRequest.sort:type_name -> resource.Sort + 5, // 21: resource.ListResponse.items:type_name -> resource.ResourceWrapper + 4, // 22: resource.WatchRequest.key:type_name -> resource.ResourceKey + 22, // 23: resource.WatchRequest.options:type_name -> resource.ListOptions + 5, // 24: resource.WatchResponse.resource:type_name -> resource.ResourceWrapper + 5, // 25: resource.WatchResponse.previous:type_name -> resource.ResourceWrapper + 4, // 26: resource.HistoryRequest.key:type_name -> resource.ResourceKey + 6, // 27: resource.HistoryResponse.items:type_name -> resource.ResourceMeta + 4, // 28: resource.OriginRequest.key:type_name -> resource.ResourceKey + 4, // 29: resource.ResourceOriginInfo.key:type_name -> resource.ResourceKey + 30, // 30: resource.OriginResponse.items:type_name -> resource.ResourceOriginInfo + 3, // 31: resource.HealthCheckResponse.status:type_name -> resource.HealthCheckResponse.ServingStatus + 16, // 32: resource.ResourceStore.GetResource:input_type -> resource.GetResourceRequest + 10, // 33: resource.ResourceStore.Create:input_type -> resource.CreateRequest + 12, // 34: resource.ResourceStore.Update:input_type -> resource.UpdateRequest + 14, // 35: resource.ResourceStore.Delete:input_type -> resource.DeleteRequest + 23, // 36: resource.ResourceStore.List:input_type -> resource.ListRequest + 25, // 37: resource.ResourceStore.Watch:input_type -> resource.WatchRequest + 18, // 38: resource.ResourceStore.GetBlob:input_type -> resource.GetBlobRequest + 27, // 39: resource.ResourceStore.History:input_type -> resource.HistoryRequest + 29, // 40: resource.ResourceStore.Origin:input_type -> resource.OriginRequest + 32, // 41: resource.ResourceStore.IsHealthy:input_type -> resource.HealthCheckRequest + 17, // 42: resource.ResourceStore.GetResource:output_type -> resource.GetResourceResponse + 11, // 43: resource.ResourceStore.Create:output_type -> resource.CreateResponse + 13, // 44: resource.ResourceStore.Update:output_type -> resource.UpdateResponse + 15, // 45: resource.ResourceStore.Delete:output_type -> resource.DeleteResponse + 24, // 46: resource.ResourceStore.List:output_type -> resource.ListResponse + 26, // 47: resource.ResourceStore.Watch:output_type -> resource.WatchResponse + 19, // 48: resource.ResourceStore.GetBlob:output_type -> resource.GetBlobResponse + 28, // 49: resource.ResourceStore.History:output_type -> resource.HistoryResponse + 31, // 50: resource.ResourceStore.Origin:output_type -> resource.OriginResponse + 33, // 51: resource.ResourceStore.IsHealthy:output_type -> resource.HealthCheckResponse 42, // [42:52] is the sub-list for method output_type 32, // [32:42] is the sub-list for method input_type 32, // [32:32] is the sub-list for extension type_name diff --git a/pkg/storage/unified/resource.proto b/pkg/storage/unified/resource/resource.proto similarity index 99% rename from pkg/storage/unified/resource.proto rename to pkg/storage/unified/resource/resource.proto index d0de9404839..3c047e12027 100644 --- a/pkg/storage/unified/resource.proto +++ b/pkg/storage/unified/resource/resource.proto @@ -1,7 +1,7 @@ syntax = "proto3"; -package unified; +package resource; -option go_package = "github.com/grafana/grafana/pkg/storage/unified"; +option go_package = "github.com/grafana/grafana/pkg/storage/unified/resource"; message ResourceKey { // Namespace (tenant) diff --git a/pkg/storage/unified/resource_grpc.pb.go b/pkg/storage/unified/resource/resource_grpc.pb.go similarity index 95% rename from pkg/storage/unified/resource_grpc.pb.go rename to pkg/storage/unified/resource/resource_grpc.pb.go index 7078ebba248..e0adfd90a4e 100644 --- a/pkg/storage/unified/resource_grpc.pb.go +++ b/pkg/storage/unified/resource/resource_grpc.pb.go @@ -4,7 +4,7 @@ // - protoc (unknown) // source: resource.proto -package unified +package resource import ( context "context" @@ -19,16 +19,16 @@ import ( const _ = grpc.SupportPackageIsVersion8 const ( - ResourceStore_GetResource_FullMethodName = "/unified.ResourceStore/GetResource" - ResourceStore_Create_FullMethodName = "/unified.ResourceStore/Create" - ResourceStore_Update_FullMethodName = "/unified.ResourceStore/Update" - ResourceStore_Delete_FullMethodName = "/unified.ResourceStore/Delete" - ResourceStore_List_FullMethodName = "/unified.ResourceStore/List" - ResourceStore_Watch_FullMethodName = "/unified.ResourceStore/Watch" - ResourceStore_GetBlob_FullMethodName = "/unified.ResourceStore/GetBlob" - ResourceStore_History_FullMethodName = "/unified.ResourceStore/History" - ResourceStore_Origin_FullMethodName = "/unified.ResourceStore/Origin" - ResourceStore_IsHealthy_FullMethodName = "/unified.ResourceStore/IsHealthy" + ResourceStore_GetResource_FullMethodName = "/resource.ResourceStore/GetResource" + ResourceStore_Create_FullMethodName = "/resource.ResourceStore/Create" + ResourceStore_Update_FullMethodName = "/resource.ResourceStore/Update" + ResourceStore_Delete_FullMethodName = "/resource.ResourceStore/Delete" + ResourceStore_List_FullMethodName = "/resource.ResourceStore/List" + ResourceStore_Watch_FullMethodName = "/resource.ResourceStore/Watch" + ResourceStore_GetBlob_FullMethodName = "/resource.ResourceStore/GetBlob" + ResourceStore_History_FullMethodName = "/resource.ResourceStore/History" + ResourceStore_Origin_FullMethodName = "/resource.ResourceStore/Origin" + ResourceStore_IsHealthy_FullMethodName = "/resource.ResourceStore/IsHealthy" ) // ResourceStoreClient is the client API for ResourceStore service. @@ -439,7 +439,7 @@ func _ResourceStore_IsHealthy_Handler(srv interface{}, ctx context.Context, dec // It's only intended for direct use with grpc.RegisterService, // and not to be introspected or modified (even as a copy) var ResourceStore_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "unified.ResourceStore", + ServiceName: "resource.ResourceStore", HandlerType: (*ResourceStoreServer)(nil), Methods: []grpc.MethodDesc{ { diff --git a/pkg/storage/unified/resource_test.go b/pkg/storage/unified/resource/resource_test.go similarity index 84% rename from pkg/storage/unified/resource_test.go rename to pkg/storage/unified/resource/resource_test.go index 6784017e339..2c0cd3866a2 100644 --- a/pkg/storage/unified/resource_test.go +++ b/pkg/storage/unified/resource/resource_test.go @@ -1,16 +1,16 @@ -package unified_test +package resource_test import ( "testing" "github.com/stretchr/testify/require" - "github.com/grafana/grafana/pkg/storage/unified" + "github.com/grafana/grafana/pkg/storage/unified/resource" ) func TestResourceModels(t *testing.T) { t.Run("key namespaced path", func(t *testing.T) { - key := &unified.ResourceKey{} + key := &resource.ResourceKey{} require.Equal(t, "__cluster__", key.NamespacedPath()) key.Namespace = "ns" diff --git a/pkg/storage/sqlstash/testdata/01_create_playlist.json b/pkg/storage/unified/resource/testdata/01_create_playlist.json similarity index 100% rename from pkg/storage/sqlstash/testdata/01_create_playlist.json rename to pkg/storage/unified/resource/testdata/01_create_playlist.json diff --git a/pkg/storage/unified/validator.go b/pkg/storage/unified/resource/validator.go similarity index 99% rename from pkg/storage/unified/validator.go rename to pkg/storage/unified/resource/validator.go index 74c9e1cd2eb..a8a55668c3e 100644 --- a/pkg/storage/unified/validator.go +++ b/pkg/storage/unified/resource/validator.go @@ -1,4 +1,4 @@ -package unified +package resource import ( "context" diff --git a/pkg/storage/sqlstash/data/common.sql b/pkg/storage/unified/sqlstash/data/common.sql similarity index 100% rename from pkg/storage/sqlstash/data/common.sql rename to pkg/storage/unified/sqlstash/data/common.sql diff --git a/pkg/storage/sqlstash/data/resource_get.sql b/pkg/storage/unified/sqlstash/data/resource_get.sql similarity index 100% rename from pkg/storage/sqlstash/data/resource_get.sql rename to pkg/storage/unified/sqlstash/data/resource_get.sql diff --git a/pkg/storage/sqlstash/data/resource_insert.sql b/pkg/storage/unified/sqlstash/data/resource_insert.sql similarity index 100% rename from pkg/storage/sqlstash/data/resource_insert.sql rename to pkg/storage/unified/sqlstash/data/resource_insert.sql diff --git a/pkg/storage/sqlstash/data/rv_get.sql b/pkg/storage/unified/sqlstash/data/rv_get.sql similarity index 100% rename from pkg/storage/sqlstash/data/rv_get.sql rename to pkg/storage/unified/sqlstash/data/rv_get.sql diff --git a/pkg/storage/sqlstash/data/rv_inc.sql b/pkg/storage/unified/sqlstash/data/rv_inc.sql similarity index 100% rename from pkg/storage/sqlstash/data/rv_inc.sql rename to pkg/storage/unified/sqlstash/data/rv_inc.sql diff --git a/pkg/storage/sqlstash/data/rv_insert.sql b/pkg/storage/unified/sqlstash/data/rv_insert.sql similarity index 100% rename from pkg/storage/sqlstash/data/rv_insert.sql rename to pkg/storage/unified/sqlstash/data/rv_insert.sql diff --git a/pkg/storage/sqlstash/data/rv_lock.sql b/pkg/storage/unified/sqlstash/data/rv_lock.sql similarity index 100% rename from pkg/storage/sqlstash/data/rv_lock.sql rename to pkg/storage/unified/sqlstash/data/rv_lock.sql diff --git a/pkg/storage/sqlstash/queries.go b/pkg/storage/unified/sqlstash/queries.go similarity index 100% rename from pkg/storage/sqlstash/queries.go rename to pkg/storage/unified/sqlstash/queries.go diff --git a/pkg/storage/sqlstash/queries_test.go b/pkg/storage/unified/sqlstash/queries_test.go similarity index 82% rename from pkg/storage/sqlstash/queries_test.go rename to pkg/storage/unified/sqlstash/queries_test.go index 8120e8c3b00..2a5ce2c22e4 100644 --- a/pkg/storage/sqlstash/queries_test.go +++ b/pkg/storage/unified/sqlstash/queries_test.go @@ -12,7 +12,7 @@ import ( playlist "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1" "github.com/grafana/grafana/pkg/infra/appcontext" "github.com/grafana/grafana/pkg/services/user" - "github.com/grafana/grafana/pkg/storage/unified" + "github.com/grafana/grafana/pkg/storage/unified/resource" ) func TestSQLCommands(t *testing.T) { @@ -21,16 +21,16 @@ func TestSQLCommands(t *testing.T) { UserUID: "u123", OrgRole: identity.RoleAdmin, }) - validator := unified.NewEventValidator(unified.EventValidatorOptions{ + validator := resource.NewEventValidator(resource.EventValidatorOptions{ // no folders for now }) t.Run("insert playlist SQL", func(t *testing.T) { input := testdataFromJSON(t, "01_create_playlist.json", &playlist.Playlist{}) - key, err := unified.ResourceKeyFor(playlist.PlaylistResourceInfo.GroupResource(), input) + key, err := resource.ResourceKeyFor(playlist.PlaylistResourceInfo.GroupResource(), input) require.NoError(t, err) - req := &unified.CreateRequest{Key: key, Message: "test commit"} + req := &resource.CreateRequest{Key: key, Message: "test commit"} req.Value, err = json.Marshal(input) require.NoError(t, err) require.Equal(t, "default/playlist.grafana.app/playlists/fdgsv37qslr0ga", key.NamespacedPath()) diff --git a/pkg/storage/sqlstash/sql_storage_server.go b/pkg/storage/unified/sqlstash/sql_storage_server.go similarity index 77% rename from pkg/storage/sqlstash/sql_storage_server.go rename to pkg/storage/unified/sqlstash/sql_storage_server.go index a75c4f683c9..f4c93d42bf7 100644 --- a/pkg/storage/sqlstash/sql_storage_server.go +++ b/pkg/storage/unified/sqlstash/sql_storage_server.go @@ -18,7 +18,7 @@ import ( "github.com/grafana/grafana/pkg/services/store/entity/db" "github.com/grafana/grafana/pkg/services/store/entity/sqlstash" "github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate" - "github.com/grafana/grafana/pkg/storage/unified" + "github.com/grafana/grafana/pkg/storage/unified/resource" ) const resoruceTable = "resource" @@ -35,7 +35,7 @@ var ( ) // Make sure we implement correct interfaces -var _ unified.ResourceStoreServer = &sqlResourceServer{} +var _ resource.ResourceStoreServer = &sqlResourceServer{} func ProvideSQLResourceServer(db db.EntityDBInterface, tracer tracing.Tracer) (SqlResourceServer, error) { ctx, cancel := context.WithCancel(context.Background()) @@ -56,7 +56,7 @@ func ProvideSQLResourceServer(db db.EntityDBInterface, tracer tracing.Tracer) (S } type SqlResourceServer interface { - unified.ResourceStoreServer + resource.ResourceStoreServer Init() error Stop() @@ -67,12 +67,12 @@ type sqlResourceServer struct { db db.EntityDBInterface // needed to keep xorm engine in scope sess *session.SessionDB dialect migrator.Dialect - broadcaster sqlstash.Broadcaster[*unified.WatchResponse] + broadcaster sqlstash.Broadcaster[*resource.WatchResponse] ctx context.Context // TODO: remove cancel context.CancelFunc - stream chan *unified.WatchResponse + stream chan *resource.WatchResponse tracer trace.Tracer - validator unified.EventValidator + validator resource.EventValidator once sync.Once initErr error @@ -138,12 +138,12 @@ func (s *sqlResourceServer) init() error { s.sess = sess s.dialect = migrator.NewDialect(engine.DriverName()) - s.validator = unified.NewEventValidator(unified.EventValidatorOptions{ + s.validator = resource.NewEventValidator(resource.EventValidatorOptions{ // use snowflake IDs }) // set up the broadcaster - s.broadcaster, err = sqlstash.NewBroadcaster(s.ctx, func(stream chan *unified.WatchResponse) error { + s.broadcaster, err = sqlstash.NewBroadcaster(s.ctx, func(stream chan *resource.WatchResponse) error { s.stream = stream // start the poller @@ -158,7 +158,7 @@ func (s *sqlResourceServer) init() error { return nil } -func (s *sqlResourceServer) IsHealthy(ctx context.Context, r *unified.HealthCheckRequest) (*unified.HealthCheckResponse, error) { +func (s *sqlResourceServer) IsHealthy(ctx context.Context, r *resource.HealthCheckRequest) (*resource.HealthCheckResponse, error) { ctxLogger := s.log.FromContext(log.WithContextualAttributes(ctx, []any{"method", "isHealthy"})) if err := s.Init(); err != nil { ctxLogger.Error("init error", "error", err) @@ -169,14 +169,14 @@ func (s *sqlResourceServer) IsHealthy(ctx context.Context, r *unified.HealthChec return nil, err } // TODO: check the status of the watcher implementation as well - return &unified.HealthCheckResponse{Status: unified.HealthCheckResponse_SERVING}, nil + return &resource.HealthCheckResponse{Status: resource.HealthCheckResponse_SERVING}, nil } func (s *sqlResourceServer) Stop() { s.cancel() } -func (s *sqlResourceServer) GetResource(ctx context.Context, req *unified.GetResourceRequest) (*unified.GetResourceResponse, error) { +func (s *sqlResourceServer) GetResource(ctx context.Context, req *resource.GetResourceRequest) (*resource.GetResourceResponse, error) { ctx, span := s.tracer.Start(ctx, "storage_server.GetResource") defer span.End() @@ -185,10 +185,10 @@ func (s *sqlResourceServer) GetResource(ctx context.Context, req *unified.GetRes } if req.Key.Group == "" { - return &unified.GetResourceResponse{Status: badRequest("missing group")}, nil + return &resource.GetResourceResponse{Status: badRequest("missing group")}, nil } if req.Key.Resource == "" { - return &unified.GetResourceResponse{Status: badRequest("missing resource")}, nil + return &resource.GetResourceResponse{Status: badRequest("missing resource")}, nil } fmt.Printf("TODO, GET: %+v", req.Key) @@ -196,12 +196,12 @@ func (s *sqlResourceServer) GetResource(ctx context.Context, req *unified.GetRes return nil, ErrNotImplementedYet } -func (s *sqlResourceServer) Create(ctx context.Context, req *unified.CreateRequest) (*unified.CreateResponse, error) { +func (s *sqlResourceServer) Create(ctx context.Context, req *resource.CreateRequest) (*resource.CreateResponse, error) { ctx, span := s.tracer.Start(ctx, "storage_server.Create") defer span.End() if req.Key.ResourceVersion > 0 { - return &unified.CreateResponse{ + return &resource.CreateResponse{ Status: badRequest("can not update a specific resource version"), }, nil } @@ -215,7 +215,7 @@ func (s *sqlResourceServer) Create(ctx context.Context, req *unified.CreateReque return nil, err } if event.Status != nil { - return &unified.CreateResponse{Status: event.Status}, nil + return &resource.CreateResponse{Status: event.Status}, nil } fmt.Printf("TODO, CREATE: %v", event) @@ -223,12 +223,12 @@ func (s *sqlResourceServer) Create(ctx context.Context, req *unified.CreateReque return nil, ErrNotImplementedYet } -func (s *sqlResourceServer) Update(ctx context.Context, req *unified.UpdateRequest) (*unified.UpdateResponse, error) { +func (s *sqlResourceServer) Update(ctx context.Context, req *resource.UpdateRequest) (*resource.UpdateResponse, error) { ctx, span := s.tracer.Start(ctx, "storage_server.Update") defer span.End() if req.Key.ResourceVersion < 0 { - return &unified.UpdateResponse{ + return &resource.UpdateResponse{ Status: badRequest("update must include the previous version"), }, nil } @@ -237,7 +237,7 @@ func (s *sqlResourceServer) Update(ctx context.Context, req *unified.UpdateReque return nil, err } - latest, err := s.GetResource(ctx, &unified.GetResourceRequest{ + latest, err := s.GetResource(ctx, &resource.GetResourceRequest{ Key: req.Key.WithoutResourceVersion(), }) if err != nil { @@ -248,7 +248,7 @@ func (s *sqlResourceServer) Update(ctx context.Context, req *unified.UpdateReque return nil, err } if event.Status != nil { - return &unified.UpdateResponse{Status: event.Status}, nil + return &resource.UpdateResponse{Status: event.Status}, nil } fmt.Printf("TODO, UPDATE: %v", event) @@ -256,12 +256,12 @@ func (s *sqlResourceServer) Update(ctx context.Context, req *unified.UpdateReque return nil, ErrNotImplementedYet } -func (s *sqlResourceServer) Delete(ctx context.Context, req *unified.DeleteRequest) (*unified.DeleteResponse, error) { +func (s *sqlResourceServer) Delete(ctx context.Context, req *resource.DeleteRequest) (*resource.DeleteResponse, error) { ctx, span := s.tracer.Start(ctx, "storage_server.Delete") defer span.End() if req.Key.ResourceVersion < 0 { - return &unified.DeleteResponse{ + return &resource.DeleteResponse{ Status: badRequest("update must include the previous version"), }, nil } @@ -270,7 +270,7 @@ func (s *sqlResourceServer) Delete(ctx context.Context, req *unified.DeleteReque return nil, err } - latest, err := s.GetResource(ctx, &unified.GetResourceRequest{ + latest, err := s.GetResource(ctx, &resource.GetResourceRequest{ Key: req.Key.WithoutResourceVersion(), }) if err != nil { @@ -281,7 +281,7 @@ func (s *sqlResourceServer) Delete(ctx context.Context, req *unified.DeleteReque return nil, err } if event.Status != nil { - return &unified.DeleteResponse{Status: event.Status}, nil + return &resource.DeleteResponse{Status: event.Status}, nil } fmt.Printf("TODO, DELETE: %+v ", req.Key) @@ -289,7 +289,7 @@ func (s *sqlResourceServer) Delete(ctx context.Context, req *unified.DeleteReque return nil, ErrNotImplementedYet } -func (s *sqlResourceServer) List(ctx context.Context, req *unified.ListRequest) (*unified.ListResponse, error) { +func (s *sqlResourceServer) List(ctx context.Context, req *resource.ListRequest) (*resource.ListResponse, error) { ctx, span := s.tracer.Start(ctx, "storage_server.List") defer span.End() @@ -324,7 +324,7 @@ func (s *sqlResourceServer) List(ctx context.Context, req *unified.ListRequest) } // Get the raw blob bytes and metadata -func (s *sqlResourceServer) GetBlob(ctx context.Context, req *unified.GetBlobRequest) (*unified.GetBlobResponse, error) { +func (s *sqlResourceServer) GetBlob(ctx context.Context, req *resource.GetBlobRequest) (*resource.GetBlobResponse, error) { ctx, span := s.tracer.Start(ctx, "storage_server.List") defer span.End() @@ -338,7 +338,7 @@ func (s *sqlResourceServer) GetBlob(ctx context.Context, req *unified.GetBlobReq } // Show resource history (and trash) -func (s *sqlResourceServer) History(ctx context.Context, req *unified.HistoryRequest) (*unified.HistoryResponse, error) { +func (s *sqlResourceServer) History(ctx context.Context, req *resource.HistoryRequest) (*resource.HistoryResponse, error) { ctx, span := s.tracer.Start(ctx, "storage_server.History") defer span.End() @@ -352,7 +352,7 @@ func (s *sqlResourceServer) History(ctx context.Context, req *unified.HistoryReq } // Used for efficient provisioning -func (s *sqlResourceServer) Origin(ctx context.Context, req *unified.OriginRequest) (*unified.OriginResponse, error) { +func (s *sqlResourceServer) Origin(ctx context.Context, req *resource.OriginRequest) (*resource.OriginResponse, error) { ctx, span := s.tracer.Start(ctx, "storage_server.History") defer span.End() diff --git a/pkg/storage/unified/testdata/01_create_playlist.json b/pkg/storage/unified/sqlstash/testdata/01_create_playlist.json similarity index 100% rename from pkg/storage/unified/testdata/01_create_playlist.json rename to pkg/storage/unified/sqlstash/testdata/01_create_playlist.json diff --git a/pkg/storage/sqlstash/utils.go b/pkg/storage/unified/sqlstash/utils.go similarity index 95% rename from pkg/storage/sqlstash/utils.go rename to pkg/storage/unified/sqlstash/utils.go index 40c5d809e88..e50bb3742c8 100644 --- a/pkg/storage/sqlstash/utils.go +++ b/pkg/storage/unified/sqlstash/utils.go @@ -9,11 +9,11 @@ import ( "github.com/grafana/grafana/pkg/services/store/entity/db" "github.com/grafana/grafana/pkg/services/store/entity/sqlstash/sqltemplate" - "github.com/grafana/grafana/pkg/storage/unified" + "github.com/grafana/grafana/pkg/storage/unified/resource" ) -func badRequest(msg string) *unified.StatusResult { - return &unified.StatusResult{ +func badRequest(msg string) *resource.StatusResult { + return &resource.StatusResult{ Status: "Failure", Message: msg, Code: http.StatusBadRequest, diff --git a/pkg/storage/sqlstash/utils_test.go b/pkg/storage/unified/sqlstash/utils_test.go similarity index 100% rename from pkg/storage/sqlstash/utils_test.go rename to pkg/storage/unified/sqlstash/utils_test.go diff --git a/pkg/storage/sqlstash/watch.go b/pkg/storage/unified/sqlstash/watch.go similarity index 70% rename from pkg/storage/sqlstash/watch.go rename to pkg/storage/unified/sqlstash/watch.go index 0a170e1103e..3e98c5aa2a6 100644 --- a/pkg/storage/sqlstash/watch.go +++ b/pkg/storage/unified/sqlstash/watch.go @@ -4,14 +4,14 @@ import ( "time" "github.com/grafana/grafana/pkg/infra/log" - "github.com/grafana/grafana/pkg/storage/unified" + "github.com/grafana/grafana/pkg/storage/unified/resource" ) -func (s *sqlResourceServer) Watch(*unified.WatchRequest, unified.ResourceStore_WatchServer) error { +func (s *sqlResourceServer) Watch(*resource.WatchRequest, resource.ResourceStore_WatchServer) error { return ErrNotImplementedYet } -func (s *sqlResourceServer) poller(stream chan *unified.WatchResponse) { +func (s *sqlResourceServer) poller(stream chan *resource.WatchResponse) { var err error since := int64(0) @@ -34,7 +34,7 @@ func (s *sqlResourceServer) poller(stream chan *unified.WatchResponse) { } } -func (s *sqlResourceServer) poll(since int64, out chan *unified.WatchResponse) (int64, error) { +func (s *sqlResourceServer) poll(since int64, out chan *resource.WatchResponse) (int64, error) { ctx, span := s.tracer.Start(s.ctx, "storage_server.poll") defer span.End() ctxLogger := s.log.FromContext(log.WithContextualAttributes(ctx, []any{"method", "poll"})) @@ -43,7 +43,7 @@ func (s *sqlResourceServer) poll(since int64, out chan *unified.WatchResponse) ( err := func() error { if false { // TODO - out <- &unified.WatchResponse{} + out <- &resource.WatchResponse{} } // TODO, copy from entity store