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