public static void testCompression(Compression.Algorithm algo) throws IOException { if (compressionTestResults[algo.ordinal()] != null) { if (compressionTestResults[algo.ordinal()]) { return ; // already passed test, dont do it again. } else { // failed. throw new DoNotRetryIOException("Compression algorithm '" + algo.getName() + "'" + " previously failed test."); } } try { Compressor c = algo.getCompressor(); algo.returnCompressor(c); compressionTestResults[algo.ordinal()] = true; // passes } catch (Throwable t) { compressionTestResults[algo.ordinal()] = false; // failure throw new DoNotRetryIOException(t); } }
/** * Opens a scanner across memstore, snapshot, and all StoreFiles. Assumes we * are not in a compaction. * * @param store who we scan * @param scan the spec * @param columns which columns we are scanning * @throws IOException */ public StoreScanner(Store store, ScanInfo scanInfo, Scan scan, final NavigableSet<byte[]> columns, long readPt) throws IOException { this(store, scan, scanInfo, columns, readPt, scan.getCacheBlocks()); if (columns != null && scan.isRaw()) { throw new DoNotRetryIOException("Cannot specify any column for a raw scan"); } matcher = new ScanQueryMatcher(scan, scanInfo, columns, ScanType.USER_SCAN, Long.MAX_VALUE, HConstants.LATEST_TIMESTAMP, oldestUnexpiredTS, now, store.getCoprocessorHost()); this.store.addChangedReaderObserver(this); // Pass columns to try to filter out unnecessary StoreFiles. List<KeyValueScanner> scanners = getScannersNoCompaction(); // Seek all scanners to the start of the Row (or if the exact matching row // key does not exist, then to the start of the next matching Row). // Always check bloom filter to optimize the top row seek for delete // family marker. seekScanners(scanners, matcher.getStartKey(), explicitColumnQuery && lazySeekEnabledGlobally, parallelSeekEnabled); // set storeLimit this.storeLimit = scan.getMaxResultsPerColumnFamily(); // set rowOffset this.storeOffset = scan.getRowOffsetPerColumnFamily(); // Combine all seeked scanners with a heap resetKVHeap(scanners, store.getComparator()); }
public StoreScanner(Store store, ScanInfo scanInfo, Scan scan, final NavigableSet<byte[]> columns, long readPt, LMDIndexConstants constants) throws IOException { this(store, scan, scanInfo, columns, readPt, scan.getCacheBlocks()); if (columns != null && scan.isRaw()) { throw new DoNotRetryIOException("Cannot specify any column for a raw scan"); } matcher = new ScanQueryMatcher(scan, scanInfo, columns, ScanType.USER_SCAN, Long.MAX_VALUE, HConstants.LATEST_TIMESTAMP, oldestUnexpiredTS, now, store.getCoprocessorHost()); this.store.addChangedReaderObserver(this); // Pass columns to try to filter out unnecessary StoreFiles. List<KeyValueScanner> scanners = getLMDCIndexDirectScannersNoCompaction(ScanRange.ScanRangeList.getScanRangeList(scan)); // Seek all scanners to the start of the Row (or if the exact matching row // key does not exist, then to the start of the next matching Row). // Always check bloom filter to optimize the top row seek for delete // family marker. seekScanners(scanners, matcher.getStartKey(), explicitColumnQuery && lazySeekEnabledGlobally, parallelSeekEnabled); // set storeLimit this.storeLimit = scan.getMaxResultsPerColumnFamily(); // set rowOffset this.storeOffset = scan.getRowOffsetPerColumnFamily(); // Combine all seeked scanners with a heap resetKVHeap(scanners, store.getComparator()); }
/** * Create a protocol buffer MultiRequest for row mutations. * Does not propagate Action absolute position. Does not set atomic action on the created * RegionAtomic. Caller should do that if wanted. * @param regionName * @param rowMutations * @return a data-laden RegionMutation.Builder * @throws IOException */ public static RegionAction.Builder buildRegionAction(final byte [] regionName, final RowMutations rowMutations) throws IOException { RegionAction.Builder builder = getRegionActionBuilderWithRegion(RegionAction.newBuilder(), regionName); ClientProtos.Action.Builder actionBuilder = ClientProtos.Action.newBuilder(); MutationProto.Builder mutationBuilder = MutationProto.newBuilder(); for (Mutation mutation: rowMutations.getMutations()) { MutationType mutateType = null; if (mutation instanceof Put) { mutateType = MutationType.PUT; } else if (mutation instanceof Delete) { mutateType = MutationType.DELETE; } else { throw new DoNotRetryIOException("RowMutations supports only put and delete, not " + mutation.getClass().getName()); } mutationBuilder.clear(); MutationProto mp = ProtobufUtil.toMutation(mutateType, mutation, mutationBuilder); actionBuilder.clear(); actionBuilder.setMutation(mp); builder.addAction(actionBuilder.build()); } return builder; }
/** * Create a protocol buffer MultiRequest for row mutations that does not hold data. Data/Cells * are carried outside of protobuf. Return references to the Cells in <code>cells</code> param. * Does not propagate Action absolute position. Does not set atomic action on the created * RegionAtomic. Caller should do that if wanted. * @param regionName * @param rowMutations * @param cells Return in here a list of Cells as CellIterable. * @return a region mutation minus data * @throws IOException */ public static RegionAction.Builder buildNoDataRegionAction(final byte[] regionName, final RowMutations rowMutations, final List<CellScannable> cells, final RegionAction.Builder regionActionBuilder, final ClientProtos.Action.Builder actionBuilder, final MutationProto.Builder mutationBuilder) throws IOException { for (Mutation mutation: rowMutations.getMutations()) { MutationType type = null; if (mutation instanceof Put) { type = MutationType.PUT; } else if (mutation instanceof Delete) { type = MutationType.DELETE; } else { throw new DoNotRetryIOException("RowMutations supports only put and delete, not " + mutation.getClass().getName()); } mutationBuilder.clear(); MutationProto mp = ProtobufUtil.toMutationNoData(type, mutation, mutationBuilder); cells.add(mutation); actionBuilder.clear(); regionActionBuilder.addAction(actionBuilder.setMutation(mp).build()); } return regionActionBuilder; }
/** * Convert a protocol buffer Filter to a client Filter * * @param proto the protocol buffer Filter to convert * @return the converted Filter */ @SuppressWarnings("unchecked") public static Filter toFilter(FilterProtos.Filter proto) throws IOException { String type = proto.getName(); final byte [] value = proto.getSerializedFilter().toByteArray(); String funcName = "parseFrom"; try { Class<? extends Filter> c = (Class<? extends Filter>)Class.forName(type, true, CLASS_LOADER); Method parseFrom = c.getMethod(funcName, byte[].class); if (parseFrom == null) { throw new IOException("Unable to locate function: " + funcName + " in type: " + type); } return (Filter)parseFrom.invoke(c, value); } catch (Exception e) { // Either we couldn't instantiate the method object, or "parseFrom" failed. // In either case, let's not retry. throw new DoNotRetryIOException(e); } }
public static Map<String, Integer> classifyExs(List<Throwable> ths) { Map<String, Integer> cls = new HashMap<String, Integer>(); for (Throwable t : ths) { if (t == null) continue; String name = ""; if (t instanceof DoNotRetryIOException) { name = t.getMessage(); } else { name = t.getClass().getSimpleName(); } Integer i = cls.get(name); if (i == null) { i = 0; } i += 1; cls.put(name, i); } return cls; }
/** * Check that we can retry acts accordingly: logs, set the error status. * * @param originalIndex the position in the list sent * @param row the row * @param canRetry if false, we won't retry whatever the settings. * @param throwable the throwable, if any (can be null) * @param server the location, if any (can be null) * @return true if the action can be retried, false otherwise. */ public Retry manageError(int originalIndex, Row row, Retry canRetry, Throwable throwable, ServerName server) { if (canRetry == Retry.YES && throwable != null && (throwable instanceof DoNotRetryIOException || throwable instanceof NeedUnmanagedConnectionException)) { canRetry = Retry.NO_NOT_RETRIABLE; } if (canRetry != Retry.YES) { // Batch.Callback<Res> was not called on failure in 0.94. We keep this. setError(originalIndex, row, throwable, server); } else if (isActionComplete(originalIndex, row)) { canRetry = Retry.NO_OTHER_SUCCEEDED; } return canRetry; }
/** * When a new client with procedure support tries to ask an old-master without proc-support * the procedure result we get a DoNotRetryIOException (which is an UnsupportedOperationException) * The future should trap that and fallback to the waitOperationResult(). * * This happens when the operation calls happens on a "new master" but while we are waiting * the operation to be completed, we failover on an "old master". */ @Test(timeout=60000) public void testOnServerWithNoProcedureSupport() throws Exception { HBaseAdmin admin = Mockito.mock(HBaseAdmin.class); TestFuture f = new TestFuture(admin, 100L) { @Override protected GetProcedureResultResponse getProcedureResult( final GetProcedureResultRequest request) throws IOException { super.getProcedureResult(request); throw new DoNotRetryIOException(new UnsupportedOperationException("getProcedureResult")); } }; f.get(1, TimeUnit.MINUTES); assertTrue("expected getProcedureResult() to be called", f.wasGetProcedureResultCalled()); assertFalse("unexpected convertResult() called", f.wasConvertResultCalled()); assertTrue("expected waitOperationResult() to be called", f.wasWaitOperationResultCalled()); assertTrue("expected postOperationResult() to be called", f.wasPostOperationResultCalled()); }
private void checkTimestamps(final Map<byte[], List<KeyValue>> familyMap, long now) throws DoNotRetryIOException { if (timestampSlop == HConstants.LATEST_TIMESTAMP) { return; } long maxTs = now + timestampSlop; for (List<KeyValue> kvs : familyMap.values()) { for (KeyValue kv : kvs) { // see if the user-side TS is out of range. latest = server-side if (!kv.isLatestTimestamp() && kv.getTimestamp() > maxTs) { throw new DoNotRetryIOException("Timestamp for KV out of range " + kv + " (too.new=" + timestampSlop + ")"); } } } }
public void testCheckAndPut_wrongRowInPut() throws IOException { this.region = initHRegion(tableName, this.getName(), conf, COLUMNS); try { Put put = new Put(row2); put.add(fam1, qual1, value1); try { boolean res = region.checkAndMutate(row, fam1, qual1, CompareOp.EQUAL, new BinaryComparator(value2), put, null, false); fail(); } catch (DoNotRetryIOException expected) { // expected exception. } } finally { HRegion.closeHRegion(this.region); this.region = null; } }
public void testmutateRowsWithLocks_wrongCF() throws IOException { this.region = initHRegion(tableName, this.getName(), conf, fam1, fam2); try { Put put = new Put(row2); put.add(fam3, qual1, value1); RowMutations rm = new RowMutations(row2); rm.add(put); try { region.mutateRow(rm); fail(); } catch (DoNotRetryIOException expected) { // expected exception. LOG.debug("Caught expected exception: " + expected.getMessage()); } } finally { HRegion.closeHRegion(this.region); this.region = null; } }
/** * Requirement 7.2 - Throws an IOException if the check is for a row other than the one in the * mutation attempt. */ @Test public void testCheckAndPutDiffRow() throws IOException { // Initialize Table table = getConnection().getTable(TABLE_NAME); byte[] rowKey1 = dataHelper.randomData("rowKey-"); byte[] rowKey2 = dataHelper.randomData("rowKey-"); byte[] qual = dataHelper.randomData("qualifier-"); byte[] value = dataHelper.randomData("value-"); // Put then again Put put = new Put(rowKey1).addColumn(COLUMN_FAMILY, qual, value); expectedException.expect(DoNotRetryIOException.class); expectedException.expectMessage("Action's getRow must match the passed row"); table.checkAndPut(rowKey2, COLUMN_FAMILY, qual, null, put); table.close(); }
@Test public void testCheckAndDeleteDiffRow() throws IOException { // Initialize Table table = getConnection().getTable(TABLE_NAME); byte[] rowKey1 = dataHelper.randomData("rowKey-"); byte[] rowKey2 = dataHelper.randomData("rowKey-"); byte[] qual = dataHelper.randomData("qualifier-"); // Put then again Delete delete = new Delete(rowKey1).addColumns(COLUMN_FAMILY, qual); expectedException.expect(DoNotRetryIOException.class); expectedException.expectMessage("Action's getRow must match the passed row"); table.checkAndDelete(rowKey2, COLUMN_FAMILY, qual, null, delete); table.close(); }
/** * Requirement 6.6 - Increment should fail on non-64-bit values, and succeed on any 64-bit value. */ @Test @Category(KnownGap.class) public void testFailOnIncrementInt() throws IOException { // Initialize Table table = getConnection().getTable(TABLE_NAME); byte[] rowKey = dataHelper.randomData("testrow-"); byte[] qual = dataHelper.randomData("qual-"); int value = new Random().nextInt(); Put put = new Put(rowKey).addColumn(COLUMN_FAMILY, qual, Bytes.toBytes(value)); table.put(put); // Increment Increment increment = new Increment(rowKey).addColumn(COLUMN_FAMILY, qual, 1L); expectedException.expect(DoNotRetryIOException.class); expectedException.expectMessage("Attempted to increment field that isn't 64 bits wide"); table.increment(increment); }
/** * Requirement 6.6 */ @Test @Category(KnownGap.class) public void testFailOnIncrementString() throws IOException { // Initialize Table table = getConnection().getTable(TABLE_NAME); byte[] rowKey = dataHelper.randomData("testrow-"); byte[] qual = dataHelper.randomData("qual-"); byte[] value = dataHelper.randomData("value-"); Put put = new Put(rowKey).addColumn(COLUMN_FAMILY, qual, value); table.put(put); // Increment Increment increment = new Increment(rowKey).addColumn(COLUMN_FAMILY, qual, 1L); expectedException.expect(DoNotRetryIOException.class); expectedException.expectMessage("Attempted to increment field that isn't 64 bits wide"); table.increment(increment); }
@Override public void preDelete(final ObserverContext<RegionCoprocessorEnvironment> c, final Delete delete, final WALEdit edit, final Durability durability) throws IOException { // An ACL on a delete is useless, we shouldn't allow it if (delete.getAttribute(AccessControlConstants.OP_ATTRIBUTE_ACL) != null) { throw new DoNotRetryIOException("ACL on delete has no effect: " + delete.toString()); } // Require WRITE permissions on all cells covered by the delete. Unlike // for Puts we need to check all visible prior versions, because a major // compaction could remove them. If the user doesn't have permission to // overwrite any of the visible versions ('visible' defined as not covered // by a tombstone already) then we have to disallow this operation. RegionCoprocessorEnvironment env = c.getEnvironment(); Map<byte[],? extends Collection<Cell>> families = delete.getFamilyCellMap(); User user = getActiveUser(); AuthResult authResult = permissionGranted(OpType.DELETE, user, env, families, Action.WRITE); logResult(authResult); if (!authResult.isAllowed()) { if (cellFeaturesEnabled && !compatibleEarlyTermination) { delete.setAttribute(CHECK_COVERING_PERM, TRUE); } else { throw new AccessDeniedException("Insufficient permissions " + authResult.toContextString()); } } }
/** * Move the region <code>r</code> to <code>dest</code>. * @param encodedRegionName The encoded region name; i.e. the hash that makes * up the region name suffix: e.g. if regionname is * <code>TestTable,0094429456,1289497600452.527db22f95c8a9e0116f0cc13c680396.</code>, * then the encoded region name is: <code>527db22f95c8a9e0116f0cc13c680396</code>. * @param destServerName The servername of the destination regionserver. If * passed the empty byte array we'll assign to a random server. A server name * is made of host, port and startcode. Here is an example: * <code> host187.example.com,60020,1289493121758</code> * @throws UnknownRegionException Thrown if we can't find a region named * <code>encodedRegionName</code> */ @Override public void move(final byte [] encodedRegionName, final byte [] destServerName) throws IOException { executeCallable(new MasterCallable<Void>(getConnection()) { @Override public Void call(int callTimeout) throws ServiceException { try { MoveRegionRequest request = RequestConverter.buildMoveRegionRequest(encodedRegionName, destServerName); master.moveRegion(null, request); } catch (DeserializationException de) { LOG.error("Could not parse destination server name: " + de); throw new ServiceException(new DoNotRetryIOException(de)); } return null; } }); }
/** * Extract the real exception from the ExecutionException, and throws what makes more * sense. */ static void throwEnrichedException(ExecutionException e, int retries) throws RetriesExhaustedException, DoNotRetryIOException { Throwable t = e.getCause(); assert t != null; // That's what ExecutionException is about: holding an exception if (t instanceof RetriesExhaustedException) { throw (RetriesExhaustedException) t; } if (t instanceof DoNotRetryIOException) { throw (DoNotRetryIOException) t; } RetriesExhaustedException.ThrowableWithExtraContext qt = new RetriesExhaustedException.ThrowableWithExtraContext(t, EnvironmentEdgeManager.currentTime(), null); List<RetriesExhaustedException.ThrowableWithExtraContext> exceptions = Collections.singletonList(qt); throw new RetriesExhaustedException(retries, exceptions); }
/** * Check that we can retry acts accordingly: logs, set the error status. * * @param originalIndex the position in the list sent * @param row the row * @param canRetry if false, we won't retry whatever the settings. * @param throwable the throwable, if any (can be null) * @param server the location, if any (can be null) * @return true if the action can be retried, false otherwise. */ public Retry manageError(int originalIndex, Row row, Retry canRetry, Throwable throwable, ServerName server) { if (canRetry == Retry.YES && throwable != null && throwable instanceof DoNotRetryIOException) { canRetry = Retry.NO_NOT_RETRIABLE; } if (canRetry != Retry.YES) { // Batch.Callback<Res> was not called on failure in 0.94. We keep this. setError(originalIndex, row, throwable, server); } else if (isActionComplete(originalIndex, row)) { canRetry = Retry.NO_OTHER_SUCCEEDED; } return canRetry; }
/** * Performs an atomic multi-Mutate operation against the given table. */ private static void multiMutate(HTable table, byte[] row, Mutation... mutations) throws IOException { CoprocessorRpcChannel channel = table.coprocessorService(row); MutateRowsRequest.Builder mmrBuilder = MutateRowsRequest.newBuilder(); for (Mutation mutation : mutations) { if (mutation instanceof Put) { mmrBuilder.addMutationRequest(ProtobufUtil.toMutation(MutationType.PUT, mutation)); } else if (mutation instanceof Delete) { mmrBuilder.addMutationRequest(ProtobufUtil.toMutation(MutationType.DELETE, mutation)); } else { throw new DoNotRetryIOException("multi in MetaEditor doesn't support " + mutation.getClass().getName()); } } MultiRowMutationService.BlockingInterface service = MultiRowMutationService.newBlockingStub(channel); try { service.mutateRows(null, mmrBuilder.build()); } catch (ServiceException ex) { ProtobufUtil.toIOException(ex); } }
/** * Check that we can retry acts accordingly: logs, set the error status, call the callbacks. * * @param originalIndex the position in the list sent * @param row the row * @param canRetry if false, we won't retry whatever the settings. * @param throwable the throwable, if any (can be null) * @param location the location, if any (can be null) * @return true if the action can be retried, false otherwise. */ private boolean manageError(int originalIndex, Row row, boolean canRetry, Throwable throwable, HRegionLocation location) { if (canRetry && throwable != null && throwable instanceof DoNotRetryIOException) { canRetry = false; } byte[] region = null; if (canRetry && callback != null) { region = location == null ? null : location.getRegionInfo().getEncodedNameAsBytes(); canRetry = callback.retriableFailure(originalIndex, row, region, throwable); } if (!canRetry) { if (callback != null) { if (region == null && location != null) { region = location.getRegionInfo().getEncodedNameAsBytes(); } callback.failure(originalIndex, region, row, throwable); } errors.add(throwable, row, location); this.hasError.set(true); } return canRetry; }
private void disableAndDeleteTable(MasterServices master, TableName tableName) throws IOException { LOG.error(tableName + " already exists. Disabling and deleting table " + tableName + '.'); boolean disabled = master.getAssignmentManager().getZKTable().isDisabledTable(tableName); if (false == disabled) { LOG.info("Disabling table " + tableName + '.'); new DisableTableHandler(master, tableName, master.getCatalogTracker(), master.getAssignmentManager(), master.getTableLockManager(), false).prepare().process(); if (false == master.getAssignmentManager().getZKTable().isDisabledTable(tableName)) { throw new DoNotRetryIOException("Table " + tableName + " not disabled."); } } LOG.info("Disabled table " + tableName + '.'); LOG.info("Deleting table " + tableName + '.'); new DeleteTableHandler(tableName, master, master).prepare().process(); if (true == MetaReader.tableExists(master.getCatalogTracker(), tableName)) { throw new DoNotRetryIOException("Table " + tableName + " not deleted."); } LOG.info("Deleted table " + tableName + '.'); }
private HRegion getIndexTableRegion(String tableName, HRegion userRegion, HRegionServer rs) throws IOException { TableName indexTableName = TableName.valueOf(IndexUtils.getIndexTableName(tableName)); Collection<HRegion> idxTabRegions = rs.getOnlineRegions(indexTableName); for (HRegion idxTabRegion : idxTabRegions) { // TODO start key check is enough? May be we can check for the // possibility for N-1 Mapping? if (Bytes.equals(idxTabRegion.getStartKey(), userRegion.getStartKey())) { return idxTabRegion; } } // No corresponding index region found in the RS online regions list! String message = "Index Region not found on the region server . " + "So skipping the put. Need Balancing"; LOG.warn(message); // TODO give a proper Exception msg throw new DoNotRetryIOException(message); }
@Override public boolean preScannerNext(final ObserverContext<RegionCoprocessorEnvironment> e, final InternalScanner s, final List<Result> results, final int limit, final boolean hasMore) throws IOException { try { if (s instanceof ThemisServerScanner) { ThemisServerScanner pScanner = (ThemisServerScanner)s; HRegion region = e.getEnvironment().getRegion(); boolean more = next(region, pScanner, results, limit); e.bypass(); return more; } return hasMore; } catch (Throwable ex) { throw new DoNotRetryIOException("themis exception in preScannerNext", ex); } }
@Override public RegionScanner preScannerOpen(final ObserverContext<RegionCoprocessorEnvironment> e, final Scan scan, final RegionScanner s) throws IOException { try { Long themisStartTs = getStartTsFromAttribute(scan); if (themisStartTs != null) { ThemisCpUtil.prepareScan(scan, e.getEnvironment().getRegion().getTableDesc().getFamilies()); checkFamily(e.getEnvironment().getRegion(), scan); ThemisProtocolImpl.checkReadTTL(System.currentTimeMillis(), themisStartTs, PRE_SCANNER_OPEN_FEEK_ROW); Scan internalScan = ThemisCpUtil.constructLockAndWriteScan(scan, themisStartTs); ThemisServerScanner pScanner = new ThemisServerScanner(e.getEnvironment().getRegion() .getScanner(internalScan), internalScan, themisStartTs, scan); e.bypass(); return pScanner; } return s; } catch (Throwable ex) { throw new DoNotRetryIOException("themis exception in preScannerOpen", ex); } }
/** * This is used by coprocessor hooks which are declared to throw IOException * (or its subtypes). For such hooks, we should handle throwable objects * depending on the Throwable's type. Those which are instances of * IOException should be passed on to the client. This is in conformance with * the HBase idiom regarding IOException: that it represents a circumstance * that should be passed along to the client for its own handling. For * example, a coprocessor that implements access controls would throw a * subclass of IOException, such as AccessDeniedException, in its preGet() * method to prevent an unauthorized client's performing a Get on a particular * table. * @param env Coprocessor Environment * @param e Throwable object thrown by coprocessor. * @exception IOException Exception */ protected void handleCoprocessorThrowable(final CoprocessorEnvironment env, final Throwable e) throws IOException { if (e instanceof IOException) { throw (IOException)e; } // If we got here, e is not an IOException. A loaded coprocessor has a // fatal bug, and the server (master or regionserver) should remove the // faulty coprocessor from its set of active coprocessors. Setting // 'hbase.coprocessor.abortonerror' to true will cause abortServer(), // which may be useful in development and testing environments where // 'failing fast' for error analysis is desired. if (env.getConfiguration().getBoolean("hbase.coprocessor.abortonerror",false)) { // server is configured to abort. abortServer(env, e); } else { LOG.error("Removing coprocessor '" + env.toString() + "' from " + "environment because it threw: " + e,e); coprocessors.remove(env); throw new DoNotRetryIOException("Coprocessor: '" + env.toString() + "' threw: '" + e + "' and has been removed" + "from the active " + "coprocessor set.", e); } }
@Test public void testPutMultipleWithReadOnly() throws Exception { ThriftHBaseServiceHandler handler = createHandler(); ByteBuffer table = wrap(tableAname); byte[] rowName1 = Bytes.toBytes("testPutMultiple1"); byte[] rowName2 = Bytes.toBytes("testPutMultiple2"); List<TColumnValue> columnValues = new ArrayList<>(2); columnValues.add(new TColumnValue(wrap(familyAname), wrap(qualifierAname), wrap(valueAname))); columnValues.add(new TColumnValue(wrap(familyBname), wrap(qualifierBname), wrap(valueBname))); List<TPut> puts = new ArrayList<>(2); puts.add(new TPut(wrap(rowName1), columnValues)); puts.add(new TPut(wrap(rowName2), columnValues)); boolean exceptionCaught = false; try { handler.putMultiple(table, puts); } catch (TIOError e) { exceptionCaught = true; assertTrue(e.getCause() instanceof DoNotRetryIOException); assertEquals("Thrift Server is in Read-only mode.", e.getMessage()); } finally { assertTrue(exceptionCaught); } }