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.rest.client;
019
020import java.io.ByteArrayInputStream;
021import java.io.IOException;
022import java.io.InterruptedIOException;
023import javax.xml.bind.JAXBContext;
024import javax.xml.bind.JAXBException;
025import javax.xml.bind.Unmarshaller;
026import javax.xml.stream.XMLInputFactory;
027import javax.xml.stream.XMLStreamException;
028import javax.xml.stream.XMLStreamReader;
029import org.apache.hadoop.conf.Configuration;
030import org.apache.hadoop.hbase.client.TableDescriptor;
031import org.apache.hadoop.hbase.rest.Constants;
032import org.apache.hadoop.hbase.rest.model.StorageClusterStatusModel;
033import org.apache.hadoop.hbase.rest.model.StorageClusterVersionModel;
034import org.apache.hadoop.hbase.rest.model.TableListModel;
035import org.apache.hadoop.hbase.rest.model.TableSchemaModel;
036import org.apache.hadoop.hbase.rest.model.VersionModel;
037import org.apache.hadoop.hbase.util.Bytes;
038import org.apache.yetus.audience.InterfaceAudience;
039
040@InterfaceAudience.Private
041public class RemoteAdmin {
042
043  final Client client;
044  final Configuration conf;
045  final String accessToken;
046  final int maxRetries;
047  final long sleepTime;
048
049  // This unmarshaller is necessary for getting the /version/cluster resource.
050  // This resource does not support protobufs. Therefore this is necessary to
051  // request/interpret it as XML.
052  private static volatile Unmarshaller versionClusterUnmarshaller;
053
054  /**
055   * Constructor
056   */
057  public RemoteAdmin(Client client, Configuration conf) {
058    this(client, conf, null);
059  }
060
061  static Unmarshaller getUnmarsheller() throws JAXBException {
062
063    if (versionClusterUnmarshaller == null) {
064
065      RemoteAdmin.versionClusterUnmarshaller =
066        JAXBContext.newInstance(StorageClusterVersionModel.class).createUnmarshaller();
067    }
068    return RemoteAdmin.versionClusterUnmarshaller;
069  }
070
071  /**
072   * Constructor
073   */
074  public RemoteAdmin(Client client, Configuration conf, String accessToken) {
075    this.client = client;
076    this.conf = conf;
077    this.accessToken = accessToken;
078    this.maxRetries = conf.getInt("hbase.rest.client.max.retries", 10);
079    this.sleepTime = conf.getLong("hbase.rest.client.sleep", 1000);
080  }
081
082  /**
083   * @param tableName name of table to check
084   * @return true if all regions of the table are available
085   * @throws IOException if a remote or network exception occurs
086   */
087  public boolean isTableAvailable(String tableName) throws IOException {
088    return isTableAvailable(Bytes.toBytes(tableName));
089  }
090
091  /**
092   * @return string representing the rest api's version if the endpoint does not exist, there is a
093   *         timeout, or some other general failure mode
094   */
095  public VersionModel getRestVersion() throws IOException {
096
097    StringBuilder path = new StringBuilder();
098    path.append('/');
099    if (accessToken != null) {
100      path.append(accessToken);
101      path.append('/');
102    }
103
104    path.append("version/rest");
105
106    int code = 0;
107    for (int i = 0; i < maxRetries; i++) {
108      Response response = client.get(path.toString(), Constants.MIMETYPE_PROTOBUF);
109      code = response.getCode();
110      switch (code) {
111        case 200:
112
113          VersionModel v = new VersionModel();
114          return (VersionModel) v.getObjectFromMessage(response.getBody());
115        case 404:
116          throw new IOException("REST version not found");
117        case 509:
118          try {
119            Thread.sleep(sleepTime);
120          } catch (InterruptedException e) {
121            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
122          }
123          break;
124        default:
125          throw new IOException("get request to " + path.toString() + " returned " + code);
126      }
127    }
128    throw new IOException("get request to " + path.toString() + " timed out");
129  }
130
131  /**
132   * @return string representing the cluster's version
133   * @throws IOException if the endpoint does not exist, there is a timeout, or some other general
134   *                     failure mode
135   */
136  public StorageClusterStatusModel getClusterStatus() throws IOException {
137
138    StringBuilder path = new StringBuilder();
139    path.append('/');
140    if (accessToken != null) {
141      path.append(accessToken);
142      path.append('/');
143    }
144
145    path.append("status/cluster");
146
147    int code = 0;
148    for (int i = 0; i < maxRetries; i++) {
149      Response response = client.get(path.toString(), Constants.MIMETYPE_PROTOBUF);
150      code = response.getCode();
151      switch (code) {
152        case 200:
153          StorageClusterStatusModel s = new StorageClusterStatusModel();
154          return (StorageClusterStatusModel) s.getObjectFromMessage(response.getBody());
155        case 404:
156          throw new IOException("Cluster version not found");
157        case 509:
158          try {
159            Thread.sleep(sleepTime);
160          } catch (InterruptedException e) {
161            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
162          }
163          break;
164        default:
165          throw new IOException("get request to " + path + " returned " + code);
166      }
167    }
168    throw new IOException("get request to " + path + " timed out");
169  }
170
171  /**
172   * @return string representing the cluster's version if the endpoint does not exist, there is a
173   *         timeout, or some other general failure mode
174   */
175  public StorageClusterVersionModel getClusterVersion() throws IOException {
176
177    StringBuilder path = new StringBuilder();
178    path.append('/');
179    if (accessToken != null) {
180      path.append(accessToken);
181      path.append('/');
182    }
183
184    path.append("version/cluster");
185
186    int code = 0;
187    for (int i = 0; i < maxRetries; i++) {
188      Response response = client.get(path.toString(), Constants.MIMETYPE_XML);
189      code = response.getCode();
190      switch (code) {
191        case 200:
192          try {
193
194            return (StorageClusterVersionModel) getUnmarsheller()
195              .unmarshal(getInputStream(response));
196          } catch (JAXBException jaxbe) {
197
198            throw new IOException("Issue parsing StorageClusterVersionModel object in XML form: "
199              + jaxbe.getLocalizedMessage(), jaxbe);
200          }
201        case 404:
202          throw new IOException("Cluster version not found");
203        case 509:
204          try {
205            Thread.sleep(sleepTime);
206          } catch (InterruptedException e) {
207            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
208          }
209          break;
210        default:
211          throw new IOException(path.toString() + " request returned " + code);
212      }
213    }
214    throw new IOException("get request to " + path.toString() + " request timed out");
215  }
216
217  /**
218   * @param tableName name of table to check
219   * @return true if all regions of the table are available
220   * @throws IOException if a remote or network exception occurs
221   */
222  public boolean isTableAvailable(byte[] tableName) throws IOException {
223    StringBuilder path = new StringBuilder();
224    path.append('/');
225    if (accessToken != null) {
226      path.append(accessToken);
227      path.append('/');
228    }
229    path.append(Bytes.toStringBinary(tableName));
230    path.append('/');
231    path.append("exists");
232    int code = 0;
233    for (int i = 0; i < maxRetries; i++) {
234      Response response = client.get(path.toString(), Constants.MIMETYPE_PROTOBUF);
235      code = response.getCode();
236      switch (code) {
237        case 200:
238          return true;
239        case 404:
240          return false;
241        case 509:
242          try {
243            Thread.sleep(sleepTime);
244          } catch (InterruptedException e) {
245            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
246          }
247          break;
248        default:
249          throw new IOException("get request to " + path.toString() + " returned " + code);
250      }
251    }
252    throw new IOException("get request to " + path.toString() + " timed out");
253  }
254
255  /**
256   * Creates a new table.
257   * @param desc table descriptor for table
258   * @throws IOException if a remote or network exception occurs
259   */
260  public void createTable(TableDescriptor desc) throws IOException {
261    TableSchemaModel model = new TableSchemaModel(desc);
262    StringBuilder path = new StringBuilder();
263    path.append('/');
264    if (accessToken != null) {
265      path.append(accessToken);
266      path.append('/');
267    }
268    path.append(desc.getTableName());
269    path.append('/');
270    path.append("schema");
271    int code = 0;
272    for (int i = 0; i < maxRetries; i++) {
273      Response response =
274        client.put(path.toString(), Constants.MIMETYPE_PROTOBUF, model.createProtobufOutput());
275      code = response.getCode();
276      switch (code) {
277        case 201:
278          return;
279        case 509:
280          try {
281            Thread.sleep(sleepTime);
282          } catch (InterruptedException e) {
283            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
284          }
285          break;
286        default:
287          throw new IOException("create request to " + path.toString() + " returned " + code);
288      }
289    }
290    throw new IOException("create request to " + path.toString() + " timed out");
291  }
292
293  /**
294   * Deletes a table.
295   * @param tableName name of table to delete
296   * @throws IOException if a remote or network exception occurs
297   */
298  public void deleteTable(final String tableName) throws IOException {
299    deleteTable(Bytes.toBytes(tableName));
300  }
301
302  /**
303   * Deletes a table.
304   * @param tableName name of table to delete
305   * @throws IOException if a remote or network exception occurs
306   */
307  public void deleteTable(final byte[] tableName) throws IOException {
308    StringBuilder path = new StringBuilder();
309    path.append('/');
310    if (accessToken != null) {
311      path.append(accessToken);
312      path.append('/');
313    }
314    path.append(Bytes.toStringBinary(tableName));
315    path.append('/');
316    path.append("schema");
317    int code = 0;
318    for (int i = 0; i < maxRetries; i++) {
319      Response response = client.delete(path.toString());
320      code = response.getCode();
321      switch (code) {
322        case 200:
323          return;
324        case 509:
325          try {
326            Thread.sleep(sleepTime);
327          } catch (InterruptedException e) {
328            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
329          }
330          break;
331        default:
332          throw new IOException("delete request to " + path.toString() + " returned " + code);
333      }
334    }
335    throw new IOException("delete request to " + path.toString() + " timed out");
336  }
337
338  /**
339   * @return string representing the cluster's version if the endpoint does not exist, there is a
340   *         timeout, or some other general failure mode
341   */
342  public TableListModel getTableList() throws IOException {
343
344    StringBuilder path = new StringBuilder();
345    path.append('/');
346    if (accessToken != null) {
347      path.append(accessToken);
348      path.append('/');
349    }
350
351    int code = 0;
352    for (int i = 0; i < maxRetries; i++) {
353      // Response response = client.get(path.toString(),
354      // Constants.MIMETYPE_XML);
355      Response response = client.get(path.toString(), Constants.MIMETYPE_PROTOBUF);
356      code = response.getCode();
357      switch (code) {
358        case 200:
359          TableListModel t = new TableListModel();
360          return (TableListModel) t.getObjectFromMessage(response.getBody());
361        case 404:
362          throw new IOException("Table list not found");
363        case 509:
364          try {
365            Thread.sleep(sleepTime);
366          } catch (InterruptedException e) {
367            throw (InterruptedIOException) new InterruptedIOException().initCause(e);
368          }
369          break;
370        default:
371          throw new IOException("get request to " + path.toString() + " request returned " + code);
372      }
373    }
374    throw new IOException("get request to " + path.toString() + " request timed out");
375  }
376
377  /**
378   * Convert the REST server's response to an XML reader.
379   * @param response The REST server's response.
380   * @return A reader over the parsed XML document.
381   * @throws IOException If the document fails to parse
382   */
383  private XMLStreamReader getInputStream(Response response) throws IOException {
384    try {
385      // Prevent the parser from reading XMl with external entities defined
386      XMLInputFactory xif = XMLInputFactory.newFactory();
387      xif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, false);
388      xif.setProperty(XMLInputFactory.SUPPORT_DTD, false);
389      return xif.createXMLStreamReader(new ByteArrayInputStream(response.getBody()));
390    } catch (XMLStreamException e) {
391      throw new IOException("Failed to parse XML", e);
392    }
393  }
394}