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

com.ibm.etcd.client.kv.EtcdKvClient Maven / Gradle / Ivy

There is a newer version: 0.0.24
Show newest version
/*
 * Copyright 2017, 2018 IBM Corp. All Rights Reserved.
 *
 * 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.
 */
package com.ibm.etcd.client.kv;

import static com.ibm.etcd.client.KeyUtils.ZERO_BYTE;

import java.util.List;
import java.util.concurrent.Executor;

import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.protobuf.ByteString;
import com.ibm.etcd.api.CompactionRequest;
import com.ibm.etcd.api.CompactionResponse;
import com.ibm.etcd.api.Compare;
import com.ibm.etcd.api.Compare.CompareResult;
import com.ibm.etcd.api.Compare.CompareTarget;
import com.ibm.etcd.api.DeleteRangeRequest;
import com.ibm.etcd.api.DeleteRangeRequestOrBuilder;
import com.ibm.etcd.api.DeleteRangeResponse;
import com.ibm.etcd.api.KVGrpc;
import com.ibm.etcd.api.PutRequest;
import com.ibm.etcd.api.PutRequestOrBuilder;
import com.ibm.etcd.api.PutResponse;
import com.ibm.etcd.api.RangeRequest;
import com.ibm.etcd.api.RangeRequest.SortOrder;
import com.ibm.etcd.api.RangeRequest.SortTarget;
import com.ibm.etcd.api.RangeRequestOrBuilder;
import com.ibm.etcd.api.RangeResponse;
import com.ibm.etcd.api.RequestOp;
import com.ibm.etcd.api.RequestOp.RequestCase;
import com.ibm.etcd.api.TxnRequest;
import com.ibm.etcd.api.TxnRequestOrBuilder;
import com.ibm.etcd.api.TxnResponse;
import com.ibm.etcd.api.WatchCreateRequest;
import com.ibm.etcd.api.WatchCreateRequest.FilterType;
import com.ibm.etcd.client.Condition;
import com.ibm.etcd.client.FluentRequest.AbstractFluentRequest;
import com.ibm.etcd.client.GrpcClient;
import com.ibm.etcd.client.KeyUtils;
import com.ibm.etcd.client.watch.EtcdWatchClient;

import io.grpc.Deadline;
import io.grpc.MethodDescriptor;
import io.grpc.stub.StreamObserver;

public final class EtcdKvClient implements KvClient {

    // avoid volatile read on every invocation
    private static final MethodDescriptor METHOD_RANGE =
            KVGrpc.getRangeMethod();
    private static final MethodDescriptor METHOD_TXN =
            KVGrpc.getTxnMethod();
    private static final MethodDescriptor METHOD_PUT =
            KVGrpc.getPutMethod();
    private static final MethodDescriptor METHOD_DELETE_RANGE =
            KVGrpc.getDeleteRangeMethod();

    protected final GrpcClient client;

    public EtcdKvClient(GrpcClient client) {
        this.client = client;
    }

    @Override
    public ListenableFuture get(RangeRequest request) {
        return client.call(METHOD_RANGE, request, true);
    }

    @Override
    public FluentRangeRequest get(ByteString key) {
        return new EtcdRangeRequest(key);
    }

    //TODO reinstate txn idempotence determination
    private static Predicate IDEMPOTENT_TXN = txn ->
        Iterables.all(Iterables.concat(txn.getSuccessList(),txn.getFailureList()), op ->
            op.getRequestCase() == RequestCase.REQUEST_RANGE);

    @Override
    public ListenableFuture txn(TxnRequest txn) {
        return client.call(METHOD_TXN, txn, false); //IDEMPOTENT_TXN);
    }

    @Override
    public FluentTxnRequest txnIf() {
        return new EtcdTxnRequest();
    }

    @Override
    public FluentTxnOps batch() {
        return txnIf().then();
    }

    @Override
    public TxnResponse txnSync(TxnRequest txn, long timeoutMillis) {
        return client.waitForCall(ex -> client.call(METHOD_TXN, txn, false, timeoutMillis, ex));
    }

    @Override
    public ListenableFuture put(PutRequest request) {
        return client.call(METHOD_PUT, request, false);
    }

    @Override
    public FluentPutRequest put(ByteString key, ByteString value) {
        return new EtcdPutRequest(key, value, false);
    }

    @Override
    public FluentPutRequest put(ByteString key, ByteString value, long leaseId) {
        return new EtcdPutRequest(key, value, leaseId);
    }

    @Override
    public FluentPutRequest setLease(ByteString key, long leaseId) {
        return new EtcdPutRequest(key, leaseId);
    }

    @Override
    public FluentPutRequest setValue(ByteString key, ByteString value) {
        return new EtcdPutRequest(key, value, true);
    }

