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