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.net.InetAddress;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import javax.management.DynamicMBean;
import javax.management.ObjectName;
import com.tesora.dve.charset.*;
import com.tesora.dve.clock.*;
import com.tesora.dve.membership.GroupTopicPublisher;
import com.tesora.dve.server.connectionmanager.SSConnectionProxy;
import com.tesora.dve.server.global.BootstrapHostService;
import com.tesora.dve.server.global.HostService;
import com.tesora.dve.server.global.StatusVariableService;
import com.tesora.dve.variables.*;
import com.tesora.dve.singleton.Singletons;
import com.tesora.dve.sql.infoschema.InformationSchemaService;
import org.apache.log4j.Logger;
import com.fasterxml.uuid.EthernetAddress;
import com.fasterxml.uuid.Generators;
import com.fasterxml.uuid.impl.TimeBasedGenerator;
import com.tesora.dve.common.DBHelper;
import com.tesora.dve.common.DBType;
import com.tesora.dve.common.PEConstants;
import com.tesora.dve.common.PELogUtils;
import com.tesora.dve.common.catalog.AutoIncrementTrackerDynamicMBean;
import com.tesora.dve.common.catalog.CatalogDAO;
import com.tesora.dve.common.catalog.Project;
import com.tesora.dve.common.catalog.User;
import com.tesora.dve.common.catalog.CatalogDAO.CatalogDAOFactory;
import com.tesora.dve.comms.client.messages.ConnectRequest;
import com.tesora.dve.concurrent.NamedTaskExecutorService;
import com.tesora.dve.concurrent.PEThreadPoolExecutor;
import com.tesora.dve.db.DBNative;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.groupmanager.GroupMessageEndpoint;
import com.tesora.dve.sql.infoschema.InformationSchemas;
import com.tesora.dve.sql.util.Pair;
import com.tesora.dve.upgrade.CatalogVersions;
import com.tesora.dve.variable.status.StatusVariableHandler;
import com.tesora.dve.variable.status.StatusVariableHandlerDynamicMBean;
import com.tesora.dve.variable.status.StatusVariables;
public class Host implements HostService, StatusVariableService, RootProxyService, VariableService {
protected static Logger logger = Logger.getLogger(Host.class);
static {
//this is done statically, to install it if someone just touches the Host class, even indirectly.
BootstrapWiring.rewire();
}
private static final String MBEAN_GLOBAL_CONFIG = "com.tesora.dve:name=ConfigVariables";
private static final String MBEAN_GLOBAL_STATUS = "com.tesora.dve:name=StatusVariables";
private static final String MBEAN_AUTO_INCR = "com.tesora.dve:name=AutoIncrementTracker";
private static final String TIMEOUT_KEY = "worker.timeout";
private static final String TIMEOUT_DEFAULT = "10";
private String versionComment = null;
static Host singleton = null;
private NamedTaskExecutorService executorService;
private int dbConnectionTimeout = -1;
private String name;
private String hostName;
private String workerManagerAddress;
private String statisticsManagerAddress;
private String transactionManagerAddress;
private String broadcastMessageAgentAddress;
private String notificationManagerAddress;
protected static void hostStop() {
if(singleton != null) {
singleton.stop();
singleton.close();
}
}
@Override
public String getBroadcastMessageAgentAddress() {
return broadcastMessageAgentAddress;
}
@Override
public void setBroadcastMessageAgentAddress(String broadcastMessageAgentAddress) {
this.broadcastMessageAgentAddress = broadcastMessageAgentAddress;
}
@Override
public String getNotificationManagerAddress() {
return notificationManagerAddress;
}
@Override
public void setNotificationManagerAddress(String notificationManagerAddress) {
this.notificationManagerAddress = notificationManagerAddress;
}
@Override
public String getStatisticsManagerAddress() {
return statisticsManagerAddress;
}
@Override
public void setStatisticsManagerAddress(String statisticsManagerAddress) {
this.statisticsManagerAddress = statisticsManagerAddress;
}
@Override
public String getWorkerManagerAddress() {
return workerManagerAddress;
}
protected void setWorkerManagerAddress(String addr) {
workerManagerAddress = addr;
}
private Long startTime;
private Project project;
private Properties props;
private final VariableManager variables;
private VariableHandlerDynamicMBean globalConfigMBean = new VariableHandlerDynamicMBean();
private Map<String /* providerName */, ScopedVariables> scopedVariables = new HashMap<String, ScopedVariables>();
private StatusVariableHandlerDynamicMBean globalStatusMBean = new StatusVariableHandlerDynamicMBean();
private DynamicMBean autoIncrementTrackerMBean = new AutoIncrementTrackerDynamicMBean();
private InformationSchemas infoSchema;
private GroupMessageEndpoint broadcaster;
private TimeBasedGenerator uuidGenerator = null;
private String dveVersion = null;
public Host(String name, Properties props) {
this.name = name;
this.props = props;
singleton = this;
registerTimingService();
Singletons.replace(HostService.class, singleton);
Singletons.replace(StatusVariableService.class,singleton);
Singletons.replace(RootProxyService.class,singleton);
Singletons.replace(VariableService.class,singleton);
if (this instanceof BootstrapHost){
//dirty, but works around issue where initializers were accessing Bootstrap singleton before this constructor exited and bootstrap constructor had time to register sub-interface. -sgossard
Singletons.replace(BootstrapHostService.class, (BootstrapHost)singleton);
}
this.executorService = new PEThreadPoolExecutor("Host");
try {
this.variables = VariableManager.getManager();
ServerGlobalVariableStore.INSTANCE.reset();
hostName = InetAddress.getLocalHost().getHostName();
if (name == null) {
name = hostName;
}
CatalogVersions.catalogVersionCheck(props);
DBType dbType = DBType.fromDriverClass(props.getProperty(DBHelper.CONN_DRIVER_CLASS));
DBNative dbNative = DBNative.DBNativeFactory.newInstance(dbType);
Singletons.replace(DBNative.class,dbNative);
Singletons.replace(NativeCharSetCatalog.class,dbNative.getSupportedCharSets());
Singletons.replace(NativeCollationCatalog.class,dbNative.getSupportedCollations());
boolean startCatalog = true;
Singletons.replace(CharSetNative.class, initializeCharsetNative(dbType, startCatalog));//TODO: the only purpose of CharSetNative appears to be holding an alternate NativeCharSetCatalog? -sgossard.
// we have to set this up early for the variables
broadcaster = new GroupMessageEndpoint("broadcast");
Singletons.replace(GroupTopicPublisher.class, broadcaster);
CatalogDAO c = CatalogDAOFactory.newInstance();
try {
project = c.findProject(props.getProperty("defaultproject", Project.DEFAULT));
// bootstrap host has already ensured we are part of a cluster if need be, therefore it
// is safe to initialize variables here.
variables.initialize(c);
variables.initializeDynamicMBeanHandlers(globalConfigMBean);
infoSchema = InformationSchemas.build(dbNative, c, props);
Singletons.replace(InformationSchemaService.class,infoSchema);
} finally {
c.close();
}
startTime = System.currentTimeMillis();
} catch (Throwable e) {
throw new RuntimeException("Failed to start DVE server - " + e.getMessage(), e);
}
}
private static CharSetNative initializeCharsetNative(DBType dbType, boolean startCatalog) throws PEException {
CharSetNative charSetNative = null;
NativeCharSetCatalog catalog;
switch (dbType) {
case MYSQL:
case MARIADB:
if (startCatalog) {
catalog = new NativeCharSetCatalogImpl();
if ( !CatalogDAOFactory.isSetup() )
throw new PEException("Cannot load character sets from the catalog as the catalog is not setup.");
CatalogDAO catalog1 = CatalogDAOFactory.newInstance();
try {
DBNative.loadFromCatalog(catalog1, catalog);
} finally {
catalog1.close();
}
} else {
catalog = MysqlNativeCharSetCatalog.DEFAULT_CATALOG;
//don't load, it's already populated correctly.
}
break;
default:
throw new PEException("Attempt to create new character set native instance using invalid database type " + dbType);
}
charSetNative = new CharSetNativeImpl(catalog);
return charSetNative;
}
/**
* This constructor is to facilitate the TestHost which is used only
* for testing
*
* @param props - properties file containing the javax.persistence.jdbc.driver
* property
* @param startCatalog - should the CatalogDAOFactory be called to be available to
* connect to the catalog. Note: if true, the props must contain
* the relevant parameters to connect to it.
*/
public Host(Properties props, boolean startCatalog) {
this.props = props;
registerTimingService();
this.executorService = new PEThreadPoolExecutor("Host");
try {
variables = VariableManager.getManager();
ServerGlobalVariableStore.INSTANCE.reset();
hostName = InetAddress.getLocalHost().getHostName();
if ( startCatalog )
CatalogDAOFactory.setup(props);
String driver = DBHelper.loadDriver(props.getProperty(DBHelper.CONN_DRIVER_CLASS));
DBType dbType = DBType.fromDriverClass(driver);
DBNative dbNative = DBNative.DBNativeFactory.newInstance(dbType);
Singletons.replace(DBNative.class,dbNative);
Singletons.replace(NativeCharSetCatalog.class,dbNative.getSupportedCharSets());
Singletons.replace(NativeCollationCatalog.class,dbNative.getSupportedCollations());
Singletons.replace(CharSetNative.class, initializeCharsetNative(dbType, startCatalog));//TODO: the only purpose of CharSetNative appears to be holding an alternate NativeCharSetCatalog? -sgossard.
infoSchema = InformationSchemas.build(dbNative,null,props);
Singletons.replace(InformationSchemaService.class, infoSchema);
if (startCatalog)
variables.initialize(null);
} catch (Exception e) {
throw new RuntimeException("Failed to start DVE server - " + e.getMessage(), e);
}
singleton = this;
Singletons.replace(HostService.class, singleton);
Singletons.replace(StatusVariableService.class,singleton);
Singletons.replace(RootProxyService.class,singleton);
Singletons.replace(VariableService.class,singleton);
}
public void registerTimingService() {
Path baseLogDir = null;
try {
baseLogDir = Paths.get( System.getProperty("tesora.dve.log") );
} catch (Exception e){
logger.warn("Problem getting log dir from the 'tesora.dve.log' system property, logging may not work properly.");
}
SwitchingTimingService concreteImpl = new SwitchingTimingService(baseLogDir);
Singletons.replace(TimingService.class, concreteImpl);
Singletons.replace(TimingServiceConfiguration.class, concreteImpl);
}
protected void stop() {
// do nothing?
}
protected void close() {
}
protected void registerMBeans() {
try {
ManagementFactory.getPlatformMBeanServer().registerMBean(globalConfigMBean, new ObjectName(MBEAN_GLOBAL_CONFIG));
} catch (Throwable e) {
logger.error("Unable to register " + MBEAN_GLOBAL_CONFIG + " mbean" , e);
}
try {
ManagementFactory.getPlatformMBeanServer().registerMBean(globalStatusMBean, new ObjectName(MBEAN_GLOBAL_STATUS));
} catch (Throwable e) {
logger.error("Unable to register " + MBEAN_GLOBAL_STATUS + " mbean" , e);
}
try {
ManagementFactory.getPlatformMBeanServer().registerMBean(autoIncrementTrackerMBean, new ObjectName(MBEAN_AUTO_INCR));
} catch (Throwable e) {
logger.error("Unable to register " + MBEAN_AUTO_INCR + " mbean" , e);
}
}
protected void unregisterMBeans() {
try {
ManagementFactory.getPlatformMBeanServer().unregisterMBean(new ObjectName(MBEAN_GLOBAL_STATUS));
} catch (Exception e) {
logger.error("Unable to unregister " + MBEAN_GLOBAL_STATUS + " mBean", e);
}
try {
ManagementFactory.getPlatformMBeanServer().unregisterMBean(new ObjectName(MBEAN_GLOBAL_CONFIG));
} catch (Exception e) {
logger.error("Unable to unregister " + MBEAN_GLOBAL_CONFIG + " mBean", e);
}
try {
ManagementFactory.getPlatformMBeanServer().unregisterMBean(new ObjectName(MBEAN_AUTO_INCR));
} catch (Exception e) {
logger.error("Unable to unregister " + MBEAN_AUTO_INCR + " mBean", e);
}
}
@Override
public Properties getProperties() {
return props;
}
@Override
public String getName() {
return name;
}
@Override
public String getHostName() {
return hostName;
}
protected Project getProject() {
return project;
}
@Override
public String getDefaultProjectName() {
return props.getProperty("defaultproject", Project.DEFAULT);
}
@Override
public List<Pair<String,String>> getStatusVariables() throws PEException {
List<Pair<String,String>> out = new ArrayList<Pair<String,String>>();
for(StatusVariableHandler svh : StatusVariables.getStatusVariables()) {
out.add(new Pair<String,String>(svh.getName(),svh.getValue()));
}
return out;
}
@Override
public String getStatusVariable(String variableName) throws PEException {
return StatusVariables.lookup(variableName, true).getValue();
}
@Override
public String getServerVersion() {
// TODO turns out the Drupal installer needs this to be a MySQL version
return PEConstants.DVE_SERVER_VERSION;
}
@Override
public String getServerVersionComment() {
if(versionComment == null) {
versionComment = PEConstants.DVE_SERVER_VERSION_COMMENT + ", " +
PELogUtils.getBuildVersionString(false) + " (" + getHostName() + ")" ;
}
return versionComment;
}
@Override
public String getDveServerVersion() {
return getServerVersion();
}
@Override
public String getDveServerVersionComment() {
return getServerVersionComment();
}
@Override
public int getPortalPort(final Properties props) {
return Integer.parseInt(props.getProperty(
PEConstants.MYSQL_PORTAL_PORT_PROPERTY,
PEConstants.MYSQL_PORTAL_DEFAULT_PORT));
}
public ScopedVariables getScopedConfig(String providerName) throws PEException {
if (scopedVariables.containsKey(providerName))
return scopedVariables.get(providerName);
throw new PEException("No variable configuration defined for provider " + providerName);
}
@Override
public Collection<String> getScopedVariableScopeNames() {
return scopedVariables.keySet();
}
@Override
public Map<String, String> getScopedVariables(String scopeName) throws PEException {
HashMap<String, String> out = new HashMap<String,String>();
for(ScopedVariableHandler svh : getScopedConfig(scopeName).getHandlers()) {
out.put(svh.getName(),svh.getValue());
}
return out;
}
@Override
public void setScopedVariable(String scopeName, String variableName, String value) throws PEException {
getScopedConfig(scopeName).getScopedVariableHandler(variableName).setValue(value);
}
@Override
public void addScopedConfig(String scopeName, ScopedVariables variableConfig) {
if (variableConfig == null) return;
scopedVariables.put(scopeName,variableConfig);
}
@Override
public long getDBConnectionTimeout() {
if (dbConnectionTimeout < 0) {
dbConnectionTimeout = Integer.parseInt(props.getProperty(TIMEOUT_KEY, TIMEOUT_DEFAULT));
if (dbConnectionTimeout < 1000)
dbConnectionTimeout *= 1000;
}
return dbConnectionTimeout;
}
@Override
public TimeBasedGenerator getUuidGenerator() {
if (uuidGenerator == null) {
EthernetAddress nic = EthernetAddress.fromInterface();
if (nic == null) {
// construct address with a random number
nic = EthernetAddress.constructMulticastAddress();
}
uuidGenerator = Generators.timeBasedGenerator(nic);
}
return uuidGenerator;
}
@Override
public long getServerUptime() {
return ( System.currentTimeMillis() - startTime ) /1000;
}
@Override
public void resetServerUptime() {
startTime = System.currentTimeMillis();
}
@Override
public <T> Future<T> submit(Callable<T> task) {
return executorService.submit(task);
}
@Override
public <T> Future<T> submit(String name, Callable<T> task) {
return executorService.submit(name, task);
}
@Override
public void execute(Runnable task) {
executorService.execute(task);
}
@Override
public void execute(String name, Runnable task) {
executorService.execute(name, task);
}
@Override
public void onGarbageEvent() {
// does nothing by default
}
@Override
public VariableManager getVariableManager() {
return variables;
}
@Override
public SSConnectionProxy getRootProxy() throws PEException {
User rootUser = getProject().getRootUser();
SSConnectionProxy ssConProxy = new SSConnectionProxy();
try {
ssConProxy.executeRequest(new ConnectRequest(rootUser.getName(), rootUser.getPlaintextPassword()));
} catch (PEException pe) {
ssConProxy.close();
return null;
}
return ssConProxy;
}
}