package com.tesora.dve.server.bootstrap; /* * #%L * Tesora Inc. * Database Virtualization Engine * %% * Copyright (C) 2011 - 2014 Tesora Inc. * %% * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License, version 3, * as published by the Free Software Foundation. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. * #L% */ import java.lang.management.ManagementFactory; import java.util.Enumeration; import java.util.Properties; import javax.management.MBeanServer; import javax.management.ObjectName; import com.tesora.dve.common.catalog.CatalogURL; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Level; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.apache.log4j.jmx.HierarchyDynamicMBean; import org.apache.log4j.spi.LoggerRepository; import com.tesora.dve.common.DBHelper; import com.tesora.dve.common.PEConstants; import com.tesora.dve.common.PEFileUtils; import com.tesora.dve.common.catalog.AutoIncrementTracker; import com.tesora.dve.common.catalog.CatalogDAO; import com.tesora.dve.common.catalog.CatalogDAO.CatalogDAOFactory; import com.tesora.dve.common.catalog.ExternalService; import com.tesora.dve.common.catalog.Provider; import com.tesora.dve.common.catalog.TemporaryTable; import com.tesora.dve.common.catalog.User; import com.tesora.dve.comms.client.messages.ConnectRequest; import com.tesora.dve.comms.client.messages.GlobalRecoveryRequest; import com.tesora.dve.db.mysql.portal.MySqlPortal; import com.tesora.dve.distribution.RandomDistributionModel; import com.tesora.dve.distribution.RangeDistributionModel; import com.tesora.dve.errmap.ErrorMapper; import com.tesora.dve.exceptions.PEException; import com.tesora.dve.externalservice.ExternalServiceFactory; import com.tesora.dve.groupmanager.GroupManager; import com.tesora.dve.locking.ClusterLock; import com.tesora.dve.server.connectionmanager.BroadcastMessageAgent; import com.tesora.dve.server.connectionmanager.NotificationManager; import com.tesora.dve.server.connectionmanager.SSConnectionProxy; import com.tesora.dve.server.global.BootstrapHostService; import com.tesora.dve.server.global.MySqlPortalService; import com.tesora.dve.server.statistics.manager.StatisticsManager; import com.tesora.dve.server.connectionmanager.Transaction2PCTracker; import com.tesora.dve.singleton.Singletons; import com.tesora.dve.siteprovider.SiteProviderContextInitialisation; import com.tesora.dve.siteprovider.SiteProviderPlugin.SiteProviderContext; import com.tesora.dve.siteprovider.SiteProviderPlugin.SiteProviderFactory; import com.tesora.dve.smqplugin.SimpleMQPlugin; import com.tesora.dve.sql.schema.cache.SchemaSourceFactory; import com.tesora.dve.sql.schema.mt.TableGarbageCollector; import com.tesora.dve.sql.transexec.CatalogHelper; import com.tesora.dve.worker.DirectConnectionCache; import com.tesora.dve.worker.WorkerGroup.WorkerGroupFactory; import com.tesora.dve.worker.WorkerManager; import com.tesora.dve.worker.agent.Agent; public class BootstrapHost extends Host implements BootstrapHostMBean, BootstrapHostService { private static final String MBEAN_BOOTSTRAP_HOST = "com.tesora.dve:name=Server"; private static final String MBEAN_LOG4J_HIERARCHY = "log4j:hierarchy=default"; static { logger = Logger.getLogger(BootstrapHost.class); } WorkerManager workerManager; BroadcastMessageAgent broadcastMessageAgent; Transaction2PCTracker transactionManager; StatisticsManager statisticsManager; NotificationManager notificationManager; int statisticsPersisterInterval; TableGarbageCollector tgc; int tableCleanupInterval; public BootstrapHost(String name, Properties props) { super(name, props); Singletons.replace(BootstrapHostService.class,this); try { workerManager = new WorkerManager(); setWorkerManagerAddress(workerManager.getAddress()); notificationManager = new NotificationManager(); setNotificationManagerAddress(notificationManager.getAddress()); statisticsManager = new StatisticsManager(); setStatisticsManagerAddress(statisticsManager.getAddress()); broadcastMessageAgent = new BroadcastMessageAgent(); setBroadcastMessageAgentAddress(broadcastMessageAgent.getAddress()); WorkerGroupFactory.startup(workerManager); initializeCatalogServices(); if (GroupManager.getCoordinationServices().localMemberIsOldestMember()) doGlobalRecovery(); registerMBeans(); } catch (PEException e) { throw new RuntimeException("Failed to start DVE server - " + e.rootCause().getMessage(), e); } catch (Throwable e) { throw new RuntimeException("Failed to start DVE server - " + e.getMessage(), e); } } public WorkerManager getWorkerManager() { return workerManager; } private void doGlobalRecovery() throws PEException { User rootUser = getProject().getRootUser(); SSConnectionProxy ssConProxy = new SSConnectionProxy(); try { ssConProxy.executeRequest(new ConnectRequest(rootUser.getName(), rootUser.getPlaintextPassword())); ssConProxy.executeRequest(new GlobalRecoveryRequest()); } finally { ssConProxy.close(); } } @Override protected void registerMBeans() { MBeanServer server = ManagementFactory.getPlatformMBeanServer(); try { server.registerMBean(this, new ObjectName(MBEAN_BOOTSTRAP_HOST)); } catch(Exception e) { logger.error("Unable to register " + MBEAN_BOOTSTRAP_HOST + " mbean" , e); } try { HierarchyDynamicMBean hdm = new HierarchyDynamicMBean(); server.registerMBean(hdm, new ObjectName(MBEAN_LOG4J_HIERARCHY)); // Add the root logger to the Hierarchy MBean hdm.addLoggerMBean(Logger.getRootLogger().getName()); LoggerRepository r = LogManager.getLoggerRepository(); @SuppressWarnings("rawtypes") Enumeration loggers = r.getCurrentLoggers(); while ( loggers.hasMoreElements() ) { hdm.addLoggerMBean(((Logger)loggers.nextElement()).getName()); } } catch(Exception e) { logger.error("Unable to register " + MBEAN_LOG4J_HIERARCHY + " mbean" , e); } super.registerMBeans(); } @Override protected void unregisterMBeans() { MBeanServer server = ManagementFactory.getPlatformMBeanServer(); try { server.unregisterMBean(new ObjectName(MBEAN_BOOTSTRAP_HOST)); } catch (Exception e) { logger.error("Unable to unregister " + MBEAN_BOOTSTRAP_HOST + " mBean", e); } try { server.unregisterMBean(new ObjectName(MBEAN_LOG4J_HIERARCHY)); } catch (Exception e) { logger.error("Unable to unregister " + MBEAN_LOG4J_HIERARCHY + " mBean", e); } super.unregisterMBeans(); } public static <T> BootstrapHost startServices(Class<T> bootstrapClass) throws Exception { return startServices(bootstrapClass, PEFileUtils.loadPropertiesFile(bootstrapClass, PEConstants.CONFIG_FILE_NAME)); } public static <T> BootstrapHost startServices(Class<T> bootstrapClass, Properties props) throws Exception { String url = props.getProperty(DBHelper.CONN_URL); String database = props.getProperty(DBHelper.CONN_DBNAME, PEConstants.CATALOG); // Fail to start if we don't have a valid catalog connection URL if(StringUtils.isBlank(url)) throw new PEException("Value for " + DBHelper.CONN_URL + " not specified in server configuration"); if(StringUtils.isBlank(database)) throw new PEException("Value for " + DBHelper.CONN_DBNAME + " not specified in server configuration"); // Attempt to load the JDBC driver - fail early if it isn't available DBHelper.loadDriver(props.getProperty(DBHelper.CONN_DRIVER_CLASS)); props.put(DBHelper.CONN_URL, CatalogURL.buildCatalogBaseUrlFrom(props.getProperty(DBHelper.CONN_URL)).toString()); props.put(DBHelper.CONN_DBNAME, database); DBHelper helper = new DBHelper(props); int catalogAccessible = 1; try { // Check that we can connect to the database and that the catalog database exists helper.connect(); // and also check that it is a valid catalog helper.executeQuery("SELECT name FROM " + database + ".user_table"); } catch(Exception e) { String message = e.getMessage(); if (message != null && message.startsWith("Error using")) catalogAccessible = -1; else catalogAccessible = 0; if (catalogAccessible == 0) throw new Exception("A DVE catalog couldn't be found at '" + url + "' with name '" + database + "' - use dve_config to set the connection credentials for the server (error was " + message + ")"); } finally { helper.disconnect(); } if (catalogAccessible == -1) { new CatalogHelper(bootstrapClass).createBootstrapCatalog(); } // Temporarily set the log level to info so that the below message will print and force the // header to print Level logLevel = logger.getLevel(); // this is needed in case an explicit level is set on this category Level effectiveLevel = logger.getEffectiveLevel(); // if an explicit level isn't set, this gets the level from the hierarchy logger.setLevel(Level.INFO); if (catalogAccessible == -1) { logger.info("INSTALLED BOOTSTRAP CATALOG " + database + " AT " + url); } logger.info("Starting DVE server using:"); logger.info("... Catalog URL : " + url); logger.info("... Catalog database : " + database); logger.info("... Catalog User : " + props.getProperty(DBHelper.CONN_USER)); logger.info("Resetting log level back to: " + effectiveLevel); logger.setLevel(logLevel); // We have to initialize the Group Manager before anything else. GroupManager.initialize(props); GroupManager.getCoordinationServices().registerWithGroup(props); CatalogDAOFactory.setup(props); SchemaSourceFactory.reset(); ErrorMapper.initialize(); Agent.setPluginProvider(SimpleMQPlugin.PROVIDER); Agent.startServices(props); BootstrapHost host = new BootstrapHost(bootstrapClass.getName(), props); ClusterLock genLock = GroupManager.getCoordinationServices().getClusterLock(RangeDistributionModel.GENERATION_LOCKNAME); String reason = "boostrapping system"; genLock.sharedLock(null, reason); genLock.sharedUnlock(null,reason); TemporaryTable.onStartServices(); MySqlPortal.start(props); return host; } public static void stopServices() throws Exception { MySqlPortal.stop(); TemporaryTable.onStopServices(); if (GroupManager.getCoordinationServices() != null) GroupManager.getCoordinationServices().unRegisterWithGroup(); Host.hostStop(); Agent.stopServices(); CatalogDAOFactory.shutdown(); AutoIncrementTracker.clearCache(); RangeDistributionModel.clearCache(); RandomDistributionModel.clearCache(); SiteProviderFactory.closeSiteProviders(); ExternalServiceFactory.closeExternalServices(); GroupManager.shutdown(); DirectConnectionCache.clearConnectionCache(); } @Override protected void stop() { stopTableCleanup(); super.stop(); } @Override protected void close() { unregisterMBeans(); super.close(); // No shutdown methods should throw exceptions as we could do nothing but print it notificationManager.shutdown(); WorkerGroupFactory.shutdown(workerManager); workerManager.shutdown(); statisticsManager.shutdown(); broadcastMessageAgent.shutdown(); } private void initializeCatalogServices() throws Exception { CatalogDAO c = CatalogDAOFactory.newInstance(); try { c.recoverTransactions(); // Create and configure all Dynamic Site Providers for (Provider provider : c.findAllProviders()) { SiteProviderContext ctxt = new SiteProviderContextInitialisation(provider.getName(), c); SiteProviderFactory.addProvider(ctxt, provider.getPlugin(), provider.getName(), provider.isEnabled(), provider.getConfig()); } // Create and configure External Services for (ExternalService es : c.findAllExternalServices()) { // enclose in try/catch in case an early service fails we still // start the later ones try { ExternalServiceFactory.register(es.getName(), es.getPlugin()); } catch (Exception e) { e.printStackTrace(); } } // GlobalConfigVariableHandler.initializeGlobalVariables(getGlobalVariables(c)); } finally { c.close(); } } // ------------------------------------------------------------------------- public void startTableCleanup() { if (tableCleanupInterval > 0) { if (tgc != null) tgc.stopTableCleanup(); tgc = new TableGarbageCollector(tableCleanupInterval); tgc.startTableCleanup(); } } private void stopTableCleanup() { if (tgc != null) tgc.stopTableCleanup(); } public void setTableCleanupInterval(int value, boolean action) { int oldValue = tableCleanupInterval; tableCleanupInterval = value; if (action) { if (oldValue == 0) startTableCleanup(); else if (tableCleanupInterval == 0) stopTableCleanup(); } } // ------------------------------------------------------------------------- // ------------------------------------------------------------------------- // The following methods are for the BootstrapHost MBean @Override public int getPortalWorkerGroupCount() { MySqlPortalService portal = Singletons.lookup(MySqlPortalService.class); return portal == null ? 0 : portal.getWorkerGroupCount(); } @Override public int getPortalClientExecutorActiveCount() { MySqlPortalService portal = Singletons.lookup(MySqlPortalService.class); return portal == null ? 0 : portal.getClientExecutorActiveCount(); } @Override public int getPortalClientExecutorPoolSize() { MySqlPortalService portal = Singletons.lookup(MySqlPortalService.class); return portal == null ? 0 : portal.getClientExecutorPoolSize(); } @Override public int getPortalClientExecutorLargestPoolSize() { MySqlPortalService portal = Singletons.lookup(MySqlPortalService.class); return portal == null ? 0 : portal.getClientExecutorLargestPoolSize(); } @Override public int getPortalClientExecutorMaximumPoolSize() { MySqlPortalService portal = Singletons.lookup(MySqlPortalService.class); return portal == null ? 0 : portal.getClientExecutorMaximumPoolSize(); } @Override public int getPortalClientExecutorQueueSize() { MySqlPortalService portal = Singletons.lookup(MySqlPortalService.class); return portal == null ? 0 : portal.getClientExecutorQueueSize(); } @Override public void onGarbageEvent() { if (tgc != null) tgc.onGarbageEvent(); } }