View Javadoc

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 com.google.protobuf.ServiceException;
21  import java.io.IOException;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.HashSet;
25  import java.util.Set;
26  import org.apache.commons.lang.StringUtils;
27  import org.apache.hadoop.hbase.classification.InterfaceAudience;
28  import org.apache.hadoop.hbase.client.Admin;
29  import org.apache.hadoop.hbase.client.Connection;
30  import org.apache.hadoop.hbase.client.ConnectionFactory;
31  import org.apache.hadoop.hbase.coprocessor.protobuf.generated.ShellExecEndpoint.ShellExecRequest;
32  import org.apache.hadoop.hbase.coprocessor.protobuf.generated.ShellExecEndpoint.ShellExecResponse;
33  import org.apache.hadoop.hbase.coprocessor.protobuf.generated.ShellExecEndpoint.ShellExecService;
34  import org.apache.hadoop.hbase.ipc.HBaseRpcControllerImpl;
35  import org.apache.hadoop.hbase.util.Pair;
36  import org.slf4j.Logger;
37  import org.slf4j.LoggerFactory;
38  
39  /**
40   * Overrides commands to make use of coprocessor where possible. Only supports actions taken
41   * against Master and Region Server hosts.
42   */
43  @InterfaceAudience.Private
44  @SuppressWarnings("unused") // no way to test this without a distributed cluster.
45  public class CoprocClusterManager extends HBaseClusterManager {
46    private static final Logger LOG = LoggerFactory.getLogger(CoprocClusterManager.class);
47    private static final Set<ServiceType> supportedServices = buildSupportedServicesSet();
48  
49    @Override
50    protected Pair<Integer, String> exec(String hostname, ServiceType service, String... cmd)
51      throws IOException {
52      if (!supportedServices.contains(service)) {
53        throw unsupportedServiceType(service);
54      }
55  
56      // We only support actions vs. Master or Region Server processes. We're issuing those actions
57      // via the coprocessor that's running within those processes. Thus, there's no support for
58      // honoring the configured service user.
59      final String command = StringUtils.join(cmd, " ");
60      LOG.info(String.format("Executing remote command: %s, hostname:%s", command, hostname));
61  
62      try (final Connection conn = ConnectionFactory.createConnection(getConf())) {
63        final Admin admin = conn.getAdmin();
64        final ShellExecRequest req = ShellExecRequest.newBuilder()
65          .setCommand(command)
66          .setAwaitResponse(false)
67          .build();
68  
69        final ShellExecResponse resp;
70        try {
71          switch (service) {
72            case HBASE_MASTER:
73              // What happens if the intended action was killing a backup master? Right now we have
74              // no `RestartBackupMasterAction` so it's probably fine.
75              resp = masterExec(admin, req);
76              break;
77            case HBASE_REGIONSERVER:
78              final ServerName targetHost = resolveRegionServerName(admin, hostname);
79              resp = regionServerExec(admin, req, targetHost);
80              break;
81            default:
82              throw new RuntimeException("should not happen");
83          }
84        } catch (ServiceException se) {
85          LOG.error(String.format("Error running command: %s, error: %s", command, se.getMessage()));
86          // Wrap and re-throw, can be improved but didn't want to change exception handling
87          // in the parent classes.
88          throw new IOException(se);
89        }
90  
91        if (LOG.isDebugEnabled()) {
92          LOG.debug(String.format("Executed remote command: %s, exit code:%s , output:%s", command,
93              resp.getExitCode(), resp.getStdout()));
94        } else {
95          LOG.info(String.format("Executed remote command: %s, exit code:%s", command,
96              resp.getExitCode()));
97        }
98        return new Pair<>(resp.getExitCode(), resp.getStdout());
99      }
100   }
101 
102   private static Set<ServiceType> buildSupportedServicesSet() {
103     final Set<ServiceType> set = new HashSet<>();
104     set.add(ServiceType.HBASE_MASTER);
105     set.add(ServiceType.HBASE_REGIONSERVER);
106     return Collections.unmodifiableSet(set);
107   }
108 
109   private static ShellExecResponse masterExec(final Admin admin,
110     final ShellExecRequest req) throws ServiceException {
111     // TODO: Admin API provides no means of sending exec to a backup master.
112     return ShellExecService.newBlockingStub(
113         admin.coprocessorService()).shellExec(new HBaseRpcControllerImpl(), req);
114   }
115 
116   private static ShellExecResponse regionServerExec(final Admin admin,
117     final ShellExecRequest req, final ServerName targetHost) throws ServiceException {
118     return  ShellExecService.newBlockingStub(
119         admin.coprocessorService(targetHost)).shellExec(new HBaseRpcControllerImpl(), req);
120   }
121 
122   private static ServerName resolveRegionServerName(final Admin admin,
123     final String hostname) throws IOException {
124     Collection<ServerName> liveServers = admin.getClusterStatus().getServers();
125     for (ServerName sname: liveServers) {
126       if (sname.getServerName().equals(hostname)) {
127         return sname;
128       }
129     }
130     throw serverNotFound(hostname);
131   }
132 
133   private static RuntimeException serverNotFound(final String hostname) {
134     return new RuntimeException(
135       String.format("Did not find %s amongst the servers known to the client.", hostname));
136   }
137 
138   private static RuntimeException unsupportedServiceType(final ServiceType serviceType) {
139     return new RuntimeException(
140       String.format("Unable to service request for service=%s", serviceType));
141   }
142 }