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}