    @Override
    public ListenableFuture delete(DeleteRangeRequest request) {
        return client.call(METHOD_DELETE_RANGE, request, false);
    }

    @Override
    public FluentDeleteRequest delete(ByteString key) {
        return new EtcdDeleteRequest(key);
    }

    final class EtcdRangeRequest extends AbstractFluentRequest implements FluentRangeRequest {

        EtcdRangeRequest(ByteString key) {
            super(EtcdKvClient.this.client, RangeRequest.newBuilder());
            if (key != ALL_KEYS) {
                builder.setKey(key);
            } else {
                builder.setKey(ZERO_BYTE).setRangeEnd(ZERO_BYTE);
            }
        }
        @Override
        protected MethodDescriptor getMethod() {
            return METHOD_RANGE;
        }
        @Override
        protected boolean idempotent() {
            return true;
        }
        @Override
        public FluentRangeRequest rangeEnd(ByteString key) {
            builder.setRangeEnd(key);
            return this;
        }
        @Override
        public FluentRangeRequest asPrefix() {
            builder.setRangeEnd(KeyUtils.plusOne(builder.getKey()));
            return this;
        }
        @Override
        public FluentRangeRequest andHigher() {
            builder.setRangeEnd(ZERO_BYTE);
            return this;
        }
        @Override
        public FluentRangeRequest limit(long limit) {
            builder.setLimit(limit);
            return this;
        }
        @Override
        public FluentRangeRequest revision(long rev) {
            builder.setRevision(rev);
            return this;
        }
        @Override
        public FluentRangeRequest sorted(SortTarget target, SortOrder order) {
            builder.setSortTarget(target).setSortOrder(order);
            return this;
        }
        @Override
        public FluentRangeRequest serializable(boolean serializable) {
            builder.setSerializable(serializable);
            return this;
        }
        @Override
        public FluentRangeRequest serializable() {
            builder.setSerializable(true);
            return this;
        }
        @Override
        public FluentRangeRequest keysOnly() {
            builder.setKeysOnly(true);
            return this;
        }
        @Override
        public FluentRangeRequest countOnly() {
            builder.setCountOnly(true);
            return this;
        }
        @Override
        public FluentRangeRequest minModRevision(long rev) {
            builder.setMinModRevision(rev);
            return this;
        }
        @Override
        public FluentRangeRequest maxModRevision(long rev) {
            builder.setMaxModRevision(rev);
            return this;
        }
        @Override
        public FluentRangeRequest minCreateRevision(long rev) {
            builder.setMinCreateRevision(rev);
            return this;
        }
        @Override
        public FluentRangeRequest maxCreateRevision(long rev) {
            builder.setMaxCreateRevision(rev);
            return this;
        }
    }

    final class EtcdDeleteRequest extends AbstractFluentRequest implements FluentDeleteRequest {

        EtcdDeleteRequest(ByteString key) {
            super(EtcdKvClient.this.client, DeleteRangeRequest.newBuilder());
            if (key != ALL_KEYS) {
                builder.setKey(key);
            } else {
                builder.setKey(ZERO_BYTE).setRangeEnd(ZERO_BYTE);
            }
        }
        @Override
        protected MethodDescriptor getMethod() {
            return METHOD_DELETE_RANGE;
        }
        @Override
        protected boolean idempotent() {
            return false;
        }
        @Override
        public FluentDeleteRequest rangeEnd(ByteString key) {
            builder.setRangeEnd(key);
            return this;
        }
        @Override
        public FluentDeleteRequest asPrefix() {
            builder.setRangeEnd(KeyUtils.plusOne(builder.getKey()));
            return this;
        }
        @Override
        public FluentDeleteRequest andHigher() {
            builder.setRangeEnd(ZERO_BYTE);
            return this;
        }
        @Override
        public FluentDeleteRequest prevKv() {
            builder.setPrevKv(true);
            return this;
        }
    }

    final class EtcdPutRequest extends AbstractFluentRequest implements FluentPutRequest {
        EtcdPutRequest() {
            super(EtcdKvClient.this.client, PutRequest.newBuilder());
        }
        EtcdPutRequest(ByteString key, ByteString value, boolean ignoreLease) {
            this();
            builder.setKey(key).setValue(value).setIgnoreLease(ignoreLease);
        }

        EtcdPutRequest(ByteString key, ByteString value, long leaseId) {
            this();
            builder.setKey(key).setValue(value).setLease(leaseId);
        }

        EtcdPutRequest(ByteString key, long lease) {
            this();
            builder.setKey(key).setLease(lease).setIgnoreValue(true);
        }
        @Override
        protected MethodDescriptor getMethod() {
            return METHOD_PUT;
        }
        @Override
        protected boolean idempotent() {
            return false;
        }
        @Override
        public FluentPutRequest prevKv() {
            builder.setPrevKv(true);
            return this;
        }
    }

