/* 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 org.voltdb; import java.io.File; import java.io.IOException; import java.util.Calendar; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.apache.zookeeper_voltpatches.CreateMode; import org.apache.zookeeper_voltpatches.KeeperException; import org.apache.zookeeper_voltpatches.WatchedEvent; import org.apache.zookeeper_voltpatches.Watcher; import org.apache.zookeeper_voltpatches.ZooDefs.Ids; import org.apache.zookeeper_voltpatches.ZooKeeper; import org.json_voltpatches.JSONArray; import org.json_voltpatches.JSONObject; import org.voltcore.logging.VoltLogger; import org.voltcore.messaging.HostMessenger; import org.voltcore.utils.CoreUtils; import org.voltcore.utils.Pair; import org.voltcore.zk.ZKUtil; import org.voltdb.VoltDB.Configuration; import org.voltdb.VoltZK.MailboxType; import org.voltdb.catalog.Catalog; import org.voltdb.catalog.Cluster; import org.voltdb.catalog.Column; import org.voltdb.catalog.Database; import org.voltdb.catalog.Procedure; import org.voltdb.catalog.Table; import org.voltdb.compiler.deploymentfile.DeploymentType; import org.voltdb.compiler.deploymentfile.PathsType; import org.voltdb.dtxn.SiteTracker; import org.voltdb.iv2.Cartographer; import org.voltdb.iv2.SpScheduler.DurableUniqueIdListener; import org.voltdb.licensetool.LicenseApi; import org.voltdb.settings.ClusterSettings; import org.voltdb.settings.DbSettings; import org.voltdb.settings.NodeSettings; import org.voltdb.snmp.DummySnmpTrapSender; import org.voltdb.snmp.SnmpTrapSender; import com.google_voltpatches.common.util.concurrent.ListenableFuture; import com.google_voltpatches.common.util.concurrent.ListeningExecutorService; import com.google_voltpatches.common.util.concurrent.MoreExecutors; public class MockVoltDB implements VoltDBInterface { private static final VoltLogger logger = new VoltLogger("MockVoltDB"); private Catalog m_catalog; private CatalogContext m_context; final String m_clusterName = "cluster"; final String m_databaseName = "database"; StatsAgent m_statsAgent = null; HostMessenger m_hostMessenger = new HostMessenger(new HostMessenger.Config(), null); private OperationMode m_mode = OperationMode.RUNNING; private volatile String m_localMetadata; final SnapshotCompletionMonitor m_snapshotCompletionMonitor = new SnapshotCompletionMonitor(); boolean m_noLoadLib = false; OperationMode m_startMode = OperationMode.RUNNING; ReplicationRole m_replicationRole = ReplicationRole.NONE; long m_clusterCreateTime = 0; VoltDB.Configuration voltconfig = null; private final ListeningExecutorService m_es = MoreExecutors.listeningDecorator(CoreUtils.getSingleThreadExecutor("Mock Computation Service")); public int m_hostId = 0; private SiteTracker m_siteTracker; private final Map<MailboxType, List<MailboxNodeContent>> m_mailboxMap = new HashMap<>(); private boolean m_replicationActive = false; private CommandLog m_cl = null; public MockVoltDB() { this(VoltDB.DEFAULT_PORT, VoltDB.DEFAULT_ADMIN_PORT, -1, VoltDB.DEFAULT_DR_PORT); } /* * Fake do nothing constructor... */ public MockVoltDB(Object foo, Object bar) { } public MockVoltDB(int clientPort, int adminPort, int httpPort, int drPort) { try { JSONObject obj = new JSONObject(); JSONArray jsonArray = new JSONArray(); jsonArray.put("127.0.0.1"); obj.put("interfaces", jsonArray); obj.put("clientPort", clientPort); obj.put("adminPort", adminPort); obj.put("httpPort", httpPort); obj.put("drPort", drPort); obj.put("drInterface", "127.0.0.1"); m_localMetadata = obj.toString(4); m_catalog = new Catalog(); m_catalog.execute(String.format("add / clusters %s", m_clusterName)); m_catalog.execute(String.format("add /clusters#%s databases %s", m_clusterName, m_databaseName)); Cluster cluster = m_catalog.getClusters().get(m_clusterName); // Set a sane default for TestMessaging (at least) cluster.setHeartbeattimeout(10000); assert(cluster != null); try { m_hostMessenger.start(); } catch (Exception e) { throw new RuntimeException(e); } VoltZK.createPersistentZKNodes(m_hostMessenger.getZK()); m_hostMessenger.getZK().create( VoltZK.cluster_metadata + "/" + m_hostMessenger.getHostId(), getLocalMetadata().getBytes("UTF-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); m_hostMessenger.getZK().create( VoltZK.start_action, null, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT, new ZKUtil.StringCallback(), null); m_statsAgent = new StatsAgent(); m_statsAgent.registerMailbox(m_hostMessenger, m_hostMessenger.getHSIdForLocalSite(HostMessenger.STATS_SITE_ID)); for (MailboxType type : MailboxType.values()) { m_mailboxMap.put(type, new LinkedList<MailboxNodeContent>()); } m_mailboxMap.get(MailboxType.StatsAgent).add( new MailboxNodeContent(m_hostMessenger.getHSIdForLocalSite(HostMessenger.STATS_SITE_ID), null)); m_siteTracker = new SiteTracker(m_hostId, m_mailboxMap); } catch (Exception e) { throw new RuntimeException(e); } } public Procedure addProcedureForTest(String name) { Procedure retval = getCluster().getDatabases().get(m_databaseName).getProcedures().add(name); retval.setClassname(name); retval.setHasjava(true); retval.setSystemproc(false); retval.setDefaultproc(false); return retval; } public void addSite(long siteId, MailboxType type) { m_mailboxMap.get(type).add(new MailboxNodeContent(siteId, null)); m_siteTracker = new SiteTracker(m_hostId, m_mailboxMap); } public void addSite(long siteId, int partitionId) { MailboxNodeContent mnc = new MailboxNodeContent( siteId, partitionId); m_mailboxMap.get(MailboxType.ExecutionSite).add(mnc); m_siteTracker = new SiteTracker(m_hostId, m_mailboxMap); } public synchronized void killSite(long siteId) { m_catalog = m_catalog.deepCopy(); for (List<MailboxNodeContent> lmnc : m_mailboxMap.values()) { Iterator<MailboxNodeContent> iter = lmnc.iterator(); while (iter.hasNext()) { if (iter.next().HSId == siteId) { iter.remove(); } } } m_siteTracker = new SiteTracker(m_hostId, m_mailboxMap); } public void addTable(String tableName, boolean isReplicated) { getDatabase().getTables().add(tableName); getTable(tableName).setIsreplicated(isReplicated); getTable(tableName).setSignature(tableName); } public void setDRProducerEnabled() { getCluster().setDrproducerenabled(true); } public void setDRConsumerConnectionEnabled(boolean enabled) { getCluster().setDrconsumerenabled(enabled); } String m_clSnapshotPath = "command_log_snapshot"; public void configureLogging(boolean enabled, boolean sync, int fsyncInterval, int maxTxns, String logPath, String snapshotPath) { org.voltdb.catalog.CommandLog logConfig = getCluster().getLogconfig().get("log"); if (logConfig == null) { logConfig = getCluster().getLogconfig().add("log"); } logConfig.setEnabled(enabled); logConfig.setSynchronous(sync); logConfig.setFsyncinterval(fsyncInterval); logConfig.setMaxtxns(maxTxns); m_clSnapshotPath = snapshotPath; } @Override public String getCommandLogSnapshotPath() { return m_clSnapshotPath; } String m_autoSnapshotPath = "snapshots"; public void configureSnapshotSchedulePath(String autoSnapshotPath) { org.voltdb.catalog.SnapshotSchedule scheduleConfig = getDatabase().getSnapshotschedule().get("default"); if (scheduleConfig == null) { scheduleConfig = getDatabase().getSnapshotschedule().add("default"); } m_autoSnapshotPath = autoSnapshotPath; } @Override public String getSnapshotPath() { return m_autoSnapshotPath; } public void addColumnToTable(String tableName, String columnName, VoltType columnType, boolean isNullable, String defaultValue, VoltType defaultType) { int index = getTable(tableName).getColumns().size(); getTable(tableName).getColumns().add(columnName); getColumnFromTable(tableName, columnName).setIndex(index); getColumnFromTable(tableName, columnName).setType(columnType.getValue()); getColumnFromTable(tableName, columnName).setNullable(isNullable); getColumnFromTable(tableName, columnName).setName(columnName); getColumnFromTable(tableName, columnName).setDefaultvalue(defaultValue); getColumnFromTable(tableName, columnName).setDefaulttype(defaultType.getValue()); } public Cluster getCluster() { return m_catalog.getClusters().get(m_clusterName); } public Database getDatabase() { return getCluster().getDatabases().get(m_databaseName); } public Table getTable(String tableName) { return getDatabase().getTables().get(tableName); } Column getColumnFromTable(String tableName, String columnName) { return getTable(tableName).getColumns().get(columnName); } @Override public String getBuildString() { return "MOCK_VOLTDB"; } @Override public CatalogContext getCatalogContext() { long now = System.currentTimeMillis(); DbSettings settings = new DbSettings(ClusterSettings.create().asSupplier(), NodeSettings.create()); m_context = new CatalogContext( now, now, m_catalog, settings, new byte[] {}, null, new byte[] {}, 0, m_hostMessenger) { @Override public long getCatalogCRC() { return 13; } }; return m_context; } @Override public ClientInterface getClientInterface() { return null; } public void setConfig(VoltDB.Configuration config) { voltconfig = config; } @Override public Configuration getConfig() { if (voltconfig == null) { voltconfig = new VoltDB.Configuration(); } return voltconfig; } public void setHostMessenger(HostMessenger msg) { m_hostMessenger = msg; } @Override public HostMessenger getHostMessenger() { return m_hostMessenger; } public void setStatsAgent(StatsAgent agent) { m_statsAgent = agent; } @Override public OpsAgent getOpsAgent(OpsSelector selector) { return null; } @Override public StatsAgent getStatsAgent() { return m_statsAgent; } @Override public MemoryStats getMemoryStatsSource() { return null; } @Override public String getVersionString() { if (!m_noLoadLib) { return new RealVoltDB().getVersionString(); } else { return null; } } @Override public String getEELibraryVersionString() { return getVersionString(); } @Override public boolean isCompatibleVersionString(String versionString) { return true; } @Override public boolean isRunningWithOldVerbs() { return voltconfig.m_startAction.isLegacy(); } @Override public void initialize(Configuration config) { m_noLoadLib = config.m_noLoadLibVOLTDB; voltconfig = config; } @Override public void cli(Configuration config) { m_noLoadLib = config.m_noLoadLibVOLTDB; voltconfig = config; } public void createStartActionNode(int index, StartAction action) { VoltZK.createStartActionNode(m_hostMessenger.getZK(), m_hostMessenger.getHostId() + index, action); } class StartActionWatcher implements Watcher { @Override public void process(WatchedEvent event) { m_es.submit(new Runnable() { @Override public void run() { validateStartAction(); } }); } } public void validateStartAction() { try { ZooKeeper zk = m_hostMessenger.getZK(); boolean initCompleted = zk.exists(VoltZK.init_completed, false) != null; List<String> children = zk.getChildren(VoltZK.start_action, new StartActionWatcher(), null); if (!children.isEmpty()) { for (String child : children) { byte[] data = zk.getData(VoltZK.start_action + "/" + child, false, null); if (data == null) { VoltDB.crashLocalVoltDB("Couldn't find " + VoltZK.start_action + "/" + child); } String startAction = new String(data); if ((startAction.equals(StartAction.JOIN.toString()) || startAction.equals(StartAction.REJOIN.toString()) || startAction.equals(StartAction.LIVE_REJOIN.toString())) && !initCompleted) { int nodeId = VoltZK.getHostIDFromChildName(child); if (nodeId == m_hostMessenger.getHostId()) { VoltDB.crashLocalVoltDB("This node was started with start action " + startAction + " during cluster creation. All nodes should be started with matching " + "create or recover actions when bring up a cluster. Join and Rejoin " + "are for adding nodes to an already running cluster."); } else { logger.warn("Node " + nodeId + " tried to " + startAction + " cluster but it is not allowed during cluster creation. " + "All nodes should be started with matching create or recover actions when bring up a cluster. " + "Join and rejoin are for adding nodes to an already running cluster."); } } } } } catch (KeeperException e) { logger.error("Failed to validate the start actions:" + e.getMessage()); } catch (InterruptedException e) { VoltDB.crashLocalVoltDB("Interrupted during start action validation:" + e.getMessage(), true, e); } } @Override public boolean isRunning() { return false; } @Override public void readBuildInfo(String editionTag) { } @Override public void run() { } @Override public boolean shutdown(Thread mainSiteThread) throws InterruptedException { VoltDB.wasCrashCalled = false; VoltDB.crashMessage = null; m_snapshotCompletionMonitor.shutdown(); m_es.shutdown(); m_es.awaitTermination( 1, TimeUnit.DAYS); m_statsAgent.shutdown(); m_hostMessenger.shutdown(); return true; } @Override public boolean isMpSysprocSafeToExecute(long txnId) { return true; } @Override public void startSampler() { } @Override public Pair<CatalogContext, CatalogSpecificPlanner> catalogUpdate(String diffCommands, byte[] catalogBytes, byte[] catalogHash, int expectedCatalogVersion, long currentTxnId, long currentTxnTimestamp, byte[] deploymentBytes, byte[] deploymentHash, boolean requireCatalogDiffCmdsApplyToEE, boolean hasSchemaChange, boolean requiresNewExportGeneration) { throw new UnsupportedOperationException("unimplemented"); } @Override public Pair<CatalogContext, CatalogSpecificPlanner> settingsUpdate(ClusterSettings settings, int expectedVersionId) { throw new UnsupportedOperationException("unimplemented"); } @Override public BackendTarget getBackendTargetType() { return BackendTarget.NONE; } @Override public void logUpdate(String xmlConfig, long currentTxnId, File voltroot) { } @Override public void onExecutionSiteRejoinCompletion(long transferred) { } @Override public CommandLog getCommandLog() { if (m_cl != null) { return m_cl; } else { return new DummyCommandLog(); } } public void setCommandLog(CommandLog cl) { m_cl = cl; } @Override public boolean rejoining() { return false; } @Override public String getVoltDBRootPath(PathsType.Voltdbroot path) { return path.getPath(); } @Override public String getCommandLogPath(PathsType.Commandlog path) { return path.getPath(); } @Override public String getCommandLogSnapshotPath(PathsType.Commandlogsnapshot path) { return path.getPath(); } @Override public String getSnapshotPath(PathsType.Snapshots path) { return path.getPath(); } @Override public String getExportOverflowPath(PathsType.Exportoverflow path) { return path.getPath(); } @Override public String getDROverflowPath(PathsType.Droverflow path) { return path.getPath(); } @Override public String getCommandLogPath() { return "command_log"; } @Override public void loadLegacyPathProperties(DeploymentType deployment) throws IOException { } @Override public String getVoltDBRootPath() { return "voltdbroot"; } @Override public String getExportOverflowPath() { return "export_overflow"; } @Override public String getDROverflowPath() { return "dr_overflow"; } @Override public boolean isBare() { return false; } @Override public boolean rejoinDataPending() { return false; } @Override public OperationMode getMode() { return m_mode; } @Override public void setMode(OperationMode mode) { m_mode = mode; } @Override public String getLocalMetadata() { return m_localMetadata; } @Override public void setStartMode(OperationMode mode) { m_startMode = mode; } @Override public OperationMode getStartMode() { return m_startMode; } @Override public void promoteToMaster() { m_replicationRole = ReplicationRole.NONE; } @Override public ReplicationRole getReplicationRole() { return m_replicationRole; } @Override public SnapshotCompletionMonitor getSnapshotCompletionMonitor() { return m_snapshotCompletionMonitor; } @Override public ScheduledExecutorService getSES(boolean priority) { return null; } @Override public ScheduledFuture<?> scheduleWork(Runnable work, long initialDelay, long delay, TimeUnit unit) { return null; } @Override public ListeningExecutorService getComputationService() { return m_es; } @Override public void setReplicationActive(boolean active) { m_replicationActive = active; } @Override public boolean getReplicationActive() { return m_replicationActive; } @Override public ProducerDRGateway getNodeDRGateway() { return null; } @Override public SiteTracker getSiteTrackerForSnapshot() { return m_siteTracker; } @Override public void recoveryComplete(String requestId) { } @Override public LicenseApi getLicenseApi() { return new LicenseApi() { @Override public boolean initializeFromFile(File license) { return true; } @Override public boolean isTrial() { return false; } @Override public int maxHostcount() { return Integer.MAX_VALUE; } @Override public Calendar expires() { Calendar result = Calendar.getInstance(); result.add(Calendar.YEAR, 20); // good enough? return result; } @Override public boolean verify() { return true; } @Override public boolean isDrReplicationAllowed() { // TestExecutionSite (and probably others) // use MockVoltDB without requiring unique // zmq ports for the DR replicator. Note // that getReplicationActive(), above, is // hardcoded to false, too. return false; } @Override public boolean isDrActiveActiveAllowed() { // TestExecutionSite (and probably others) // use MockVoltDB without requiring unique // zmq ports for the DR replicator. return false; } @Override public boolean isCommandLoggingAllowed() { return true; } @Override public boolean isAWSMarketplace() { return false; } @Override public boolean isEnterprise() { return false; } @Override public boolean isPro() { return false; } @Override public String licensee() { return null; } @Override public Calendar issued() { return null; } @Override public String note() { return null; } @Override public boolean hardExpiration() { return false; } @Override public boolean secondaryInitialization() { return true; } }; } @Override public String getLicenseInformation() { return ""; } @Override public <T> ListenableFuture<T> submitSnapshotIOWork(Callable<T> work) { return null; } @Override public ScheduledFuture<?> schedulePriorityWork(Runnable work, long initialDelay, long delay, TimeUnit unit) { return null; } @Override public long getClusterUptime() { return 0; } @Override public long getClusterCreateTime() { return m_clusterCreateTime; } @Override public void setClusterCreateTime(long clusterCreateTime) { m_clusterCreateTime = clusterCreateTime; } @Override public void halt() { assert (true); } @Override public ConsumerDRGateway getConsumerDRGateway() { return null; } @Override public void setDurabilityUniqueIdListener(Integer partition, DurableUniqueIdListener listener) { } @Override public void onSyncSnapshotCompletion() { } @Override public boolean isPreparingShuttingdown() { return false; } @Override public void setShuttingdown(boolean shuttingdown) { } @Override public Cartographer getCartograhper() { return null; } @Override public SnmpTrapSender getSnmpTrapSender() { return new DummySnmpTrapSender(); } @Override public void swapTables(String oneTable, String otherTable) { } }