/* * Copyright © 2015 Cask Data, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package co.cask.cdap.gateway.handlers; import co.cask.cdap.common.conf.Constants; import co.cask.cdap.gateway.handlers.util.AbstractAppFabricHttpHandler; import co.cask.http.ChunkResponder; import co.cask.http.HttpResponder; import co.cask.tephra.InvalidTruncateTimeException; import co.cask.tephra.TransactionCouldNotTakeSnapshotException; import co.cask.tephra.TransactionSystemClient; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.io.Closeables; import com.google.gson.reflect.TypeToken; import com.google.inject.Inject; import org.jboss.netty.buffer.ChannelBuffers; import org.jboss.netty.handler.codec.http.HttpRequest; import org.jboss.netty.handler.codec.http.HttpResponseStatus; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Type; import java.util.Map; import java.util.Set; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.PathParam; /** * Handler to for managing transaction states. */ @Path(Constants.Gateway.API_VERSION_3) public class TransactionHttpHandler extends AbstractAppFabricHttpHandler { private static final Logger LOG = LoggerFactory.getLogger(TransactionHttpHandler.class); private static final Type STRING_LONG_MAP_TYPE = new TypeToken<Map<String, Long>>() { }.getType(); private static final Type STRING_LONG_SET_MAP_TYPE = new TypeToken<Map<String, Set<Long>>>() { }.getType(); private final TransactionSystemClient txClient; @Inject public TransactionHttpHandler(TransactionSystemClient txClient) { this.txClient = txClient; } /** * Retrieve the state of the transaction manager. */ @Path("/transactions/state") @GET public void getTxManagerSnapshot(HttpRequest request, HttpResponder responder) throws TransactionCouldNotTakeSnapshotException, IOException { LOG.trace("Taking transaction manager snapshot at time {}", System.currentTimeMillis()); LOG.trace("Took and retrieved transaction manager snapshot successfully."); try (InputStream in = txClient.getSnapshotInputStream()) { ChunkResponder chunkResponder = responder.sendChunkStart(HttpResponseStatus.OK, ImmutableMultimap.<String, String>of()); while (true) { // netty doesn't copy the readBytes buffer, so we have to reallocate a new buffer byte[] readBytes = new byte[4096]; int res = in.read(readBytes, 0, 4096); if (res == -1) { break; } // If failed to send chunk, IOException will be raised. // It'll just propagated to the netty-http library to handle it chunkResponder.sendChunk(ChannelBuffers.wrappedBuffer(readBytes, 0, res)); } Closeables.closeQuietly(chunkResponder); } } /** * Invalidate a transaction. * @param txId transaction ID. */ @Path("/transactions/{tx-id}/invalidate") @POST public void invalidateTx(HttpRequest request, HttpResponder responder, @PathParam("tx-id") String txId) { try { long txIdLong = Long.parseLong(txId); boolean success = txClient.invalidate(txIdLong); if (success) { LOG.info("Transaction {} successfully invalidated", txId); responder.sendStatus(HttpResponseStatus.OK); } else { LOG.info("Transaction {} could not be invalidated: not in progress.", txId); responder.sendStatus(HttpResponseStatus.CONFLICT); } } catch (NumberFormatException e) { LOG.info("Could not invalidate transaction: {} is not a valid tx id", txId); responder.sendStatus(HttpResponseStatus.BAD_REQUEST); } } @Path("/transactions/invalid/remove/until") @POST public void truncateInvalidTxBefore(HttpRequest request, HttpResponder responder) throws InvalidTruncateTimeException { Map<String, Long> body; try { body = parseBody(request, STRING_LONG_MAP_TYPE); } catch (IllegalArgumentException e) { responder.sendString(HttpResponseStatus.BAD_REQUEST, "Invalid time value in request"); return; } if (body == null || !body.containsKey("time")) { responder.sendString(HttpResponseStatus.BAD_REQUEST, "Time not specified"); return; } long time = body.get("time"); txClient.truncateInvalidTxBefore(time); responder.sendStatus(HttpResponseStatus.OK); } @Path("/transactions/invalid/remove/ids") @POST public void truncateInvalidTx(HttpRequest request, HttpResponder responder) { Map<String, Set<Long>> body; try { body = parseBody(request, STRING_LONG_SET_MAP_TYPE); } catch (IllegalArgumentException e) { responder.sendString(HttpResponseStatus.BAD_REQUEST, "Invalid ids specified in request"); return; } if (body == null || !body.containsKey("ids")) { responder.sendString(HttpResponseStatus.BAD_REQUEST, "Transaction ids not specified"); return; } Set<Long> txIds = body.get("ids"); txClient.truncateInvalidTx(txIds); responder.sendStatus(HttpResponseStatus.OK); } @Path("/transactions/invalid/size") @GET public void invalidTxSize(HttpRequest request, HttpResponder responder) { int invalidSize = txClient.getInvalidSize(); responder.sendJson(HttpResponseStatus.OK, ImmutableMap.of("size", invalidSize)); } /** * Reset the state of the transaction manager. */ @Path("/transactions/state") @POST public void resetTxManagerState(HttpRequest request, HttpResponder responder) { txClient.resetState(); responder.sendStatus(HttpResponseStatus.OK); } }