/* * Copyright (c) 2008-2011 EMC Corporation * All Rights Reserved */ package com.emc.storageos.db.server.impl; import static com.emc.storageos.services.util.FileUtils.readValueFromFile; import static com.emc.storageos.security.dbInfo.DbInfoUtils.checkDBOfflineInfo; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import com.emc.storageos.services.util.*; import org.apache.cassandra.config.DatabaseDescriptor; import org.apache.cassandra.service.CassandraDaemon; import org.apache.cassandra.service.StorageService; import org.apache.cassandra.service.StorageServiceMBean; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.apache.curator.framework.recipes.locks.InterProcessLock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import com.emc.storageos.coordinator.client.beacon.ServiceBeacon; import com.emc.storageos.coordinator.client.beacon.impl.ServiceBeaconImpl; import com.emc.storageos.coordinator.client.model.Constants; import com.emc.storageos.coordinator.client.service.CoordinatorClient; import com.emc.storageos.coordinator.client.service.impl.CoordinatorClientInetAddressMap; import com.emc.storageos.coordinator.client.service.impl.CoordinatorClientImpl; import com.emc.storageos.coordinator.common.Configuration; import com.emc.storageos.coordinator.common.Service; import com.emc.storageos.coordinator.common.impl.ConfigurationImpl; import com.emc.storageos.db.client.impl.DbClientContext; import com.emc.storageos.db.client.impl.DbClientImpl; import com.emc.storageos.db.common.DbConfigConstants; import com.emc.storageos.db.common.DbServiceStatusChecker; import com.emc.storageos.db.event.ActionableEventScrubberExecutor; import com.emc.storageos.db.gc.GarbageCollectionExecutor; import com.emc.storageos.db.server.DbService; import com.emc.storageos.db.server.MigrationHandler; import com.emc.storageos.db.server.impl.StartupMode.DbReinitMode; import com.emc.storageos.db.server.impl.StartupMode.GeodbRestoreMode; import com.emc.storageos.db.server.impl.StartupMode.HibernateMode; import com.emc.storageos.db.server.impl.StartupMode.NormalMode; import com.emc.storageos.db.server.impl.StartupMode.ObsoletePeersCleanupMode; import com.emc.storageos.db.task.TaskScrubberExecutor; /** * Default database service implementation */ public class DbServiceImpl implements DbService { private static final Logger _log = LoggerFactory.getLogger(DbServiceImpl.class); private static final String DB_INITIALIZED_FLAG_FILE = "/var/run/storageos/dbsvc_initialized"; public static DbServiceImpl instance = null; // run failure detector every 5 min by default private static final int DEFAULT_DETECTOR_RUN_INTERVAL_MIN = 5; private int _detectorInterval = DEFAULT_DETECTOR_RUN_INTERVAL_MIN; // Service outage time should be less than 5 days, or else service will not be allowed to get started any more. // As we checked the downtime every 15 mins, to avoid actual downtime undervalued, setting the max value as 4 days. private static final long MAX_SERVICE_OUTAGE_TIME = 4 * TimeUtils.DAYS; private AlertsLogger alertLog = AlertsLogger.getAlertsLogger(); private String _config; private CoordinatorClient _coordinator; private CassandraDaemon _service; private SchemaUtil _schemaUtil; private MigrationHandler _handler; private GarbageCollectionExecutor _gcExecutor; private TaskScrubberExecutor _taskScrubber; private ActionableEventScrubberExecutor _eventScrubber; // 3 threads two threads for node repair, one is for failure detector private static final String POOL_NAME = "DBBackgroundPool"; private ScheduledExecutorService _exe = new NamedScheduledThreadPoolExecutor(POOL_NAME, 3); protected Service _serviceInfo; private JmxServerWrapper _jmxServer; private DbClientImpl _dbClient; private ServiceBeacon _svcBeacon; private DbServiceStatusChecker _statusChecker; // db directory private String dbDir; private String keystorePath; private String truststorePath; private boolean cassandraInitialized = false; private boolean disableScheduledDbRepair = false; private Boolean backCompatPreYoda = false; @Autowired private DbCompactWorker compactWorker; @Autowired private DbManager dbMgr; public void setDbMgr(DbManager dbMgr) { this.dbMgr = dbMgr; } /** * Set db client */ public void setDbClient(DbClientImpl dbClient) { _dbClient = dbClient; } /** * Set coordinator client * */ public void setCoordinator(CoordinatorClient coordinator) { _coordinator = coordinator; } public CoordinatorClient getCoordinator() { return _coordinator; } /** * Set DB schema utility * * @param schemaUtil */ public void setSchemaUtil(SchemaUtil schemaUtil) { _schemaUtil = schemaUtil; } public void setMigrationHandler(MigrationHandler handler) { _handler = handler; } /** * Service setter * * @param service * service info */ public void setService(final Service service) { _serviceInfo = service; } /** * Set database config file. It must be in URI form or file must be * be in classpath * * @param config database config file */ public void setConfig(String config) { _config = config; } /** * JMX server wrapper */ public void setJmxServerWrapper(JmxServerWrapper jmxServer) { _jmxServer = jmxServer; } public void setGarbageCollector(GarbageCollectionExecutor gcExecutor) { _gcExecutor = gcExecutor; } public ActionableEventScrubberExecutor getEventScrubber() { return _eventScrubber; } public void setEventScrubber(ActionableEventScrubberExecutor eventScrubber) { this._eventScrubber = eventScrubber; } public TaskScrubberExecutor getTaskScrubber() { return _taskScrubber; } public void setTaskScrubber(TaskScrubberExecutor taskScrubber) { this._taskScrubber = taskScrubber; } public void setBeacon(ServiceBeacon beacon) { _svcBeacon = beacon; } @Autowired public void setStatusChecker(DbServiceStatusChecker statusChecker) { _statusChecker = statusChecker; } public void setDbDir(String dbDir) { this.dbDir = dbDir; } public String getDbDir() { return this.dbDir; } public void setDisableScheduledDbRepair(boolean disableScheduledDbRepair) { this.disableScheduledDbRepair = disableScheduledDbRepair; } public void setBackCompatPreYoda(Boolean backCompatPreYoda) { this.backCompatPreYoda = backCompatPreYoda; } /** * Check if it is GeoDbSvc * * @return */ private boolean isGeoDbsvc() { return _schemaUtil.isGeoDbsvc(); } /** * Get schema lock name using by current service. * * @return */ private String getSchemaLockName() { return isGeoDbsvc() ? DbConfigConstants.GEODB_SCHEMA_LOCK : DbConfigConstants.DB_SCHEMA_LOCK; } public String getConfigValue(String key) { String configKind = _coordinator.getDbConfigPath(_serviceInfo.getName()); Configuration config = _coordinator.queryConfiguration(_coordinator.getSiteId(), configKind, _serviceInfo.getId()); if (config != null) { return config.getConfig(key); } return null; } public void setConfigValue(String key, String value) { String configKind = _coordinator.getDbConfigPath(_serviceInfo.getName()); Configuration config = _coordinator.queryConfiguration(_coordinator.getSiteId(), configKind, _serviceInfo.getId()); if (config != null) { config.setConfig(key, value); _coordinator.persistServiceConfiguration(_coordinator.getSiteId(), config); } } /** * Checks and registers db configuration information, * this is one time when cluster is coming up for the first time */ private Configuration checkConfiguration() { String configKind = _coordinator.getDbConfigPath(_serviceInfo.getName()); Configuration config = _coordinator.queryConfiguration(_coordinator.getSiteId(), configKind, _serviceInfo.getId()); if (config == null) { // check if it is upgraded from previous version to yoda - configuration may be stored in // zk global area /config. Since SeedProvider still need access that, so we remove the config // from global in migration callback after migration is done. config = _coordinator.queryConfiguration(configKind, _serviceInfo.getId()); if (config != null) { _log.info("Upgrade from pre-yoda release, move dbconfig to new location"); _coordinator.persistServiceConfiguration(_coordinator.getSiteId(), config); return config; } // this is a new node // 1. register its configuration with coordinator // 2. assume autobootstrap configuration // this means that when a node is added, it take 1/2 of biggest token rage and // copies its data over ConfigurationImpl cfg = new ConfigurationImpl(); cfg.setId(_serviceInfo.getId()); cfg.setKind(configKind); cfg.setConfig(DbConfigConstants.NODE_ID, _coordinator.getInetAddessLookupMap().getNodeId()); cfg.setConfig(DbConfigConstants.AUTOBOOT, Boolean.TRUE.toString()); // check other existing db nodes List<Configuration> configs = _coordinator.queryAllConfiguration(_coordinator.getSiteId(), configKind); if (configs.isEmpty()) { // we are the first node - turn off autobootstrap cfg.setConfig(DbConfigConstants.AUTOBOOT, Boolean.FALSE.toString()); } // persist configuration _coordinator.persistServiceConfiguration(_coordinator.getSiteId(), cfg); config = cfg; } return config; } private void removeStaleConfiguration() { removeStaleServiceConfiguration(); removeStaleVersionedDbConfiguration(); } private void removeStaleVersionedDbConfiguration() { String configKind = _coordinator.getVersionedDbConfigPath(_serviceInfo.getName(), _serviceInfo.getVersion()); List<Configuration> configs = _coordinator.queryAllConfiguration(_coordinator.getSiteId(), configKind); for (Configuration config : configs) { if (isStaleConfiguration(config)) { _coordinator.removeServiceConfiguration(_coordinator.getSiteId(), config); _log.info("Remove stale version db config, id: {}", config.getId()); } } } private void removeStaleServiceConfiguration() { boolean isGeoDBSvc = isGeoDbsvc(); boolean resetAutoBootFlag = false; String configKind = _coordinator.getDbConfigPath(_serviceInfo.getName()); List<Configuration> configs = _coordinator.queryAllConfiguration(_coordinator.getSiteId(), configKind); for (Configuration config : configs) { if (isStaleConfiguration(config)) { boolean autoboot = Boolean.parseBoolean(config.getConfig(DbConfigConstants.AUTOBOOT)); String configId = config.getId(); if (isGeoDBSvc && !autoboot && (configId.equals("geodb-4") || configId.equals("geodb-5"))) { // for geodbsvc, if restore with the backup of 5 nodes to 3 nodes and the backup is made // on the cluster that the 'autoboot=false' is set on vipr4 or vipr5 // we should set the autoboot=false on the current node or no node with autoboot=false // TODO:This is a temporary/safest solution in Yoda, we'll provide a better soltuion post Yoda resetAutoBootFlag = true; } if (isStaleConfiguration(config)) { _coordinator.removeServiceConfiguration(_coordinator.getSiteId(), config); _log.info("Remove stale db config, id: {}", config.getId()); } } } if (resetAutoBootFlag) { _log.info("set autoboot flag to false on {}", _serviceInfo.getId()); Configuration config = _coordinator.queryConfiguration(_coordinator.getSiteId(), configKind, _serviceInfo.getId()); config.setConfig(DbConfigConstants.AUTOBOOT, Boolean.FALSE.toString()); _coordinator.persistServiceConfiguration(_coordinator.getSiteId(), config); } } private boolean isStaleConfiguration(Configuration config) { String delimiter = "-"; String configId = config.getId(); // Bypasses item of "global" and folders of "version", just check db configurations. if (configId == null || configId.equals(Constants.GLOBAL_ID) || !configId.contains(delimiter)) { return false; } if (_serviceInfo.getId().endsWith(Constants.STANDALONE_ID)) { if (!configId.equals(_serviceInfo.getId())) { return true; } } else { CoordinatorClientInetAddressMap nodeMap = _coordinator.getInetAddessLookupMap(); int nodeCount = nodeMap.getControllerNodeIPLookupMap().size(); String nodeIndex = configId.split(delimiter)[1]; if (Constants.STANDALONE_ID.equalsIgnoreCase(nodeIndex) || Integer.parseInt(nodeIndex) > nodeCount) { return true; } } return false; } // check and initialize global configuration private Configuration checkGlobalConfiguration() { String configKind = _coordinator.getDbConfigPath(_serviceInfo.getName()); Configuration config = _coordinator.queryConfiguration(_coordinator.getSiteId(), configKind, Constants.GLOBAL_ID); if (config == null) { // check if it is upgraded from previous version to yoda - configuration may be stored in // znode /config. Since SeedProvider still need access that, so we remove the config // from global in migration callback after migration is done. config = _coordinator.queryConfiguration(configKind, Constants.GLOBAL_ID); if (config != null) { _log.info("Upgrade from pre-yoda release, move global config to new location"); _coordinator.persistServiceConfiguration(_coordinator.getSiteId(), config); return config; } ConfigurationImpl cfg = new ConfigurationImpl(); cfg.setId(Constants.GLOBAL_ID); cfg.setKind(configKind); cfg.setConfig(Constants.SCHEMA_VERSION, this._serviceInfo.getVersion()); // persist configuration _coordinator.persistServiceConfiguration(_coordinator.getSiteId(), cfg); config = cfg; } return config; } // check and initialize versioned configuration private Configuration checkVersionedConfiguration() { String serviceVersion = _serviceInfo.getVersion(); String dbSchemaVersion = _dbClient.getSchemaVersion(); if (!serviceVersion.equals(dbSchemaVersion)) { _log.warn("The db service version {} doesn't equals Db schema version {}, " + "set db service version to Db schema version", serviceVersion, dbSchemaVersion); _serviceInfo.setVersion(dbSchemaVersion); } String kind = _coordinator.getVersionedDbConfigPath(_serviceInfo.getName(), _serviceInfo.getVersion()); Configuration config = _coordinator.queryConfiguration(_coordinator.getSiteId(), kind, _serviceInfo.getId()); if (config == null) { // check if it is upgraded from previous version to yoda - configuration may be stored in // znode /config config = _coordinator.queryConfiguration(kind, _serviceInfo.getId()); if (config != null) { _log.info("Upgrade from pre-2.5 release, move versioned dbconfig to new location"); _coordinator.persistServiceConfiguration(_coordinator.getSiteId(), config); return config; } ConfigurationImpl cfg = new ConfigurationImpl(); cfg.setId(_serviceInfo.getId()); cfg.setKind(kind); // persist configuration _coordinator.persistServiceConfiguration(_coordinator.getSiteId(), cfg); config = cfg; } return config; } /** * Checks and sets INIT_DONE state * this means we are done with the actual cf changes on the cassandra side for the target version */ private void setDbConfigInitDone() { String configKind = _coordinator.getVersionedDbConfigPath(_serviceInfo.getName(), _serviceInfo.getVersion()); Configuration config = _coordinator.queryConfiguration(_coordinator.getSiteId(), configKind, _serviceInfo.getId()); if (config != null) { if (config.getConfig(DbConfigConstants.INIT_DONE) == null) { config.setConfig(DbConfigConstants.INIT_DONE, Boolean.TRUE.toString()); _coordinator.persistServiceConfiguration(_coordinator.getSiteId(), config); } } else { // we are expecting this to exist, because its initialized from checkVersionedConfiguration throw new IllegalStateException("unexpected error, db versioned configuration is null"); } } /** * Initializes the keystore/truststore if the paths have been provided. */ private void initKeystoreAndTruststore() { try { DbClientContext ctx = _dbClient.getLocalContext(); if (isGeoDbsvc()) { ctx = _dbClient.getGeoContext(); } String keystorePath = ctx.getKeyStoreFile(); String truststorePath = ctx.getTrustStoreFile(); if (keystorePath == null && truststorePath == null) { _log.info("Skipping keystore/truststore initialization, no paths provided"); return; } String password = ctx.getTrustStorePassword(); CassandraKeystoreHandler keystoreHandler = new CassandraKeystoreHandler(_coordinator, keystorePath, truststorePath, password); if (keystorePath != null) { _log.info("Initializing keystore for current node: {}", keystorePath); keystoreHandler.saveKeyStore(); } else { _log.info("Skipping keystore initialization, no path provided"); } if (truststorePath != null) { _log.info("Initializing truststore for current node: {}", truststorePath); keystoreHandler.saveTrustStore(); } else { _log.info("Skipping truststore initialization, no path provided"); } } catch (Exception e) { _log.error("Unexpected exception during initializing cassandra keystore", e); throw new IllegalStateException(e); } } /** * Use a db initialized flag file to block the peripheral services from starting. * This gurantees CPU cyles for the core services during boot up. */ protected void setDbInitializedFlag() { // set the flag file only for dbsvc (not for geodbsvc) since it always uses more time to // complete comparing to the other if (isGeoDbsvc()) return; File dbInitializedFlag = new File(DB_INITIALIZED_FLAG_FILE); try { if (!dbInitializedFlag.exists()) { new FileOutputStream(dbInitializedFlag).close(); } } catch (Exception e) { _log.error("Failed to create file {} e=", dbInitializedFlag.getName(), e); } } @Override public void start() throws IOException { if (_log.isInfoEnabled()) { _log.info("Starting DB service..."); } // Suppress Sonar violation of Lazy initialization of static fields should be synchronized // start() method will be only called one time when startup dbsvc, so it's safe to ignore sonar violation instance = this; // NOSONAR ("squid:S2444") if (backCompatPreYoda) { _log.info("Pre-yoda back compatible flag detected. Initialize local keystore/truststore for Cassandra native encryption"); initKeystoreAndTruststore(); _schemaUtil.setBackCompatPreYoda(true); } System.setProperty("cassandra.config", _config); System.setProperty("cassandra.config.loader", CassandraConfigLoader.class.getName()); // Set to false to clear all gossip state for the node on restart. // // We encounter a weird Cassandra grossip issue(COP-19246) - some nodes are missing from gossip // when rebooting the entire cluster simultaneously. Critical Gossip fields(ApplicationState.STATUS, ApplicationState.TOKENS) // are not synchronized during handshaking. It looks like some problem caused by incorrect gossip version/generation // at system local table. So add this option to cleanup local gossip state during reboot // // Make sure add-vdc/add-standby passed when you would remove this option in the future. // // We need make sure majority local nodes are added as seed nodes. Otherwise cassandra may not see other nodes if it loses // connection to other sites System.setProperty("cassandra.load_ring_state", "false"); // Nodes in new data center should not auto-bootstrap. // See https://docs.datastax.com/en/cassandra/2.0/cassandra/operations/ops_add_dc_to_cluster_t.html if (_schemaUtil.isStandby()) { System.setProperty("cassandra.auto_bootstrap", "false"); } InterProcessLock lock = null; Configuration config = null; StartupMode mode = null; try { // we use this lock to discourage more than one node bootstrapping / joining at the same time // Cassandra can handle this but it's generally not recommended to make changes to schema concurrently lock = getLock(getSchemaLockName()); config = checkConfiguration(); checkGlobalConfiguration(); checkVersionedConfiguration(); removeStaleConfiguration(); mode = checkStartupMode(config); _log.info("Current startup mode is {}", mode); // Check if service is allowed to get started by querying db offline info to avoid bringing back stale data. // Skipping hibernate mode for node recovery procedure to recover the overdue node. int nodeCount = ((CoordinatorClientImpl)_coordinator).getNodeCount(); if (nodeCount != 1 && mode.type != StartupMode.StartupModeType.HIBERNATE_MODE) { checkDBOfflineInfo(_coordinator, _serviceInfo.getName(), dbDir, true); } // this call causes instantiation of a seed provider instance, so the check*Configuration // calls must be preceed it removeCassandraSavedCaches(); mode.onPreStart(); if (_jmxServer != null) { _jmxServer.start(); System.setProperty("com.sun.management.jmxremote.port", Integer.toString(_jmxServer.getPort())); } _service = new CassandraDaemon(); _service.init(null); _service.start(); cassandraInitialized = true; mode.onPostStart(); } catch (Exception e) { if (mode != null && mode.type == StartupMode.StartupModeType.HIBERNATE_MODE) { printRecoveryWorkAround(e); } _log.error("e=", e); throw new IllegalStateException(e); } finally { if (lock != null) { try { lock.release(); } catch (Exception ignore) { _log.debug("lock release failed"); } } } if (config.getConfig(DbConfigConstants.JOINED) == null) { config.setConfig(DbConfigConstants.JOINED, Boolean.TRUE.toString()); _coordinator.persistServiceConfiguration(_coordinator.getSiteId(), config); } _statusChecker.waitForAllNodesJoined(); _svcBeacon.start(); if (backCompatPreYoda) { _log.info("Enable duplicated beacon in global area during pre-yoda upgrade"); startDupBeacon(); } setDbInitializedFlag(); setDbConfigInitDone(); _dbClient.start(); if (_schemaUtil.isStandby()) { String localDataRevision = getLocalDataRevision(); if (localDataRevision != null) { _schemaUtil.checkDataRevision(localDataRevision); } } // Setup the vdc information, so that login enabled before migration if (!isGeoDbsvc()) { _schemaUtil.checkAndSetupBootStrapInfo(_dbClient); } dbMgr.init(); if (_handler.run()) { // Setup the bootstrap info root tenant, if root tenant migrated from local db, then skip it if (isGeoDbsvc()) { _schemaUtil.checkAndSetupBootStrapInfo(_dbClient); } else { _schemaUtil.checkAndInitStorageSystemTypes(_dbClient); } startBackgroundTasks(); _log.info("DB service started"); } else { _log.error("DB migration failed. Skipping starting background tasks."); } } private InterProcessLock getLock(String name) throws Exception { InterProcessLock lock = null; while (true) { try { lock = _coordinator.getSiteLocalLock(name); lock.acquire(); break; // got lock } catch (Exception e) { if (_coordinator.isConnected()) { throw e; } } } return lock; } private void startDupBeacon() { ServiceBeaconImpl dupBeacon = new ServiceBeaconImpl(); dupBeacon.setService(((ServiceBeaconImpl)_svcBeacon).getService()); dupBeacon.setZkConnection(((ServiceBeaconImpl)_svcBeacon).getZkConnection()); dupBeacon.setSiteSpecific(false); dupBeacon.start(); } /** * Check startup mode on disk. Startup mode is specified by a property file on disk ${dbdir}/startupmode * * @param config * The Confiugration instance * @return BootMode instance if detected, null for no on-disk startup mode */ private StartupMode checkStartupModeOnDisk(Configuration config) throws IOException { String modeType = readStartupModeFromDisk(); if (modeType != null) { if (Constants.STARTUPMODE_HIBERNATE.equalsIgnoreCase(modeType)) { HibernateMode mode = new HibernateMode(config); mode.setCoordinator(_coordinator); mode.setSchemaUtil(_schemaUtil); mode.setDbDir(dbDir); return mode; } else if (Constants.STARTUPMODE_RESTORE_REINIT.equalsIgnoreCase(modeType)) { _log.info("GeodbRestore startup mode found. Current vdc list {}", _schemaUtil.getVdcList().size()); if (isGeoDbsvc() && _schemaUtil.getVdcList().size() > 1) { GeodbRestoreMode mode = new GeodbRestoreMode(config); mode.setCoordinator(_coordinator); mode.setSchemaUtil(_schemaUtil); mode.setDbDir(dbDir); return mode; } } else { throw new IllegalStateException("Unexpected startup mode " + modeType); } } return null; } public String readStartupModeFromDisk() throws IOException { File startupModeFile = new File(dbDir, Constants.STARTUPMODE); String modeType = readValueFromFile(startupModeFile, Constants.STARTUPMODE); _log.info("On disk startup mode found {}", modeType); return modeType; } /** * Remove startup mode flag on disk */ protected void removeStartupModeOnDisk() { _log.info("Remove bootmode file"); File bootModeFile = new File(dbDir, Constants.STARTUPMODE); bootModeFile.delete(); } /** * Read bool value from given db config * * @param config * @param name * @return */ private boolean checkConfigBool(Configuration config, String name) { String value = config.getConfig(name); return value != null && Boolean.parseBoolean(value); } /** * Read a string list(connected by ',') from given db config * * @param config * @return */ private List<String> checkConfigList(Configuration config, String name) { String peerIPs = config.getConfig(name); ArrayList<String> peers = new ArrayList<String>(); if (peerIPs != null) { for (String ip : StringUtils.split(peerIPs, ",")) { peers.add(ip); } } return peers; } /** * Determine current startup mode. See BootMode for detailed explanation * of each mode. * * @param config * @return */ private StartupMode checkStartupMode(Configuration config) throws IOException { // Check on disk mode first StartupMode bootMode = checkStartupModeOnDisk(config); if (bootMode != null) { return bootMode; } // Check geodb restore flag in zk if (checkConfigBool(config, Constants.STARTUPMODE_RESTORE_REINIT)) { _log.info("Found geodbrestore config: {}", Constants.STARTUPMODE_RESTORE_REINIT); GeodbRestoreMode mode = new GeodbRestoreMode(config); mode.setCoordinator(_coordinator); mode.setSchemaUtil(_schemaUtil); mode.setDbDir(dbDir); return mode; } // Check geodb reinit ZK flag for add-vdc if (checkConfigBool(config, Constants.REINIT_DB)) { _log.info("Found reinit config: {}", Constants.REINIT_DB); // reinit both system table and StorageOS tables DbReinitMode mode = new DbReinitMode(config); mode.setCoordinator(_coordinator); mode.setSchemaUtil(_schemaUtil); mode.setDbDir(dbDir); return mode; } // check geodb cleanup mode for remove-vdc List<String> obsoletePeers = checkConfigList(config, Constants.OBSOLETE_CASSANDRA_PEERS); if (!obsoletePeers.isEmpty()) { // drop peers ip/tokens from system table ObsoletePeersCleanupMode mode = new ObsoletePeersCleanupMode(config); mode.setCoordinator(_coordinator); mode.setSchemaUtil(_schemaUtil); mode.setObsoletePeers(obsoletePeers); return mode; } else { NormalMode mode = new NormalMode(config); mode.setCoordinator(_coordinator); mode.setSchemaUtil(_schemaUtil); return mode; } } /** * Kick off background jobs */ private void startBackgroundTasks() { if (!_schemaUtil.isStandby()) { if (!disableScheduledDbRepair) { startBackgroundNodeRepairTask(); } if (_gcExecutor != null) { _gcExecutor.setDbServiceId(_serviceInfo.getId()); _gcExecutor.start(); } if (_taskScrubber != null) { _taskScrubber.start(); } if (_eventScrubber != null) { _eventScrubber.start(); } } startBackgroundDetectorTask(); startBackgroundCompactTask(); } private void startBackgroundCompactTask() { if (this.compactWorker != null) { // compactWorker is null in JUnit environment this.compactWorker.start(); } } /** * Start the node repair task in background */ private void startBackgroundNodeRepairTask() { this.dbMgr.start(); } /** * Start the detector task to monitor Cassandra events. * When Cassandra encounter internal exception or FS error, it will stop Gossip and RPC, * watch such events so that dbsvc could recover from Cassandra internal exception via restart * TODO: include other meaningful stats into consideration like memory usage, etc.. */ private void startBackgroundDetectorTask() { /* start after _detectorInterval 5 mins by default */ _exe.scheduleWithFixedDelay(new Runnable() { @Override public void run() { try { _log.debug("Starting failure detector"); StorageServiceMBean svc = null; svc = StorageService.instance; boolean isRPCRunning = svc.isRPCServerRunning(); boolean isGossipEnabled = svc.isInitialized(); _log.debug("Thrift status = " + isRPCRunning + ", gossip status = " + isGossipEnabled); if (!isRPCRunning && !isGossipEnabled) { _log.info("Thrift RPC and Gossip both stopped on this node"); _log.error("Cassandra service stopped unexpectedly, stopping dbsvc forcely ..."); /* * As Gossip and RPC stopped, we are not able to flush table out before exit */ System.exit(1); } _log.debug("End failure detector"); } catch (Exception e) { _log.warn("Unexpected exception during cassandra failure detect", e); } } }, _detectorInterval, _detectorInterval, TimeUnit.MINUTES); } /* * Cassandra saved caches would occasionally get corrupted after the reboot, and then * dbsvc will fail to start due to the error of OOM. Delete these files before the start * of dbsvc to avoid this issue, and these files could be rebuilt afterwards. * we should elminate this trick update after Cassandra solve this issue in future. */ private void removeCassandraSavedCaches() { _log.info("Try to remove cassandra saved caches"); String savedCachesLocation = DatabaseDescriptor.getSavedCachesLocation(); File savedCachesDir = new File(savedCachesLocation); if (savedCachesDir != null && savedCachesDir.exists()) { for (File file : savedCachesDir.listFiles()) { FileUtils.deleteQuietly(file); } _log.info("Delete cassandra saved caches({}) successfully", savedCachesLocation); } } @Override public void stop() { if (_log.isInfoEnabled()) { _log.info("Stopping DB service..."); } if (_gcExecutor != null) { _gcExecutor.stop(); } _exe.shutdownNow(); if (_jmxServer != null) { _jmxServer.stop(); } if (_log.isInfoEnabled()) { _log.info("DB service stopped..."); } } /** * Output more clear message in the log when a node down during node recovery introduced by CASSANDRA-2434 in cassandra 2.1. */ private void printRecoveryWorkAround(Exception e) { if (e.getMessage().startsWith("A node required to move the data consistently is down (")) { String sourceIp = e.getMessage().split("\\(")[1].split("\\)")[0]; _log.error("{} of node {} is unavailable during node recovery, please double check the node status.", isGeoDbsvc() ? "geodbsvc" : "dbsvc",sourceIp); _log.error("Node recovery will fail in 30 minutes if {} not back to normal state.", sourceIp); } } /** * Read local data revision number. Db data directory is a symbol link to a data revision directory as the following * /data/db/1 -> /data/db/1459567039514.0 * Here data version number is 1459567039514 and 0 is incremental snapshot number. It is always 0 for db revisions * * @return * @throws IOException */ private String getLocalDataRevision() { Path dbDataDir = Paths.get(dbDir, "1"); try { if (Files.isSymbolicLink(dbDataDir)) { Path symDir = Files.readSymbolicLink(dbDataDir); String versionName = symDir.toFile().getName(); int i = versionName.lastIndexOf("."); return versionName.substring(0, i); } } catch (Exception ex) { _log.error("Retrieve local data revision error", ex); } return null; } }