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}