Skip to content

Commit e37aafc

Browse files
committed
HBASE-24260 Add a ClusterManager that issues commands via coprocessor
Implements `ClusterManager` that relies on the new `ShellExecEndpointCoprocessor` for remote shell command execution. Signed-off-by: Bharath Vissapragada <bharathv@apache.org>
1 parent 7313b4f commit e37aafc

File tree

16 files changed

+565
-44
lines changed

16 files changed

+565
-44
lines changed

hbase-client/src/main/java/org/apache/hadoop/hbase/Coprocessor.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.util.Collections;
2424
import org.apache.yetus.audience.InterfaceAudience;
2525
import org.apache.yetus.audience.InterfaceStability;
26-
2726
import org.apache.hbase.thirdparty.com.google.protobuf.Service;
2827

2928
/**

hbase-it/pom.xml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -256,7 +256,17 @@
256256
<dependency>
257257
<groupId>junit</groupId>
258258
<artifactId>junit</artifactId>
259-
<scope>compile</scope>
259+
<scope>test</scope>
260+
</dependency>
261+
<dependency>
262+
<groupId>org.hamcrest</groupId>
263+
<artifactId>hamcrest-core</artifactId>
264+
<scope>test</scope>
265+
</dependency>
266+
<dependency>
267+
<groupId>org.hamcrest</groupId>
268+
<artifactId>hamcrest-library</artifactId>
269+
<scope>test</scope>
260270
</dependency>
261271
<dependency>
262272
<groupId>org.mockito</groupId>
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
package org.apache.hadoop.hbase;
19+
20+
import java.io.IOException;
21+
import java.util.Collections;
22+
import java.util.HashSet;
23+
import java.util.Objects;
24+
import java.util.Set;
25+
import org.apache.commons.lang3.StringUtils;
26+
import org.apache.hadoop.hbase.client.AsyncAdmin;
27+
import org.apache.hadoop.hbase.client.AsyncConnection;
28+
import org.apache.hadoop.hbase.client.ConnectionFactory;
29+
import org.apache.hadoop.hbase.coprocessor.protobuf.generated.ShellExecEndpoint.ShellExecRequest;
30+
import org.apache.hadoop.hbase.coprocessor.protobuf.generated.ShellExecEndpoint.ShellExecResponse;
31+
import org.apache.hadoop.hbase.coprocessor.protobuf.generated.ShellExecEndpoint.ShellExecService;
32+
import org.apache.hadoop.hbase.util.Pair;
33+
import org.apache.yetus.audience.InterfaceAudience;
34+
import org.slf4j.Logger;
35+
import org.slf4j.LoggerFactory;
36+
37+
/**
38+
* Overrides commands to make use of coprocessor where possible. Only supports actions taken
39+
* against Master and Region Server hosts.
40+
*/
41+
@InterfaceAudience.Private
42+
@SuppressWarnings("unused") // no way to test this without a distributed cluster.
43+
public class CoprocClusterManager extends HBaseClusterManager {
44+
private static final Logger LOG = LoggerFactory.getLogger(CoprocClusterManager.class);
45+
private static final Set<ServiceType> supportedServices = buildSupportedServicesSet();
46+
47+
@Override
48+
protected Pair<Integer, String> exec(String hostname, ServiceType service, String... cmd)
49+
throws IOException {
50+
if (!supportedServices.contains(service)) {
51+
throw unsupportedServiceType(service);
52+
}
53+
54+
// We only support actions vs. Master or Region Server processes. We're issuing those actions
55+
// via the coprocessor that's running within those processes. Thus, there's no support for
56+
// honoring the configured service user.
57+
final String command = StringUtils.join(cmd, " ");
58+
LOG.info("Executing remote command: {}, hostname:{}", command, hostname);
59+
60+
try (final AsyncConnection conn = ConnectionFactory.createAsyncConnection(getConf()).join()) {
61+
final AsyncAdmin admin = conn.getAdmin();
62+
final ShellExecRequest req = ShellExecRequest.newBuilder()
63+
.setCommand(command)
64+
.setAwaitResponse(false)
65+
.build();
66+
67+
final ShellExecResponse resp;
68+
switch(service) {
69+
case HBASE_MASTER:
70+
// What happens if the intended action was killing a backup master? Right now we have
71+
// no `RestartBackupMasterAction` so it's probably fine.
72+
resp = masterExec(admin, req);
73+
break;
74+
case HBASE_REGIONSERVER:
75+
final ServerName targetHost = resolveRegionServerName(admin, hostname);
76+
resp = regionServerExec(admin, req, targetHost);
77+
break;
78+
default:
79+
throw new RuntimeException("should not happen");
80+
}
81+
82+
if (LOG.isDebugEnabled()) {
83+
LOG.debug("Executed remote command: {}, exit code:{} , output:{}", command, resp.getExitCode(),
84+
resp.getStdout());
85+
} else {
86+
LOG.info("Executed remote command: {}, exit code:{}", command, resp.getExitCode());
87+
}
88+
return new Pair<>(resp.getExitCode(), resp.getStdout());
89+
}
90+
}
91+
92+
private static Set<ServiceType> buildSupportedServicesSet() {
93+
final Set<ServiceType> set = new HashSet<>();
94+
set.add(ServiceType.HBASE_MASTER);
95+
set.add(ServiceType.HBASE_REGIONSERVER);
96+
return Collections.unmodifiableSet(set);
97+
}
98+
99+
private static ShellExecResponse masterExec(final AsyncAdmin admin,
100+
final ShellExecRequest req) {
101+
// TODO: Admin API provides no means of sending exec to a backup master.
102+
return admin.<ShellExecService.Stub, ShellExecResponse>coprocessorService(
103+
ShellExecService::newStub,
104+
(stub, controller, callback) -> stub.shellExec(controller, req, callback))
105+
.join();
106+
}
107+
108+
private static ShellExecResponse regionServerExec(final AsyncAdmin admin,
109+
final ShellExecRequest req, final ServerName targetHost) {
110+
return admin.<ShellExecService.Stub, ShellExecResponse>coprocessorService(
111+
ShellExecService::newStub,
112+
(stub, controller, callback) -> stub.shellExec(controller, req, callback),
113+
targetHost)
114+
.join();
115+
}
116+
117+
private static ServerName resolveRegionServerName(final AsyncAdmin admin,
118+
final String hostname) {
119+
return admin.getRegionServers()
120+
.thenApply(names -> names.stream()
121+
.filter(sn -> Objects.equals(sn.getHostname(), hostname))
122+
.findAny())
123+
.join()
124+
.orElseThrow(() -> serverNotFound(hostname));
125+
}
126+
127+
private static RuntimeException serverNotFound(final String hostname) {
128+
return new RuntimeException(
129+
String.format("Did not find %s amongst the servers known to the client.", hostname));
130+
}
131+
132+
private static RuntimeException unsupportedServiceType(final ServiceType serviceType) {
133+
return new RuntimeException(
134+
String.format("Unable to service request for service=%s", serviceType));
135+
}
136+
}

