/*
* Copyright (c) 2008-2013 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.db.server;
import static org.junit.Assert.fail;
import java.beans.Introspector;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.URI;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import org.apache.cassandra.config.Schema;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.apache.cassandra.service.StorageService;
import org.apache.cassandra.service.StorageServiceMBean;
import com.emc.storageos.coordinator.client.model.Constants;
import com.emc.storageos.coordinator.client.model.DbVersionInfo;
import com.emc.storageos.coordinator.client.service.CoordinatorClient;
import com.emc.storageos.coordinator.client.service.DrUtil;
import com.emc.storageos.coordinator.client.service.impl.CoordinatorClientInetAddressMap;
import com.emc.storageos.coordinator.common.impl.ConfigurationImpl;
import com.emc.storageos.coordinator.common.impl.ServiceImpl;
import com.emc.storageos.db.client.DbClient;
import com.emc.storageos.db.client.impl.DbClientContext;
import com.emc.storageos.db.client.impl.DbClientImpl;
import com.emc.storageos.db.client.impl.EncryptionProviderImpl;
import com.emc.storageos.db.client.impl.TypeMap;
import com.emc.storageos.db.client.model.DataObject;
import com.emc.storageos.db.client.model.Volume;
import com.emc.storageos.db.client.upgrade.BaseCustomMigrationCallback;
import com.emc.storageos.db.client.upgrade.InternalDbClient;
import com.emc.storageos.db.common.DataObjectScanner;
import com.emc.storageos.db.common.DbServiceStatusChecker;
import com.emc.storageos.db.common.DependencyChecker;
import com.emc.storageos.db.common.VdcUtil;
import com.emc.storageos.db.server.impl.DbManager;
import com.emc.storageos.db.server.impl.DbServiceImpl;
import com.emc.storageos.db.server.impl.MigrationHandlerImpl;
import com.emc.storageos.db.server.impl.SchemaUtil;
import com.emc.storageos.db.server.upgrade.MockMigrationHandler;
import com.emc.storageos.db.server.util.StubBeaconImpl;
import com.emc.storageos.db.server.util.StubCoordinatorClientImpl;
import com.emc.storageos.security.geo.GeoDependencyChecker;
import com.emc.storageos.security.password.PasswordUtils;
import com.emc.storageos.services.util.JmxServerWrapper;
import com.emc.storageos.services.util.LoggingUtils;
/**
* Dbsvc unit test base
*/
// Suppress Sonar violation of Lazy initialization of static fields should be synchronized
// Junit test will be called in single thread by default, it's safe to ignore this violation
@SuppressWarnings("squid:S2444")
public class DbsvcTestBase {
static {
LoggingUtils.configureIfNecessary("dbtest-log4j.properties");
}
private static final Logger _log = LoggerFactory.getLogger(DbsvcTestBase.class);
protected static TestMockDbServiceImpl _dbsvc;
protected static ServiceImpl service;
protected static DbClientImpl _dbClient;
protected static boolean isDbStarted = false;
protected static DbVersionInfo sourceVersion;
protected static File _dataDir;
protected static CoordinatorClient _coordinator = new StubCoordinatorClientImpl(
URI.create("thrift://localhost:9160"));
protected static EncryptionProviderImpl _encryptionProvider = new EncryptionProviderImpl();
protected static Map<String, List<BaseCustomMigrationCallback>> customMigrationCallbacks = new HashMap<>();
protected static DbServiceStatusChecker statusChecker = null;
protected static GeoDependencyChecker _geoDependencyChecker;
protected static SchemaUtil schemaUtil;
protected static final String dataDir="./dbtest";
// This controls whether the JMX server is started with DBSVC or not. JMX server is used to snapshot Cassandra
// DB files and dump SSTables to JSON files. However, current JmxServerWrapper.start() implementation blindly
// calls LocateRegistry.createRegistry() to start a new RMI Registry server on port e.g. 7199. In test cases
// like DbUpgradeTest which stops the DBSVC then start it again in same OS process, the previous RMI Registry
// server is still alive and listening on the port, so the 2nd call to LocateRegistry.createRegistry() will
// throw an exception. There's no standard way to shutdown a RMI Registry server.
protected static boolean _startJmx;
private static final String args[] = {
"dbversion-info.xml",
};
/**
* Deletes given directory
*
* @param dir
*/
protected static void cleanDirectory(File dir) {
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
cleanDirectory(file);
} else {
file.delete();
}
}
dir.delete();
}
@BeforeClass
public static void setup() throws IOException {
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("dbversion-info.xml");
sourceVersion = (DbVersionInfo)ctx.getBean("dbVersionInfo");
_dataDir = new File(dataDir);
if (_dataDir.exists() && _dataDir.isDirectory()) {
cleanDirectory(_dataDir);
}
_dataDir.mkdir();
startDb(sourceVersion.getSchemaVersion(), sourceVersion.getSchemaVersion(), null);
}
@AfterClass
public static void stop() {
if (isDbStarted) {
stopAll();
}
if (_dataDir != null) {
cleanDirectory(_dataDir);
_dataDir = null;
}
_log.info("The Dbsvc is stopped");
}
protected static void stopAll() {
TypeMap.clear();
Introspector.flushCaches();
isDbStarted = false;
_dbsvc.stop();
StorageServiceMBean svc = StorageService.instance;
if (svc.isInitialized()) {
svc.stopGossiping();
}
if (svc.isRPCServerRunning()) {
svc.stopRPCServer();
}
Schema.instance.clear();
_log.info("The dbsvc is stopped");
}
/**
* Start embedded DB
*/
protected static void startDb(String currentVersion, String targetVersion, String extraModelsPkg) throws IOException {
startDb(currentVersion, targetVersion, extraModelsPkg, null, false);
}
/**
* Start embedded DB
*/
protected static void startDb(String currentVersion, String targetVersion, String extraModelsPkg, DataObjectScanner scanner,
boolean createMockHandler) throws IOException {
sourceVersion = new DbVersionInfo();
sourceVersion.setSchemaVersion(currentVersion);
DbVersionInfo targetVersionInfo = new DbVersionInfo();
targetVersionInfo.setSchemaVersion(targetVersion);
List<String> packages = new ArrayList<String>();
packages.add("com.emc.storageos.db.client.model");
packages.add("com.emc.sa.model");
if (extraModelsPkg != null) {
packages.add(extraModelsPkg);
}
String[] pkgsArray = packages.toArray(new String[packages.size()]);
service = new ServiceImpl();
service.setName("dbsvc");
service.setVersion(targetVersion);
service.setEndpoint(URI.create("thrift://localhost:9160"));
service.setId("db-standalone");
StubBeaconImpl beacon = new StubBeaconImpl(service);
if (scanner == null) {
scanner = new DataObjectScanner();
scanner.setPackages(pkgsArray);
scanner.init();
}
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("nodeaddrmap-var.xml");
CoordinatorClientInetAddressMap inetAddressMap = (CoordinatorClientInetAddressMap) ctx.getBean("inetAddessLookupMap");
if (inetAddressMap == null) {
_log.error("CoordinatorClientInetAddressMap is not initialized. Node address lookup will fail.");
}
_coordinator.setInetAddessLookupMap(inetAddressMap);
_coordinator.setDbVersionInfo(sourceVersion);
ConfigurationImpl cfg = new ConfigurationImpl();
cfg.setKind(Constants.DB_CONFIG);
cfg.setId(Constants.GLOBAL_ID);
cfg.setConfig(Constants.SCHEMA_VERSION, currentVersion);
_coordinator.persistServiceConfiguration(cfg);
statusChecker = new DbServiceStatusChecker();
statusChecker.setCoordinator(_coordinator);
statusChecker.setClusterNodeCount(1);
statusChecker.setDbVersionInfo(sourceVersion);
statusChecker.setServiceName(service.getName());
SecretKey key = null;
try {
KeyGenerator keyGenerator = null;
keyGenerator = KeyGenerator.getInstance("HmacSHA256");
key = keyGenerator.generateKey();
} catch (NoSuchAlgorithmException e) {
fail("generate key fail");
}
schemaUtil = new MockSchemaUtil();
schemaUtil.setKeyspaceName("Test");
schemaUtil.setClusterName("Test");
schemaUtil.setDataObjectScanner(scanner);
schemaUtil.setService(service);
schemaUtil.setStatusChecker(statusChecker);
schemaUtil.setCoordinator(_coordinator);
schemaUtil.setVdcShortId("datacenter1");
schemaUtil.setDrUtil(new DrUtil(_coordinator));
DbClientContext dbctx = new MockDbClientContext();
dbctx.setClusterName("Test");
dbctx.setKeyspaceName("Test");
schemaUtil.setClientContext(dbctx);
Properties props = new Properties();
props.put(DbClientImpl.DB_STAT_OPTIMIZE_DISK_SPACE, "false");
schemaUtil.setDbCommonInfo(props);
List<String> vdcHosts = new ArrayList();
vdcHosts.add("127.0.0.1");
schemaUtil.setVdcNodeList(vdcHosts);
schemaUtil.setDbCommonInfo(new java.util.Properties());
JmxServerWrapper jmx = new JmxServerWrapper();
if (_startJmx) {
jmx.setEnabled(true);
jmx.setServiceUrl("service:jmx:rmi://localhost:%d/jndi/rmi://%s:%d/jmxrmi");
jmx.setHost("127.0.0.1");
jmx.setPort(7199);
jmx.setExportPort(7200);
} else {
jmx.setEnabled(false);
}
_encryptionProvider.setCoordinator(_coordinator);
_dbClient = getDbClientBase();
_dbClient.setDbVersionInfo(targetVersionInfo);
PasswordUtils passwordUtils = new PasswordUtils();
passwordUtils.setCoordinator(_coordinator);
passwordUtils.setEncryptionProvider(_encryptionProvider);
passwordUtils.setDbClient(_dbClient);
schemaUtil.setPasswordUtils(passwordUtils);
DependencyChecker localDependencyChecker = new DependencyChecker(_dbClient, scanner);
_geoDependencyChecker = new GeoDependencyChecker(_dbClient, _coordinator, localDependencyChecker);
_dbsvc = new TestMockDbServiceImpl();
_dbsvc.setConfig("db-test.yaml");
_dbsvc.setSchemaUtil(schemaUtil);
_dbsvc.setCoordinator(_coordinator);
_dbsvc.setStatusChecker(statusChecker);
_dbsvc.setService(service);
_dbsvc.setJmxServerWrapper(jmx);
_dbsvc.setDbClient(_dbClient);
_dbsvc.setBeacon(beacon);
_dbsvc.setDbDir(dataDir);
_dbsvc.setDisableScheduledDbRepair(true);
_dbsvc.setMigrationHandler(getMigrationHandler(createMockHandler, pkgsArray));
_dbsvc.setDbMgr(new MockDbManager());
_dbsvc.start();
isDbStarted = true;
}
private static MigrationHandlerImpl getMigrationHandler(boolean createMockHandler, String[] pkgsArray) {
MigrationHandlerImpl handler = null;
if (createMockHandler) {
handler = new MockMigrationHandler();
} else {
handler = new MigrationHandlerImpl();
}
handler.setPackages(pkgsArray);
handler.setService(service);
handler.setStatusChecker(statusChecker);
handler.setCoordinator(_coordinator);
handler.setDbClient(_dbClient);
handler.setSchemaUtil(schemaUtil);
handler.setPackages(pkgsArray);
handler.setCustomMigrationCallbacks(customMigrationCallbacks);
return handler;
}
/**
* Create DbClient to embedded DB
*
* @return
*/
protected static DbClient getDbClient() {
return getDbClient(new InternalDbClient());
}
protected static DbClient getDbClient(InternalDbClient dbClient) {
dbClient = getDbClientBase(dbClient);
dbClient.setBypassMigrationLock(false);
dbClient.start();
return dbClient;
}
protected static DbClientImpl getDbClientBase() {
return getDbClientBase(new InternalDbClient());
}
protected static InternalDbClient getDbClientBase(InternalDbClient dbClient) {
dbClient.setCoordinatorClient(_coordinator);
dbClient.setDbVersionInfo(sourceVersion);
dbClient.setBypassMigrationLock(true);
_encryptionProvider.setCoordinator(_coordinator);
dbClient.setEncryptionProvider(_encryptionProvider);
DbClientContext localCtx = new MockDbClientContext();
localCtx.setClusterName("Test");
localCtx.setKeyspaceName("Test");
dbClient.setLocalContext(localCtx);
dbClient.setDrUtil(new DrUtil(_coordinator));
VdcUtil.setDbClient(dbClient);
return dbClient;
}
protected static CoordinatorClient getCoordinator() {
return _coordinator;
}
static class MockSchemaUtil extends SchemaUtil {
@Override
public void insertVdcVersion(final DbClient dbClient) {
// Do nothing
}
}
static class MockDbClientContext extends DbClientContext {
@Override
public int getThriftPort() {
return 9160;
}
}
static class MockDbManager extends DbManager {
@Override
public void init() {
//do nothing
}
@Override
public void start() {
//do nothing
}
}
protected static class TestMockDbServiceImpl extends DbServiceImpl {
@Override
public void setDbInitializedFlag() {
String os = System.getProperty("os.name").toLowerCase();
if( os.indexOf("windows") >= 0) {
String currentPath = System.getProperty("user.dir");
_log.info("CurrentPath is {}", currentPath);
File dbInitializedFlag = new File(".");
try {
if (!dbInitializedFlag.exists())
new FileOutputStream(dbInitializedFlag).close();
}catch (Exception e) {
_log.error("Failed to create file {} e", dbInitializedFlag.getName(), e);
}
}else{
super.setDbInitializedFlag();
}
}
}
protected void cleanupDataObjectCF(Class<? extends DataObject> clazz) {
List<URI> uriList = _dbClient.queryByType(Volume.class, false);
List<DataObject> dataObjects = new ArrayList<DataObject>();
for (URI uri : uriList) {
try {
DataObject dataObject = clazz.newInstance();
dataObject.setId(uri);
dataObjects.add(dataObject);
} catch (Exception e) {
_log.error("Failed to create instance of Class {} e", clazz, e);
}
}
_dbClient.internalRemoveObjects(dataObjects.toArray(new DataObject[0]));
}
}