    final class EtcdTxnRequest extends AbstractFluentRequest implements FluentTxnRequest {

        final Compare.Builder cmpBld = Compare.newBuilder(); //reused
        final FluentCmpTarget CMP_TARGET = new EtcdCmpTarget();
        FluentTxnSuccOps TXN_OPS = null; // lazy instantiate
        boolean idempotent = true; // set to false when any non-idempotent ops are added

        public EtcdTxnRequest() {
            super(EtcdKvClient.this.client, TxnRequest.newBuilder());
        }
        @Override
        protected MethodDescriptor getMethod() {
            return METHOD_TXN;
        }
        @Override
        protected boolean idempotent() {
            return idempotent;
        }        
        @Override
        public FluentCmpTarget cmpEqual(ByteString key) {
            return cmp(key, CompareResult.EQUAL);
        }
        @Override
        public FluentCmpTarget cmpNotEqual(ByteString key) {
            return cmp(key, CompareResult.NOT_EQUAL);
        }
        @Override
        public FluentCmpTarget cmpLess(ByteString key) {
            return cmp(key, CompareResult.LESS);
        }
        @Override
        public FluentCmpTarget cmpGreater(ByteString key) {
            return cmp(key, CompareResult.GREATER);
        }
        @Override
        public FluentTxnRequest exists(ByteString key) {
            return cmpNotEqual(key).version(0L);
        }
        @Override
        public FluentTxnRequest notExists(ByteString key) {
            return cmpEqual(key).version(0L);
        }
        private FluentCmpTarget cmp(ByteString key, CompareResult cr) {
            cmpBld.setKey(key).setResult(cr);
            return CMP_TARGET;
        }

        @Override
        public FluentTxnSuccOps then() {
            return TXN_OPS != null ? TXN_OPS : (TXN_OPS = new EtcdTxnOps());
        }

        final class EtcdCmpTarget implements FluentCmpTarget {
            @Override
            public FluentCmpTarget allInRange(ByteString rangeEnd) {
                cmpBld.setRangeEnd(rangeEnd);
                return this;
            }
            @Override
            public FluentCmpTarget allWithPrefix() {
                cmpBld.setRangeEnd(KeyUtils.plusOne(cmpBld.getKey()));
                return this;
            }
            @Override
            public FluentCmpTarget andAllHigher() {
                cmpBld.setRangeEnd(ZERO_BYTE);
                return this;
            }
            @Override
            public FluentTxnRequest version(long version) {
                cmpBld.setVersion(version);
                return add(CompareTarget.VERSION);
            }
            @Override
            public FluentTxnRequest mod(long rev) {
                cmpBld.setModRevision(rev);
                return add(CompareTarget.MOD);
            }
            @Override
            public FluentTxnRequest create(long rev) {
                cmpBld.setCreateRevision(rev);
                return add(CompareTarget.CREATE);
            }
            @Override
            public FluentTxnRequest value(ByteString value) {
                cmpBld.setValue(value);
                return add(CompareTarget.VALUE);
            }
            @Override
            public FluentTxnRequest lease(long leaseId) {
                cmpBld.setLease(leaseId);
                return add(CompareTarget.LEASE);
            }
            private FluentTxnRequest add(CompareTarget ct) {
                builder.addCompare(cmpBld.setTarget(ct));
                return EtcdTxnRequest.this;
            }
        }

        final class EtcdTxnOps implements FluentTxnSuccOps {
            private final RequestOp.Builder opBld = RequestOp.newBuilder();
            private boolean succ = true;