hbase-it/src/test/java/org/apache/hadoop/hbase/HBaseClusterManager.java

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import java.io.IOException;
2323
import java.util.Locale;
2424
import java.util.Map;
25-
2625
import org.apache.commons.lang3.StringUtils;
2726
import org.apache.hadoop.conf.Configuration;
2827
import org.apache.hadoop.conf.Configured;
@@ -45,9 +44,12 @@
4544
*/
4645
@InterfaceAudience.Private
4746
public class HBaseClusterManager extends Configured implements ClusterManager {
48-
private static final String SIGKILL = "SIGKILL";
49-
private static final String SIGSTOP = "SIGSTOP";
50-
private static final String SIGCONT = "SIGCONT";
47+
48+
protected enum Signal {
49+
SIGKILL,
50+
SIGSTOP,
51+
SIGCONT,
52+
}
5153

5254
protected static final Logger LOG = LoggerFactory.getLogger(HBaseClusterManager.class);
5355
private String sshUserName;
@@ -107,7 +109,7 @@ public void setConf(Configuration conf) {
107109
.setSleepInterval(conf.getLong(RETRY_SLEEP_INTERVAL_KEY, DEFAULT_RETRY_SLEEP_INTERVAL)));
108110
}
109111

110-
private String getServiceUser(ServiceType service) {
112+
protected String getServiceUser(ServiceType service) {
111113
Configuration conf = getConf();
112114
switch (service) {
113115
case HADOOP_DATANODE:
@@ -329,9 +331,9 @@ protected CommandProvider getCommandProvider(ServiceType service) throws IOExcep
329331
* @return pair of exit code and command output
330332
* @throws IOException if something goes wrong.
331333
*/
332-
private Pair<Integer, String> exec(String hostname, ServiceType service, String... cmd)
334+
protected Pair<Integer, String> exec(String hostname, ServiceType service, String... cmd)
333335
throws IOException {
334-
LOG.info("Executing remote command: {} , hostname:{}", StringUtils.join(cmd, " "),
336+
LOG.info("Executing remote command: {}, hostname:{}", StringUtils.join(cmd, " "),
335337
hostname);
336338

337339
RemoteShell shell = new RemoteShell(hostname, getServiceUser(service), cmd);
@@ -444,8 +446,9 @@ public void restart(ServiceType service, String hostname, int port) throws IOExc
444446
exec(hostname, service, Operation.RESTART);
445447
}
446448

447-
public void signal(ServiceType service, String signal, String hostname) throws IOException {
448-
execWithRetries(hostname, service, getCommandProvider(service).signalCommand(service, signal));
449+
public void signal(ServiceType service, Signal signal, String hostname) throws IOException {
450+
execWithRetries(hostname, service,
451+
getCommandProvider(service).signalCommand(service, signal.toString()));
449452
}
450453

451454
@Override
@@ -457,16 +460,16 @@ public boolean isRunning(ServiceType service, String hostname, int port) throws
457460

458461
@Override
459462
public void kill(ServiceType service, String hostname, int port) throws IOException {
460-
signal(service, SIGKILL, hostname);
463+
signal(service, Signal.SIGKILL, hostname);
461464
}
462465

463466
@Override
464467
public void suspend(ServiceType service, String hostname, int port) throws IOException {
465-
signal(service, SIGSTOP, hostname);
468+
signal(service, Signal.SIGSTOP, hostname);
466469
}
467470

468471
@Override
469472
public void resume(ServiceType service, String hostname, int port) throws IOException {
470-
signal(service, SIGCONT, hostname);
473+
signal(service, Signal.SIGCONT, hostname);
471474
}
472475
}

hbase-it/src/test/java/org/apache/hadoop/hbase/IntegrationTestingUtility.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ public void createDistributedHBaseCluster() throws IOException {
147147
HConstants.HBASE_DIR, conf.get(HConstants.HBASE_DIR));
148148
Class<? extends ClusterManager> clusterManagerClass = conf.getClass(HBASE_CLUSTER_MANAGER_CLASS,
149149
DEFAULT_HBASE_CLUSTER_MANAGER_CLASS, ClusterManager.class);
150+
LOG.info("Instantiating {}", clusterManagerClass.getName());
150151
ClusterManager clusterManager = ReflectionUtils.newInstance(
151152
clusterManagerClass, conf);
152153
setHBaseCluster(new DistributedHBaseCluster(conf, clusterManager));

hbase-it/src/test/java/org/apache/hadoop/hbase/RESTApiClusterManager.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ public class RESTApiClusterManager extends Configured implements ClusterManager
7474
"hbase.it.clustermanager.restapi.password";
7575
private static final String REST_API_CLUSTER_MANAGER_CLUSTER_NAME =
7676
"hbase.it.clustermanager.restapi.clustername";
77+
private static final String REST_API_DELEGATE_CLUSTER_MANAGER =
78+
"hbase.it.clustermanager.restapi.delegate";
7779

7880
private static final JsonParser parser = new JsonParser();
7981

@@ -86,8 +88,6 @@ public class RESTApiClusterManager extends Configured implements ClusterManager
8688
// Fields for the hostname, username, password, and cluster name of the cluster management server
8789
// to be used.
8890
private String serverHostname;
89-
private String serverUsername;
90-
private String serverPassword;
9191
private String clusterName;
9292

9393
// Each version of Cloudera Manager supports a particular API versions. Version 6 of this API
@@ -103,10 +103,7 @@ public class RESTApiClusterManager extends Configured implements ClusterManager
103103

104104
private static final Logger LOG = LoggerFactory.getLogger(RESTApiClusterManager.class);
105105

106-
RESTApiClusterManager() {
107-
hBaseClusterManager = ReflectionUtils.newInstance(HBaseClusterManager.class,
108-
new IntegrationTestingUtility().getConfiguration());
109-
}
106+
RESTApiClusterManager() { }
110107

111108
@Override
112109
public void setConf(Configuration conf) {
@@ -115,12 +112,17 @@ public void setConf(Configuration conf) {
115112
// `Configured()` constructor calls `setConf(null)` before calling again with a real value.
116113
return;
117114
}
115+
116+
final Class<? extends ClusterManager> clazz = conf.getClass(REST_API_DELEGATE_CLUSTER_MANAGER,
117+
HBaseClusterManager.class, ClusterManager.class);
118+
hBaseClusterManager = ReflectionUtils.newInstance(clazz, conf);
119+
118120
serverHostname = conf.get(REST_API_CLUSTER_MANAGER_HOSTNAME, DEFAULT_SERVER_HOSTNAME);
119-
serverUsername = conf.get(REST_API_CLUSTER_MANAGER_USERNAME, DEFAULT_SERVER_USERNAME);
120-
serverPassword = conf.get(REST_API_CLUSTER_MANAGER_PASSWORD, DEFAULT_SERVER_PASSWORD);
121121
clusterName = conf.get(REST_API_CLUSTER_MANAGER_CLUSTER_NAME, DEFAULT_CLUSTER_NAME);
122122

123123
// Add filter to Client instance to enable server authentication.
124+
String serverUsername = conf.get(REST_API_CLUSTER_MANAGER_USERNAME, DEFAULT_SERVER_USERNAME);
125+
String serverPassword = conf.get(REST_API_CLUSTER_MANAGER_PASSWORD, DEFAULT_SERVER_PASSWORD);
124126
client.register(HttpAuthenticationFeature.basic(serverUsername, serverPassword));
125127
}
126128

0 commit comments

Comments
 (0)