View Javadoc

1   /*
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  
20  package org.apache.hadoop.hbase.rest.client;
21  
22  import java.io.ByteArrayInputStream;
23  import java.io.IOException;
24  import java.io.InterruptedIOException;
25  
26  import javax.xml.bind.JAXBContext;
27  import javax.xml.bind.JAXBException;
28  import javax.xml.bind.Unmarshaller;
29  import javax.xml.stream.XMLInputFactory;
30  import javax.xml.stream.XMLStreamException;
31  import javax.xml.stream.XMLStreamReader;
32  
33  import org.apache.hadoop.hbase.classification.InterfaceAudience;
34  import org.apache.hadoop.hbase.classification.InterfaceStability;
35  import org.apache.hadoop.conf.Configuration;
36  
37  import org.apache.hadoop.hbase.HTableDescriptor;
38  import org.apache.hadoop.hbase.rest.Constants;
39  import org.apache.hadoop.hbase.rest.model.StorageClusterStatusModel;
40  import org.apache.hadoop.hbase.rest.model.StorageClusterVersionModel;
41  import org.apache.hadoop.hbase.rest.model.TableListModel;
42  import org.apache.hadoop.hbase.rest.model.TableSchemaModel;
43  import org.apache.hadoop.hbase.rest.model.VersionModel;
44  import org.apache.hadoop.hbase.util.Bytes;
45  
46  @InterfaceAudience.Public
47  @InterfaceStability.Stable
48  public class RemoteAdmin {
49  
50    final Client client;
51    final Configuration conf;
52    final String accessToken;
53    final int maxRetries;
54    final long sleepTime;
55  
56    // This unmarshaller is necessary for getting the /version/cluster resource.
57    // This resource does not support protobufs. Therefore this is necessary to
58    // request/interpret it as XML.
59    private static volatile Unmarshaller versionClusterUnmarshaller;
60  
61    /**
62     * Constructor
63     * 
64     * @param client
65     * @param conf
66     */
67    public RemoteAdmin(Client client, Configuration conf) {
68      this(client, conf, null);
69    }
70  
71    static Unmarshaller getUnmarsheller() throws JAXBException {
72  
73      if (versionClusterUnmarshaller == null) {
74  
75        RemoteAdmin.versionClusterUnmarshaller = JAXBContext.newInstance(
76            StorageClusterVersionModel.class).createUnmarshaller();
77      }
78      return RemoteAdmin.versionClusterUnmarshaller;
79    }
80  
81    /**
82     * Constructor
83     * @param client
84     * @param conf
85     * @param accessToken
86     */
87    public RemoteAdmin(Client client, Configuration conf, String accessToken) {
88      this.client = client;
89      this.conf = conf;
90      this.accessToken = accessToken;
91      this.maxRetries = conf.getInt("hbase.rest.client.max.retries", 10);
92      this.sleepTime = conf.getLong("hbase.rest.client.sleep", 1000);
93    }
94  
95    /**
96     * @param tableName name of table to check
97     * @return true if all regions of the table are available
98     * @throws IOException if a remote or network exception occurs
99     */
100   public boolean isTableAvailable(String tableName) throws IOException {
101     return isTableAvailable(Bytes.toBytes(tableName));
102   }
103 
104   /**
105    * @return string representing the rest api's version
106    * @throws IOException
107    *           if the endpoint does not exist, there is a timeout, or some other
108    *           general failure mode
109    */
110   public VersionModel getRestVersion() throws IOException {
111 
112     StringBuilder path = new StringBuilder();
113     path.append('/');
114     if (accessToken != null) {
115       path.append(accessToken);
116       path.append('/');
117     }
118 
119     path.append("version/rest");
120 
121     int code = 0;
122     for (int i = 0; i < maxRetries; i++) {
123       Response response = client.get(path.toString(),
124           Constants.MIMETYPE_PROTOBUF);
125       code = response.getCode();
126       switch (code) {
127       case 200:
128 
129         VersionModel v = new VersionModel();
130         return (VersionModel) v.getObjectFromMessage(response.getBody());
131       case 404:
132         throw new IOException("REST version not found");
133       case 509:
134         try {
135           Thread.sleep(sleepTime);
136         } catch (InterruptedException e) {
137           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
138         }
139         break;
140       default:
141         throw new IOException("get request to " + path.toString()
142             + " returned " + code);
143       }
144     }
145     throw new IOException("get request to " + path.toString() + " timed out");
146   }
147 
148   /**
149    * @return string representing the cluster's version
150    * @throws IOException if the endpoint does not exist, there is a timeout, or some other general failure mode
151    */
152   public StorageClusterStatusModel getClusterStatus() throws IOException {
153 
154       StringBuilder path = new StringBuilder();
155       path.append('/');
156       if (accessToken !=null) {
157           path.append(accessToken);
158           path.append('/');
159       }
160 
161     path.append("status/cluster");
162 
163     int code = 0;
164     for (int i = 0; i < maxRetries; i++) {
165       Response response = client.get(path.toString(),
166           Constants.MIMETYPE_PROTOBUF);
167       code = response.getCode();
168       switch (code) {
169       case 200:
170         StorageClusterStatusModel s = new StorageClusterStatusModel();
171         return (StorageClusterStatusModel) s.getObjectFromMessage(response
172             .getBody());
173       case 404:
174         throw new IOException("Cluster version not found");
175       case 509:
176         try {
177           Thread.sleep(sleepTime);
178         } catch (InterruptedException e) {
179           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
180         }
181         break;
182       default:
183         throw new IOException("get request to " + path + " returned " + code);
184       }
185     }
186     throw new IOException("get request to " + path + " timed out");
187   }
188 
189   /**
190    * @return string representing the cluster's version
191    * @throws IOException
192    *           if the endpoint does not exist, there is a timeout, or some other
193    *           general failure mode
194    */
195   public StorageClusterVersionModel getClusterVersion() throws IOException {
196 
197     StringBuilder path = new StringBuilder();
198     path.append('/');
199     if (accessToken != null) {
200       path.append(accessToken);
201       path.append('/');
202     }
203 
204     path.append("version/cluster");
205 
206     int code = 0;
207     for (int i = 0; i < maxRetries; i++) {
208       Response response = client.get(path.toString(), Constants.MIMETYPE_XML);
209       code = response.getCode();
210       switch (code) {
211       case 200:
212         try {
213 
214           return (StorageClusterVersionModel) getUnmarsheller().unmarshal(
215               getInputStream(response));
216         } catch (JAXBException jaxbe) {
217 
218           throw new IOException(
219               "Issue parsing StorageClusterVersionModel object in XML form: "
220                   + jaxbe.getLocalizedMessage(), jaxbe);
221         }
222       case 404:
223         throw new IOException("Cluster version not found");
224       case 509:
225         try {
226           Thread.sleep(sleepTime);
227         } catch (InterruptedException e) {
228           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
229         }
230         break;
231       default:
232         throw new IOException(path.toString() + " request returned " + code);
233       }
234     }
235     throw new IOException("get request to " + path.toString()
236         + " request timed out");
237   }
238 
239   /**
240    * @param tableName name of table to check
241    * @return true if all regions of the table are available
242    * @throws IOException if a remote or network exception occurs
243    */
244   public boolean isTableAvailable(byte[] tableName) throws IOException {
245     StringBuilder path = new StringBuilder();
246     path.append('/');
247     if (accessToken != null) {
248       path.append(accessToken);
249       path.append('/');
250     }
251     path.append(Bytes.toStringBinary(tableName));
252     path.append('/');
253     path.append("exists");
254     int code = 0;
255     for (int i = 0; i < maxRetries; i++) {
256       Response response = client.get(path.toString(), Constants.MIMETYPE_PROTOBUF);
257       code = response.getCode();
258       switch (code) {
259       case 200:
260         return true;
261       case 404:
262         return false;
263       case 509:
264         try {
265           Thread.sleep(sleepTime);
266         } catch (InterruptedException e) {
267           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
268         }
269         break;
270       default:
271         throw new IOException("get request to " + path.toString() + " returned " + code);
272       }
273     }
274     throw new IOException("get request to " + path.toString() + " timed out");
275   }
276 
277   /**
278    * Creates a new table.
279    * @param desc table descriptor for table
280    * @throws IOException if a remote or network exception occurs
281    */
282   public void createTable(HTableDescriptor desc)
283       throws IOException {
284     TableSchemaModel model = new TableSchemaModel(desc);
285     StringBuilder path = new StringBuilder();
286     path.append('/');
287     if (accessToken != null) {
288       path.append(accessToken);
289       path.append('/');
290     }
291     path.append(desc.getTableName());
292     path.append('/');
293     path.append("schema");
294     int code = 0;
295     for (int i = 0; i < maxRetries; i++) {
296       Response response = client.put(path.toString(), Constants.MIMETYPE_PROTOBUF,
297         model.createProtobufOutput());
298       code = response.getCode();
299       switch (code) {
300       case 201:
301         return;
302       case 509:
303         try {
304           Thread.sleep(sleepTime);
305         } catch (InterruptedException e) {
306           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
307         }
308         break;
309       default:
310         throw new IOException("create request to " + path.toString() + " returned " + code);
311       }
312     }
313     throw new IOException("create request to " + path.toString() + " timed out");
314   }
315 
316   /**
317    * Deletes a table.
318    * @param tableName name of table to delete
319    * @throws IOException if a remote or network exception occurs
320    */
321   public void deleteTable(final String tableName) throws IOException {
322     deleteTable(Bytes.toBytes(tableName));
323   }
324 
325   /**
326    * Deletes a table.
327    * @param tableName name of table to delete
328    * @throws IOException if a remote or network exception occurs
329    */
330   public void deleteTable(final byte [] tableName) throws IOException {
331     StringBuilder path = new StringBuilder();
332     path.append('/');
333     if (accessToken != null) {
334       path.append(accessToken);
335       path.append('/');
336     }
337     path.append(Bytes.toStringBinary(tableName));
338     path.append('/');
339     path.append("schema");
340     int code = 0;
341     for (int i = 0; i < maxRetries; i++) {
342       Response response = client.delete(path.toString());
343       code = response.getCode();
344       switch (code) {
345       case 200:
346         return;
347       case 509:
348         try {
349           Thread.sleep(sleepTime);
350         } catch (InterruptedException e) {
351           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
352         }
353         break;
354       default:
355         throw new IOException("delete request to " + path.toString() + " returned " + code);
356       }
357     }
358     throw new IOException("delete request to " + path.toString() + " timed out");
359   }
360 
361   /**
362    * @return string representing the cluster's version
363    * @throws IOException
364    *           if the endpoint does not exist, there is a timeout, or some other
365    *           general failure mode
366    */
367   public TableListModel getTableList() throws IOException {
368 
369     StringBuilder path = new StringBuilder();
370     path.append('/');
371     if (accessToken != null) {
372       path.append(accessToken);
373       path.append('/');
374     }
375 
376     int code = 0;
377     for (int i = 0; i < maxRetries; i++) {
378       // Response response = client.get(path.toString(),
379       // Constants.MIMETYPE_XML);
380       Response response = client.get(path.toString(),
381           Constants.MIMETYPE_PROTOBUF);
382       code = response.getCode();
383       switch (code) {
384       case 200:
385         TableListModel t = new TableListModel();
386         return (TableListModel) t.getObjectFromMessage(response.getBody());
387       case 404:
388         throw new IOException("Table list not found");
389       case 509:
390         try {
391           Thread.sleep(sleepTime);
392         } catch (InterruptedException e) {
393           throw (InterruptedIOException)new InterruptedIOException().initCause(e);
394         }
395         break;
396       default:
397         throw new IOException("get request to " + path.toString()
398             + " request returned " + code);
399       }
400     }
401     throw new IOException("get request to " + path.toString()
402         + " request timed out");
403   }
404 
405   /**
406    * Convert the REST server's response to an XML reader.
407    *
408    * @param response The REST server's response.
409    * @return A reader over the parsed XML document.
410    * @throws IOException If the document fails to parse
411    */
412   private XMLStreamReader getInputStream(Response response) throws IOException {
413     try {
414       // Prevent the parser from reading XMl with external entities defined
415       XMLInputFactory xif = XMLInputFactory.newFactory();
416       xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
417       xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
418       return xif.createXMLStreamReader(new ByteArrayInputStream(response.getBody()));
419     } catch (XMLStreamException e) {
420       throw new IOException("Failed to parse XML", e);
421     }
422   }
423 }