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 java.lang.reflect.Method;
021import java.util.HashMap;
022import java.util.Map;
023import org.apache.commons.lang3.StringUtils;
024import org.apache.hadoop.hbase.HBaseRpcServicesBase;
025import org.apache.hadoop.hbase.HConstants;
026import org.apache.hadoop.hbase.security.User;
027import org.apache.yetus.audience.InterfaceAudience;
028
029import org.apache.hbase.thirdparty.com.google.protobuf.Message;
030
031import org.apache.hadoop.hbase.shaded.protobuf.generated.RPCProtos.RequestHeader;
032
033/**
034 * Reads special method annotations and table names to figure a priority for use by QoS facility in
035 * ipc; e.g: rpcs to hbase:meta get priority.
036 */
037// TODO: Remove. This is doing way too much work just to figure a priority. Do as Elliott
038// suggests and just have the client specify a priority.
039
040// The logic for figuring out high priority RPCs is as follows:
041// 1. if the method is annotated with a QosPriority of QOS_HIGH,
042// that is honored
043// 2. parse out the protobuf message and see if the request is for meta
044// region, and if so, treat it as a high priority RPC
045// Some optimizations for (2) are done in the sub classes -
046// Clients send the argument classname as part of making the RPC. The server
047// decides whether to deserialize the proto argument message based on the
048// pre-established set of argument classes (knownArgumentClasses below).
049// This prevents the server from having to deserialize all proto argument
050// messages prematurely.
051// All the argument classes declare a 'getRegion' method that returns a
052// RegionSpecifier object. Methods can be invoked on the returned object
053// to figure out whether it is a meta region or not.
054@InterfaceAudience.Private
055public abstract class AnnotationReadingPriorityFunction<T extends HBaseRpcServicesBase<?>>
056  implements PriorityFunction {
057
058  protected final Map<String, Integer> annotatedQos;
059  // We need to mock the regionserver instance for some unit tests (set via
060  // setRegionServer method.
061  protected final T rpcServices;
062
063  /**
064   * Constructs the priority function given the RPC server implementation and the annotations on the
065   * methods.
066   * @param rpcServices The RPC server implementation
067   */
068  public AnnotationReadingPriorityFunction(final T rpcServices) {
069    Map<String, Integer> qosMap = new HashMap<>();
070    for (Method m : rpcServices.getClass().getMethods()) {
071      QosPriority p = m.getAnnotation(QosPriority.class);
072      if (p != null) {
073        // Since we protobuf'd, and then subsequently, when we went with pb style, method names
074        // are capitalized. This meant that this brittle compare of method names gotten by
075        // reflection no longer matched the method names coming in over pb.
076        // TODO: Get rid of this check. For now, workaround is to capitalize the names we got from
077        // reflection so they have chance of matching the pb ones.
078        String capitalizedMethodName = StringUtils.capitalize(m.getName());
079        qosMap.put(capitalizedMethodName, p.priority());
080      }
081    }
082    this.rpcServices = rpcServices;
083    this.annotatedQos = qosMap;
084  }
085
086  /**
087   * Returns a 'priority' based on the request type.
088   * <p/>
089   * Currently the returned priority is used for queue selection.
090   * <p/>
091   * See the {@code SimpleRpcScheduler} as example. It maintains a queue per 'priority type':
092   * <ul>
093   * <li>HIGH_QOS (meta requests)</li>
094   * <li>REPLICATION_QOS (replication requests)</li>
095   * <li>NORMAL_QOS (user requests).</li>
096   * </ul>
097   */
098  @Override
099  public int getPriority(RequestHeader header, Message param, User user) {
100    int priorityByAnnotation = getAnnotatedPriority(header);
101
102    if (priorityByAnnotation >= 0) {
103      return priorityByAnnotation;
104    }
105    if (param == null) {
106      return HConstants.NORMAL_QOS;
107    }
108    return getBasePriority(header, param);
109  }
110
111  /**
112   * See if the method has an annotation.
113   * @return Return the priority from the annotation. If there isn't an annotation, this returns
114   *         something below zero.
115   */
116  protected int getAnnotatedPriority(RequestHeader header) {
117    String methodName = header.getMethodName();
118    Integer priorityByAnnotation = annotatedQos.get(methodName);
119    if (priorityByAnnotation != null) {
120      return normalizePriority(priorityByAnnotation);
121    }
122    return -1;
123  }
124
125  protected abstract int normalizePriority(int priority);
126
127  /**
128   * Get the priority for a given request from the header and the param.
129   * <p/>
130   * This doesn't consider which user is sending the request at all.
131   * <p/>
132   * This doesn't consider annotations
133   */
134  protected abstract int getBasePriority(RequestHeader header, Message param);
135}