package com.tesora.dve.siteprovider.onpremise;
/*
* #%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.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import org.apache.log4j.Logger;
import com.tesora.dve.common.PECryptoUtils;
import com.tesora.dve.common.PEUrl;
import com.tesora.dve.common.PEXmlUtils;
import com.tesora.dve.common.catalog.CatalogEntity;
import com.tesora.dve.common.catalog.StorageSite;
import com.tesora.dve.exceptions.PEException;
import com.tesora.dve.server.messaging.GetWorkerRequest;
import com.tesora.dve.siteprovider.AbstractSiteProvider;
import com.tesora.dve.siteprovider.CmdSiteManagerCommand;
import com.tesora.dve.siteprovider.ConfigSiteManagerCommand;
import com.tesora.dve.siteprovider.DynamicSiteClass;
import com.tesora.dve.siteprovider.DynamicSiteClassMap;
import com.tesora.dve.siteprovider.DynamicSiteInfo;
import com.tesora.dve.siteprovider.DynamicSiteStatus;
import com.tesora.dve.siteprovider.onpremise.jaxb.OnPremiseSiteProviderConfig;
import com.tesora.dve.siteprovider.onpremise.jaxb.PoolConfig;
import com.tesora.dve.variables.ScopedVariables;
import com.tesora.dve.variables.VariableValueConverter;
import com.tesora.dve.worker.SiteManagerCommand;
public class OnPremiseSiteProvider extends AbstractSiteProvider {
public static final Logger log = Logger.getLogger(OnPremiseSiteProvider.class);
public static final String DEFAULT_NAME = "OnPremise";
public static final String DEFAULT_POLICY_NAME = DEFAULT_NAME + "Policy";
private static final String OPTION_SITE = "site";
private static final String OPTION_POOL = "pool";
private static final String OPTION_URL = "url";
private static final String OPTION_USER = "user";
private static final String OPTION_PASSWORD = "password";
private static final String OPTION_MAX_QUERIES = "maxQueries";
private final static String OPTION_STATUS = "status";
private final static String OPTION_ALTERNATE_POOL = "alternatePool";
private DynamicSiteClassMap siteClassMap = null;
private OnPremiseSiteProviderConfig jaxbConfig = null;
public OnPremiseSiteProvider() throws PEException {
}
@Override
public void initialize(SiteProviderContext ctxt, String name, boolean isEnabled, String config) throws PEException {
super.initialize(name, isEnabled);
if (config != null && config.length() > 0) {
applyConfigurationChanges(unmarshallJAXB(config));
} else {
jaxbConfig = new OnPremiseSiteProviderConfig();
siteClassMap = new DynamicSiteClassMap();
}
}
@Override
public void returnSitesByClass(SiteProviderContext ctxt, String siteCls, Collection<? extends StorageSite> sites)
throws PEException {
DynamicSiteClass dsc = siteClassMap.get(siteCls);
if (dsc == null)
throw new PEException("Provider '" + getProviderName() + "' requested pool '" + siteCls
+ "' that doesn't exist");
List<StorageSite> remaining = dsc.returnSites(sites, false);
// If we have some remaining then maybe we used an alternate pool
if (remaining.size() > 0) {
String alternate = dsc.getAlternate();
if (alternate != null) {
// Try returning to the alternate pool
dsc = siteClassMap.get(alternate);
if (dsc == null)
throw new PEException("Provider '" + getProviderName() + "' requested alternate pool '" + alternate
+ "' that doesn't exist");
remaining = dsc.returnSites(remaining, false);
}
}
if (remaining.size() > 0)
log.error("Provider '" + getProviderName() + "' failed to return " + remaining.size() + " sites the pool");
}
@Override
public void provisionWorkerRequest(SiteProviderContext ctxt, GetWorkerRequest req) throws PEException {
int count = req.getSiteCount();
boolean strict = req.getIsStrict();
Collection<? extends StorageSite> sites = allocateSites(req.getSiteClassName(), count, strict);
// In strict mode we will get back zero sites if we can't
// satisfy the request.
// In non-strict mode we will get back what's available
if (count > 0 && sites.size() == 0) {
// If we have no sites - then try the alternate if available
DynamicSiteClass dsc = siteClassMap.get(req.getSiteClassName());
if (dsc.getAlternate() != null)
sites = allocateSites(dsc.getAlternate(), count, strict);
}
if (count > 0 && sites.size() == 0)
throw new PEException("Provider '" + getProviderName() + "' failed to obtain " + count
+ " sites from pool '" + req.getSiteClassName() + "'");
req.fulfillGetWorkerRequest(sites);
}
private Collection<? extends StorageSite> allocateSites(String className, int count, boolean strict)
throws PEException {
DynamicSiteClass dsc = siteClassMap.get(className);
if (dsc == null)
throw new PEException("Provider '" + getProviderName() + "' requested pool '" + className
+ "' that doesn't exist");
Collection<? extends StorageSite> sites = dsc.allocateSites(count);
// In Strict mode we want exactly the requested number of sites.
// Therefore if we don't get back the requested number return them
if (strict && count > 0 && sites.size() != count) {
dsc.returnSites(sites, true);
sites.clear();
}
return sites;
}
@Override
public void close() {
if (siteClassMap != null)
siteClassMap.clear();
}
// ------------------------------------------------------------------------
@Override
protected List<CatalogEntity> onShowSites(SiteManagerCommand smc) throws PEException {
return siteClassMap.toCatalogEntity(getProviderName());
}
@Override
public Collection<? extends StorageSite> getAllSites() {
List<StorageSite> out = new ArrayList<StorageSite>();
for (DynamicSiteClass dsc : siteClassMap.getAll())
out.addAll(dsc.getSites());
return out;
}
private String getStringOption(CmdSiteManagerCommand csmc, String option) throws PEException {
return getStringOption(csmc, option, true);
}
private String getStringOption(CmdSiteManagerCommand csmc, String option, boolean required) throws PEException {
String val = (String) findOption(csmc, option);
if (required && val == null)
throw new PEException(option + "='<value>' not specified");
return val;
}
private long getLongOption(CmdSiteManagerCommand csmc, String option) throws PEException {
Long val = (Long) findOption(csmc, option);
if (val == null)
throw new PEException(option + "='<value>' not specified");
return val;
}
// ------------------------------------------------------------------------
// ALTER DYNAMIC SITE PROVIDER <provider> command=<something>
//
@Override
protected CmdSiteManagerCommand onPrepareCommand(SiteManagerCommand smc, String command) throws PEException {
// alter dynamic site provider `name` cmd='<command>' <options>
// available commands and options are:
// cmd='set status' site='<name>' pool='<pool>' status='<ONLINE | OFFLINE>'
// cmd='add site' site='<name>' pool='<pool>' url='<url>' user='<user>' password='<password>' maxQueries=<max>
// cmd='drop site' site='<name>' pool='<pool>'
// cmd='alter site' site='<name>' pool='<pool>' maxQueries=<max>
CmdSiteManagerCommand csmc = new CmdSiteManagerCommand(smc, command);
if ("add site".equalsIgnoreCase(command)) {
// cmd='add site', site'<name>', pool='<pool>', url='<url>',
// maxQueries=<max>
String site = getStringOption(csmc, OPTION_SITE);
String pool = getStringOption(csmc, OPTION_POOL);
String url = getStringOption(csmc, OPTION_URL);
String user = getStringOption(csmc, OPTION_USER);
long maxQueries = getLongOption(csmc, OPTION_MAX_QUERIES);
// Validate the URL
PEUrl.fromUrlString(url);
// Encrypt the password
String encryptedPassword = PECryptoUtils.encrypt(getStringOption(csmc, OPTION_PASSWORD));
// Clone existing configuration so that we can modify it
// Simple clone is to marshall / re-marshall
OnPremiseSiteProviderConfig copy = unmarshallJAXB(marshallJAXB(jaxbConfig));
// Add the new site to the configuration
PoolConfig poolCfg = findSitePool(copy, pool);
if (poolCfg == null)
throw new PEException("Pool '" + pool + "' does not exist in provider configuration");
PoolConfig.Site newSite = findSite(poolCfg, site);
if (newSite != null)
throw new PEException("Site '" + site + "' in pool '" + pool
+ "' already exists in provider configuration");
// Add new site to the pool
newSite = new PoolConfig.Site();
newSite.setName(site);
newSite.setUrl(url);
newSite.setUser(user);
newSite.setPassword(encryptedPassword);
newSite.setMaxQueries((int) maxQueries);
poolCfg.getSite().add(newSite);
csmc.getTarget().setConfig(marshallJAXB(copy));
} else if ("alter site".equalsIgnoreCase(command)) {
// cmd='alter site', site='<name>', pool='<pool>', maxQueries=<max>
String site = getStringOption(csmc, OPTION_SITE);
String pool = getStringOption(csmc, OPTION_POOL);
long maxQueries = getLongOption(csmc, OPTION_MAX_QUERIES);
// Clone existing configuration so that we can modify it
// Simple clone is to marshall / re-marshall
OnPremiseSiteProviderConfig copy = unmarshallJAXB(marshallJAXB(jaxbConfig));
// Find the pool
PoolConfig poolCfg = findSitePool(copy, pool);
if (poolCfg == null)
throw new PEException("Pool '" + pool + "' does not exist in provider configuration");
// Find the site in the pool
PoolConfig.Site siteCfg = findSite(poolCfg, site);
if (siteCfg == null)
throw new PEException("Site '" + site + "' in pool '" + pool
+ "' does not exist in provider configuration");
siteCfg.setMaxQueries((int) maxQueries);
csmc.getTarget().setConfig(marshallJAXB(copy));
} else if ("drop site".equalsIgnoreCase(command)) {
// cmd='drop site', site='<name>', pool='<pool>'
String site = getStringOption(csmc, OPTION_SITE);
String pool = getStringOption(csmc, OPTION_POOL);
// Clone existing configuration so that we can modify it
// Simple clone is to marshall / re-marshall
OnPremiseSiteProviderConfig copy = unmarshallJAXB(marshallJAXB(jaxbConfig));
// Find the pool
PoolConfig poolCfg = findSitePool(copy, pool);
if (poolCfg == null)
throw new PEException("Pool '" + pool + "' does not exist in provider configuration");
// Find the site in the pool
PoolConfig.Site siteCfg = findSite(poolCfg, site);
if (siteCfg == null)
throw new PEException("Site '" + site + "' in pool '" + pool
+ "' does not exist in provider configuration");
// Remove the site from the pool
poolCfg.getSite().remove(siteCfg);
csmc.getTarget().setConfig(marshallJAXB(copy));
} else if ("add pool".equalsIgnoreCase(command)) {
// cmd='add pool', pool='<pool>', alternatePool='<pool>'
String pool = getStringOption(csmc, OPTION_POOL);
String alternate = getStringOption(csmc, OPTION_ALTERNATE_POOL, false);
// Clone existing configuration so that we can modify it
// Simple clone is to marshall / re-marshall
OnPremiseSiteProviderConfig copy = unmarshallJAXB(marshallJAXB(jaxbConfig));
// Check to see if the pool already exists
if (findSitePool(copy, pool) != null)
throw new PEException("Pool '" + pool + "' already exists in provider configuration");
// Check that the specified alternative also exists
if (alternate != null) {
if (findSitePool(copy, alternate) == null)
throw new PEException("Alternate pool '" + alternate + "' does not exist in provider configuration");
}
PoolConfig poolCfg = new PoolConfig();
poolCfg.setName(pool);
poolCfg.setAlternatePool(alternate);
copy.getPool().add(poolCfg);
csmc.getTarget().setConfig(marshallJAXB(copy));
} else if ("alter pool".equalsIgnoreCase(command)) {
// cmd='alter pool', pool='<pool>', alternatePool='<pool>'
String pool = getStringOption(csmc, OPTION_POOL);
String alternate = getStringOption(csmc, OPTION_ALTERNATE_POOL, false);
// Clone existing configuration so that we can modify it
// Simple clone is to marshall / re-marshall
OnPremiseSiteProviderConfig copy = unmarshallJAXB(marshallJAXB(jaxbConfig));
// Find the pool
PoolConfig poolCfg = findSitePool(copy, pool);
if (poolCfg == null)
throw new PEException("Pool '" + pool + "' does not exist in provider configuration");
// Also make sure the specified alternate is a valid pool
if (alternate != null) {
if (findSitePool(copy, alternate) == null)
throw new PEException("Alternate pool '" + alternate + "' does not exist in provider configuration");
}
poolCfg.setAlternatePool(alternate);
csmc.getTarget().setConfig(marshallJAXB(copy));
} else if ("drop pool".equalsIgnoreCase(command)) {
// cmd='drop pool', pool='<pool>'
String pool = getStringOption(csmc, OPTION_POOL);
// Clone existing configuration so that we can modify it
// Simple clone is to marshall / re-marshall
OnPremiseSiteProviderConfig copy = unmarshallJAXB(marshallJAXB(jaxbConfig));
// Find the pool
PoolConfig poolCfg = findSitePool(copy, pool);
if (poolCfg == null)
throw new PEException("Pool '" + pool + "' does not exist in provider configuration");
copy.getPool().remove(poolCfg);
csmc.getTarget().setConfig(marshallJAXB(copy));
} else if ("set status".equalsIgnoreCase(command)) {
// cmd='set status', site'<name>', pool='<pool>', status='<ONLINE |
// OFFLINE>'
String site = getStringOption(csmc, OPTION_SITE);
String pool = getStringOption(csmc, OPTION_POOL);
String status = getStringOption(csmc, OPTION_STATUS);
DynamicSiteClass dsc = siteClassMap.get(pool);
if (dsc == null)
throw new PEException("Pool '" + pool + "' does not exist in provider configuration");
// check that the specified site exists
DynamicSiteInfo existingSiteInfo = dsc.getSiteByShortName(site);
if (existingSiteInfo == null)
throw new PEException("Site '" + site + "' in pool '" + pool
+ "' does not exist in provider configuration");
csmc.setSite(existingSiteInfo);
// This will throw an exception if the value isn't valid
DynamicSiteStatus dss = DynamicSiteStatus.fromString(status);
if (!dss.isValidUserOption())
throw new PEException("You are not allowed to set the status to '" + status + "'");
csmc.setStatus(dss);
} else
throw new PEException("Unable to parse ALTER command");
return csmc;
}
@Override
protected int onUpdateCommand(CmdSiteManagerCommand csmc) throws PEException {
// Bit of a temporary hack to special case the enable site command
// Eventually we should probably be persisting the site state into the
// configuration and storing it in the catalog
if (csmc.getCommand().equalsIgnoreCase("set status")) {
csmc.getSite().setState(csmc.getStatus(), true);
return 1;
}
return applyConfigurationChanges(unmarshallJAXB(csmc.getTarget().getConfig()));
}
@Override
protected void onRollbackCommand(CmdSiteManagerCommand asmc) throws PEException {
// TODO
}
// ------------------------------------------------------------------------
// ALTER DYNAMIC SITE PROVIDER <provider> config=<something>
//
@Override
protected ConfigSiteManagerCommand onPrepareConfig(SiteManagerCommand smc, String enabled, String config)
throws PEException {
ConfigSiteManagerCommand csmc = new ConfigSiteManagerCommand(smc, enabled, config);
if (config != null) {
// This is just a test to check for valid syntax
unmarshallJAXB(config);
csmc.getTarget().setConfig(config);
}
if (enabled != null) {
csmc.getTarget().setIsEnabled(VariableValueConverter.toInternalBoolean(enabled));
}
return csmc;
}
@Override
protected int onUpdateConfig(ConfigSiteManagerCommand csmc) throws PEException {
int ret = 0;
if (csmc.getConfig() != null) {
ret = applyConfigurationChanges(unmarshallJAXB(csmc.getTarget().getConfig()));
}
if (csmc.getEnabled() != null) {
setEnabled(csmc.getTarget().isEnabled());
ret = 1;
}
return ret;
}
@Override
protected void onRollbackConfig(ConfigSiteManagerCommand csmc) throws PEException {
// TODO
}
// ------------------------------------------------------------------------
private int applyConfigurationChanges(OnPremiseSiteProviderConfig config) throws PEException {
jaxbConfig = config;
// Convert jaxb config into a DynamicSiteClassMap so that we can compare
// it to our existing configuration
DynamicSiteClassMap newMap = configToSiteClassMap(config);
if (siteClassMap == null) {
// We don't already have a siteClassMap
siteClassMap = newMap;
} else {
// For each site class in the newMap
for (DynamicSiteClass newDsc : newMap.getAll()) {
DynamicSiteClass existingDsc = siteClassMap.get(newDsc.getName());
if (existingDsc == null)
siteClassMap.put(newDsc);
else
applyConfigurationChanges(newDsc, existingDsc);
}
// Find any site classes that have been removed
for (DynamicSiteClass existingDsc : siteClassMap.getAll()) {
if (newMap.get(existingDsc.getName()) == null) {
// Site class is no longer in the configuration so
// mark all sites in that class as ready for removal
existingDsc.markForRemoval();
}
}
}
// Iterate over the classmap cleaning up any sites that are no longer
// needed
for (Iterator<DynamicSiteClass> iter = siteClassMap.getAll().iterator(); iter.hasNext();) {
DynamicSiteClass dsc = iter.next();
dsc.cleanupAndRemove();
// After cleaning up the site class - check to see if we can remove
// the siteclass altogether
if (dsc.getSites().size() == 0)
iter.remove();
}
return 1;
}
private void applyConfigurationChanges(DynamicSiteClass newDsc, DynamicSiteClass existingDsc) {
existingDsc.setAlternate(newDsc.getAlternate());
for (DynamicSiteInfo newSite : newDsc.getSites()) {
DynamicSiteInfo existingSite = existingDsc.getSite(newSite.getFullName());
if (existingSite != null) {
// Already exists. Right now we only let you change the
// maxQueries
if (existingSite.getMaxQueries() != newSite.getMaxQueries()) {
existingSite.setMaxQueries(newSite.getMaxQueries());
}
} else {
// Doesn't exist - so add it
existingDsc.addSite(newSite);
}
}
// now lets see if there are any sites that need to be deleted
for (DynamicSiteInfo existingSite : existingDsc.getSites()) {
// if we have an existing site that isn't in the new list
// then we need to terminate it. Set its state and it will get
// removed at the appropriate time when there are no longer any
// open queries
if (newDsc.getSite(existingSite.getFullName()) == null)
existingSite.markForRemoval();
}
}
private DynamicSiteClassMap configToSiteClassMap(OnPremiseSiteProviderConfig slc) throws PEException {
DynamicSiteClassMap map = new DynamicSiteClassMap();
// Sites configured on specific urls
if (slc.getPool() != null) {
for (PoolConfig pool : slc.getPool()) {
DynamicSiteClass dsc = new DynamicSiteClass(pool.getName(), pool.getAlternatePool());
for (PoolConfig.Site site : pool.getSite()) {
// This is where we decrypt the password
String decryptedPassword = PECryptoUtils.decrypt(site.getPassword());
dsc.addSite(new DynamicSiteInfo(getProviderName(), dsc.getName(), site.getName(), site.getUrl(),
site.getUser(), decryptedPassword, site.getMaxQueries()));
}
map.put(dsc);
}
}
return map;
}
private PoolConfig findSitePool(OnPremiseSiteProviderConfig slc, String name) {
for (PoolConfig pool : slc.getPool()) {
if (pool.getName().equalsIgnoreCase(name))
return pool;
}
return null;
}
private PoolConfig.Site findSite(PoolConfig pool, String name) {
for (PoolConfig.Site site : pool.getSite()) {
if (site.getName().equalsIgnoreCase(name))
return site;
}
return null;
}
// ------------------------------------------------------------------------
private OnPremiseSiteProviderConfig unmarshallJAXB(String str) throws PEException {
return PEXmlUtils.unmarshalJAXB(str, OnPremiseSiteProviderConfig.class);
}
private String marshallJAXB(OnPremiseSiteProviderConfig slc) throws PEException {
return PEXmlUtils.marshalJAXB(slc);
}
@Override
public ScopedVariables getVariableConfiguration() {
return null;
}
}