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  
19  package org.apache.hadoop.hbase.rest;
20  
21  import java.io.IOException;
22  import java.util.Map;
23  import java.util.concurrent.ConcurrentHashMap;
24  import java.util.concurrent.locks.Lock;
25  
26  import org.apache.hadoop.classification.InterfaceAudience;
27  import org.apache.hadoop.conf.Configuration;
28  import org.apache.hadoop.hbase.Chore;
29  import org.apache.hadoop.hbase.Stoppable;
30  import org.apache.hadoop.hbase.client.HBaseAdmin;
31  import org.apache.hadoop.hbase.client.HConnection;
32  import org.apache.hadoop.hbase.client.HConnectionManager;
33  import org.apache.hadoop.hbase.client.HTableInterface;
34  import org.apache.hadoop.hbase.security.User;
35  import org.apache.hadoop.hbase.security.UserProvider;
36  import org.apache.hadoop.hbase.util.EnvironmentEdgeManager;
37  import org.apache.hadoop.hbase.util.KeyLocker;
38  import org.apache.hadoop.hbase.util.Threads;
39  import org.apache.hadoop.security.UserGroupInformation;
40  import org.apache.log4j.Logger;
41  
42  /**
43   * Singleton class encapsulating global REST servlet state and functions.
44   */
45  @InterfaceAudience.Private
46  public class RESTServlet implements Constants {
47    private static Logger LOG = Logger.getLogger(RESTServlet.class);
48    private static RESTServlet INSTANCE;
49    private final Configuration conf;
50    private final MetricsREST metrics = new MetricsREST();
51    private final Map<String, ConnectionInfo>
52      connections = new ConcurrentHashMap<String, ConnectionInfo>();
53    private final KeyLocker<String> locker = new KeyLocker<String>();
54    private final UserGroupInformation realUser;
55  
56    static final String CLEANUP_INTERVAL = "hbase.rest.connection.cleanup-interval";
57    static final String MAX_IDLETIME = "hbase.rest.connection.max-idletime";
58  
59    static final String NULL_USERNAME = "--NULL--";
60  
61    private final ThreadLocal<String> effectiveUser = new ThreadLocal<String>() {
62      protected String initialValue() {
63        return NULL_USERNAME;
64      }
65    };
66  
67    // A chore to clean up idle connections.
68    private final Chore connectionCleaner;
69    private final Stoppable stoppable;
70    private UserProvider userProvider;
71  
72    class ConnectionInfo {
73      final HConnection connection;
74      final String userName;
75  
76      volatile HBaseAdmin admin;
77      private long lastAccessTime;
78      private boolean closed;
79  
80      ConnectionInfo(HConnection conn, String user) {
81        lastAccessTime = EnvironmentEdgeManager.currentTimeMillis();
82        connection = conn;
83        closed = false;
84        userName = user;
85      }
86  
87      synchronized boolean updateAccessTime() {
88        if (closed) {
89          return false;
90        }
91        if (connection.isAborted() || connection.isClosed()) {
92          LOG.info("Unexpected: cached HConnection is aborted/closed, removed from cache");
93          connections.remove(userName);
94          return false;
95        }
96        lastAccessTime = EnvironmentEdgeManager.currentTimeMillis();
97        return true;
98      }
99  
100     synchronized boolean timedOut(int maxIdleTime) {
101       long timeoutTime = lastAccessTime + maxIdleTime;
102       if (EnvironmentEdgeManager.currentTimeMillis() > timeoutTime) {
103         connections.remove(userName);
104         closed = true;
105       }
106       return false;
107     }
108   }
109 
110   class ConnectionCleaner extends Chore {
111     private final int maxIdleTime;
112 
113     public ConnectionCleaner(int cleanInterval, int maxIdleTime) {
114       super("REST-ConnectionCleaner", cleanInterval, stoppable);
115       this.maxIdleTime = maxIdleTime;
116     }
117 
118     @Override
119     protected void chore() {
120       for (Map.Entry<String, ConnectionInfo> entry: connections.entrySet()) {
121         ConnectionInfo connInfo = entry.getValue();
122         if (connInfo.timedOut(maxIdleTime)) {
123           if (connInfo.admin != null) {
124             try {
125               connInfo.admin.close();
126             } catch (Throwable t) {
127               LOG.info("Got exception in closing idle admin", t);
128             }
129           }
130           try {
131             connInfo.connection.close();
132           } catch (Throwable t) {
133             LOG.info("Got exception in closing idle connection", t);
134           }
135         }
136       }
137     }
138   }
139 
140   /**
141    * @return the RESTServlet singleton instance
142    */
143   public synchronized static RESTServlet getInstance() {
144     assert(INSTANCE != null);
145     return INSTANCE;
146   }
147 
148   /**
149    * @param conf Existing configuration to use in rest servlet
150    * @param realUser the login user
151    * @return the RESTServlet singleton instance
152    */
153   public synchronized static RESTServlet getInstance(Configuration conf,
154       UserGroupInformation realUser) {
155     if (INSTANCE == null) {
156       INSTANCE = new RESTServlet(conf, realUser);
157     }
158     return INSTANCE;
159   }
160 
161   public synchronized static void stop() {
162     if (INSTANCE != null)  INSTANCE = null;
163   }
164 
165   /**
166    * Constructor with existing configuration
167    * @param conf existing configuration
168    * @param realUser the login user
169    */
170   RESTServlet(final Configuration conf,
171       final UserGroupInformation realUser) {
172     this.userProvider = UserProvider.instantiate(conf);
173     stoppable = new Stoppable() {
174       private volatile boolean isStopped = false;
175       @Override public void stop(String why) { isStopped = true;}
176       @Override public boolean isStopped() {return isStopped;}
177     };
178 
179     int cleanInterval = conf.getInt(CLEANUP_INTERVAL, 10 * 1000);
180     int maxIdleTime = conf.getInt(MAX_IDLETIME, 10 * 60 * 1000);
181     connectionCleaner = new ConnectionCleaner(cleanInterval, maxIdleTime);
182     Threads.setDaemonThreadRunning(connectionCleaner.getThread());
183 
184     this.realUser = realUser;
185     this.conf = conf;
186   }
187 
188   /**
189    * Caller doesn't close the admin afterwards.
190    * We need to manage it and close it properly.
191    */
192   HBaseAdmin getAdmin() throws IOException {
193     ConnectionInfo connInfo = getCurrentConnection();
194     if (connInfo.admin == null) {
195       Lock lock = locker.acquireLock(effectiveUser.get());
196       try {
197         if (connInfo.admin == null) {
198           connInfo.admin = new HBaseAdmin(connInfo.connection);
199         }
200       } finally {
201         lock.unlock();
202       }
203     }
204     return connInfo.admin;
205   }
206 
207   /**
208    * Caller closes the table afterwards.
209    */
210   HTableInterface getTable(String tableName) throws IOException {
211     ConnectionInfo connInfo = getCurrentConnection();
212     return connInfo.connection.getTable(tableName);
213   }
214 
215   Configuration getConfiguration() {
216     return conf;
217   }
218 
219   MetricsREST getMetrics() {
220     return metrics;
221   }
222 
223   /**
224    * Helper method to determine if server should
225    * only respond to GET HTTP method requests.
226    * @return boolean for server read-only state
227    */
228   boolean isReadOnly() {
229     return getConfiguration().getBoolean("hbase.rest.readonly", false);
230   }
231 
232   void setEffectiveUser(String effectiveUser) {
233     this.effectiveUser.set(effectiveUser);
234   }
235 
236   private ConnectionInfo getCurrentConnection() throws IOException {
237     String userName = effectiveUser.get();
238     ConnectionInfo connInfo = connections.get(userName);
239     if (connInfo == null || !connInfo.updateAccessTime()) {
240       Lock lock = locker.acquireLock(userName);
241       try {
242         connInfo = connections.get(userName);
243         if (connInfo == null) {
244           UserGroupInformation ugi = realUser;
245           if (!userName.equals(NULL_USERNAME)) {
246             ugi = UserGroupInformation.createProxyUser(userName, realUser);
247           }
248           User user = userProvider.create(ugi);
249           HConnection conn = HConnectionManager.createConnection(conf, user);
250           connInfo = new ConnectionInfo(conn, userName);
251           connections.put(userName, connInfo);
252         }
253       } finally {
254         lock.unlock();
255       }
256     }
257     return connInfo;
258   }
259 }