/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package schemachange; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; import org.voltcore.logging.VoltLogger; import org.voltdb.ClientResponseImpl; import org.voltdb.TableHelper; import org.voltdb.VoltTable; import org.voltdb.client.Client; import org.voltdb.client.ClientResponse; import org.voltdb.client.NoConnectionsException; import org.voltdb.client.ProcCallException; public class SchemaChangeUtility { static VoltLogger log = new VoltLogger("HOST"); /** * Call a procedure and check the return code. * Success just returns the result to the caller. * Unpossible errors end the process. * Some errors will retry the call until the global progress timeout with various waits. * After the global progress timeout, the process is killed. */ static ClientResponse callROProcedureWithRetry(Client client, String procName, int timeout, Object... params) { long startTime = System.currentTimeMillis(); long now = startTime; int retry = 0; while (now - startTime < (timeout * 1000)) { ClientResponse cr = null; try { cr = client.callProcedure(procName, params); } catch (ProcCallException e) { log.debug("callROProcedureWithRetry operation exception:", e); cr = e.getClientResponse(); } catch (NoConnectionsException e) { log.debug("callROProcedureWithRetry operation exception:", e); // wait a bit to retry try { Thread.sleep(1000); } catch (InterruptedException e1) {} } catch (IOException e) { log.debug("callROProcedureWithRetry operation exception:", e); // IOException is not cool man logStackTrace(e); System.exit(-1); } if (cr != null) { if (cr.getStatus() != ClientResponse.SUCCESS) { log.debug("callROProcedureWithRetry operation failed: " + ((ClientResponseImpl)cr).toJSONString()); } switch (cr.getStatus()) { case ClientResponse.SUCCESS: // hooray! return cr; case ClientResponse.CONNECTION_LOST: case ClientResponse.CONNECTION_TIMEOUT: // can retry after a delay try { Thread.sleep(5 * 1000); } catch (Exception e) {} break; case ClientResponse.RESPONSE_UNKNOWN: // can try again immediately - cluster is up but a node died break; case ClientResponse.SERVER_UNAVAILABLE: // shouldn't be in admin mode (paused) in this app, but can retry after a delay try { Thread.sleep(30 * 1000); } catch (Exception e) {} break; case ClientResponse.GRACEFUL_FAILURE: //log.error(_F("GRACEFUL_FAILURE response in procedure call for: %s", procName)); //log.error(((ClientResponseImpl)cr).toJSONString()); //logStackTrace(new Throwable()); return cr; // caller should always check return status case ClientResponse.UNEXPECTED_FAILURE: case ClientResponse.USER_ABORT: // for starters, I'm assuming these errors can't happen for reads in a sound system String ss = cr.getStatusString(); if (ss.contains("Statement: select count(*) from")) { // We might need to retry log.warn(ss); if ((ss.matches("(?s).*AdHoc transaction [0-9]+ wasn.t planned against the current catalog version.*") || ss.matches("(?s).*Invalid catalog update. Catalog or deployment change was planned against one version of the cluster configuration but that version was no longer live.*") )) { log.info("retrying..."); } else { log.error(String.format("Error in procedure call for: %s", procName)); log.error(((ClientResponseImpl)cr).toJSONString()); assert(false); System.exit(-1); } } } } now = System.currentTimeMillis(); } log.error(String.format("Error no progress timeout (%d seconds) reached, terminating", timeout)); System.exit(-1); return null; } static void logStackTrace(Throwable t) { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw, true); t.printStackTrace(pw); log.error(sw.toString()); } /** * Find the largest pkey value in the table. */ static long maxId(Client client, VoltTable t, int timeout) { if (t == null) { return 0; } ClientResponse cr = callROProcedureWithRetry(client, "@AdHoc", timeout, String.format("select pkey from %s order by pkey desc limit 1;", TableHelper.getTableName(t))); assert(cr.getStatus() == ClientResponse.SUCCESS); VoltTable result = cr.getResults()[0]; return result.getRowCount() > 0 ? result.asScalarLong() : 0; } /** * Die happily or tragically with a formatted message. * Simply exits if message is null. */ static void die(boolean happy, String format, Object... params) { if (format != null) { String message = String.format(format, params); if (happy) { log.info(message); } else { log.error(message); } } System.exit(happy ? 0 : -1); } }