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.HTableDescriptor; 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(HTableDescriptor 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}