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