            private FluentTxnSuccOps add(RequestOp.Builder op) {
                if (succ) {
                    builder.addSuccess(op);
                } else {
                    builder.addFailure(op);
                }
                return this;
            }
            @Override
            public FluentTxnOps elseDo() {
                succ = false;
                return this;
            }
            @Override
            public FluentTxnSuccOps put(PutRequestOrBuilder putReq) {
                idempotent = false;
                return add(putReq instanceof PutRequest ?
                        opBld.setRequestPut((PutRequest) putReq)
                        : opBld.setRequestPut((PutRequest.Builder) putReq));
            }
            @Override
            public FluentTxnSuccOps get(RangeRequestOrBuilder getReq) {
                return add(getReq instanceof RangeRequest ?
                        opBld.setRequestRange((RangeRequest) getReq)
                        : opBld.setRequestRange((RangeRequest.Builder) getReq));
            }
            @Override
            public FluentTxnSuccOps delete(DeleteRangeRequestOrBuilder delReq) {
                idempotent = false;
                return add(delReq instanceof DeleteRangeRequest ?
                        opBld.setRequestDeleteRange((DeleteRangeRequest) delReq)
                        : opBld.setRequestDeleteRange((DeleteRangeRequest.Builder) delReq));
            }
            @Override
            public FluentTxnSuccOps subTxn(TxnRequestOrBuilder txnReq) {
                idempotent = false; //TODO determine idempotence of sub txn
                return add(txnReq instanceof TxnRequest ?
                        opBld.setRequestTxn((TxnRequest) txnReq)
                        : opBld.setRequestTxn((TxnRequest.Builder) txnReq));
            }
            @Override
            public FluentTxnOps backoffRetry() {
                EtcdTxnRequest.this.backoffRetry();
                return this;
            }
            @Override
            public FluentTxnOps backoffRetry(Condition precondition) {
                EtcdTxnRequest.this.backoffRetry(precondition);
                return this;
            }
            @Override
            public FluentTxnOps timeout(long millisecs) {
                EtcdTxnRequest.this.timeout(millisecs);
                return this;
            }
            @Override
            public FluentTxnOps deadline(Deadline deadline) {
                EtcdTxnRequest.this.deadline(deadline);
                return this;
            }
            @Override
            public ListenableFuture async() {
                return EtcdTxnRequest.this.async();
            }
            @Override
            public ListenableFuture async(Executor executor) {
                return EtcdTxnRequest.this.async(executor);
            }
            @Override
            public TxnRequest asRequest() {
                return EtcdTxnRequest.this.asRequest();
            }
            @Override
            public TxnResponse sync() {
                return EtcdTxnRequest.this.sync();
            }
        }
    }

    private volatile EtcdWatchClient watchClient;

    private boolean closed;

    private EtcdWatchClient watchClient() {
        EtcdWatchClient wc = watchClient;
        if (wc == null) synchronized (this) {
            if (closed) {
                throw new IllegalStateException("client closed");
            }
            if ((wc = watchClient) == null) {
                watchClient = wc = new EtcdWatchClient(client);
            }
        }
        return wc;
    }

    public void close() {
        synchronized (this) {
            if (!closed) {
                closed = true;
                if (watchClient != null) {
                    watchClient.close();
                }
            }
        }
    }

    final class EtcdWatchRequest implements FluentWatchRequest {
        private final WatchCreateRequest.Builder builder = WatchCreateRequest.newBuilder();

        private Executor executor;

        EtcdWatchRequest(ByteString key) {
            if (key != ALL_KEYS) {
                builder.setKey(key);
            } else {
                builder.setKey(ZERO_BYTE).setRangeEnd(ZERO_BYTE);
            }
        }

        @Override
        public FluentWatchRequest filters(List filters) {
            builder.addAllFilters(filters);
            return this;
        }
        @Override
        public FluentWatchRequest filters(FilterType... filters) {
            for (FilterType ft : filters) {
                builder.addFilters(ft);
            }
            return this;
        }
        @Override
        public FluentWatchRequest prevKv() {
            builder.setPrevKv(true);
            return this;
        }
        @Override
        public FluentWatchRequest rangeEnd(ByteString key) {
            builder.setRangeEnd(key);
            return this;
        }
        @Override
        public FluentWatchRequest asPrefix() {
            builder.setRangeEnd(KeyUtils.plusOne(builder.getKey()));
            return this;
        }
        @Override
        public FluentWatchRequest andHigher() {
            builder.setRangeEnd(ZERO_BYTE);
            return this;
        }
        @Override
        public FluentWatchRequest progressNotify() {
            builder.setProgressNotify(true);
            return this;
        }
        @Override
        public FluentWatchRequest startRevision(long rev) {
            builder.setStartRevision(rev);
            return this;
        }
        @Override
        public FluentWatchRequest executor(Executor executor) {
            this.executor = executor;
            return this;
        }

        @Override
        public Watch start(StreamObserver updateObserver) {
            return watchClient().watch(builder.build(), updateObserver, executor);
        }
        @Override
        public WatchIterator start() {
            if (executor != null) {
                throw new IllegalArgumentException("executor provided for iterator-based watch");
            }
            return watchClient().watch(builder.build());
        }
    }

    @Override
    public Watch watch(WatchCreateRequest request, StreamObserver updates) {
        return watchClient().watch(request, updates);
    }

    @Override
    public FluentWatchRequest watch(ByteString key) {
        return new EtcdWatchRequest(key);
    }

    @Override
    public ListenableFuture compact(long minRevision, boolean physical) {
        // 10 min timeout - caller can cancel if necessary
        return client.call(KVGrpc.getCompactMethod(), CompactionRequest.newBuilder()
                .setRevision(minRevision).setPhysical(physical).build(), true,
                600_000L, null);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy