This is an automated email from the ASF dual-hosted git repository. hanahmily pushed a commit to branch sync-api in repository https://gitbox.apache.org/repos/asf/skywalking-banyandb-java-client.git
commit 647d0211d1379e701bee73d4824dc34730a28ca8 Author: Gao Hongtao <hanahm...@gmail.com> AuthorDate: Wed Mar 19 08:55:03 2025 +0800 Bump up API Signed-off-by: Gao Hongtao <hanahm...@gmail.com> --- .mvn/wrapper/MavenWrapperDownloader.java | 117 ------------------ .mvn/wrapper/maven-wrapper.properties | 3 - CHANGES.md | 1 + README.md | 134 ++++++++++++++------- .../banyandb/v1/client/BanyanDBClient.java | 73 +++++++++++ .../banyandb/v1/client/PropertyStore.java | 2 +- .../client/metadata/PropertyMetadataRegistry.java | 92 ++++++++++++++ src/main/proto/banyandb/v1/banyandb-common.proto | 30 ++++- src/main/proto/banyandb/v1/banyandb-database.proto | 116 +++++++++++++++--- src/main/proto/banyandb/v1/banyandb-property.proto | 28 ++--- .../banyandb/v1/client/BanyanDBClientTestCI.java | 2 +- .../v1/client/ITBanyanDBPropertyTests.java | 45 ++++--- .../v1/client/ITGroupMetadataRegistryTest.java | 81 +++++++++++++ .../v1/client/ITMeasureMetadataRegistryTest.java | 17 ++- .../v1/client/ITPropertyMetadataRegistryTest.java | 114 ++++++++++++++++++ 15 files changed, 642 insertions(+), 213 deletions(-) diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java deleted file mode 100644 index 187216f..0000000 --- a/.mvn/wrapper/MavenWrapperDownloader.java +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Copyright 2007-present the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import java.net.*; -import java.io.*; -import java.nio.channels.*; -import java.util.Properties; - -public class MavenWrapperDownloader { - - private static final String WRAPPER_VERSION = "0.5.5"; - /** - * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. - */ - private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" - + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; - - /** - * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to - * use instead of the default one. - */ - private static final String MAVEN_WRAPPER_PROPERTIES_PATH = - ".mvn/wrapper/maven-wrapper.properties"; - - /** - * Path where the maven-wrapper.jar will be saved to. - */ - private static final String MAVEN_WRAPPER_JAR_PATH = - ".mvn/wrapper/maven-wrapper.jar"; - - /** - * Name of the property which should be used to override the default download url for the wrapper. - */ - private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; - - public static void main(String args[]) { - System.out.println("- Downloader started"); - File baseDirectory = new File(args[0]); - System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); - - // If the maven-wrapper.properties exists, read it and check if it contains a custom - // wrapperUrl parameter. - File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); - String url = DEFAULT_DOWNLOAD_URL; - if(mavenWrapperPropertyFile.exists()) { - FileInputStream mavenWrapperPropertyFileInputStream = null; - try { - mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); - Properties mavenWrapperProperties = new Properties(); - mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); - url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); - } catch (IOException e) { - System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); - } finally { - try { - if(mavenWrapperPropertyFileInputStream != null) { - mavenWrapperPropertyFileInputStream.close(); - } - } catch (IOException e) { - // Ignore ... - } - } - } - System.out.println("- Downloading from: " + url); - - File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); - if(!outputFile.getParentFile().exists()) { - if(!outputFile.getParentFile().mkdirs()) { - System.out.println( - "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); - } - } - System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); - try { - downloadFileFromURL(url, outputFile); - System.out.println("Done"); - System.exit(0); - } catch (Throwable e) { - System.out.println("- Error downloading"); - e.printStackTrace(); - System.exit(1); - } - } - - private static void downloadFileFromURL(String urlString, File destination) throws Exception { - if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { - String username = System.getenv("MVNW_USERNAME"); - char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); - Authenticator.setDefault(new Authenticator() { - @Override - protected PasswordAuthentication getPasswordAuthentication() { - return new PasswordAuthentication(username, password); - } - }); - } - URL website = new URL(urlString); - ReadableByteChannel rbc; - rbc = Channels.newChannel(website.openStream()); - FileOutputStream fos = new FileOutputStream(destination); - fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); - fos.close(); - rbc.close(); - } - -} diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties deleted file mode 100644 index 54ab0bc..0000000 --- a/.mvn/wrapper/maven-wrapper.properties +++ /dev/null @@ -1,3 +0,0 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.1/apache-maven-3.6.1-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.5/maven-wrapper-0.5.5.jar - diff --git a/CHANGES.md b/CHANGES.md index 8543da4..7f652d3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,7 @@ Release Notes. * Bump up the API to support the new property. * Bump up the API to adopt the status field which is changed to the string type due to the compatibility issue. * Bump up the API to support getting the API version. +* Bump up the API to support the lifecycle management. 0.7.0 ------------------ diff --git a/README.md b/README.md index 402e187..0b07cdf 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,62 @@ client.define(g); Then we may define a stream with customized configurations. +#### Define a how-warm-cold Group + +Here illustrates how to use the lifecycle stages feature for hot-warm-cold data architecture: + +```java +// build a group sw_record for Stream with hot-warm-cold lifecycle stages +Group g = Group.newBuilder().setMetadata(Metadata.newBuilder().setName("sw_record")) + .setCatalog(Catalog.CATALOG_STREAM) + .setResourceOpts(ResourceOpts.newBuilder() + // Hot configuration + .setShardNum(3) + // Default segment interval (will be overridden by stages if defined) + .setSegmentInterval( + IntervalRule.newBuilder() + .setUnit(IntervalRule.Unit.UNIT_DAY) + .setNum(1)) + // Default TTL (will be overridden by stages if defined) + .setTtl( + IntervalRule.newBuilder() + .setUnit(IntervalRule.Unit.UNIT_DAY) + .setNum(3)) + // Define lifecycle stages (hot → warm → cold) + .addStages(LifecycleStage.newBuilder() + .setName("warm") + .setShardNum(2) // Fewer shards + .setSegmentInterval(IntervalRule.newBuilder() + .setUnit(IntervalRule.Unit.UNIT_DAY) + .setNum(1)) + .setTtl(IntervalRule.newBuilder() + .setUnit(IntervalRule.Unit.UNIT_DAY) + .setNum(7)) // Keep in warm for 7 days + .setNodeSelector("hdd-nodes") // Store on cheaper HDD nodes + .build()) + .addStages(LifecycleStage.newBuilder() + .setName("cold") + .setShardNum(1) // Minimal shards for archived data + .setSegmentInterval(IntervalRule.newBuilder() + .setUnit(IntervalRule.Unit.UNIT_DAY) + .setNum(7)) // Larger segments for cold data + .setTtl(IntervalRule.newBuilder() + .setUnit(IntervalRule.Unit.UNIT_DAY) + .setNum(30)) // Keep in cold for 30 more days + .setNodeSelector("archive-nodes") // Store on archive nodes + .setClose(true) // Close segments that are no longer live + .build())) + .build(); +client.define(g); +``` + +This configuration creates a hot-warm-cold architecture where: +- Hot stage: Data is stored on fast SSD nodes with many shards for 1 day, optimized for high query performance +- Warm stage: Data moves to HDD nodes with fewer shards for 7 days, balanced between performance and cost +- Cold stage: Data finally moves to archive nodes with minimal shards for 30 days, optimized for storage efficiency + +Data automatically flows through these stages according to the defined TTLs. The total retention of data is 38 days (1+7+30). + #### Define a Stream ```java // build a stream trace with above group @@ -469,68 +525,64 @@ MeasureWrite measureWrite = client.createMeasureWrite("sw_metric", "service_cpm_ CompletableFuture<Void> f = measureBulkWriteProcessor.add(measureWrite); f.get(10, TimeUnit.SECONDS); ``` +# Property APIs -## Property APIs - -Property APIs are used to store key-value pairs. - -### Apply(Create/Update) - -`apply` will always succeed whenever the property exists or not. -The old value will be overwritten if already existed, otherwise a new value will be set. +Before using properties, you need to define a property schema: ```java -Property property = Property.create("default", "sw", "ui_template") - .addTag(TagAndValue.newStringTag("name", "hello")) - .addTag(TagAndValue.newStringTag("state", "successd")) - .build(); -this.client.apply(property); //created:true tagsNum:2 +// Define property schema +BanyandbDatabase.Property propertyDef = + BanyandbDatabase.Property.newBuilder() + .setMetadata(Metadata.newBuilder() + .setGroup("default") + .setName("ui_template")) + .setTagType(TagType.TAG_TYPE_STRING) + .build(); + +client.define(propertyDef); ``` -The operation supports updating partial tags. +After defining the schema, you can apply (create/update) properties: ```java -Property property = Property.create("default", "sw", "ui_template") - .addTag(TagAndValue.newStringTag("state", "failed")) +// Apply a property (create or update) +Property property = Property.newBuilder() + .setMetadata(Metadata.newBuilder() + .setGroup("default") + .setName("ui_template")) + .setId("dashboard-1") + .setTagValue(BanyandbModel.TagValue.newBuilder() + .setStr("template-data-json")) .build(); -this.client.apply(property); //created:false tagsNum:1 -``` - -### Query -Property can be queried via `Client.findProperty`, - -```java - BanyandbProperty.QueryResponse resp = client.query(BanyandbProperty.QueryRequest.newBuilder() - .addGroups("default") - .setContainer("sw") - .addIds("ui_template") - .build()); +ApplyResponse response = client.apply(property); ``` -The query operation could filter tags, +You can also apply with a specific strategy: ```java -BanyandbProperty.QueryResponse resp = client.query(BanyandbProperty.QueryRequest.newBuilder() - .addGroups("default") - .setContainer("sw") - .addIds("ui_template") - .addTagProjection("state") - .build()); +// Apply with merge strategy +ApplyResponse response = client.apply(property, Strategy.STRATEGY_MERGE); ``` -### Delete - -Property can be deleted by calling `Client.deleteProperty`, +Query properties: ```java -this.client.deleteProperty("default", "sw", "ui_template"); //deleted:true tagsNum:2 +// Query properties +BanyandbProperty.QueryRequest queryRequest = BanyandbProperty.QueryRequest.newBuilder() + .setMetadata(Metadata.newBuilder() + .setGroup("default") + .setName("ui_template")) + .build(); + +BanyandbProperty.QueryResponse queryResponse = client.query(queryRequest); ``` -The delete operation could remove specific tags instead of the whole property. +Delete a property: ```java -this.client.deleteProperty("default", "sw", "ui_template", "state"); //deleted:true tagsNum:1 +// Delete a property +DeleteResponse deleteResponse = client.deleteProperty("default", "ui_template", "dashboard-1"); ``` # Compiling project diff --git a/src/main/java/org/apache/skywalking/banyandb/v1/client/BanyanDBClient.java b/src/main/java/org/apache/skywalking/banyandb/v1/client/BanyanDBClient.java index 912a14f..cdea7bb 100644 --- a/src/main/java/org/apache/skywalking/banyandb/v1/client/BanyanDBClient.java +++ b/src/main/java/org/apache/skywalking/banyandb/v1/client/BanyanDBClient.java @@ -62,6 +62,7 @@ import org.apache.skywalking.banyandb.v1.client.metadata.IndexRuleBindingMetadat import org.apache.skywalking.banyandb.v1.client.metadata.IndexRuleMetadataRegistry; import org.apache.skywalking.banyandb.v1.client.metadata.MeasureMetadataRegistry; import org.apache.skywalking.banyandb.v1.client.metadata.MetadataCache; +import org.apache.skywalking.banyandb.v1.client.metadata.PropertyMetadataRegistry; import org.apache.skywalking.banyandb.v1.client.metadata.ResourceExist; import org.apache.skywalking.banyandb.v1.client.metadata.StreamMetadataRegistry; import org.apache.skywalking.banyandb.v1.client.metadata.TopNAggregationMetadataRegistry; @@ -812,6 +813,64 @@ public class BanyanDBClient implements Closeable { return registry.list(group); } + /** + * Define a new property. + * + * @param property the property to be stored in the BanyanBD + * @throws BanyanDBException if the property is invalid + */ + public void define(org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property property) throws BanyanDBException { + PropertyMetadataRegistry registry = new PropertyMetadataRegistry(checkNotNull(this.channel)); + registry.create(property); + } + + /** + * Update the property. + * + * @param property the property to be stored in the BanyanBD + * @throws BanyanDBException if the property is invalid + */ + public void update(org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property property) throws BanyanDBException { + PropertyMetadataRegistry registry = new PropertyMetadataRegistry(checkNotNull(this.channel)); + registry.update(property); + } + + /** + * Find the property with given group and name + * + * @param group group of the metadata + * @param name name of the metadata + * @return the property found in BanyanDB. Otherwise, null is returned. + */ + public org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property findPropertyDefinition(String group, String name) throws BanyanDBException { + PropertyMetadataRegistry registry = new PropertyMetadataRegistry(checkNotNull(this.channel)); + return registry.get(group, name); + } + + /** + * Find the properties with given group + * + * @param group group of the metadata + * @return the properties found in BanyanDB + */ + public List<org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property> findPropertiesDefinition(String group) throws BanyanDBException { + PropertyMetadataRegistry registry = new PropertyMetadataRegistry(checkNotNull(this.channel)); + return registry.list(group); + } + + /** + * Delete the property + * + * @param group group of the metadata + * @param name name of the metadata + * @param id identity of the property + * @return if this property has been deleted + */ + public boolean deletePropertyDefinition(String group, String name) throws BanyanDBException { + PropertyMetadataRegistry registry = new PropertyMetadataRegistry(checkNotNull(this.channel)); + return registry.delete(group, name); + } + /** * Apply(Create or update) the property with {@link BanyandbProperty.ApplyRequest.Strategy#STRATEGY_MERGE} * @@ -1046,6 +1105,20 @@ public class BanyanDBClient implements Closeable { return new TopNAggregationMetadataRegistry(checkNotNull(this.channel)).exist(group, name); } + /** + * Check if the given property exists. + * + * @param group group of the property + * @param name name of the property + * @return ResourceExist which indicates whether group and property exist + */ + public ResourceExist existProperty(String group, String name) throws BanyanDBException { + Preconditions.checkArgument(!Strings.isNullOrEmpty(group)); + Preconditions.checkArgument(!Strings.isNullOrEmpty(name)); + + return new PropertyMetadataRegistry(checkNotNull(this.channel)).exist(group, name); + } + /** * Update the stream metadata cache from the server * @param group the group of the stream diff --git a/src/main/java/org/apache/skywalking/banyandb/v1/client/PropertyStore.java b/src/main/java/org/apache/skywalking/banyandb/v1/client/PropertyStore.java index 8e2fbe3..638ee8c 100644 --- a/src/main/java/org/apache/skywalking/banyandb/v1/client/PropertyStore.java +++ b/src/main/java/org/apache/skywalking/banyandb/v1/client/PropertyStore.java @@ -54,7 +54,7 @@ public class PropertyStore { return HandleExceptionsWith.callAndTranslateApiException(() -> this.stub.delete(BanyandbProperty.DeleteRequest.newBuilder() .setGroup(group) - .setContainer(name) + .setName(name) .setId(id) .build())); } diff --git a/src/main/java/org/apache/skywalking/banyandb/v1/client/metadata/PropertyMetadataRegistry.java b/src/main/java/org/apache/skywalking/banyandb/v1/client/metadata/PropertyMetadataRegistry.java new file mode 100644 index 0000000..efbb4bc --- /dev/null +++ b/src/main/java/org/apache/skywalking/banyandb/v1/client/metadata/PropertyMetadataRegistry.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.banyandb.v1.client.metadata; + +import io.grpc.Channel; +import org.apache.skywalking.banyandb.common.v1.BanyandbCommon; +import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase; +import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property; +import org.apache.skywalking.banyandb.database.v1.PropertyRegistryServiceGrpc; +import org.apache.skywalking.banyandb.v1.client.grpc.MetadataClient; +import org.apache.skywalking.banyandb.v1.client.grpc.exception.BanyanDBException; + +import java.util.List; + +public class PropertyMetadataRegistry extends MetadataClient<PropertyRegistryServiceGrpc.PropertyRegistryServiceBlockingStub, + BanyandbDatabase.Property> { + + public PropertyMetadataRegistry(Channel channel) { + super(PropertyRegistryServiceGrpc.newBlockingStub(channel)); + } + + @Override + public long create(final Property payload) throws BanyanDBException { + BanyandbDatabase.PropertyRegistryServiceCreateResponse resp = execute(() -> + stub.create(BanyandbDatabase.PropertyRegistryServiceCreateRequest.newBuilder() + .setProperty(payload) + .build())); + return resp.getModRevision(); + } + + @Override + public void update(final Property payload) throws BanyanDBException { + execute(() -> + stub.update(BanyandbDatabase.PropertyRegistryServiceUpdateRequest.newBuilder() + .setProperty(payload) + .build())); + } + + @Override + public boolean delete(final String group, final String name) throws BanyanDBException { + BanyandbDatabase.PropertyRegistryServiceDeleteResponse resp = execute(() -> + stub.delete(BanyandbDatabase.PropertyRegistryServiceDeleteRequest.newBuilder() + .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build()) + .build())); + return resp != null && resp.getDeleted(); + } + + @Override + public Property get(final String group, final String name) throws BanyanDBException { + BanyandbDatabase.PropertyRegistryServiceGetResponse resp = execute(() -> + stub.get(BanyandbDatabase.PropertyRegistryServiceGetRequest.newBuilder() + .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build()) + .build())); + + return resp.getProperty(); + } + + @Override + public ResourceExist exist(String group, String name) throws BanyanDBException { + BanyandbDatabase.PropertyRegistryServiceExistResponse resp = execute(() -> + stub.exist(BanyandbDatabase.PropertyRegistryServiceExistRequest.newBuilder() + .setMetadata(BanyandbCommon.Metadata.newBuilder().setGroup(group).setName(name).build()) + .build())); + return ResourceExist.create(resp.getHasGroup(), resp.getHasProperty()); + } + + @Override + public List<Property> list(final String group) throws BanyanDBException { + BanyandbDatabase.PropertyRegistryServiceListResponse resp = execute(() -> + stub.list(BanyandbDatabase.PropertyRegistryServiceListRequest.newBuilder() + .setGroup(group) + .build())); + + return resp.getPropertiesList(); + } +} diff --git a/src/main/proto/banyandb/v1/banyandb-common.proto b/src/main/proto/banyandb/v1/banyandb-common.proto index 75aa950..979bf6c 100644 --- a/src/main/proto/banyandb/v1/banyandb-common.proto +++ b/src/main/proto/banyandb/v1/banyandb-common.proto @@ -58,6 +58,31 @@ message IntervalRule { uint32 num = 2 [(validate.rules).uint32.gt = 0]; } +message LifecycleStage { + // The stage name (e.g., "warm", "cold"). + // This should be a non-empty string. + string name = 1 [(validate.rules).string.min_len = 1]; + + // Number of shards allocated for this stage. + // Must be greater than zero. + uint32 shard_num = 2 [(validate.rules).uint32.gt = 0]; + + // Defines the interval for data segmentation in this stage. + // This is a required field and uses the IntervalRule structure. + IntervalRule segment_interval = 3 [(validate.rules).message.required = true]; + + // Specifies the time-to-live for data in this stage before moving to the next. + // This is also a required field using the IntervalRule structure. + IntervalRule ttl = 4 [(validate.rules).message.required = true]; + + // Node selector specifying target nodes for this stage. + // Optional; if provided, it must be a non-empty string. + string node_selector = 5 [(validate.rules).string.min_len = 1]; + + // Indicates whether segments that are no longer live should be closed. + bool close = 6; +} + message ResourceOpts { // shard_num is the number of shards uint32 shard_num = 1 [(validate.rules).uint32.gt = 0]; @@ -65,6 +90,10 @@ message ResourceOpts { IntervalRule segment_interval = 2; // ttl indicates time to live, how long the data will be cached IntervalRule ttl = 3; + // stages defines the ordered lifecycle stages. Data progresses through these stages sequentially. + repeated LifecycleStage stages = 4; + // default_node_selector is the default node selector for queries if node_selector is not specified + string default_node_selector = 5; } // Group is an internal object for Group management @@ -79,7 +108,6 @@ message Group { google.protobuf.Timestamp updated_at = 4; } - // Trace is the top level message of a trace. message Trace { // trace_id is the unique identifier of the trace. diff --git a/src/main/proto/banyandb/v1/banyandb-database.proto b/src/main/proto/banyandb/v1/banyandb-database.proto index 2426849..01ce14d 100644 --- a/src/main/proto/banyandb/v1/banyandb-database.proto +++ b/src/main/proto/banyandb/v1/banyandb-database.proto @@ -94,8 +94,6 @@ message FieldSpec { EncodingMethod encoding_method = 3 [(validate.rules).enum.defined_only = true]; // compression_method indicates how to compress data during writing CompressionMethod compression_method = 4 [(validate.rules).enum.defined_only = true]; - // aggregate_function indicates how to aggregate data - model.v1.MeasureAggregate aggregate_function = 5; } // Measure intends to store data point @@ -118,13 +116,6 @@ message Measure { bool index_mode = 7; } -message MeasureAggregateFunction { - // type indicates the type of function argument - FieldType type = 1 [(validate.rules).enum.defined_only = true]; - // aggregate_function indicates specific function for measure data - model.v1.MeasureAggregate aggregate_function = 2; -} - // TopNAggregation generates offline TopN statistics for a measure's TopN approximation message TopNAggregation { // metadata is the identity of an aggregation @@ -211,6 +202,15 @@ message IndexRuleBinding { google.protobuf.Timestamp updated_at = 6; } +// Property stores the user defined data +message Property { + // metadata is the identity of a property + common.v1.Metadata metadata = 1 [(validate.rules).message.required = true]; + // tag stores the content of a property + repeated TagSpec tags = 2 [(validate.rules).repeated.min_items = 1]; + // updated_at indicates when the property is updated + google.protobuf.Timestamp updated_at = 6; +} message StreamRegistryServiceCreateRequest { banyandb.database.v1.Stream stream = 1; @@ -263,11 +263,15 @@ message StreamRegistryServiceListResponse { service StreamRegistryService { rpc Create(StreamRegistryServiceCreateRequest) returns (StreamRegistryServiceCreateResponse); + rpc Update(StreamRegistryServiceUpdateRequest) returns (StreamRegistryServiceUpdateResponse); + rpc Delete(StreamRegistryServiceDeleteRequest) returns (StreamRegistryServiceDeleteResponse); + rpc Get(StreamRegistryServiceGetRequest) returns (StreamRegistryServiceGetResponse); + rpc List(StreamRegistryServiceListRequest) returns (StreamRegistryServiceListResponse); - // Exist doesn't expose an HTTP endpoint. Please use HEAD method to touch Get instead + rpc Exist(StreamRegistryServiceExistRequest) returns (StreamRegistryServiceExistResponse); } @@ -275,15 +279,13 @@ message IndexRuleBindingRegistryServiceCreateRequest { banyandb.database.v1.IndexRuleBinding index_rule_binding = 1; } -message IndexRuleBindingRegistryServiceCreateResponse { -} +message IndexRuleBindingRegistryServiceCreateResponse {} message IndexRuleBindingRegistryServiceUpdateRequest { banyandb.database.v1.IndexRuleBinding index_rule_binding = 1; } -message IndexRuleBindingRegistryServiceUpdateResponse { -} +message IndexRuleBindingRegistryServiceUpdateResponse {} message IndexRuleBindingRegistryServiceDeleteRequest { banyandb.common.v1.Metadata metadata = 1; @@ -556,3 +558,89 @@ service TopNAggregationRegistryService { rpc List(TopNAggregationRegistryServiceListRequest) returns (TopNAggregationRegistryServiceListResponse); rpc Exist(TopNAggregationRegistryServiceExistRequest) returns (TopNAggregationRegistryServiceExistResponse); } + + +message SnapshotRequest { + message Group { + common.v1.Catalog catalog = 1; + string group = 2; + } + repeated Group groups = 1; +} + +message Snapshot { + common.v1.Catalog catalog = 1; + string name = 2; + string error = 3; +} + +message SnapshotResponse { + repeated Snapshot snapshots = 1; +} + +service SnapshotService { + rpc Snapshot(SnapshotRequest) returns (SnapshotResponse); +} + +message PropertyRegistryServiceCreateRequest { + banyandb.database.v1.Property property = 1; +} + +message PropertyRegistryServiceCreateResponse { + int64 mod_revision = 1; +} + +message PropertyRegistryServiceUpdateRequest { + banyandb.database.v1.Property property = 1; +} + +message PropertyRegistryServiceUpdateResponse { + int64 mod_revision = 1; +} + +message PropertyRegistryServiceDeleteRequest { + banyandb.common.v1.Metadata metadata = 1; +} + +message PropertyRegistryServiceDeleteResponse { + bool deleted = 1; +} + +message PropertyRegistryServiceGetRequest { + banyandb.common.v1.Metadata metadata = 1; +} + +message PropertyRegistryServiceGetResponse { + banyandb.database.v1.Property property = 1; +} + +message PropertyRegistryServiceListRequest { + string group = 1; +} + +message PropertyRegistryServiceListResponse { + repeated banyandb.database.v1.Property properties = 1; +} + +message PropertyRegistryServiceExistRequest { + banyandb.common.v1.Metadata metadata = 1; +} + +message PropertyRegistryServiceExistResponse { + bool has_group = 1; + bool has_property = 2; +} + +service PropertyRegistryService { + rpc Create(PropertyRegistryServiceCreateRequest) returns (PropertyRegistryServiceCreateResponse); + + rpc Update(PropertyRegistryServiceUpdateRequest) returns (PropertyRegistryServiceUpdateResponse); + + rpc Delete(PropertyRegistryServiceDeleteRequest) returns (PropertyRegistryServiceDeleteResponse); + + rpc Get(PropertyRegistryServiceGetRequest) returns (PropertyRegistryServiceGetResponse); + + rpc List(PropertyRegistryServiceListRequest) returns (PropertyRegistryServiceListResponse); + + rpc Exist(PropertyRegistryServiceExistRequest) returns (PropertyRegistryServiceExistResponse); +} diff --git a/src/main/proto/banyandb/v1/banyandb-property.proto b/src/main/proto/banyandb/v1/banyandb-property.proto index 357a82c..2f00370 100644 --- a/src/main/proto/banyandb/v1/banyandb-property.proto +++ b/src/main/proto/banyandb/v1/banyandb-property.proto @@ -26,22 +26,16 @@ import "validate/validate.proto"; import "banyandb/v1/banyandb-common.proto"; import "banyandb/v1/banyandb-model.proto"; -// Metadata is for multi-tenant use -message Metadata { - // container is created when it receives the first property - common.v1.Metadata container = 1 [(validate.rules).message.required = true]; - // id identifies a property - string id = 2 [(validate.rules).string.min_len = 1]; -} - // Property stores the user defined data message Property { // metadata is the identity of a property - Metadata metadata = 1 [(validate.rules).message.required = true]; + common.v1.Metadata metadata = 1 [(validate.rules).message.required = true]; + // id is the identity of a property + string id = 2 [(validate.rules).string.min_len = 1]; // tag stores the content of a property - repeated model.v1.Tag tags = 2 [(validate.rules).repeated.min_items = 1]; + repeated model.v1.Tag tags = 3 [(validate.rules).repeated.min_items = 1]; // updated_at indicates when the property is updated - google.protobuf.Timestamp updated_at = 3; + google.protobuf.Timestamp updated_at = 4; } message ApplyRequest { @@ -65,9 +59,9 @@ message ApplyResponse { message DeleteRequest { // groups indicate where the data points are stored. string group = 1 [(validate.rules).string.min_len = 1]; - // container is created when it receives the first property - string container = 2 [(validate.rules).string.min_len = 1]; - // id is the identity of properties + // name is the identity of a property. + string name = 2 [(validate.rules).string.min_len = 1]; + // id is the identity of item in the property. string id = 3; } @@ -79,8 +73,8 @@ message DeleteResponse { message QueryRequest { // groups indicate where the data points are stored. repeated string groups = 1 [(validate.rules).repeated.min_items = 1]; - // container is created when it receives the first property - string container = 2; + // name is created when it receives the first property + string name = 2; // ids is the identities of properties repeated string ids = 3; // criteria is used to filter properties based on tags @@ -90,6 +84,8 @@ message QueryRequest { uint32 limit = 6; // trace is used to enable trace for the query bool trace = 7; + // node_selector is used to select the node to query + string node_selector = 8; } // QueryResponse is the response for a query to the Query module. diff --git a/src/test/java/org/apache/skywalking/banyandb/v1/client/BanyanDBClientTestCI.java b/src/test/java/org/apache/skywalking/banyandb/v1/client/BanyanDBClientTestCI.java index a73a404..ca826a3 100644 --- a/src/test/java/org/apache/skywalking/banyandb/v1/client/BanyanDBClientTestCI.java +++ b/src/test/java/org/apache/skywalking/banyandb/v1/client/BanyanDBClientTestCI.java @@ -31,7 +31,7 @@ import java.io.IOException; public class BanyanDBClientTestCI { private static final String REGISTRY = "ghcr.io"; private static final String IMAGE_NAME = "apache/skywalking-banyandb"; - private static final String TAG = "362d68ed79c532e6d61dd6674cce38090caa0da7"; + private static final String TAG = "53b3be42d162e2f4ef0c667dc30f25e42ff17d70"; private static final String IMAGE = REGISTRY + "/" + IMAGE_NAME + ":" + TAG; diff --git a/src/test/java/org/apache/skywalking/banyandb/v1/client/ITBanyanDBPropertyTests.java b/src/test/java/org/apache/skywalking/banyandb/v1/client/ITBanyanDBPropertyTests.java index 828284a..af9b9aa 100644 --- a/src/test/java/org/apache/skywalking/banyandb/v1/client/ITBanyanDBPropertyTests.java +++ b/src/test/java/org/apache/skywalking/banyandb/v1/client/ITBanyanDBPropertyTests.java @@ -33,6 +33,8 @@ import org.apache.skywalking.banyandb.common.v1.BanyandbCommon; import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Group; import org.apache.skywalking.banyandb.property.v1.BanyandbProperty.Property; import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Metadata; +import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TagSpec; +import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TagType; import static org.awaitility.Awaitility.await; @@ -49,6 +51,21 @@ public class ITBanyanDBPropertyTests extends BanyanDBClientTestCI { .build(); client.define(expectedGroup); Assert.assertNotNull(expectedGroup); + org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property expectedProperty = + org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property.newBuilder() + .setMetadata( + BanyandbCommon.Metadata.newBuilder() + .setGroup("default") + .setName("sw") + .build()) + .addTags( + TagSpec.newBuilder() + .setName("name") + .setType( + TagType.TAG_TYPE_STRING)) + .build(); + client.define(expectedProperty); + Assert.assertNotNull(expectedProperty); } @After @@ -66,7 +83,7 @@ public class ITBanyanDBPropertyTests extends BanyanDBClientTestCI { await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { BanyandbProperty.QueryResponse resp = client.query(BanyandbProperty.QueryRequest.newBuilder() .addGroups("default") - .setContainer("sw") + .setName("sw") .addIds("ui_template") .build()); Assert.assertEquals(1, resp.getPropertiesCount()); @@ -86,7 +103,7 @@ public class ITBanyanDBPropertyTests extends BanyanDBClientTestCI { await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { BanyandbProperty.QueryResponse resp = client.query(BanyandbProperty.QueryRequest.newBuilder() .addGroups("default") - .setContainer("sw") + .setName("sw") .addIds("ui_template") .build()); Assert.assertEquals(1, resp.getPropertiesCount()); @@ -98,7 +115,7 @@ public class ITBanyanDBPropertyTests extends BanyanDBClientTestCI { Assert.assertTrue(this.client.deleteProperty("default", "sw", "ui_template").getDeleted()); BanyandbProperty.QueryResponse resp = client.query(BanyandbProperty.QueryRequest.newBuilder() .addGroups("default") - .setContainer("sw") + .setName("sw") .addIds("ui_template") .build()); Assert.assertEquals(0, resp.getPropertiesCount()); @@ -119,7 +136,7 @@ public class ITBanyanDBPropertyTests extends BanyanDBClientTestCI { await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { BanyandbProperty.QueryResponse resp = client.query(BanyandbProperty.QueryRequest.newBuilder() .addGroups("default") - .setContainer("sw") + .setName("sw") .addIds("ui_template") .build()); Assert.assertEquals(1, resp.getPropertiesCount()); @@ -143,14 +160,14 @@ public class ITBanyanDBPropertyTests extends BanyanDBClientTestCI { await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { BanyandbProperty.QueryResponse resp = client.query(BanyandbProperty.QueryRequest.newBuilder() .addGroups("default") - .setContainer("sw") + .setName("sw") .build()); Assert.assertEquals(2, resp.getPropertiesCount()); }); await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { BanyandbProperty.QueryResponse resp = client.query(BanyandbProperty.QueryRequest.newBuilder() .addGroups("default") - .setContainer("sw") + .setName("sw") .addIds("id1") .addIds("id2") .build()); @@ -159,7 +176,7 @@ public class ITBanyanDBPropertyTests extends BanyanDBClientTestCI { await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> { BanyandbProperty.QueryResponse resp = client.query(BanyandbProperty.QueryRequest.newBuilder() .addGroups("default") - .setContainer("sw") + .setName("sw") .addIds("id2") .build()); Assert.assertEquals(1, resp.getPropertiesCount()); @@ -169,14 +186,12 @@ public class ITBanyanDBPropertyTests extends BanyanDBClientTestCI { private BanyandbProperty.Property buildProperty(String group, String name, String id) { BanyandbProperty.Property.Builder builder = BanyandbProperty.Property.newBuilder() .setMetadata( - BanyandbProperty.Metadata.newBuilder() - .setContainer( - BanyandbCommon.Metadata.newBuilder() - .setGroup( - group) - .setName( - name)) - .setId(id)); + BanyandbCommon.Metadata.newBuilder() + .setGroup( + group) + .setName( + name).build()) + .setId(id); return builder.build(); } } diff --git a/src/test/java/org/apache/skywalking/banyandb/v1/client/ITGroupMetadataRegistryTest.java b/src/test/java/org/apache/skywalking/banyandb/v1/client/ITGroupMetadataRegistryTest.java index dfe5a18..c9387d3 100644 --- a/src/test/java/org/apache/skywalking/banyandb/v1/client/ITGroupMetadataRegistryTest.java +++ b/src/test/java/org/apache/skywalking/banyandb/v1/client/ITGroupMetadataRegistryTest.java @@ -25,6 +25,7 @@ import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Catalog; import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.IntervalRule; import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.ResourceOpts; import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Metadata; +import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.LifecycleStage; import org.apache.skywalking.banyandb.v1.client.grpc.exception.BanyanDBException; import org.junit.After; import org.junit.Assert; @@ -96,6 +97,86 @@ public class ITGroupMetadataRegistryTest extends BanyanDBClientTestCI { Assert.assertNull(client.findGroup("sw_metric")); } + @Test + public void testGroupRegistry_hotWarmCold() throws BanyanDBException { + Group g = Group.newBuilder().setMetadata(Metadata.newBuilder().setName("sw_record")) + .setCatalog(Catalog.CATALOG_STREAM) + .setResourceOpts(ResourceOpts.newBuilder() + .setShardNum(3) + .setSegmentInterval( + IntervalRule.newBuilder() + .setUnit(IntervalRule.Unit.UNIT_DAY) + .setNum(1)) + .setTtl( + IntervalRule.newBuilder() + .setUnit(IntervalRule.Unit.UNIT_DAY) + .setNum(3)) + .addStages(LifecycleStage.newBuilder() + .setName("warm") + .setShardNum(2) + .setSegmentInterval(IntervalRule.newBuilder() + .setUnit(IntervalRule.Unit.UNIT_DAY) + .setNum(1)) + .setTtl(IntervalRule.newBuilder() + .setUnit(IntervalRule.Unit.UNIT_DAY) + .setNum(7)) + .setNodeSelector("hdd-nodes") + .build()) + .addStages(LifecycleStage.newBuilder() + .setName("cold") + .setShardNum(1) + .setSegmentInterval(IntervalRule.newBuilder() + .setUnit(IntervalRule.Unit.UNIT_DAY) + .setNum(7)) + .setTtl(IntervalRule.newBuilder() + .setUnit(IntervalRule.Unit.UNIT_DAY) + .setNum(30)) + .setNodeSelector("archive-nodes") + .setClose(true) + .build())) + .build(); + this.client.define(g); + Group actualGroup = client.findGroup("sw_record"); + Assert.assertNotNull(actualGroup); + Assert.assertNotNull(actualGroup.getUpdatedAt()); + + // Verify group exists + Assert.assertNotNull(actualGroup); + + // Verify basic metadata + Assert.assertEquals("sw_record", actualGroup.getMetadata().getName()); + Assert.assertEquals(Catalog.CATALOG_STREAM, actualGroup.getCatalog()); + + // Verify resource options + ResourceOpts actualOpts = actualGroup.getResourceOpts(); + Assert.assertEquals(3, actualOpts.getShardNum()); + Assert.assertEquals(IntervalRule.Unit.UNIT_DAY, actualOpts.getSegmentInterval().getUnit()); + Assert.assertEquals(1, actualOpts.getSegmentInterval().getNum()); + Assert.assertEquals(IntervalRule.Unit.UNIT_DAY, actualOpts.getTtl().getUnit()); + Assert.assertEquals(3, actualOpts.getTtl().getNum()); + + // Verify stages (should have 2 stages: warm and cold) + Assert.assertEquals(2, actualOpts.getStagesCount()); + + // Verify warm stage + LifecycleStage warmStage = actualOpts.getStages(0); + Assert.assertEquals("warm", warmStage.getName()); + Assert.assertEquals(2, warmStage.getShardNum()); + Assert.assertEquals(1, warmStage.getSegmentInterval().getNum()); + Assert.assertEquals(IntervalRule.Unit.UNIT_DAY, warmStage.getSegmentInterval().getUnit()); + Assert.assertEquals(7, warmStage.getTtl().getNum()); + Assert.assertEquals("hdd-nodes", warmStage.getNodeSelector()); + + // Verify cold stage + LifecycleStage coldStage = actualOpts.getStages(1); + Assert.assertEquals("cold", coldStage.getName()); + Assert.assertEquals(1, coldStage.getShardNum()); + Assert.assertEquals(7, coldStage.getSegmentInterval().getNum()); + Assert.assertEquals(30, coldStage.getTtl().getNum()); + Assert.assertEquals("archive-nodes", coldStage.getNodeSelector()); + Assert.assertTrue(coldStage.getClose()); + } + private Group buildGroup() { return Group.newBuilder().setMetadata(Metadata.newBuilder().setName("sw_metric")) .setCatalog(Catalog.CATALOG_MEASURE) diff --git a/src/test/java/org/apache/skywalking/banyandb/v1/client/ITMeasureMetadataRegistryTest.java b/src/test/java/org/apache/skywalking/banyandb/v1/client/ITMeasureMetadataRegistryTest.java index ebe75ed..36fba76 100644 --- a/src/test/java/org/apache/skywalking/banyandb/v1/client/ITMeasureMetadataRegistryTest.java +++ b/src/test/java/org/apache/skywalking/banyandb/v1/client/ITMeasureMetadataRegistryTest.java @@ -69,10 +69,19 @@ public class ITMeasureMetadataRegistryTest extends BanyanDBClientTestCI { this.client.define(expectedMeasure); List<Measure> actualMeasures = client.findMeasures("sw_metric"); Assert.assertNotNull(actualMeasures); - Assert.assertEquals(1, actualMeasures.size()); - Measure actualMeasure = actualMeasures.get(0); - actualMeasure = actualMeasure.toBuilder().clearUpdatedAt().setMetadata(actualMeasure.getMetadata().toBuilder().clearModRevision().clearCreateRevision()).build(); - Assert.assertEquals(expectedMeasure, actualMeasure); + // _topn_result is a system measure, so there should be 2 measures + Assert.assertEquals(2, actualMeasures.size()); + boolean found = false; + for (Measure actualMeasure : actualMeasures) { + if (actualMeasure.getMetadata().getName().equals("service_cpm_minute")) { + found = true; + actualMeasure = actualMeasure.toBuilder().clearUpdatedAt().setMetadata(actualMeasure.getMetadata().toBuilder().clearModRevision().clearCreateRevision()).build(); + Assert.assertEquals(expectedMeasure, actualMeasure); + } + } + if (!found) { + Assert.fail("service_cpm_minute not found"); + } } @Test diff --git a/src/test/java/org/apache/skywalking/banyandb/v1/client/ITPropertyMetadataRegistryTest.java b/src/test/java/org/apache/skywalking/banyandb/v1/client/ITPropertyMetadataRegistryTest.java new file mode 100644 index 0000000..ecfda2e --- /dev/null +++ b/src/test/java/org/apache/skywalking/banyandb/v1/client/ITPropertyMetadataRegistryTest.java @@ -0,0 +1,114 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package org.apache.skywalking.banyandb.v1.client; + +import java.io.IOException; +import java.util.List; + +import org.apache.skywalking.banyandb.common.v1.BanyandbCommon; +import org.apache.skywalking.banyandb.common.v1.BanyandbCommon.Metadata; +import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.Property; +import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TagSpec; +import org.apache.skywalking.banyandb.database.v1.BanyandbDatabase.TagType; +import org.apache.skywalking.banyandb.v1.client.grpc.exception.BanyanDBException; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +public class ITPropertyMetadataRegistryTest extends BanyanDBClientTestCI { + @Before + public void setUp() throws IOException, BanyanDBException, InterruptedException { + super.setUpConnection(); + BanyandbCommon.Group expectedGroup = buildPropertyGroup(); + client.define(expectedGroup); + Assert.assertNotNull(expectedGroup); + } + + @After + public void tearDown() throws IOException { + this.closeClient(); + } + + @Test + public void testPropertyRegistry_createAndGet() throws BanyanDBException { + Property expectedProperty = buildProperty(); + this.client.define(expectedProperty); + Property actualProperty = client.findPropertyDefinition("sw_config", "ui_template"); + Assert.assertNotNull(actualProperty); + Assert.assertNotNull(actualProperty.getUpdatedAt()); + actualProperty = actualProperty.toBuilder().clearUpdatedAt().setMetadata(actualProperty.getMetadata().toBuilder().clearModRevision().clearCreateRevision()).build(); + Assert.assertEquals(expectedProperty, actualProperty); + } + + @Test + public void testPropertyRegistry_createAndList() throws BanyanDBException { + Property expectedProperty = buildProperty(); + this.client.define(expectedProperty); + List<Property> actualProperties = client.findPropertiesDefinition("sw_config"); + Assert.assertNotNull(actualProperties); + Assert.assertEquals(1, actualProperties.size()); + Property actualProperty = actualProperties.get(0); + actualProperty = actualProperty.toBuilder().clearUpdatedAt().setMetadata(actualProperty.getMetadata().toBuilder().clearModRevision().clearCreateRevision()).build(); + Assert.assertEquals(expectedProperty, actualProperty); + } + + @Test + public void testPropertyRegistry_createAndDelete() throws BanyanDBException { + Property expectedProperty = buildProperty(); + this.client.define(expectedProperty); + boolean deleted = this.client.deletePropertyDefinition( + expectedProperty.getMetadata().getGroup(), expectedProperty.getMetadata().getName()); + Assert.assertTrue(deleted); + Assert.assertThrows( + org.apache.skywalking.banyandb.v1.client.grpc.exception.NotFoundException.class, + () -> client.findPropertyDefinition( + expectedProperty.getMetadata().getGroup(), expectedProperty.getMetadata().getName()) + ); + } + + private Property buildProperty() { + Property.Builder builder = Property.newBuilder() + .setMetadata(Metadata.newBuilder() + .setGroup("sw_config") + .setName("ui_template")) + .addTags( + TagSpec.newBuilder() + .setName("type") + .setType( + TagType.TAG_TYPE_STRING)) + .addTags( + TagSpec.newBuilder() + .setName("service") + .setType( + TagType.TAG_TYPE_STRING)); + return builder.build(); + } + + private BanyandbCommon.Group buildPropertyGroup() { + return BanyandbCommon.Group.newBuilder() + .setMetadata(BanyandbCommon.Metadata.newBuilder() + .setName("sw_config") + .build()) + .setCatalog(BanyandbCommon.Catalog.CATALOG_PROPERTY) + .setResourceOpts(BanyandbCommon.ResourceOpts.newBuilder() + .setShardNum(2)) + .build(); + } +}