use of com.ibm.etcd.client.kv.KvClient.Watch in project etcd-java by IBM.
the class WatchTest method testWatch.
@Test
public void testWatch() throws Exception {
proxy.start();
try (KvStoreClient directClient = EtcdClient.forEndpoint("localhost", 2379).withPlainText().build();
KvStoreClient client = EtcdClient.forEndpoint("localhost", 2390).withPlainText().build()) {
KvClient kvc = client.getKvClient();
long start = System.currentTimeMillis();
final BlockingQueue<Object> watchEvents = new LinkedBlockingQueue<>();
final Object COMPLETED = new Object();
final StreamObserver<WatchUpdate> observer = new StreamObserver<WatchUpdate>() {
@Override
public void onNext(WatchUpdate value) {
System.out.println(t(start) + "watch event: " + value);
watchEvents.add(value);
}
@Override
public void onError(Throwable t) {
System.out.println(t(start) + "watch error: " + t);
watchEvents.add(t);
}
@Override
public void onCompleted() {
System.out.println(t(start) + "watch completed");
watchEvents.add(COMPLETED);
}
};
Watch watch = kvc.watch(bs("/watchtest")).asPrefix().start(observer);
// assertFalse(watch.isDone());
// test blocking watch at the same time
WatchIterator watchIterator = kvc.watch(bs("/watchtest")).asPrefix().start();
assertTrue(watch.get(1, TimeUnit.SECONDS));
kvc.put(bs("/watchtest/a"), bs("a value")).sync();
directClient.getKvClient().put(bs("/watchtest/b"), bs("b value")).sync();
WatchUpdate wu = getNextSkipEmpty(watchEvents);
assertNotNull(wu);
assertEquals("event: " + wu, 1, wu.getEvents().size());
assertEquals(EventType.PUT, wu.getEvents().get(0).getType());
assertEquals(bs("a value"), wu.getEvents().get(0).getKv().getValue());
assertEquals(bs("/watchtest/a"), wu.getEvents().get(0).getKv().getKey());
WatchUpdate wu2 = getNextSkipEmpty(watchIterator);
assertEquals(EventType.PUT, wu2.getEvents().get(0).getType());
assertEquals(bs("a value"), wu2.getEvents().get(0).getKv().getValue());
assertEquals(bs("/watchtest/a"), wu2.getEvents().get(0).getKv().getKey());
watchEvents.poll(500, TimeUnit.MILLISECONDS);
watch.close();
assertEquals(COMPLETED, watchEvents.poll(500, TimeUnit.MILLISECONDS));
kvc.put(bs("/watchtest/c"), bs("c value")).sync();
assertNull(watchEvents.poll(500, TimeUnit.MILLISECONDS));
assertTrue(watchIterator.hasNext());
assertTrue(watchIterator.hasNext());
assertEquals(bs("b value"), watchIterator.next().getEvents().get(0).getKv().getValue());
assertEquals(EventType.PUT, watchIterator.next().getEvents().get(0).getType());
// fresh new watch
watch = kvc.watch(ByteString.copyFromUtf8("/watchtest")).asPrefix().start(observer);
assertTrue(watch.get(1, TimeUnit.SECONDS));
kvc.batch().put(kvc.put(bs("/watchtest/d"), bs("d value")).asRequest()).delete(kvc.delete(bs("/watchtest/a")).asRequest()).sync();
wu = getNextSkipEmpty(watchEvents);
assertEquals(2, wu.getEvents().size());
assertTrue(wu.getEvents().stream().anyMatch(e -> e.getType() == EventType.DELETE));
assertTrue(wu.getEvents().stream().anyMatch(e -> e.getType() == EventType.PUT));
// kill path to server
proxy.kill();
Thread.sleep(1000L);
// while disconnected, put a new key
directClient.getKvClient().put(bs("/watchtest/e"), bs("e value")).sync();
Thread.sleep(1000L);
// reinstate path to server
proxy.start();
// watch should be unaffected - next event seen should be the missed one
wu = (WatchUpdate) watchEvents.poll(3000L, TimeUnit.MILLISECONDS);
assertEquals(bs("/watchtest/e"), wu.getEvents().get(0).getKv().getKey());
watch.close();
// (earlier batch update)
assertEquals(2, watchIterator.next().getEvents().size());
assertEquals(bs("/watchtest/e"), watchIterator.next().getEvents().get(0).getKv().getKey());
watchIterator.close();
assertNull(watchIterator.next().getHeader());
assertFalse(watchIterator.hasNext());
try {
watchIterator.next();
fail("should throw NSEE here");
} catch (NoSuchElementException nsee) {
}
} finally {
proxy.kill();
}
}
use of com.ibm.etcd.client.kv.KvClient.Watch in project etcd-java by IBM.
the class RangeCache method fullRefreshCache.
// internal method - should not be called while watch is active
protected ListenableFuture<Boolean> fullRefreshCache() {
ListenableFuture<List<RangeResponse>> rrfut;
long seenUpTo = seenUpToRev;
boolean firstTime = (seenUpTo == 0L);
if (firstTime || entries.size() <= 20) {
// TODO *maybe* chunking (for large caches)
ListenableFuture<RangeResponse> rrf = kvClient.get(fromKey).rangeEnd(toKey).backoffRetry(() -> !closed).timeout(300_000L).async();
rrfut = Futures.transform(rrf, (Function<RangeResponse, List<RangeResponse>>) rr -> Collections.singletonList(rr));
} else {
// in case the local cache is large, reduce data transfer by requesting
// just keys, and full key+value only for those modified since seenUpToRev
RangeRequest.Builder rangeReqBld = RangeRequest.newBuilder().setKey(fromKey).setRangeEnd(toKey);
RangeRequest newModsReq = rangeReqBld.setMinModRevision(seenUpTo + 1).build();
RangeRequest otherKeysReq = rangeReqBld.clearMinModRevision().setMaxModRevision(seenUpTo).setKeysOnly(true).build();
ListenableFuture<TxnResponse> trf = kvClient.batch().get(newModsReq).get(otherKeysReq).backoffRetry(() -> !closed).timeout(300_000L).async();
rrfut = Futures.transform(trf, (Function<TxnResponse, List<RangeResponse>>) tr -> tr.getResponsesList().stream().map(r -> r.getResponseRange()).collect(Collectors.toList()));
}
return Futures.transformAsync(rrfut, rrs -> {
if (closed)
throw new CancellationException();
Set<ByteString> snapshot = firstTime ? null : new HashSet<>();
RangeResponse toUpdate = rrs.get(0);
if (toUpdate.getKvsCount() > 0)
for (KeyValue kv : toUpdate.getKvsList()) {
if (!firstTime)
snapshot.add(kv.getKey());
offerUpdate(kv, true);
}
long snapshotRev = toUpdate.getHeader().getRevision();
if (firstTime)
notifyListeners(EventType.INITIALIZED, null, true);
else {
if (rrs.size() > 1)
for (KeyValue kv : rrs.get(1).getKvsList()) {
snapshot.add(kv.getKey());
}
// prune deleted entries
KeyValue.Builder kvBld = null;
for (ByteString key : entries.keySet()) if (!snapshot.contains(key)) {
if (kvBld == null)
kvBld = KeyValue.newBuilder().setVersion(0L).setModRevision(snapshotRev);
offerUpdate(kvBld.setKey(key).build(), true);
}
}
revisionUpdate(snapshotRev);
Watch newWatch = // .prevKv() //TODO TBD
kvClient.watch(fromKey).rangeEnd(toKey).progressNotify().startRevision(snapshotRev + 1).executor(listenerExecutor).start(new StreamObserver<WatchUpdate>() {
@Override
public void onNext(WatchUpdate update) {
List<Event> events = update.getEvents();
int eventCount = events != null ? events.size() : 0;
if (eventCount > 0)
for (Event event : events) {
KeyValue kv = event.getKv();
// event.getPrevKv(); //TBD
switch(event.getType()) {
case DELETE:
if (kv.getVersion() != 0L)
kv = KeyValue.newBuilder(kv).setVersion(0L).clearValue().build();
// fall-thru
case PUT:
offerUpdate(kv, true);
break;
case UNRECOGNIZED:
default:
logger.warn("Unrecognized event for key " + kv.getKey().toStringUtf8());
break;
}
}
revisionUpdate(eventCount == 0 ? update.getHeader().getRevision() - 1L : events.get(eventCount - 1).getKv().getModRevision());
}
@Override
public void onCompleted() {
// should only happen after external close()
if (!closed) {
if (!client.isClosed()) {
logger.error("Watch completed unexpectedly (not closed)");
}
close();
}
}
@Override
public void onError(Throwable t) {
logger.error("Watch failed with exception ", t);
if (t instanceof RevisionCompactedException)
synchronized (RangeCache.this) {
// fail if happens during start, otherwise refresh
if (!closed && startFuture != null && startFuture.isDone()) {
// will renew watch
startFuture = fullRefreshCache();
}
}
}
});
synchronized (this) {
if (closed)
throw new CancellationException();
return watch = newWatch;
}
}, listenerExecutor);
}
use of com.ibm.etcd.client.kv.KvClient.Watch in project etcd-java by IBM.
the class EtcdWatchClient method watch.
/**
* Blocking watch
*/
public WatchIterator watch(WatchCreateRequest createReq) {
EtcdWatchIterator watchIt = new EtcdWatchIterator();
Watch handle = watch(createReq, watchIt, MoreExecutors.directExecutor());
return watchIt.setWatch(handle);
}
use of com.ibm.etcd.client.kv.KvClient.Watch in project etcd-java by IBM.
the class WatchTest method testResiliency.
@Test
public void testResiliency() throws Exception {
try (EtcdClient directClient = EtcdClient.forEndpoint("localhost", 2379).withPlainText().build();
EtcdClient client = EtcdClient.forEndpoint("localhost", 2396).withPlainText().build()) {
directClient.getKvClient().delete(bs("watch-tr-test/")).asPrefix().sync();
try (final LocalNettyProxy prox = new LocalNettyProxy(2396)) {
Thread proxyThread = new Thread() {
{
setDaemon(true);
}
@Override
public void run() {
try {
int N = 4;
for (int i = 1; i <= N; i++) {
prox.start();
Thread.sleep(1000L + (long) (Math.random() * 5000));
System.out.println("killing proxy " + i);
// finish in running state
if (i < N)
prox.kill();
Thread.sleep((long) (Math.random() * 4000));
}
} catch (Exception e) {
e.printStackTrace();
}
}
};
proxyThread.start();
KvClient directKv = directClient.getKvClient();
Phaser p = new Phaser(1);
Map<ByteString, Object> watchedKeys = new ConcurrentHashMap<>();
int i = 0;
// the proxy is stopped/started
while (proxyThread.isAlive()) {
// put a key
ByteString key = bs("watch-tr-test/" + Math.random());
ByteString value = bs("value " + (i++));
PutResponse pr = directKv.put(key, value).sync();
watchedKeys.put(key, "pending");
p.register();
final int ii = i;
AtomicReference<Watch> w = new AtomicReference<>();
w.set(client.getKvClient().watch(key).startRevision(pr.getHeader().getRevision()).start((ListenerObserver<WatchUpdate>) (complete, wu, err) -> {
if (complete) {
if (err != null) {
watchedKeys.replace(key, err);
err.printStackTrace();
} else
watchedKeys.remove(key);
if (w.get() == null)
p.arrive();
} else if (!wu.getEvents().isEmpty()) {
if (value.equals(wu.getEvents().get(0).getKv().getValue())) {
if (ii % 2 == 0) {
// cancel every other watch
w.get().close();
w.set(null);
} else {
watchedKeys.remove(key);
p.arrive();
}
} else {
watchedKeys.replace(key, "unexpected watch event value");
p.arrive();
}
}
}));
Thread.sleep((long) (Math.random() * 500));
}
p.arrive();
p.awaitAdvanceInterruptibly(0, 8, TimeUnit.SECONDS);
System.out.println("created " + i + " watches; left incomplete: " + watchedKeys.size());
watchedKeys.entrySet().forEach(e -> System.out.println("** " + e.getKey().toStringUtf8() + "=" + e.getValue()));
assertTrue(watchedKeys.isEmpty());
}
}
}
use of com.ibm.etcd.client.kv.KvClient.Watch in project etcd-java by IBM.
the class WatchTest method testCreateFail.
@Test
public void testCreateFail() throws Exception {
KvStoreClient client = EtcdClient.forEndpoint("localhost", 2379).withPlainText().build();
try {
KvClient kvc = client.getKvClient();
// range end before start => should fail
Watch watch2 = kvc.watch(ByteString.copyFromUtf8("/watchtest2")).rangeEnd(ByteString.copyFromUtf8("/watchtest1")).startRevision(-5000L).start((ListenerObserver<WatchUpdate>) (c, v, t) -> {
});
try {
watch2.get(1000, TimeUnit.SECONDS);
fail("watch future should fail");
} catch (ExecutionException e) {
System.out.println("watch creation failed with exception: " + e);
assertTrue(e.getCause() instanceof WatchCreateException);
}
} finally {
client.close();
}
}
Aggregations