This is an automated email from the ASF dual-hosted git repository.

kturner pushed a commit to branch elasticity
in repository https://gitbox.apache.org/repos/asf/accumulo.git

commit c54d544758ed964c29f7df69b2e7acb91e679b80
Merge: 6a3b97ca15 8e5d967f24
Author: Keith Turner <ktur...@apache.org>
AuthorDate: Tue May 28 19:29:52 2024 +0000

    Merge commit '8e5d967f24870caa66659f54a3379749c84c8a9a' into elasticity

 .../org/apache/accumulo/server/util/Admin.java     |  42 ++-
 .../accumulo/server/util/ServiceStatusCmd.java     | 341 +++++++++++++++++
 .../util/serviceStatus/ServiceStatusReport.java    | 183 +++++++++
 .../server/util/serviceStatus/StatusSummary.java   |  92 +++++
 .../accumulo/server/util/ServiceStatusCmdTest.java | 407 +++++++++++++++++++++
 .../serviceStatus/ServiceStatusReportTest.java     | 157 ++++++++
 6 files changed, 1209 insertions(+), 13 deletions(-)

diff --cc 
server/base/src/main/java/org/apache/accumulo/server/util/ServiceStatusCmd.java
index 0000000000,3f5dbafaf5..60fd82ed9b
mode 000000,100644..100644
--- 
a/server/base/src/main/java/org/apache/accumulo/server/util/ServiceStatusCmd.java
+++ 
b/server/base/src/main/java/org/apache/accumulo/server/util/ServiceStatusCmd.java
@@@ -1,0 -1,342 +1,341 @@@
+ /*
+  * 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
+  *
+  *   https://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.accumulo.server.util;
+ 
+ import static java.nio.charset.StandardCharsets.UTF_8;
+ 
+ import java.util.Collection;
+ import java.util.Map;
+ import java.util.Set;
+ import java.util.TreeMap;
+ import java.util.TreeSet;
+ import java.util.concurrent.atomic.AtomicInteger;
+ 
+ import org.apache.accumulo.core.Constants;
+ import org.apache.accumulo.core.fate.zookeeper.ZooReader;
+ import org.apache.accumulo.core.util.Pair;
+ import org.apache.accumulo.server.ServerContext;
+ import org.apache.accumulo.server.util.serviceStatus.ServiceStatusReport;
+ import org.apache.accumulo.server.util.serviceStatus.StatusSummary;
+ import org.apache.zookeeper.KeeperException;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ 
+ import com.beust.jcommander.Parameter;
+ import com.beust.jcommander.Parameters;
+ import com.google.common.annotations.VisibleForTesting;
+ 
+ public class ServiceStatusCmd {
+ 
+   // used when grouping by resource group when there is no group.
+   public static final String NO_GROUP_TAG = "NO_GROUP";
+ 
+   private static final Logger LOG = 
LoggerFactory.getLogger(ServiceStatusCmd.class);
+ 
+   public ServiceStatusCmd() {}
+ 
+   /**
+    * Read the service statuses from ZooKeeper, build the status report and 
then output the report to
+    * stdout.
+    */
+   public void execute(final ServerContext context, final Opts opts) {
+ 
+     ZooReader zooReader = context.getZooReader();
+ 
+     final String zooRoot = context.getZooKeeperRoot();
+     LOG.trace("zooRoot: {}", zooRoot);
+ 
+     final Map<ServiceStatusReport.ReportKey,StatusSummary> services = new 
TreeMap<>();
+ 
+     services.put(ServiceStatusReport.ReportKey.MANAGER, 
getManagerStatus(zooReader, zooRoot));
+     services.put(ServiceStatusReport.ReportKey.MONITOR, 
getMonitorStatus(zooReader, zooRoot));
+     services.put(ServiceStatusReport.ReportKey.T_SERVER, 
getTServerStatus(zooReader, zooRoot));
+     services.put(ServiceStatusReport.ReportKey.S_SERVER, 
getScanServerStatus(zooReader, zooRoot));
+     services.put(ServiceStatusReport.ReportKey.COORDINATOR,
+         getCoordinatorStatus(zooReader, zooRoot));
+     services.put(ServiceStatusReport.ReportKey.COMPACTOR, 
getCompactorStatus(zooReader, zooRoot));
+     services.put(ServiceStatusReport.ReportKey.GC, getGcStatus(zooReader, 
zooRoot));
+ 
+     ServiceStatusReport report = new ServiceStatusReport(services, 
opts.noHosts);
+ 
+     if (opts.json) {
+       System.out.println(report.toJson());
+     } else {
+       StringBuilder sb = new StringBuilder(8192);
+       report.report(sb);
+       System.out.println(sb);
+     }
+   }
+ 
+   /**
+    * The manager paths in ZooKeeper are: {@code 
/accumulo/[IID]/managers/lock/zlock#[NUM]} with the
+    * lock data providing host:port.
+    */
+   @VisibleForTesting
+   StatusSummary getManagerStatus(final ZooReader zooReader, String zRootPath) 
{
+     String lockPath = zRootPath + Constants.ZMANAGER_LOCK;
+     return getStatusSummary(ServiceStatusReport.ReportKey.MANAGER, zooReader, 
lockPath);
+   }
+ 
+   /**
+    * The monitor paths in ZooKeeper are: {@code 
/accumulo/[IID]/monitor/lock/zlock#[NUM]} with the
+    * lock data providing host:port.
+    */
+   @VisibleForTesting
+   StatusSummary getMonitorStatus(final ZooReader zooReader, String zRootPath) 
{
+     String lockPath = zRootPath + Constants.ZMONITOR_LOCK;
+     return getStatusSummary(ServiceStatusReport.ReportKey.MONITOR, zooReader, 
lockPath);
+   }
+ 
+   /**
+    * The tserver paths in ZooKeeper are: {@code 
/accumulo/[IID]/tservers/[host:port]/zlock#[NUM]}
+    * with the lock data providing TSERV_CLIENT=host:port.
+    */
+   @VisibleForTesting
+   StatusSummary getTServerStatus(final ZooReader zooReader, String zRootPath) 
{
+     String lockPath = zRootPath + Constants.ZTSERVERS;
+     return getServerHostStatus(zooReader, lockPath, 
ServiceStatusReport.ReportKey.T_SERVER);
+   }
+ 
+   /**
+    * The sserver paths in ZooKeeper are: {@code 
/accumulo/[IID]/sservers/[host:port]/zlock#[NUM]}
+    * with the lock data providing [UUID],[GROUP]
+    */
+   @VisibleForTesting
+   StatusSummary getScanServerStatus(final ZooReader zooReader, String 
zRootPath) {
+     String lockPath = zRootPath + Constants.ZSSERVERS;
+     return getServerHostStatus(zooReader, lockPath, 
ServiceStatusReport.ReportKey.S_SERVER);
+   }
+ 
+   /**
+    * handles paths for tservers and servers with the lock stored beneath the 
host: port like:
+    * {@code /accumulo/IID/[tservers | sservers]/HOST:PORT/[LOCK]}
+    */
+   private StatusSummary getServerHostStatus(final ZooReader zooReader, String 
basePath,
+       ServiceStatusReport.ReportKey displayNames) {
+     AtomicInteger errorSum = new AtomicInteger(0);
+ 
+     // Set<String> hostNames = new TreeSet<>();
+     Set<String> groupNames = new TreeSet<>();
+     Map<String,Set<String>> hostsByGroups = new TreeMap<>();
+ 
+     var nodeNames = readNodeNames(zooReader, basePath);
+ 
+     nodeNames.getHosts().forEach(host -> {
+       var lock = readNodeNames(zooReader, basePath + "/" + host);
+       lock.getHosts().forEach(l -> {
+         var nodeData = readNodeData(zooReader, basePath + "/" + host + "/" + 
l);
+         int err = nodeData.getErrorCount();
+         if (err > 0) {
+           errorSum.addAndGet(nodeData.getErrorCount());
+         } else {
+           // process resource groups
+           String[] tokens = nodeData.getHosts().split(",");
+           if (tokens.length == 2) {
+             String groupName = tokens[1];
+             groupNames.add(groupName);
+             hostsByGroups.computeIfAbsent(groupName, s -> new 
TreeSet<>()).add(host);
+           } else {
+             hostsByGroups.computeIfAbsent(NO_GROUP_TAG, s -> new 
TreeSet<>()).add(host);
+           }
+         }
+ 
+       });
+       errorSum.addAndGet(lock.getFirst());
+     });
+     return new StatusSummary(displayNames, groupNames, hostsByGroups, 
errorSum.get());
+   }
+ 
+   /**
+    * The gc paths in ZooKeeper are: {@code 
/accumulo/[IID]/gc/lock/zlock#[NUM]} with the lock data
+    * providing GC_CLIENT=host:port
+    */
+   @VisibleForTesting
+   StatusSummary getGcStatus(final ZooReader zooReader, String zRootPath) {
+     String lockPath = zRootPath + Constants.ZGC_LOCK;
+     return getStatusSummary(ServiceStatusReport.ReportKey.GC, zooReader, 
lockPath);
+   }
+ 
+   /**
+    * The coordinator paths in ZooKeeper are: {@code 
/accumulo/[IID]/coordinators/lock/zlock#[NUM]}
+    * with the lock data providing host:port
+    */
+   @VisibleForTesting
+   StatusSummary getCoordinatorStatus(final ZooReader zooReader, String 
zRootPath) {
 -    String lockPath = zRootPath + Constants.ZCOORDINATOR_LOCK;
 -    return getStatusSummary(ServiceStatusReport.ReportKey.COORDINATOR, 
zooReader, lockPath);
++    throw new UnsupportedOperationException();
+   }
+ 
+   /**
+    * The compactor paths in ZooKeeper are:
+    * {@code /accumulo/[IID]/compactors/[QUEUE_NAME]/host:port/zlock#[NUM]} 
with the host:port pulled
+    * from the path
+    */
+   @VisibleForTesting
+   StatusSummary getCompactorStatus(final ZooReader zooReader, String 
zRootPath) {
+     String lockPath = zRootPath + Constants.ZCOMPACTORS;
+     return getCompactorHosts(zooReader, lockPath);
+   }
+ 
+   /**
+    * Used to return status information when path is {@code 
/accumulo/IID/SERVICE_NAME/lock} like
+    * manager, monitor and others
+    *
+    * @return service status
+    */
+   private StatusSummary getStatusSummary(ServiceStatusReport.ReportKey 
displayNames,
+       ZooReader zooReader, String lockPath) {
+     var result = readAllNodesData(zooReader, lockPath);
+     Map<String,Set<String>> byGroup = new TreeMap<>();
+     byGroup.put(NO_GROUP_TAG, result.getHosts());
+     return new StatusSummary(displayNames, Set.of(), byGroup, 
result.getErrorCount());
+   }
+ 
+   /**
+    * Pull host:port from path {@code 
/accumulo/IID/compactors/[QUEUE][host:port]}
+    */
+   private StatusSummary getCompactorHosts(final ZooReader zooReader, final 
String zRootPath) {
+     final AtomicInteger errors = new AtomicInteger(0);
+ 
+     Map<String,Set<String>> hostsByGroups = new TreeMap<>();
+ 
+     // get group names
+     Result<Integer,Set<String>> queueNodes = readNodeNames(zooReader, 
zRootPath);
+     errors.addAndGet(queueNodes.getErrorCount());
+     Set<String> queues = new TreeSet<>(queueNodes.getHosts());
+ 
+     queues.forEach(group -> {
+       var hostNames = readNodeNames(zooReader, zRootPath + "/" + group);
+       errors.addAndGet(hostNames.getErrorCount());
+       Collection<String> hosts = hostNames.getHosts();
+       hosts.forEach(host -> {
+         hostsByGroups.computeIfAbsent(group, set -> new 
TreeSet<>()).add(host);
+       });
+     });
+ 
+     return new StatusSummary(ServiceStatusReport.ReportKey.COMPACTOR, queues, 
hostsByGroups,
+         errors.get());
+   }
+ 
+   /**
+    * Read the node names from ZooKeeper. Exceptions are counted but ignored.
+    *
+    * @return Result with error count, Set of the node names.
+    */
+   @VisibleForTesting
+   Result<Integer,Set<String>> readNodeNames(final ZooReader zooReader, final 
String path) {
+     Set<String> nodeNames = new TreeSet<>();
+     final AtomicInteger errorCount = new AtomicInteger(0);
+     try {
+       var children = zooReader.getChildren(path);
+       if (children != null) {
+         nodeNames.addAll(children);
+       }
+     } catch (KeeperException | InterruptedException ex) {
+       if (Thread.currentThread().isInterrupted()) {
+         Thread.currentThread().interrupt();
+         throw new IllegalStateException(ex);
+       }
+       errorCount.incrementAndGet();
+     }
+     return new Result<>(errorCount.get(), nodeNames);
+   }
+ 
+   /**
+    * Read the data from a ZooKeeper node, tracking if an error occurred. 
ZooKeeper's exceptions are
+    * counted but otherwise ignored.
+    *
+    * @return Pair with error count, the node data as String.
+    */
+   @VisibleForTesting
+   Result<Integer,String> readNodeData(final ZooReader zooReader, final String 
path) {
+     try {
+       byte[] data = zooReader.getData(path);
+       return new Result<>(0, new String(data, UTF_8));
+     } catch (KeeperException | InterruptedException ex) {
+       if (Thread.currentThread().isInterrupted()) {
+         Thread.currentThread().interrupt();
+         throw new IllegalStateException(ex);
+       }
+       LOG.info("Could not read locks from ZooKeeper for path {}", path, ex);
+       return new Result<>(1, "");
+     }
+   }
+ 
+   /**
+    * Read the data from all ZooKeeper nodes under a ptah, tracking if errors 
occurred. ZooKeeper's
+    * exceptions are counted but otherwise ignored.
+    *
+    * @return Pair with error count, the data from each node as a String.
+    */
+   @VisibleForTesting
+   Result<Integer,Set<String>> readAllNodesData(final ZooReader zooReader, 
final String path) {
+     Set<String> hosts = new TreeSet<>();
+     final AtomicInteger errorCount = new AtomicInteger(0);
+     try {
+       var locks = zooReader.getChildren(path);
+       locks.forEach(lock -> {
+         var nodeData = readNodeData(zooReader, path + "/" + lock);
+         int err = nodeData.getErrorCount();
+         if (err > 0) {
+           errorCount.addAndGet(nodeData.getErrorCount());
+         } else {
+           hosts.add(nodeData.getHosts());
+         }
+       });
+     } catch (KeeperException | InterruptedException ex) {
+       if (Thread.currentThread().isInterrupted()) {
+         Thread.currentThread().interrupt();
+         throw new IllegalStateException(ex);
+       }
+       LOG.info("Could not read node names from ZooKeeper for path {}", path, 
ex);
+       errorCount.incrementAndGet();
+     }
+     return new Result<>(errorCount.get(), hosts);
+   }
+ 
+   @Parameters(commandDescription = "show service status")
+   public static class Opts {
+     @Parameter(names = "--json", description = "provide output in json format 
(--noHosts ignored)")
+     boolean json = false;
+     @Parameter(names = "--noHosts",
+         description = "provide a summary of service counts without host 
details")
+     boolean noHosts = false;
+   }
+ 
+   /**
+    * Provides explicit method names instead of generic getFirst to get the 
error count and getSecond
+    * hosts information
+    *
+    * @param <A> errorCount
+    * @param <B> hosts
+    */
+   private static class Result<A extends Integer,B> extends Pair<A,B> {
+     public Result(A errorCount, B hosts) {
+       super(errorCount, hosts);
+     }
+ 
+     public A getErrorCount() {
+       return getFirst();
+     }
+ 
+     public B getHosts() {
+       return getSecond();
+     }
+   }
+ }
diff --cc 
server/base/src/test/java/org/apache/accumulo/server/util/ServiceStatusCmdTest.java
index 0000000000,f4af749798..b97384f545
mode 000000,100644..100644
--- 
a/server/base/src/test/java/org/apache/accumulo/server/util/ServiceStatusCmdTest.java
+++ 
b/server/base/src/test/java/org/apache/accumulo/server/util/ServiceStatusCmdTest.java
@@@ -1,0 -1,406 +1,407 @@@
+ /*
+  * 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
+  *
+  *   https://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.accumulo.server.util;
+ 
+ import static java.nio.charset.StandardCharsets.UTF_8;
+ import static org.apache.accumulo.core.Constants.ZGC_LOCK;
+ import static org.apache.accumulo.server.util.ServiceStatusCmd.NO_GROUP_TAG;
+ import static org.easymock.EasyMock.createMock;
+ import static org.easymock.EasyMock.eq;
+ import static org.easymock.EasyMock.expect;
+ import static org.easymock.EasyMock.replay;
+ import static org.easymock.EasyMock.verify;
+ import static org.junit.jupiter.api.Assertions.assertEquals;
+ import static org.junit.jupiter.api.Assertions.assertFalse;
+ 
+ import java.util.List;
+ import java.util.Map;
+ import java.util.Set;
+ import java.util.TreeMap;
+ import java.util.TreeSet;
+ import java.util.UUID;
+ 
+ import org.apache.accumulo.core.Constants;
+ import org.apache.accumulo.core.data.InstanceId;
+ import org.apache.accumulo.core.fate.zookeeper.ZooReader;
+ import org.apache.accumulo.server.ServerContext;
+ import org.apache.accumulo.server.util.serviceStatus.ServiceStatusReport;
+ import org.apache.accumulo.server.util.serviceStatus.StatusSummary;
+ import org.apache.zookeeper.KeeperException;
+ import org.junit.jupiter.api.AfterEach;
+ import org.junit.jupiter.api.BeforeEach;
+ import org.junit.jupiter.api.Test;
+ import org.slf4j.Logger;
+ import org.slf4j.LoggerFactory;
+ 
+ public class ServiceStatusCmdTest {
+ 
+   private static final Logger LOG = 
LoggerFactory.getLogger(ServiceStatusCmdTest.class);
+ 
+   private ServerContext context;
+   private String zRoot;
+   private ZooReader zooReader;
+ 
+   @BeforeEach
+   public void populateContext() {
+     InstanceId iid = InstanceId.of(UUID.randomUUID());
+     zRoot = "/accumulo/" + iid.canonical();
+     context = createMock(ServerContext.class);
+     expect(context.getInstanceID()).andReturn(iid).anyTimes();
+     expect(context.getZooKeeperRoot()).andReturn(zRoot).anyTimes();
+ 
+     zooReader = createMock(ZooReader.class);
+ 
+     expect(context.getZooReader()).andReturn(zooReader).anyTimes();
+ 
+     replay(context);
+   }
+ 
+   @AfterEach
+   public void validateMocks() {
+     verify(context, zooReader);
+   }
+ 
+   @Test
+   void testManagerHosts() throws Exception {
+     String lock1Name = "zlock#" + UUID.randomUUID() + "#0000000001";
+     String lock2Name = "zlock#" + UUID.randomUUID() + "#0000000002";
+     String lock3Name = "zlock#" + UUID.randomUUID() + "#0000000003";
+ 
+     String host1 = "hostA:8080";
+     String host2 = "hostB:9090";
+     String host3 = "host1:9091";
+ 
+     String lockPath = zRoot + Constants.ZMANAGER_LOCK;
+     expect(zooReader.getChildren(eq(lockPath))).andReturn(List.of(lock1Name, 
lock2Name, lock3Name))
+         .anyTimes();
+     expect(zooReader.getData(eq(lockPath + "/" + 
lock1Name))).andReturn(host1.getBytes(UTF_8))
+         .anyTimes();
+     expect(zooReader.getData(eq(lockPath + "/" + 
lock2Name))).andReturn(host2.getBytes(UTF_8))
+         .anyTimes();
+     expect(zooReader.getData(eq(lockPath + "/" + 
lock3Name))).andReturn(host3.getBytes(UTF_8))
+         .anyTimes();
+ 
+     replay(zooReader);
+ 
+     ServiceStatusCmd cmd = new ServiceStatusCmd();
+     StatusSummary status = cmd.getManagerStatus(zooReader, zRoot);
+     LOG.info("manager status data: {}", status);
+ 
+     assertEquals(3, status.getServiceCount());
+ 
+     // expect sorted by name
+     Set<String> hosts = new TreeSet<>(List.of(host1, host2, host3));
+     Map<String,Set<String>> hostByGroup = new TreeMap<>();
+     hostByGroup.put(NO_GROUP_TAG, hosts);
+ 
+     StatusSummary expected =
+         new StatusSummary(ServiceStatusReport.ReportKey.MANAGER, Set.of(), 
hostByGroup, 0);
+ 
+     assertEquals(expected.hashCode(), status.hashCode());
+     assertEquals(expected.getDisplayName(), status.getDisplayName());
+     assertEquals(expected.getResourceGroups(), status.getResourceGroups());
+     assertEquals(expected.getServiceByGroups(), status.getServiceByGroups());
+     assertEquals(expected.getServiceCount(), status.getServiceCount());
+     assertEquals(expected.getErrorCount(), status.getErrorCount());
+     assertEquals(expected, status);
+   }
+ 
+   @Test
+   void testMonitorHosts() throws Exception {
+     String lock1Name = "zlock#" + UUID.randomUUID() + "#0000000001";
+     String lock2Name = "zlock#" + UUID.randomUUID() + "#0000000002";
+ 
+     String host1 = "hostA:8080";
+     String host2 = "host1:9091";
+ 
+     String lockPath = zRoot + Constants.ZMONITOR_LOCK;
+     expect(zooReader.getChildren(eq(lockPath))).andReturn(List.of(lock1Name, 
lock2Name)).anyTimes();
+     expect(zooReader.getData(eq(lockPath + "/" + 
lock1Name))).andReturn(host1.getBytes(UTF_8))
+         .anyTimes();
+     expect(zooReader.getData(eq(lockPath + "/" + 
lock2Name))).andReturn(host2.getBytes(UTF_8))
+         .anyTimes();
+ 
+     replay(zooReader);
+ 
+     ServiceStatusCmd cmd = new ServiceStatusCmd();
+     StatusSummary status = cmd.getMonitorStatus(zooReader, zRoot);
+     LOG.info("monitor status data: {}", status);
+ 
+     assertEquals(2, status.getServiceCount());
+ 
+     // expect sorted by name
+     Map<String,Set<String>> hostByGroup = new TreeMap<>();
+     hostByGroup.put(NO_GROUP_TAG, new TreeSet<>(List.of(host1, host2)));
+ 
+     StatusSummary expected =
+         new StatusSummary(ServiceStatusReport.ReportKey.MONITOR, Set.of(), 
hostByGroup, 0);
+ 
+     assertEquals(expected.hashCode(), status.hashCode());
+     assertEquals(expected.getDisplayName(), status.getDisplayName());
+     assertEquals(expected.getResourceGroups(), status.getResourceGroups());
+     assertEquals(expected.getServiceByGroups(), status.getServiceByGroups());
+     assertEquals(expected.getServiceCount(), status.getServiceCount());
+     assertEquals(expected.getErrorCount(), status.getErrorCount());
+     assertEquals(expected, status);
+   }
+ 
+   @Test
+   void testTServerHosts() throws Exception {
+     String lock1Name = "zlock#" + UUID.randomUUID() + "#0000000001";
+     String lock2Name = "zlock#" + UUID.randomUUID() + "#0000000002";
+     String lock3Name = "zlock#" + UUID.randomUUID() + "#0000000003";
+ 
+     String host1 = "hostA:8080";
+     String host2 = "hostB:9090";
+     String host3 = "host1:9091";
+ 
+     String basePath = zRoot + Constants.ZTSERVERS;
+     expect(zooReader.getChildren(eq(basePath))).andReturn(List.of(host1, 
host2, host3)).anyTimes();
+ 
+     expect(zooReader.getChildren(eq(basePath + "/" + 
host1))).andReturn(List.of(lock1Name)).once();
+     expect(zooReader.getData(eq(basePath + "/" + host1 + "/" + lock1Name)))
+         .andReturn(("TSERV_CLIENT=" + host1).getBytes(UTF_8)).anyTimes();
+ 
+     expect(zooReader.getChildren(eq(basePath + "/" + 
host2))).andReturn(List.of(lock2Name)).once();
+     expect(zooReader.getData(eq(basePath + "/" + host2 + "/" + lock2Name)))
+         .andReturn(("TSERV_CLIENT=" + host2).getBytes(UTF_8)).anyTimes();
+ 
+     expect(zooReader.getChildren(eq(basePath + "/" + 
host3))).andReturn(List.of(lock3Name)).once();
+     expect(zooReader.getData(eq(basePath + "/" + host3 + "/" + lock3Name)))
+         .andReturn(("TSERV_CLIENT=" + host3).getBytes(UTF_8)).anyTimes();
+ 
+     replay(zooReader);
+ 
+     ServiceStatusCmd cmd = new ServiceStatusCmd();
+     StatusSummary status = cmd.getTServerStatus(zooReader, zRoot);
+     LOG.info("tserver status data: {}", status);
+ 
+     assertEquals(3, status.getServiceCount());
+ 
+     // expect sorted by name
+     Map<String,Set<String>> hostByGroup = new TreeMap<>();
+     hostByGroup.put(NO_GROUP_TAG, new TreeSet<>(List.of(host1, host2, 
host3)));
+ 
+     StatusSummary expected =
+         new StatusSummary(ServiceStatusReport.ReportKey.T_SERVER, Set.of(), 
hostByGroup, 0);
+ 
+     assertEquals(expected.hashCode(), status.hashCode());
+     assertEquals(expected.getDisplayName(), status.getDisplayName());
+     assertEquals(expected.getResourceGroups(), status.getResourceGroups());
+     assertEquals(expected.getServiceByGroups(), status.getServiceByGroups());
+     assertEquals(expected.getServiceCount(), status.getServiceCount());
+     assertEquals(expected.getErrorCount(), status.getErrorCount());
+     assertEquals(expected, status);
+   }
+ 
+   @Test
+   void testScanServerHosts() throws Exception {
+     UUID uuid1 = UUID.randomUUID();
+     String lock1Name = "zlock#" + uuid1 + "#0000000001";
+     UUID uuid2 = UUID.randomUUID();
+     String lock2Name = "zlock#" + uuid2 + "#0000000022";
+     UUID uuid3 = UUID.randomUUID();
+     String lock3Name = "zlock#" + uuid3 + "#0000000033";
+     String lock4Name = "zlock#" + uuid3 + "#0000000044";
+ 
+     // UUID uuidLock = UUID.randomUUID();
+ 
+     String host1 = "host1:8080";
+     String host2 = "host2:9090";
+     String host3 = "host3:9091";
+     String host4 = "host4:9091";
+ 
+     String lockPath = zRoot + Constants.ZSSERVERS;
+     expect(zooReader.getChildren(eq(lockPath))).andReturn(List.of(host1, 
host2, host3, host4))
+         .anyTimes();
+ 
+     expect(zooReader.getChildren(eq(lockPath + "/" + 
host1))).andReturn(List.of(lock1Name)).once();
+     expect(zooReader.getData(eq(lockPath + "/" + host1 + "/" + lock1Name)))
+         .andReturn((UUID.randomUUID() + ",rg1").getBytes(UTF_8)).once();
+ 
+     expect(zooReader.getChildren(eq(lockPath + "/" + 
host2))).andReturn(List.of(lock2Name)).once();
+     expect(zooReader.getData(eq(lockPath + "/" + host2 + "/" + lock2Name)))
+         .andReturn((UUID.randomUUID() + ",default").getBytes(UTF_8)).once();
+ 
+     expect(zooReader.getChildren(eq(lockPath + "/" + 
host3))).andReturn(List.of(lock3Name)).once();
+     expect(zooReader.getData(eq(lockPath + "/" + host3 + "/" + lock3Name)))
+         .andReturn((UUID.randomUUID() + ",rg1").getBytes(UTF_8)).once();
+ 
+     expect(zooReader.getChildren(eq(lockPath + "/" + 
host4))).andReturn(List.of(lock4Name)).once();
+     expect(zooReader.getData(eq(lockPath + "/" + host4 + "/" + lock4Name)))
+         .andReturn((UUID.randomUUID() + ",default").getBytes(UTF_8)).once();
+ 
+     replay(zooReader);
+ 
+     ServiceStatusCmd cmd = new ServiceStatusCmd();
+     StatusSummary status = cmd.getScanServerStatus(zooReader, zRoot);
+     assertEquals(4, status.getServiceCount());
+ 
+     Map<String,Set<String>> hostByGroup = new TreeMap<>();
+     hostByGroup.put("default", new TreeSet<>(List.of("host2:9090", 
"host4:9091")));
+     hostByGroup.put("rg1", new TreeSet<>(List.of("host1:8080", 
"host3:9091")));
+ 
+     StatusSummary expected = new 
StatusSummary(ServiceStatusReport.ReportKey.S_SERVER,
+         Set.of("default", "rg1"), hostByGroup, 0);
+ 
+     assertEquals(expected, status);
+ 
+   }
+ 
+   @Test
+   void testCoordinatorHosts() throws Exception {
+     String lock1Name = "zlock#" + UUID.randomUUID() + "#0000000001";
+     String lock2Name = "zlock#" + UUID.randomUUID() + "#0000000002";
+     String lock3Name = "zlock#" + UUID.randomUUID() + "#0000000003";
+ 
+     String host1 = "hostA:8080";
+     String host2 = "hostB:9090";
+     String host3 = "host1:9091";
+ 
 -    String lockPath = zRoot + Constants.ZCOORDINATOR_LOCK;
++    // String lockPath = zRoot + Constants.ZCOORDINATOR_LOCK;
++    String lockPath = null;
+     expect(zooReader.getChildren(eq(lockPath))).andReturn(List.of(lock1Name, 
lock2Name, lock3Name))
+         .anyTimes();
+     expect(zooReader.getData(eq(lockPath + "/" + 
lock1Name))).andReturn(host1.getBytes(UTF_8))
+         .anyTimes();
+     expect(zooReader.getData(eq(lockPath + "/" + 
lock2Name))).andReturn(host2.getBytes(UTF_8))
+         .anyTimes();
+     expect(zooReader.getData(eq(lockPath + "/" + 
lock3Name))).andReturn(host3.getBytes(UTF_8))
+         .anyTimes();
+ 
+     replay(zooReader);
+ 
+     ServiceStatusCmd cmd = new ServiceStatusCmd();
+     StatusSummary status = cmd.getCoordinatorStatus(zooReader, zRoot);
+     LOG.info("tserver status data: {}", status);
+ 
+     assertEquals(3, status.getServiceCount());
+ 
+     // expect sorted by name
+     Set<String> hosts = new TreeSet<>(List.of(host1, host2, host3));
+     Map<String,Set<String>> hostByGroup = new TreeMap<>();
+     hostByGroup.put(NO_GROUP_TAG, hosts);
+ 
+     StatusSummary expected =
+         new StatusSummary(ServiceStatusReport.ReportKey.COORDINATOR, 
Set.of(), hostByGroup, 0);
+ 
+     assertEquals(expected.hashCode(), status.hashCode());
+     assertEquals(expected.getDisplayName(), status.getDisplayName());
+     assertEquals(expected.getResourceGroups(), status.getResourceGroups());
+     assertEquals(expected.getServiceByGroups(), status.getServiceByGroups());
+     assertEquals(expected.getServiceCount(), status.getServiceCount());
+     assertEquals(expected.getErrorCount(), status.getErrorCount());
+     assertEquals(expected, status);
+   }
+ 
+   @Test
+   public void testCompactorStatus() throws Exception {
+     String lockPath = zRoot + Constants.ZCOMPACTORS;
+     expect(zooReader.getChildren(eq(lockPath))).andReturn(List.of("q1", 
"q2")).once();
+ 
+     expect(zooReader.getChildren(eq(lockPath + "/q1")))
+         .andReturn(List.of("hostA:8080", "hostC:8081")).once();
+     expect(zooReader.getChildren(eq(lockPath + "/q2")))
+         .andReturn(List.of("hostB:9090", "hostD:9091")).once();
+ 
+     replay(zooReader);
+ 
+     ServiceStatusCmd cmd = new ServiceStatusCmd();
+     StatusSummary status = cmd.getCompactorStatus(zooReader, zRoot);
+     LOG.info("compactor group counts: {}", status);
+     assertEquals(2, status.getResourceGroups().size());
+   }
+ 
+   @Test
+   public void testGcHosts() throws Exception {
+ 
+     String lockPath = zRoot + ZGC_LOCK;
+     UUID uuid1 = UUID.randomUUID();
+     String lock1Name = "zlock#" + uuid1 + "#0000000001";
+     UUID uuid2 = UUID.randomUUID();
+     String lock2Name = "zlock#" + uuid2 + "#0000000022";
+ 
+     String host1 = "host1:8080";
+     String host2 = "host2:9090";
+ 
+     expect(zooReader.getChildren(eq(lockPath))).andReturn(List.of(lock1Name, 
lock2Name)).once();
+     expect(zooReader.getData(eq(lockPath + "/" + lock1Name)))
+         .andReturn(("GC_CLIENT=" + host1).getBytes(UTF_8)).once();
+     expect(zooReader.getData(eq(lockPath + "/" + lock2Name)))
+         .andReturn(("GC_CLIENT=" + host2).getBytes(UTF_8)).once();
+ 
+     replay(zooReader);
+ 
+     ServiceStatusCmd cmd = new ServiceStatusCmd();
+     StatusSummary status = cmd.getGcStatus(zooReader, zRoot);
+     LOG.info("gc server counts: {}", status);
+     assertEquals(0, status.getResourceGroups().size());
+     assertEquals(2, status.getServiceCount());
+     assertEquals(0, status.getErrorCount());
+     assertEquals(1, status.getServiceByGroups().size());
+     assertEquals(2, status.getServiceByGroups().get(NO_GROUP_TAG).size());
+     assertEquals(new TreeSet<>(List.of(host1, host2)),
+         status.getServiceByGroups().get(NO_GROUP_TAG));
+   }
+ 
+   /**
+    * Simulates node being deleted after lock list is read from ZooKeeper. 
Expect that the no node
+    * error is skipped and available hosts are returned.
+    */
+   @Test
+   void zkNodeDeletedTest() throws Exception {
+     String lock1Name = "zlock#" + UUID.randomUUID() + "#0000000001";
+     String lock2Name = "zlock#" + UUID.randomUUID() + "#0000000022";
+     String lock3Name = "zlock#" + UUID.randomUUID() + "#0000000099";
+     String host2 = "hostZ:8080";
+     String host3 = "hostA:8080";
+ 
+     String lockPath = zRoot + Constants.ZMANAGER_LOCK;
+     expect(zooReader.getChildren(eq(lockPath))).andReturn(List.of(lock1Name, 
lock2Name, lock3Name))
+         .anyTimes();
+     expect(zooReader.getData(eq(lockPath + "/" + lock1Name)))
+         .andThrow(new KeeperException.NoNodeException("no node forced 
exception")).anyTimes();
+     expect(zooReader.getData(eq(lockPath + "/" + 
lock2Name))).andReturn(host2.getBytes(UTF_8))
+         .anyTimes();
+     expect(zooReader.getData(eq(lockPath + "/" + 
lock3Name))).andReturn(host3.getBytes(UTF_8))
+         .anyTimes();
+     replay(zooReader);
+ 
+     ServiceStatusCmd cmd = new ServiceStatusCmd();
+     StatusSummary status = cmd.getManagerStatus(zooReader, zRoot);
+     LOG.info("manager status data: {}", status);
+ 
+     assertEquals(1, status.getServiceByGroups().size());
+     assertEquals(2, status.getServiceByGroups().get(NO_GROUP_TAG).size());
+     assertEquals(1, status.getErrorCount());
+ 
+     // host 1 missing - no node exception
+     Set<String> sortedHosts = new TreeSet<>(List.of(host3, host2));
+     assertEquals(sortedHosts, status.getServiceByGroups().get(NO_GROUP_TAG));
+   }
+ 
+   @Test
+   public void testServiceStatusCommandOpts() {
+     replay(zooReader); // needed for @AfterAll verify
+     ServiceStatusCmd.Opts opts = new ServiceStatusCmd.Opts();
+     assertFalse(opts.json);
+     assertFalse(opts.noHosts);
+   }
+ 
+ }

Reply via email to