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.ipc;
19  
20  import io.netty.util.HashedWheelTimer;
21  import io.netty.util.Timeout;
22  import io.netty.util.TimerTask;
23  
24  import java.io.IOException;
25  import java.net.UnknownHostException;
26  import java.util.concurrent.TimeUnit;
27  
28  import org.apache.commons.logging.Log;
29  import org.apache.commons.logging.LogFactory;
30  import org.apache.hadoop.conf.Configuration;
31  import org.apache.hadoop.hbase.HConstants;
32  import org.apache.hadoop.hbase.classification.InterfaceAudience;
33  import org.apache.hadoop.hbase.codec.Codec;
34  import org.apache.hadoop.hbase.protobuf.ProtobufUtil;
35  import org.apache.hadoop.hbase.protobuf.generated.AuthenticationProtos;
36  import org.apache.hadoop.hbase.protobuf.generated.RPCProtos.ConnectionHeader;
37  import org.apache.hadoop.hbase.protobuf.generated.RPCProtos.UserInformation;
38  import org.apache.hadoop.hbase.security.AuthMethod;
39  import org.apache.hadoop.hbase.security.SecurityInfo;
40  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
41  import org.apache.hadoop.io.Text;
42  import org.apache.hadoop.io.compress.CompressionCodec;
43  import org.apache.hadoop.security.SecurityUtil;
44  import org.apache.hadoop.security.UserGroupInformation;
45  import org.apache.hadoop.security.token.Token;
46  import org.apache.hadoop.security.token.TokenIdentifier;
47  import org.apache.hadoop.security.token.TokenSelector;
48  
49  /**
50   * Base class for ipc connection.
51   */
52  @InterfaceAudience.Private
53  abstract class RpcConnection {
54  
55    private static final Log LOG = LogFactory.getLog(RpcConnection.class);
56  
57    protected final ConnectionId remoteId;
58  
59    protected final AuthMethod authMethod;
60  
61    protected final boolean useSasl;
62  
63    protected final Token<? extends TokenIdentifier> token;
64  
65    protected final String serverPrincipal; // server's krb5 principal name
66  
67    protected final int reloginMaxBackoff; // max pause before relogin on sasl failure
68  
69    protected final Codec codec;
70  
71    protected final CompressionCodec compressor;
72  
73    protected final HashedWheelTimer timeoutTimer;
74  
75    // the last time we were picked up from connection pool.
76    protected long lastTouched;
77  
78    protected RpcConnection(Configuration conf, HashedWheelTimer timeoutTimer, ConnectionId remoteId,
79        String clusterId, boolean isSecurityEnabled, Codec codec, CompressionCodec compressor)
80        throws IOException {
81      if (remoteId.getAddress().isUnresolved()) {
82        throw new UnknownHostException("unknown host: " + remoteId.getAddress().getHostName());
83      }
84      this.timeoutTimer = timeoutTimer;
85      this.codec = codec;
86      this.compressor = compressor;
87  
88      UserGroupInformation ticket = remoteId.getTicket().getUGI();
89      SecurityInfo securityInfo = SecurityInfo.getInfo(remoteId.getServiceName());
90      this.useSasl = isSecurityEnabled;
91      Token<? extends TokenIdentifier> token = null;
92      String serverPrincipal = null;
93      if (useSasl && securityInfo != null) {
94        AuthenticationProtos.TokenIdentifier.Kind tokenKind = securityInfo.getTokenKind();
95        if (tokenKind != null) {
96          TokenSelector<? extends TokenIdentifier> tokenSelector = AbstractRpcClient.TOKEN_HANDLERS
97              .get(tokenKind);
98          if (tokenSelector != null) {
99            token = tokenSelector.selectToken(new Text(clusterId), ticket.getTokens());
100         } else if (LOG.isDebugEnabled()) {
101           LOG.debug("No token selector found for type " + tokenKind);
102         }
103       }
104       String serverKey = securityInfo.getServerPrincipal();
105       if (serverKey == null) {
106         throw new IOException("Can't obtain server Kerberos config key from SecurityInfo");
107       }
108       serverPrincipal = SecurityUtil.getServerPrincipal(conf.get(serverKey),
109         remoteId.address.getAddress().getCanonicalHostName().toLowerCase());
110       if (LOG.isDebugEnabled()) {
111         LOG.debug("RPC Server Kerberos principal name for service=" + remoteId.getServiceName()
112             + " is " + serverPrincipal);
113       }
114     }
115     this.token = token;
116     this.serverPrincipal = serverPrincipal;
117     if (!useSasl) {
118       authMethod = AuthMethod.SIMPLE;
119     } else if (token != null) {
120       authMethod = AuthMethod.DIGEST;
121     } else {
122       authMethod = AuthMethod.KERBEROS;
123     }
124 
125     if (LOG.isDebugEnabled()) {
126       LOG.debug("Use " + authMethod + " authentication for service " + remoteId.serviceName
127           + ", sasl=" + useSasl);
128     }
129     reloginMaxBackoff = conf.getInt("hbase.security.relogin.maxbackoff", 5000);
130     this.remoteId = remoteId;
131   }
132 
133   private UserInformation getUserInfo(UserGroupInformation ugi) {
134     if (ugi == null || authMethod == AuthMethod.DIGEST) {
135       // Don't send user for token auth
136       return null;
137     }
138     UserInformation.Builder userInfoPB = UserInformation.newBuilder();
139     if (authMethod == AuthMethod.KERBEROS) {
140       // Send effective user for Kerberos auth
141       userInfoPB.setEffectiveUser(ugi.getUserName());
142     } else if (authMethod == AuthMethod.SIMPLE) {
143       // Send both effective user and real user for simple auth
144       userInfoPB.setEffectiveUser(ugi.getUserName());
145       if (ugi.getRealUser() != null) {
146         userInfoPB.setRealUser(ugi.getRealUser().getUserName());
147       }
148     }
149     return userInfoPB.build();
150   }
151 
152   protected UserGroupInformation getUGI() {
153     UserGroupInformation ticket = remoteId.getTicket().getUGI();
154     if (authMethod == AuthMethod.KERBEROS) {
155       if (ticket != null && ticket.getRealUser() != null) {
156         ticket = ticket.getRealUser();
157       }
158     }
159     return ticket;
160   }
161 
162   protected boolean shouldAuthenticateOverKrb() throws IOException {
163     UserGroupInformation loginUser = UserGroupInformation.getLoginUser();
164     UserGroupInformation currentUser = UserGroupInformation.getCurrentUser();
165     UserGroupInformation realUser = currentUser.getRealUser();
166     return authMethod == AuthMethod.KERBEROS && loginUser != null &&
167     // Make sure user logged in using Kerberos either keytab or TGT
168         loginUser.hasKerberosCredentials() &&
169         // relogin only in case it is the login user (e.g. JT)
170         // or superuser (like oozie).
171         (loginUser.equals(currentUser) || loginUser.equals(realUser));
172   }
173 
174   protected void relogin() throws IOException {
175     if (UserGroupInformation.isLoginKeytabBased()) {
176       UserGroupInformation.getLoginUser().reloginFromKeytab();
177     } else {
178       UserGroupInformation.getLoginUser().reloginFromTicketCache();
179     }
180   }
181 
182   protected void scheduleTimeoutTask(final Call call) {
183     if (call.timeout > 0) {
184       call.timeoutTask = timeoutTimer.newTimeout(new TimerTask() {
185 
186         @Override
187         public void run(Timeout timeout) throws Exception {
188           call.setTimeout(new CallTimeoutException("Call id=" + call.id + ", waitTime="
189               + (EnvironmentEdgeManager.currentTime() - call.getStartTime()) + ", rpcTimetout="
190               + call.timeout));
191           callTimeout(call);
192         }
193       }, call.timeout, TimeUnit.MILLISECONDS);
194     }
195   }
196 
197   protected byte[] getConnectionHeaderPreamble() {
198     // Assemble the preamble up in a buffer first and then send it. Writing individual elements,
199     // they are getting sent across piecemeal according to wireshark and then server is messing
200     // up the reading on occasion (the passed in stream is not buffered yet).
201 
202     // Preamble is six bytes -- 'HBas' + VERSION + AUTH_CODE
203     int rpcHeaderLen = HConstants.RPC_HEADER.length;
204     byte[] preamble = new byte[rpcHeaderLen + 2];
205     System.arraycopy(HConstants.RPC_HEADER, 0, preamble, 0, rpcHeaderLen);
206     preamble[rpcHeaderLen] = HConstants.RPC_CURRENT_VERSION;
207     synchronized (this) {
208       preamble[rpcHeaderLen + 1] = authMethod.code;
209     }
210     return preamble;
211   }
212 
213   protected ConnectionHeader getConnectionHeader() {
214     ConnectionHeader.Builder builder = ConnectionHeader.newBuilder();
215     builder.setServiceName(remoteId.getServiceName());
216     UserInformation userInfoPB;
217     if ((userInfoPB = getUserInfo(remoteId.ticket.getUGI())) != null) {
218       builder.setUserInfo(userInfoPB);
219     }
220     if (this.codec != null) {
221       builder.setCellBlockCodecClass(this.codec.getClass().getCanonicalName());
222     }
223     if (this.compressor != null) {
224       builder.setCellBlockCompressorClass(this.compressor.getClass().getCanonicalName());
225     }
226     builder.setVersionInfo(ProtobufUtil.getVersionInfo());
227     return builder.build();
228   }
229 
230   protected abstract void callTimeout(Call call);
231 
232   public ConnectionId remoteId() {
233     return remoteId;
234   }
235 
236   public long getLastTouched() {
237     return lastTouched;
238   }
239 
240   public void setLastTouched(long lastTouched) {
241     this.lastTouched = lastTouched;
242   }
243 
244   /**
245    * Tell the idle connection sweeper whether we could be swept.
246    */
247   public abstract boolean isActive();
248 
249   /**
250    * Just close connection. Do not need to remove from connection pool.
251    */
252   public abstract void shutdown();
253 
254   public abstract void sendRequest(Call call, HBaseRpcController hrc) throws IOException;
255 
256   /**
257    * Does the clean up work after the connection is removed from the connection pool
258    */
259   public abstract void cleanupConnection();
260 }