001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.ipc;
019
020import static org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.RegionSpecifier.RegionSpecifierType.REGION_NAME;
021
022import edu.umd.cs.findbugs.annotations.Nullable;
023import java.io.IOException;
024import java.io.InterruptedIOException;
025import org.apache.hadoop.hbase.DoNotRetryIOException;
026import org.apache.hadoop.hbase.HConstants;
027import org.apache.hadoop.hbase.exceptions.UnknownProtocolException;
028import org.apache.hadoop.util.StringUtils;
029import org.apache.yetus.audience.InterfaceAudience;
030import org.slf4j.Logger;
031import org.slf4j.LoggerFactory;
032
033import org.apache.hbase.thirdparty.com.google.protobuf.ByteString;
034import org.apache.hbase.thirdparty.com.google.protobuf.Descriptors;
035import org.apache.hbase.thirdparty.com.google.protobuf.Descriptors.MethodDescriptor;
036import org.apache.hbase.thirdparty.com.google.protobuf.Descriptors.ServiceDescriptor;
037import org.apache.hbase.thirdparty.com.google.protobuf.Message;
038import org.apache.hbase.thirdparty.com.google.protobuf.RpcCallback;
039import org.apache.hbase.thirdparty.com.google.protobuf.RpcController;
040import org.apache.hbase.thirdparty.com.google.protobuf.Service;
041
042import org.apache.hadoop.hbase.shaded.protobuf.ProtobufUtil;
043import org.apache.hadoop.hbase.shaded.protobuf.RequestConverter;
044import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos;
045import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.CoprocessorServiceCall;
046import org.apache.hadoop.hbase.shaded.protobuf.generated.ClientProtos.CoprocessorServiceRequest;
047import org.apache.hadoop.hbase.shaded.protobuf.generated.HBaseProtos.RegionSpecifier.RegionSpecifierType;
048
049/**
050 * Utilities for handling coprocessor rpc service calls.
051 */
052@InterfaceAudience.Private
053public final class CoprocessorRpcUtils {
054  private static final Logger LOG = LoggerFactory.getLogger(CoprocessorRpcUtils.class);
055  /**
056   * We assume that all HBase protobuf services share a common package name (defined in the .proto
057   * files).
058   */
059  private static final String hbaseServicePackage;
060  static {
061    Descriptors.ServiceDescriptor clientService = ClientProtos.ClientService.getDescriptor();
062    hbaseServicePackage = clientService.getFullName().substring(0,
063      clientService.getFullName().lastIndexOf(clientService.getName()));
064  }
065
066  private CoprocessorRpcUtils() {
067    // private for utility class
068  }
069
070  /**
071   * Returns the name to use for coprocessor service calls. For core HBase services (in the hbase.pb
072   * protobuf package), this returns the unqualified name in order to provide backward compatibility
073   * across the package name change. For all other services, the fully-qualified service name is
074   * used.
075   */
076  public static String getServiceName(Descriptors.ServiceDescriptor service) {
077    if (service.getFullName().startsWith(hbaseServicePackage)) {
078      return service.getName();
079    }
080    return service.getFullName();
081  }
082
083  public static CoprocessorServiceRequest
084    getCoprocessorServiceRequest(final Descriptors.MethodDescriptor method, final Message request) {
085    return getCoprocessorServiceRequest(method, request, HConstants.EMPTY_BYTE_ARRAY,
086      HConstants.EMPTY_BYTE_ARRAY);
087  }
088
089  public static CoprocessorServiceRequest getCoprocessorServiceRequest(
090    final Descriptors.MethodDescriptor method, final Message request, final byte[] row,
091    final byte[] regionName) {
092    return CoprocessorServiceRequest.newBuilder()
093      .setCall(getCoprocessorServiceCall(method, request, row))
094      .setRegion(RequestConverter.buildRegionSpecifier(REGION_NAME, regionName)).build();
095  }
096
097  private static CoprocessorServiceCall getCoprocessorServiceCall(final MethodDescriptor method,
098    final Message request, final byte[] row) {
099    return CoprocessorServiceCall.newBuilder()
100      .setRow(org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations.unsafeWrap(row))
101      .setServiceName(CoprocessorRpcUtils.getServiceName(method.getService()))
102      .setMethodName(method.getName())
103      // TODO!!!!! Come back here after!!!!! This is a double copy of the request if I read
104      // it right copying from non-shaded to shaded version!!!!!! FIXXXXX!!!!!
105      .setRequest(org.apache.hbase.thirdparty.com.google.protobuf.UnsafeByteOperations
106        .unsafeWrap(request.toByteArray()))
107      .build();
108  }
109
110  public static MethodDescriptor getMethodDescriptor(final String methodName,
111    final ServiceDescriptor serviceDesc) throws UnknownProtocolException {
112    MethodDescriptor methodDesc = serviceDesc.findMethodByName(methodName);
113    if (methodDesc == null) {
114      throw new UnknownProtocolException(
115        "Unknown method " + methodName + " called on service " + serviceDesc.getFullName());
116    }
117    return methodDesc;
118  }
119
120  public static Message getRequest(Service service, MethodDescriptor methodDesc,
121    ByteString shadedRequest) throws IOException {
122    Message.Builder builderForType = service.getRequestPrototype(methodDesc).newBuilderForType();
123    ProtobufUtil.mergeFrom(builderForType,
124      // TODO: COPY FROM SHADED TO NON_SHADED. DO I HAVE TOO?
125      shadedRequest.toByteArray());
126    return builderForType.build();
127  }
128
129  public static Message getResponse(ClientProtos.CoprocessorServiceResponse result,
130    Message responsePrototype) throws IOException {
131    Message response;
132    if (result.getValue().hasValue()) {
133      Message.Builder builder = responsePrototype.newBuilderForType();
134      builder.mergeFrom(result.getValue().getValue().newInput());
135      response = builder.build();
136    } else {
137      response = responsePrototype.getDefaultInstanceForType();
138    }
139    if (LOG.isTraceEnabled()) {
140      LOG.trace("Master Result is value=" + response);
141    }
142    return response;
143  }
144
145  public static ClientProtos.CoprocessorServiceResponse getResponse(final Message result,
146    final byte[] regionName) {
147    ClientProtos.CoprocessorServiceResponse.Builder builder =
148      ClientProtos.CoprocessorServiceResponse.newBuilder();
149    builder.setRegion(
150      RequestConverter.buildRegionSpecifier(RegionSpecifierType.REGION_NAME, regionName));
151    // TODO: UGLY COPY IN HERE!!!!
152    builder.setValue(builder.getValueBuilder().setName(result.getClass().getName()).setValue(
153      org.apache.hbase.thirdparty.com.google.protobuf.ByteString.copyFrom(result.toByteArray())));
154    return builder.build();
155  }
156
157  /**
158   * Simple {@link RpcCallback} implementation providing a {@link java.util.concurrent.Future}-like
159   * {@link BlockingRpcCallback#get()} method, which will block util the instance's
160   * {@link BlockingRpcCallback#run(Object)} method has been called. {@code R} is the RPC response
161   * type that will be passed to the {@link #run(Object)} method.
162   */
163  @InterfaceAudience.Private
164  // Copy of BlockingRpcCallback but deriving from RpcCallback non-shaded.
165  public static class BlockingRpcCallback<R> implements RpcCallback<R> {
166    private R result;
167    private boolean resultSet = false;
168
169    /**
170     * Called on completion of the RPC call with the response object, or {@code null} in the case of
171     * an error.
172     * @param parameter the response object or {@code null} if an error occurred
173     */
174    @Override
175    public void run(R parameter) {
176      synchronized (this) {
177        result = parameter;
178        resultSet = true;
179        this.notifyAll();
180      }
181    }
182
183    /**
184     * Returns the parameter passed to {@link #run(Object)} or {@code null} if a null value was
185     * passed. When used asynchronously, this method will block until the {@link #run(Object)}
186     * method has been called.
187     * @return the response object or {@code null} if no response was passed
188     */
189    public synchronized R get() throws IOException {
190      while (!resultSet) {
191        try {
192          this.wait();
193        } catch (InterruptedException ie) {
194          InterruptedIOException exception = new InterruptedIOException(ie.getMessage());
195          exception.initCause(ie);
196          throw exception;
197        }
198      }
199      return result;
200    }
201  }
202
203  /**
204   * Stores an exception encountered during RPC invocation so it can be passed back through to the
205   * client.
206   * @param controller the controller instance provided by the client when calling the service
207   * @param ioe        the exception encountered
208   */
209  public static void setControllerException(RpcController controller, IOException ioe) {
210    if (controller == null) {
211      return;
212    }
213    if (controller instanceof org.apache.hadoop.hbase.ipc.ServerRpcController) {
214      ((ServerRpcController) controller).setFailedOn(ioe);
215    } else {
216      controller.setFailed(StringUtils.stringifyException(ioe));
217    }
218  }
219
220  /**
221   * Retreivies exception stored during RPC invocation.
222   * @param controller the controller instance provided by the client when calling the service
223   * @return exception if any, or null; Will return DoNotRetryIOException for string represented
224   *         failure causes in controller.
225   */
226  @Nullable
227  public static IOException getControllerException(RpcController controller) throws IOException {
228    if (controller == null || !controller.failed()) {
229      return null;
230    }
231    if (controller instanceof ServerRpcController) {
232      return ((ServerRpcController) controller).getFailedOn();
233    }
234    return new DoNotRetryIOException(controller.errorText());
235  }
236}