This is an automated email from the ASF dual-hosted git repository. hanahmily pushed a commit to branch property-ttl in repository https://gitbox.apache.org/repos/asf/skywalking-banyandb.git
commit 3fc9abf8bacf5414fcfb2e5eeb25c987bdede718 Author: Gao Hongtao <[email protected]> AuthorDate: Sun Sep 17 11:43:54 2023 +0000 Add Keepalive Signed-off-by: Gao Hongtao <[email protected]> --- CHANGES.md | 1 + api/proto/banyandb/property/v1/property.proto | 2 +- api/proto/banyandb/property/v1/rpc.proto | 10 ++ banyand/liaison/grpc/property.go | 12 ++- banyand/metadata/schema/property.go | 107 ++++++++++++--------- banyand/metadata/schema/schema.go | 3 +- bydbctl/internal/cmd/property.go | 23 ++++- bydbctl/internal/cmd/property_test.go | 47 ++++++++- bydbctl/internal/cmd/rest.go | 1 + docs/api-reference.md | 29 ++++++ docs/concept/data-model.md | 28 ++++++ docs/crud/property.md | 39 ++++++++ test/integration/standalone/other/property_test.go | 28 +++++- 13 files changed, 279 insertions(+), 51 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 58fcd220..6c5157f4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -16,6 +16,7 @@ Release Notes. - Fix parse environment variables error - Implement the distributed query engine. - Add mod revision check to write requests. +- Add TTL to the property. ### Bugs diff --git a/api/proto/banyandb/property/v1/property.proto b/api/proto/banyandb/property/v1/property.proto index 40aac5f6..ad124a4b 100644 --- a/api/proto/banyandb/property/v1/property.proto +++ b/api/proto/banyandb/property/v1/property.proto @@ -43,7 +43,7 @@ message Property { repeated model.v1.Tag tags = 2 [(validate.rules).repeated.min_items = 1]; // updated_at indicates when the property is updated google.protobuf.Timestamp updated_at = 3; - // readonly. lease_id is the ID of the lease that attached to key. + // readonly. lease_id is the ID of the lease that attached to key. int64 lease_id = 4; // ttl indicates the time to live of the property. // It's a string in the format of "1h", "2m", "3s", "1500ms". diff --git a/api/proto/banyandb/property/v1/rpc.proto b/api/proto/banyandb/property/v1/rpc.proto index c00a7686..a7955fd7 100644 --- a/api/proto/banyandb/property/v1/rpc.proto +++ b/api/proto/banyandb/property/v1/rpc.proto @@ -45,6 +45,7 @@ message ApplyResponse { // True: the property is absent. False: the property existed. bool created = 1; uint32 tags_num = 2; + int64 lease_id = 3; } message DeleteRequest { @@ -76,6 +77,12 @@ message ListResponse { repeated banyandb.property.v1.Property property = 1; } +message KeepAliveRequest { + int64 lease_id = 1; +} + +message KeepAliveResponse {} + service PropertyService { // Apply creates a property if it's absent, or update a existed one based on a strategy. rpc Apply(ApplyRequest) returns (ApplyResponse) { @@ -99,4 +106,7 @@ service PropertyService { additional_bindings {get: "/v1/property/lists/{container.group}"} }; } + rpc KeepAlive(KeepAliveRequest) returns (KeepAliveResponse) { + option (google.api.http) = {put: "/v1/property/lease/{lease_id}"}; + } } diff --git a/banyand/liaison/grpc/property.go b/banyand/liaison/grpc/property.go index 05a542f8..6c0bcbc5 100644 --- a/banyand/liaison/grpc/property.go +++ b/banyand/liaison/grpc/property.go @@ -30,11 +30,11 @@ type propertyServer struct { } func (ps *propertyServer) Apply(ctx context.Context, req *propertyv1.ApplyRequest) (*propertyv1.ApplyResponse, error) { - created, tagsNum, err := ps.schemaRegistry.PropertyRegistry().ApplyProperty(ctx, req.Property, req.Strategy) + created, tagsNum, leaseID, err := ps.schemaRegistry.PropertyRegistry().ApplyProperty(ctx, req.Property, req.Strategy) if err != nil { return nil, err } - return &propertyv1.ApplyResponse{Created: created, TagsNum: tagsNum}, nil + return &propertyv1.ApplyResponse{Created: created, TagsNum: tagsNum, LeaseId: leaseID}, nil } func (ps *propertyServer) Delete(ctx context.Context, req *propertyv1.DeleteRequest) (*propertyv1.DeleteResponse, error) { @@ -67,3 +67,11 @@ func (ps *propertyServer) List(ctx context.Context, req *propertyv1.ListRequest) Property: entities, }, nil } + +func (ps *propertyServer) KeepAlive(ctx context.Context, req *propertyv1.KeepAliveRequest) (*propertyv1.KeepAliveResponse, error) { + err := ps.schemaRegistry.PropertyRegistry().KeepAlive(ctx, req.GetLeaseId()) + if err != nil { + return nil, err + } + return &propertyv1.KeepAliveResponse{}, nil +} diff --git a/banyand/metadata/schema/property.go b/banyand/metadata/schema/property.go index 44dad59b..906d3e59 100644 --- a/banyand/metadata/schema/property.go +++ b/banyand/metadata/schema/property.go @@ -105,15 +105,15 @@ func (e *etcdSchemaRegistry) ListProperty(ctx context.Context, container *common return entities, nil } -func (e *etcdSchemaRegistry) ApplyProperty(ctx context.Context, property *propertyv1.Property, strategy propertyv1.ApplyRequest_Strategy) (bool, uint32, error) { +func (e *etcdSchemaRegistry) ApplyProperty(ctx context.Context, property *propertyv1.Property, strategy propertyv1.ApplyRequest_Strategy) (bool, uint32, int64, error) { if !e.closer.AddRunning() { - return false, 0, ErrClosed + return false, 0, 0, ErrClosed } defer e.closer.Done() m := transformKey(property.GetMetadata()) group := m.GetGroup() if _, getGroupErr := e.GetGroup(ctx, group); getGroupErr != nil { - return false, 0, errors.Wrap(getGroupErr, "group is not exist") + return false, 0, 0, errors.Wrap(getGroupErr, "group is not exist") } md := Metadata{ TypeMeta: TypeMeta{ @@ -125,17 +125,17 @@ func (e *etcdSchemaRegistry) ApplyProperty(ctx context.Context, property *proper } key, err := md.key() if err != nil { - return false, 0, err + return false, 0, 0, err } key = e.prependNamespace(key) var ttl int64 if property.Ttl != "" { t, err := timestamp.ParseDuration(property.Ttl) if err != nil { - return false, 0, err + return false, 0, 0, err } if t < time.Second { - return false, 0, errors.New("ttl should be greater than 1s") + return false, 0, 0, errors.New("ttl should be greater than 1s") } ttl = int64(t / time.Second) } @@ -145,28 +145,37 @@ func (e *etcdSchemaRegistry) ApplyProperty(ctx context.Context, property *proper return e.mergeProperty(ctx, key, property, ttl) } -func (e *etcdSchemaRegistry) replaceProperty(ctx context.Context, key string, property *propertyv1.Property, ttl int64) (bool, uint32, error) { - val, opts, err := e.marshalProperty(ctx, property, ttl) +func (e *etcdSchemaRegistry) replaceProperty(ctx context.Context, key string, property *propertyv1.Property, ttl int64) (bool, uint32, int64, error) { + leaseID, err := e.grant(ctx, ttl) if err != nil { - return false, 0, err + return false, 0, 0, err } - _, err = e.client.Put(ctx, key, string(val), opts...) + property.LeaseId = leaseID + val, err := e.marshalProperty(property) if err != nil { - return false, 0, err + return false, 0, 0, err } - return true, uint32(len(property.Tags)), nil + _, err = e.client.Put(ctx, key, string(val), clientv3.WithLease(clientv3.LeaseID(leaseID))) + if err != nil { + return false, 0, 0, err + } + return true, uint32(len(property.Tags)), leaseID, nil } -func (e *etcdSchemaRegistry) mergeProperty(ctx context.Context, key string, property *propertyv1.Property, ttl int64) (bool, uint32, error) { +func (e *etcdSchemaRegistry) mergeProperty(ctx context.Context, key string, property *propertyv1.Property, ttl int64) (bool, uint32, int64, error) { tagsNum := uint32(len(property.Tags)) - existed, errGet := e.GetProperty(ctx, property.Metadata, nil) - if errors.Is(errGet, ErrGRPCResourceNotFound) { + existed, err := e.GetProperty(ctx, property.Metadata, nil) + if errors.Is(err, ErrGRPCResourceNotFound) { return e.replaceProperty(ctx, key, property, ttl) } - if errGet != nil { - return false, 0, errGet + if err != nil { + return false, 0, 0, err + } + prevLeaseID := existed.LeaseId + leaseID, err := e.grant(ctx, ttl) + if err != nil { + return false, 0, 0, err } - var prevLeaseID int64 merge := func(existed *propertyv1.Property) (*propertyv1.Property, error) { tags := make([]*modelv1.Tag, len(property.Tags)) copy(tags, property.Tags) @@ -180,20 +189,20 @@ func (e *etcdSchemaRegistry) mergeProperty(ctx context.Context, key string, prop } } existed.Tags = append(existed.Tags, tags...) - prevLeaseID = existed.LeaseId - val, opts, err := e.marshalProperty(ctx, existed, ttl) - if err != nil { - return nil, err + existed.LeaseId = leaseID + val, errMerge := e.marshalProperty(existed) + if errMerge != nil { + return nil, errMerge } - txnResp, err := e.client.Txn(ctx).If( + txnResp, errMerge := e.client.Txn(ctx).If( clientv3.Compare(clientv3.ModRevision(key), "=", existed.Metadata.Container.ModRevision), ).Then( - clientv3.OpPut(key, string(val), opts...), + clientv3.OpPut(key, string(val), clientv3.WithLease(clientv3.LeaseID(leaseID))), ).Else( clientv3.OpGet(key), ).Commit() - if err != nil { - return nil, err + if errMerge != nil { + return nil, errMerge } if txnResp.Succeeded { return nil, nil @@ -203,20 +212,19 @@ func (e *etcdSchemaRegistry) mergeProperty(ctx context.Context, key string, prop return nil, ErrGRPCResourceNotFound } p := new(propertyv1.Property) - if err = proto.Unmarshal(getResp.Kvs[0].Value, p); err != nil { - return nil, err + if errMerge = proto.Unmarshal(getResp.Kvs[0].Value, p); errMerge != nil { + return nil, errMerge } return p, nil } // Self-spin to merge property - var err error for i := 0; i < 10; i++ { existed, err = merge(existed) if errors.Is(err, ErrGRPCResourceNotFound) { return e.replaceProperty(ctx, key, property, ttl) } if err != nil { - return false, 0, err + return false, 0, 0, err } if existed == nil { break @@ -224,30 +232,32 @@ func (e *etcdSchemaRegistry) mergeProperty(ctx context.Context, key string, prop time.Sleep(time.Millisecond * 100) } if existed != nil { - return false, 0, errors.New("merge property failed: retry timeout") + return false, 0, 0, errors.New("merge property failed: retry timeout") } if prevLeaseID > 0 { _, _ = e.client.Revoke(ctx, clientv3.LeaseID(prevLeaseID)) } - return false, tagsNum, nil + return false, tagsNum, leaseID, nil } -func (e *etcdSchemaRegistry) marshalProperty(ctx context.Context, property *propertyv1.Property, ttl int64) ([]byte, []clientv3.OpOption, error) { - var opts []clientv3.OpOption - if ttl > 0 { - lease, err := e.client.Grant(ctx, ttl) - if err != nil { - return nil, nil, err - } - property.LeaseId = int64(lease.ID) - opts = append(opts, clientv3.WithLease(lease.ID)) +func (e *etcdSchemaRegistry) grant(ctx context.Context, ttl int64) (int64, error) { + if ttl < 1 { + return 0, nil + } + lease, err := e.client.Grant(ctx, ttl) + if err != nil { + return 0, err } + return int64(lease.ID), nil +} + +func (e *etcdSchemaRegistry) marshalProperty(property *propertyv1.Property) ([]byte, error) { property.UpdatedAt = timestamppb.Now() val, err := proto.Marshal(property) if err != nil { - return nil, nil, err + return nil, err } - return val, opts, nil + return val, nil } func (e *etcdSchemaRegistry) DeleteProperty(ctx context.Context, metadata *propertyv1.Metadata, tags []string) (bool, uint32, error) { @@ -278,10 +288,19 @@ func (e *etcdSchemaRegistry) DeleteProperty(ctx context.Context, metadata *prope } } } - _, num, err := e.ApplyProperty(ctx, filtered, propertyv1.ApplyRequest_STRATEGY_REPLACE) + _, num, _, err := e.ApplyProperty(ctx, filtered, propertyv1.ApplyRequest_STRATEGY_REPLACE) return true, num, err } +func (e *etcdSchemaRegistry) KeepAlive(ctx context.Context, leaseID int64) error { + if !e.closer.AddRunning() { + return ErrClosed + } + defer e.closer.Done() + _, err := e.client.KeepAliveOnce(ctx, clientv3.LeaseID(leaseID)) + return err +} + func transformKey(metadata *propertyv1.Metadata) *commonv1.Metadata { return &commonv1.Metadata{ Group: metadata.Container.GetGroup(), diff --git a/banyand/metadata/schema/schema.go b/banyand/metadata/schema/schema.go index 6806b1a5..0c7095af 100644 --- a/banyand/metadata/schema/schema.go +++ b/banyand/metadata/schema/schema.go @@ -193,8 +193,9 @@ type TopNAggregation interface { type Property interface { GetProperty(ctx context.Context, metadata *propertyv1.Metadata, tags []string) (*propertyv1.Property, error) ListProperty(ctx context.Context, container *commonv1.Metadata, ids []string, tags []string) ([]*propertyv1.Property, error) - ApplyProperty(ctx context.Context, property *propertyv1.Property, strategy propertyv1.ApplyRequest_Strategy) (bool, uint32, error) + ApplyProperty(ctx context.Context, property *propertyv1.Property, strategy propertyv1.ApplyRequest_Strategy) (bool, uint32, int64, error) DeleteProperty(ctx context.Context, metadata *propertyv1.Metadata, tags []string) (bool, uint32, error) + KeepAlive(ctx context.Context, leaseID int64) error } // Node allows CRUD node schemas in a group. diff --git a/bydbctl/internal/cmd/property.go b/bydbctl/internal/cmd/property.go index 27c8420d..529f68f5 100644 --- a/bydbctl/internal/cmd/property.go +++ b/bydbctl/internal/cmd/property.go @@ -18,6 +18,8 @@ package cmd import ( + "strconv" + "github.com/go-resty/resty/v2" "github.com/spf13/cobra" "google.golang.org/protobuf/encoding/protojson" @@ -116,6 +118,25 @@ func newPropertyCmd() *cobra.Command { listCmd.Flags().StringArrayVarP(&ids, "ids", "", nil, "id selector") listCmd.Flags().StringArrayVarP(&tags, "tags", "t", nil, "tag selector") - propertyCmd.AddCommand(getCmd, applyCmd, deleteCmd, listCmd) + var leaseID int64 + keepAliveCmd := &cobra.Command{ + Use: "keepalive -i lease_id", + Version: version.Build(), + Short: "Keep alive a property", + RunE: func(_ *cobra.Command, _ []string) (err error) { + return rest(func() ([]reqBody, error) { + if leaseID == 0 { + return nil, errMalformedInput + } + return []reqBody{{leaseID: leaseID}}, nil + }, func(request request) (*resty.Response, error) { + return request.req.SetPathParam("lease_id", strconv.FormatInt(request.leaseID, 10)). + Put(getPath(propertySchemaPath + "/lease/{lease_id}")) + }, yamlPrinter) + }, + } + keepAliveCmd.Flags().Int64VarP(&leaseID, "lease_id", "i", 0, "the lease id of the property") + + propertyCmd.AddCommand(getCmd, applyCmd, deleteCmd, listCmd, keepAliveCmd) return propertyCmd } diff --git a/bydbctl/internal/cmd/property_test.go b/bydbctl/internal/cmd/property_test.go index 86fc241a..e16a2750 100644 --- a/bydbctl/internal/cmd/property_test.go +++ b/bydbctl/internal/cmd/property_test.go @@ -18,6 +18,7 @@ package cmd_test import ( + "strconv" "strings" "github.com/google/go-cmp/cmp" @@ -39,6 +40,7 @@ var equalsOpts = []cmp.Option{ protocmp.Transform(), protocmp.IgnoreUnknown(), protocmp.IgnoreFields(&propertyv1.Property{}, "updated_at"), + protocmp.IgnoreFields(&propertyv1.Property{}, "lease_id"), protocmp.IgnoreFields(&commonv1.Metadata{}, "mod_revision"), protocmp.IgnoreFields(&commonv1.Metadata{}, "create_revision"), } @@ -79,6 +81,20 @@ tags: int: value: 3 ` + p3Yaml := ` +metadata: + container: + group: ui-template + name: security + id: login-token +tags: + - key: content + value: + str: + value: foo +ttl: 30m +` + p1Proto := new(propertyv1.Property) helpers.UnmarshalYAML([]byte(p1YAML), p1Proto) p2Proto := new(propertyv1.Property) @@ -280,7 +296,36 @@ tags: helpers.UnmarshalYAML([]byte(out), resp) Expect(resp.Property).To(HaveLen(2)) }) - + It("keepalive not found", func() { + rootCmd.SetArgs([]string{ + "property", "keepalive", "-i", "111", + }) + out := capturer.CaptureStdout(func() { + err := rootCmd.Execute() + Expect(err).Should(MatchError("rpc error: code = Unknown desc = etcdserver: requested lease not found")) + }) + GinkgoWriter.Println(out) + }) + It("keepalive", func() { + rootCmd.SetArgs([]string{"property", "apply", "-a", addr, "-f", "-"}) + rootCmd.SetIn(strings.NewReader(p3Yaml)) + out := capturer.CaptureStdout(func() { + err := rootCmd.Execute() + Expect(err).NotTo(HaveOccurred()) + }) + GinkgoWriter.Println(out) + resp := new(propertyv1.ApplyResponse) + helpers.UnmarshalYAML([]byte(out), resp) + Expect(resp.LeaseId).Should(BeNumerically(">", 0)) + rootCmd.SetArgs([]string{ + "property", "keepalive", "-i", strconv.Itoa(int(resp.LeaseId)), + }) + out = capturer.CaptureStdout(func() { + err := rootCmd.Execute() + Expect(err).NotTo(HaveOccurred()) + }) + GinkgoWriter.Println(out) + }) AfterEach(func() { deferFunc() }) diff --git a/bydbctl/internal/cmd/rest.go b/bydbctl/internal/cmd/rest.go index e161bdda..02e15e86 100644 --- a/bydbctl/internal/cmd/rest.go +++ b/bydbctl/internal/cmd/rest.go @@ -61,6 +61,7 @@ type reqBody struct { tags []string parsedData map[string]interface{} data []byte + leaseID int64 } type request struct { diff --git a/docs/api-reference.md b/docs/api-reference.md index c7dae331..24550676 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -191,6 +191,8 @@ - [DeleteResponse](#banyandb-property-v1-DeleteResponse) - [GetRequest](#banyandb-property-v1-GetRequest) - [GetResponse](#banyandb-property-v1-GetResponse) + - [KeepAliveRequest](#banyandb-property-v1-KeepAliveRequest) + - [KeepAliveResponse](#banyandb-property-v1-KeepAliveResponse) - [ListRequest](#banyandb-property-v1-ListRequest) - [ListResponse](#banyandb-property-v1-ListResponse) @@ -2750,6 +2752,7 @@ Property stores the user defined data | ----- | ---- | ----- | ----------- | | created | [bool](#bool) | | created indicates whether the property existed. True: the property is absent. False: the property existed. | | tags_num | [uint32](#uint32) | | | +| lease_id | [int64](#int64) | | | @@ -2819,6 +2822,31 @@ Property stores the user defined data +<a name="banyandb-property-v1-KeepAliveRequest"></a> + +### KeepAliveRequest + + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| lease_id | [int64](#int64) | | | + + + + + + +<a name="banyandb-property-v1-KeepAliveResponse"></a> + +### KeepAliveResponse + + + + + + + <a name="banyandb-property-v1-ListRequest"></a> ### ListRequest @@ -2881,6 +2909,7 @@ Property stores the user defined data | Delete | [DeleteRequest](#banyandb-property-v1-DeleteRequest) | [DeleteResponse](#banyandb-property-v1-DeleteResponse) | | | Get | [GetRequest](#banyandb-property-v1-GetRequest) | [GetResponse](#banyandb-property-v1-GetResponse) | | | List | [ListRequest](#banyandb-property-v1-ListRequest) | [ListResponse](#banyandb-property-v1-ListResponse) | | +| KeepAlive | [KeepAliveRequest](#banyandb-property-v1-KeepAliveRequest) | [KeepAliveResponse](#banyandb-property-v1-KeepAliveResponse) | | diff --git a/docs/concept/data-model.md b/docs/concept/data-model.md index 06b719fb..4c63f2ed 100644 --- a/docs/concept/data-model.md +++ b/docs/concept/data-model.md @@ -198,6 +198,34 @@ tags: `Property` supports a three-level hierarchy, `group`/`name`/`id`, that is more flexible than schemaful data models. +The property supports the TTL mechanism. You could set the `ttl` field to specify the time to live. + +```yaml +metadata: + container: + group: sw + name: ui_template + id: General-Service +tags: +- key: name + value: + str: + value: "hello" +- key: state + value: + str: + value: "succeed" +ttl: "1h" +``` + +"General-Service" will be dropped after 1 hour. If you want to extend the TTL, you could use the "keepalive" operation. The "lease_id" is returned in the apply response. You can use get operation to get the property with the lease_id as well. + +```yaml +lease_id: 1 +``` + +"General-Service" lives another 1 hour. + You could Create, Read, Update and Drop a property, and update or drop several tags instead of the entire property. [Property Operations](../api-reference.md#propertyservice) diff --git a/docs/crud/property.md b/docs/crud/property.md index 4c010e7c..6bf627f4 100644 --- a/docs/crud/property.md +++ b/docs/crud/property.md @@ -128,6 +128,45 @@ List operation lists all properties in a group with a name. $ bydbctl property list -g sw -n ui_template ``` +## TTL field in a property + +TTL field in a property is used to set the time to live of the property. The property will be deleted automatically after the TTL. + +This functionality is supported by the lease mechanism. The readonly lease_id field is used to identify the lease of the property. + +### Examples of setting TTL + +```shell +$ bydbctl property apply -f - <<EOF +metadata: + container: + group: sw + name: ui_template + id: General-Service +tags: +- key: state + value: + str: + value: "failed" +ttl: "1h" +EOF +``` + +The lease_id is returned in the response. +You can use get operation to get the property with the lease_id as well. + +```shell +$ bydbctl property get -g sw -n ui_template --id General-Service +``` + +The lease_id is used to keep the property alive. You can use keepalive operation to keep the property alive. +When the keepalive operation is called, the property's TTL will be reset to the original value. + +```shell +$ bydbctl property keepalive --lease_id 1 +``` + + ## API Reference [MeasureService v1](../../api-reference.md#PropertyService) diff --git a/test/integration/standalone/other/property_test.go b/test/integration/standalone/other/property_test.go index 2e614911..9b5ad68c 100644 --- a/test/integration/standalone/other/property_test.go +++ b/test/integration/standalone/other/property_test.go @@ -120,6 +120,7 @@ var _ = Describe("Property application", func() { Expect(err).NotTo(HaveOccurred()) Expect(resp.Created).To(BeTrue()) Expect(resp.TagsNum).To(Equal(uint32(2))) + Expect(resp.LeaseId).To(BeNumerically(">", 0)) got, err := client.Get(context.Background(), &propertyv1.GetRequest{Metadata: md}) Expect(err).NotTo(HaveOccurred()) Expect(got.Property.Tags).To(Equal([]*modelv1.Tag{ @@ -127,7 +128,7 @@ var _ = Describe("Property application", func() { {Key: "t2", Value: &modelv1.TagValue{Value: &modelv1.TagValue_Str{Str: &modelv1.Str{Value: "v2"}}}}, })) Expect(got.Property.GetTtl()).To(Equal("1h")) - Expect(got.Property.GetLeaseId()).To(BeNumerically(">", 0)) + Expect(got.Property.GetLeaseId()).To(Equal(resp.LeaseId)) resp, err = client.Apply(context.Background(), &propertyv1.ApplyRequest{Property: &propertyv1.Property{ Metadata: md, Tags: []*modelv1.Tag{ @@ -143,6 +144,31 @@ var _ = Describe("Property application", func() { return err }, flags.EventuallyTimeout).Should(MatchError("rpc error: code = NotFound desc = banyandb: resource not found")) }) + It("keeps alive", func() { + _, err := client.KeepAlive(context.Background(), &propertyv1.KeepAliveRequest{LeaseId: 0}) + Expect(err).Should(MatchError("rpc error: code = Unknown desc = etcdserver: requested lease not found")) + md := &propertyv1.Metadata{ + Container: &commonv1.Metadata{ + Name: "p", + Group: "g", + }, + Id: "1", + } + resp, err := client.Apply(context.Background(), &propertyv1.ApplyRequest{Property: &propertyv1.Property{ + Metadata: md, + Tags: []*modelv1.Tag{ + {Key: "t1", Value: &modelv1.TagValue{Value: &modelv1.TagValue_Str{Str: &modelv1.Str{Value: "v1"}}}}, + {Key: "t2", Value: &modelv1.TagValue{Value: &modelv1.TagValue_Str{Str: &modelv1.Str{Value: "v2"}}}}, + }, + Ttl: "30m", + }}) + Expect(err).NotTo(HaveOccurred()) + Expect(resp.Created).To(BeTrue()) + Expect(resp.TagsNum).To(Equal(uint32(2))) + Expect(resp.LeaseId).To(BeNumerically(">", 0)) + _, err = client.KeepAlive(context.Background(), &propertyv1.KeepAliveRequest{LeaseId: resp.LeaseId}) + Expect(err).NotTo(HaveOccurred()) + }) }) var _ = Describe("Property application", func() {
