All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.skywalking.banyandb.v1.client.BanyanDBClient Maven / Gradle / Ivy

/*
 * 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 com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.grpc.Status;
import io.grpc.stub.StreamObserver;
import lombok.AccessLevel;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.skywalking.banyandb.measure.v1.BanyandbMeasure;
import org.apache.skywalking.banyandb.measure.v1.MeasureServiceGrpc;
import org.apache.skywalking.banyandb.stream.v1.BanyandbStream;
import org.apache.skywalking.banyandb.stream.v1.StreamServiceGrpc;
import org.apache.skywalking.banyandb.v1.client.grpc.HandleExceptionsWith;
import org.apache.skywalking.banyandb.v1.client.grpc.channel.ChannelManager;
import org.apache.skywalking.banyandb.v1.client.grpc.channel.DefaultChannelFactory;
import org.apache.skywalking.banyandb.v1.client.grpc.exception.BanyanDBException;
import org.apache.skywalking.banyandb.v1.client.metadata.Group;
import org.apache.skywalking.banyandb.v1.client.metadata.GroupMetadataRegistry;
import org.apache.skywalking.banyandb.v1.client.metadata.IndexRule;
import org.apache.skywalking.banyandb.v1.client.metadata.IndexRuleBinding;
import org.apache.skywalking.banyandb.v1.client.metadata.IndexRuleBindingMetadataRegistry;
import org.apache.skywalking.banyandb.v1.client.metadata.IndexRuleMetadataRegistry;
import org.apache.skywalking.banyandb.v1.client.metadata.Measure;
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.Property;
import org.apache.skywalking.banyandb.v1.client.metadata.PropertyStore;
import org.apache.skywalking.banyandb.v1.client.metadata.ResourceExist;
import org.apache.skywalking.banyandb.v1.client.metadata.Stream;
import org.apache.skywalking.banyandb.v1.client.metadata.StreamMetadataRegistry;
import org.apache.skywalking.banyandb.v1.client.metadata.TopNAggregation;
import org.apache.skywalking.banyandb.v1.client.metadata.TopNAggregationMetadataRegistry;

import java.io.Closeable;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

/**
 * BanyanDBClient represents a client instance interacting with BanyanDB server.
 * This is built on the top of BanyanDB v1 gRPC APIs.
 *
 * 
{@code
 * // use `default` group
 * client = new BanyanDBClient("127.0.0.1", 17912);
 * // to send any request, a connection to the server must be estabilished
 * client.connect();
 * }
*/ @Slf4j public class BanyanDBClient implements Closeable { /** * The hostname of BanyanDB server. */ private final String host; /** * The port of BanyanDB server. */ private final int port; /** * Options for server connection. */ @Getter(value = AccessLevel.PACKAGE) private final Options options; /** * gRPC connection. */ @Getter(value = AccessLevel.PACKAGE) private volatile Channel channel; /** * gRPC client stub */ @Getter(value = AccessLevel.PACKAGE) private StreamServiceGrpc.StreamServiceStub streamServiceStub; /** * gRPC client stub */ @Getter(value = AccessLevel.PACKAGE) private MeasureServiceGrpc.MeasureServiceStub measureServiceStub; /** * gRPC future stub. */ @Getter(value = AccessLevel.PACKAGE) private StreamServiceGrpc.StreamServiceBlockingStub streamServiceBlockingStub; /** * gRPC future stub. */ @Getter(value = AccessLevel.PACKAGE) private MeasureServiceGrpc.MeasureServiceBlockingStub measureServiceBlockingStub; /** * The connection status. */ private volatile boolean isConnected = false; /** * A lock to control the race condition in establishing and disconnecting network connection. */ private final ReentrantLock connectionEstablishLock; /** * Client local metadata cache. */ private final MetadataCache metadataCache; /** * Create a BanyanDB client instance with a default options. * * @param host IP or domain name * @param port Server port */ public BanyanDBClient(String host, int port) { this(host, port, new Options()); } /** * Create a BanyanDB client instance with a customized options. * * @param host IP or domain name * @param port Server port * @param options customized options */ public BanyanDBClient(String host, int port, Options options) { this.host = host; this.port = port; this.options = options; this.connectionEstablishLock = new ReentrantLock(); this.metadataCache = new MetadataCache(); } /** * Construct a connection to the server. * * @throws IOException thrown if fail to create a connection */ public void connect() throws IOException { connectionEstablishLock.lock(); try { if (!isConnected) { this.channel = ChannelManager.create(this.options.buildChannelManagerSettings(), new DefaultChannelFactory(this.host, this.port, this.options)); streamServiceBlockingStub = StreamServiceGrpc.newBlockingStub(this.channel); measureServiceBlockingStub = MeasureServiceGrpc.newBlockingStub(this.channel); streamServiceStub = StreamServiceGrpc.newStub(this.channel); measureServiceStub = MeasureServiceGrpc.newStub(this.channel); isConnected = true; } } finally { connectionEstablishLock.unlock(); } } @VisibleForTesting void connect(Channel channel) { connectionEstablishLock.lock(); try { if (!isConnected) { this.channel = channel; streamServiceBlockingStub = StreamServiceGrpc.newBlockingStub(this.channel); measureServiceBlockingStub = MeasureServiceGrpc.newBlockingStub(this.channel); streamServiceStub = StreamServiceGrpc.newStub(this.channel); measureServiceStub = MeasureServiceGrpc.newStub(this.channel); isConnected = true; } } finally { connectionEstablishLock.unlock(); } } /** * Perform a single write with given entity. * * @param streamWrite the entity to be written */ public void write(StreamWrite streamWrite) { checkState(this.streamServiceStub != null, "stream service is null"); final StreamObserver writeRequestStreamObserver = this.streamServiceStub .withDeadlineAfter(this.getOptions().getDeadline(), TimeUnit.SECONDS) .write( new StreamObserver() { @Override public void onNext(BanyandbStream.WriteResponse writeResponse) { } @Override public void onError(Throwable throwable) { log.error("Error occurs in flushing streams.", throwable); } @Override public void onCompleted() { } }); try { writeRequestStreamObserver.onNext(streamWrite.build()); } finally { writeRequestStreamObserver.onCompleted(); } } /** * Create a build process for stream write. * * @param maxBulkSize the max bulk size for the flush operation * @param flushInterval if given maxBulkSize is not reached in this period, the flush would be trigger * automatically. Unit is second * @param concurrency the number of concurrency would run for the flush max * @return stream bulk write processor */ public StreamBulkWriteProcessor buildStreamWriteProcessor(int maxBulkSize, int flushInterval, int concurrency) { checkState(this.streamServiceStub != null, "stream service is null"); return new StreamBulkWriteProcessor(this.streamServiceStub, maxBulkSize, flushInterval, concurrency); } /** * Create a build process for measure write. * * @param maxBulkSize the max bulk size for the flush operation * @param flushInterval if given maxBulkSize is not reached in this period, the flush would be trigger * automatically. Unit is second * @param concurrency the number of concurrency would run for the flush max * @return stream bulk write processor */ public MeasureBulkWriteProcessor buildMeasureWriteProcessor(int maxBulkSize, int flushInterval, int concurrency) { checkState(this.measureServiceStub != null, "measure service is null"); return new MeasureBulkWriteProcessor(this.measureServiceStub, maxBulkSize, flushInterval, concurrency); } /** * Build a MeasureWrite request. * * @param group the group of the measure * @param name the name of the measure * @param timestamp the timestamp of the measure * @return the request to be built */ public MeasureWrite createMeasureWrite(String group, String name, long timestamp) { return new MeasureWrite(this.metadataCache.findMetadata(group, name), timestamp); } /** * Build a StreamWrite request. * * @param group the group of the stream * @param name the name of the stream * @param elementId the primary key of the stream * @return the request to be built */ public StreamWrite createStreamWrite(String group, String name, final String elementId) { return new StreamWrite(this.metadataCache.findMetadata(group, name), elementId); } /** * Build a StreamWrite request. * * @param group the group of the stream * @param name the name of the stream * @param elementId the primary key of the stream * @param timestamp the timestamp of the stream * @return the request to be built */ public StreamWrite createStreamWrite(String group, String name, final String elementId, long timestamp) { return new StreamWrite(this.metadataCache.findMetadata(group, name), elementId, timestamp); } /** * Query streams according to given conditions * * @param streamQuery condition for query * @return hint streams. */ public StreamQueryResponse query(StreamQuery streamQuery) throws BanyanDBException { checkState(this.streamServiceStub != null, "stream service is null"); final BanyandbStream.QueryResponse response = HandleExceptionsWith.callAndTranslateApiException(() -> this.streamServiceBlockingStub .withDeadlineAfter(this.getOptions().getDeadline(), TimeUnit.SECONDS) .query(streamQuery.build(this.metadataCache.findMetadata(streamQuery.group, streamQuery.name)))); return new StreamQueryResponse(response); } /** * Query TopN according to given conditions * * @param topNQuery condition for query * @return hint topN. */ public TopNQueryResponse query(TopNQuery topNQuery) throws BanyanDBException { checkState(this.measureServiceStub != null, "measure service is null"); final BanyandbMeasure.TopNResponse response = HandleExceptionsWith.callAndTranslateApiException(() -> this.measureServiceBlockingStub .withDeadlineAfter(this.getOptions().getDeadline(), TimeUnit.SECONDS) .topN(topNQuery.build())); return new TopNQueryResponse(response); } /** * Query measures according to given conditions * * @param measureQuery condition for query * @return hint measures. */ public MeasureQueryResponse query(MeasureQuery measureQuery) throws BanyanDBException { checkState(this.streamServiceStub != null, "measure service is null"); final BanyandbMeasure.QueryResponse response = HandleExceptionsWith.callAndTranslateApiException(() -> this.measureServiceBlockingStub .withDeadlineAfter(this.getOptions().getDeadline(), TimeUnit.SECONDS) .query(measureQuery.build(this.metadataCache.findMetadata(measureQuery.group, measureQuery.name)))); return new MeasureQueryResponse(response); } /** * Define a new group and attach to the current client. * * @param group the group to be created * @return a grouped client */ public Group define(Group group) throws BanyanDBException { GroupMetadataRegistry registry = new GroupMetadataRegistry(checkNotNull(this.channel)); registry.create(group); return registry.get(null, group.name()); } /** * Define a new stream * * @param stream the stream to be created */ public void define(Stream stream) throws BanyanDBException { StreamMetadataRegistry streamRegistry = new StreamMetadataRegistry(checkNotNull(this.channel)); streamRegistry.create(stream); defineIndexRules(stream, stream.indexRules()); this.metadataCache.register(stream); } /** * Define a new measure * * @param measure the measure to be created */ public void define(Measure measure) throws BanyanDBException { MeasureMetadataRegistry measureRegistry = new MeasureMetadataRegistry(checkNotNull(this.channel)); measureRegistry.create(measure); defineIndexRules(measure, measure.indexRules()); this.metadataCache.register(measure); } /** * Define a new TopNAggregation * * @param topNAggregation the topN rule to be created */ public void define(TopNAggregation topNAggregation) throws BanyanDBException { TopNAggregationMetadataRegistry registry = new TopNAggregationMetadataRegistry(checkNotNull(this.channel)); registry.create(topNAggregation); } /** * Apply(Create or update) the property with {@link PropertyStore.Strategy#MERGE} * * @param property the property to be stored in the BanyanBD */ public PropertyStore.ApplyResult apply(Property property) throws BanyanDBException { PropertyStore store = new PropertyStore(checkNotNull(this.channel)); return store.apply(property); } /** * Apply(Create or update) the property * * @param property the property to be stored in the BanyanBD * @param strategy dedicates how to apply the property */ public PropertyStore.ApplyResult apply(Property property, PropertyStore.Strategy strategy) throws BanyanDBException { PropertyStore store = new PropertyStore(checkNotNull(this.channel)); return store.apply(property, strategy); } /** * Find property * * @param group group of the metadata * @param name name of the metadata * @param id identity of the property * @param tags tags to be returned * @return property if it can be found */ public Property findProperty(String group, String name, String id, String... tags) throws BanyanDBException { PropertyStore store = new PropertyStore(checkNotNull(this.channel)); return store.get(group, name, id, tags); } /** * List Properties * * @param group group of the metadata * @param name name of the metadata * @return all properties belonging to the group and the name */ public List findProperties(String group, String name) throws BanyanDBException { return findProperties(group, name, null, null); } /** * List Properties * * @param group group of the metadata * @param name name of the metadata * @param ids identities of the properties * @param tags tags to be returned * @return all properties belonging to the group and the name */ public List findProperties(String group, String name, List ids, List tags) throws BanyanDBException { PropertyStore store = new PropertyStore(checkNotNull(this.channel)); return store.list(group, name, ids, tags); } /** * Delete property * * @param group group of the metadata * @param name name of the metadata * @param id identity of the property * @param tags tags to be deleted. If null, the property is deleted * @return if this property has been deleted */ public PropertyStore.DeleteResult deleteProperty(String group, String name, String id, String... tags) throws BanyanDBException { PropertyStore store = new PropertyStore(checkNotNull(this.channel)); return store.delete(group, name, id, tags); } /** * Bind index rule to the stream * * @param stream the subject of index rule binding * @param indexRules rules to be bounded */ private void defineIndexRules(Stream stream, List indexRules) throws BanyanDBException { Preconditions.checkArgument(stream != null, "measure cannot be null"); IndexRuleMetadataRegistry irRegistry = new IndexRuleMetadataRegistry(checkNotNull(this.channel)); for (final IndexRule ir : indexRules) { try { irRegistry.create(ir); } catch (BanyanDBException ex) { if (ex.getStatus().equals(Status.Code.ALREADY_EXISTS)) { continue; } throw ex; } } if (indexRules.isEmpty()) { return; } List indexRuleNames = indexRules.stream().map(IndexRule::name).collect(Collectors.toList()); IndexRuleBindingMetadataRegistry irbRegistry = new IndexRuleBindingMetadataRegistry(checkNotNull(this.channel)); IndexRuleBinding binding = IndexRuleBinding.create(stream.group(), IndexRuleBinding.defaultBindingRule(stream.name()), IndexRuleBinding.Subject.referToStream(stream.name()), indexRuleNames); irbRegistry.create(binding); } /** * Bind index rule to the measure. * By default, the index rule binding will be active from now, and it will never be expired. * * @param measure the subject of index rule binding * @param indexRules rules to be bounded */ private void defineIndexRules(Measure measure, List indexRules) throws BanyanDBException { Preconditions.checkArgument(measure != null, "measure cannot be null"); IndexRuleMetadataRegistry irRegistry = new IndexRuleMetadataRegistry(checkNotNull(this.channel)); for (final IndexRule ir : indexRules) { try { irRegistry.create(ir); } catch (BanyanDBException ex) { // multiple entity can share a single index rule if (ex.getStatus().equals(Status.Code.ALREADY_EXISTS)) { continue; } throw ex; } } if (indexRules.isEmpty()) { return; } List indexRuleNames = indexRules.stream().map(IndexRule::name).collect(Collectors.toList()); IndexRuleBindingMetadataRegistry irbRegistry = new IndexRuleBindingMetadataRegistry(checkNotNull(this.channel)); IndexRuleBinding binding = IndexRuleBinding.create(measure.group(), IndexRuleBinding.defaultBindingRule(measure.name()), IndexRuleBinding.Subject.referToMeasure(measure.name()), indexRuleNames); irbRegistry.create(binding); } /** * Try to find the group defined * * @param name name of the group * @return the group found in BanyanDB */ public Group findGroup(String name) throws BanyanDBException { Preconditions.checkArgument(!Strings.isNullOrEmpty(name)); try { return new GroupMetadataRegistry(checkNotNull(this.channel)).get(name, name); } catch (BanyanDBException ex) { if (ex.getStatus().equals(Status.Code.NOT_FOUND)) { return null; } throw ex; } } /** * Try to find the TopNAggregation from the BanyanDB with given group and name. * * @param group group of the TopNAggregation * @param name name of the TopNAggregation * @return TopNAggregation if found. Otherwise, null is returned. */ public TopNAggregation findTopNAggregation(String group, String name) throws BanyanDBException { Preconditions.checkArgument(!Strings.isNullOrEmpty(group)); Preconditions.checkArgument(!Strings.isNullOrEmpty(name)); return new TopNAggregationMetadataRegistry(checkNotNull(this.channel)).get(group, name); } /** * Try to find the stream from the BanyanDB with given group and name. * * @param group group of the stream * @param name name of the stream * @return Steam with index rules if found. Otherwise, null is returned. */ public Stream findStream(String group, String name) throws BanyanDBException { Preconditions.checkArgument(!Strings.isNullOrEmpty(group)); Preconditions.checkArgument(!Strings.isNullOrEmpty(name)); Stream s = new StreamMetadataRegistry(checkNotNull(this.channel)).get(group, name); s = s.withIndexRules(findIndexRulesByGroupAndBindingName(group, IndexRuleBinding.defaultBindingRule(name))); this.metadataCache.register(s); return s; } /** * Try to find the measure from the BanyanDB with given group and name. * * @param group group of the measure * @param name name of the measure * @return Measure with index rules if found. Otherwise, null is returned. */ public Measure findMeasure(String group, String name) throws BanyanDBException { Preconditions.checkArgument(!Strings.isNullOrEmpty(group)); Preconditions.checkArgument(!Strings.isNullOrEmpty(name)); Measure m = new MeasureMetadataRegistry(checkNotNull(this.channel)).get(group, name); m = m.withIndexRules(findIndexRulesByGroupAndBindingName(group, IndexRuleBinding.defaultBindingRule(name))); this.metadataCache.register(m); return m; } private List findIndexRulesByGroupAndBindingName(String group, String bindingName) throws BanyanDBException { IndexRuleBindingMetadataRegistry irbRegistry = new IndexRuleBindingMetadataRegistry(checkNotNull(this.channel)); IndexRuleBinding irb; try { irb = irbRegistry.get(group, bindingName); } catch (BanyanDBException ex) { if (ex.getStatus().equals(Status.Code.NOT_FOUND)) { return Collections.emptyList(); } throw ex; } if (irb == null) { return Collections.emptyList(); } IndexRuleMetadataRegistry irRegistry = new IndexRuleMetadataRegistry(checkNotNull(this.channel)); List indexRules = new ArrayList<>(irb.rules().size()); for (final String rule : irb.rules()) { try { indexRules.add(irRegistry.get(group, rule)); } catch (BanyanDBException ex) { if (ex.getStatus().equals(Status.Code.NOT_FOUND)) { continue; } throw ex; } } return indexRules; } /** * Check if the given stream exists. * * @param group group of the stream * @param name name of the stream * @return ResourceExist which indicates whether group and stream exist */ public ResourceExist existStream(String group, String name) throws BanyanDBException { Preconditions.checkArgument(!Strings.isNullOrEmpty(group)); Preconditions.checkArgument(!Strings.isNullOrEmpty(name)); return new StreamMetadataRegistry(checkNotNull(this.channel)).exist(group, name); } /** * Check if the given measure exists. * * @param group group of the measure * @param name name of the measure * @return ResourceExist which indicates whether group and measure exist */ public ResourceExist existMeasure(String group, String name) throws BanyanDBException { Preconditions.checkArgument(!Strings.isNullOrEmpty(group)); Preconditions.checkArgument(!Strings.isNullOrEmpty(name)); return new MeasureMetadataRegistry(checkNotNull(this.channel)).exist(group, name); } /** * Check if the given TopNAggregation exists. * * @param group group of the TopNAggregation * @param name name of the TopNAggregation * @return ResourceExist which indicates whether group and TopNAggregation exist */ public ResourceExist existTopNAggregation(String group, String name) throws BanyanDBException { Preconditions.checkArgument(!Strings.isNullOrEmpty(group)); Preconditions.checkArgument(!Strings.isNullOrEmpty(name)); return new TopNAggregationMetadataRegistry(checkNotNull(this.channel)).exist(group, name); } @Override public void close() throws IOException { connectionEstablishLock.lock(); if (!(this.channel instanceof ManagedChannel)) { return; } final ManagedChannel managedChannel = (ManagedChannel) this.channel; try { if (isConnected) { managedChannel.shutdown().awaitTermination(5, TimeUnit.SECONDS); isConnected = false; } } catch (InterruptedException interruptedException) { Thread.currentThread().interrupt(); log.warn("fail to wait for channel termination, shutdown now!", interruptedException); managedChannel.shutdownNow(); isConnected = false; } finally { connectionEstablishLock.unlock(); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy