From 85041dd8795b84a48d0b48dd746bfbcb230c8794 Mon Sep 17 00:00:00 2001 From: Matej Perina Date: Wed, 3 Apr 2019 09:09:00 +0200 Subject: Proposal to remove OSGi dependencies from the CCSDK project Dependencies on the OSGi frameworks and libraries are removed by integrating the CCSDK project with the lighty.io. It's a toolkit that allows to use ODL services (in this case core services and the Restconf) without the dependency on the Karaf framework and the Blueprint DI. In this change are created the lighty.io modules which initialize and expose same services as the Blueprint DI in the blueprint.xml files. More info about the lighty.io - https://lighty.io Change-Id: I38171e83b018a18bfd8eaec95d4dc2fa2e3f5b36 Signed-off-by: Matej Perina Signed-off-by: Samuel Kontris --- .../core/dblib/DBLIBResourceProviderLighty.java | 174 ++++ .../sli/core/dblib/DBResourceManagerLighty.java | 977 +++++++++++++++++++++ .../ccsdk/sli/core/dblib/lighty/DblibModule.java | 56 ++ 3 files changed, 1207 insertions(+) create mode 100644 dblib/lighty/src/main/java/org/onap/ccsdk/sli/core/dblib/DBLIBResourceProviderLighty.java create mode 100644 dblib/lighty/src/main/java/org/onap/ccsdk/sli/core/dblib/DBResourceManagerLighty.java create mode 100644 dblib/lighty/src/main/java/org/onap/ccsdk/sli/core/dblib/lighty/DblibModule.java (limited to 'dblib/lighty/src') diff --git a/dblib/lighty/src/main/java/org/onap/ccsdk/sli/core/dblib/DBLIBResourceProviderLighty.java b/dblib/lighty/src/main/java/org/onap/ccsdk/sli/core/dblib/DBLIBResourceProviderLighty.java new file mode 100644 index 00000000..82579d7e --- /dev/null +++ b/dblib/lighty/src/main/java/org/onap/ccsdk/sli/core/dblib/DBLIBResourceProviderLighty.java @@ -0,0 +1,174 @@ +/*- + * ============LICENSE_START======================================================= + * onap + * ================================================================================ + * Copyright (C) 2016 - 2017 ONAP + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.ccsdk.sli.core.dblib; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.util.Optional; +import java.util.Properties; +import java.util.Vector; +import org.onap.ccsdk.sli.core.utils.KarafRootFileResolver; +import org.onap.ccsdk.sli.core.utils.PropertiesFileResolver; +import org.onap.ccsdk.sli.core.utils.common.CoreDefaultFileResolver; +import org.onap.ccsdk.sli.core.utils.common.SdncConfigEnvVarFileResolver; +import org.opendaylight.aaa.encrypt.AAAEncryptionService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * THIS CLASS IS A COPY OF {@link DBLIBResourceProvider} WITH REMOVED OSGi DEPENDENCIES + */ +public class DBLIBResourceProviderLighty { + + private static final Logger LOG = LoggerFactory.getLogger(DBLIBResourceProviderLighty.class); + + /** + * The name of the properties file for database configuration + */ + private static final String DBLIB_PROP_FILE_NAME = "dblib.properties"; + + private static final String DBLIB_PROPERTY_NAME = "org.onap.ccsdk.sli.jdbc.password"; + private final AAAEncryptionService aaaEncryptionService; + + /** + * A prioritized list of strategies for resolving dblib properties files. + */ + private Vector dblibPropertiesFileResolvers = new Vector<>(); + + /** + * The configuration properties for the db connection. + */ + private Properties properties; + + /** + * Set up the prioritized list of strategies for resolving dblib properties files. + */ + public DBLIBResourceProviderLighty(AAAEncryptionService aaaEncryptionService) { + this.aaaEncryptionService = aaaEncryptionService; + + dblibPropertiesFileResolvers.add(new SdncConfigEnvVarFileResolver( + "Using property file (1) from environment variable" + )); + dblibPropertiesFileResolvers.add(new CoreDefaultFileResolver( + "Using property file (2) from default directory" + )); + dblibPropertiesFileResolvers.add(new KarafRootFileResolver( + "Using property file (4) from karaf root", this)); + + // determines properties file as according to the priority described in the class header comment + final File propertiesFile = determinePropertiesFile(this); + if (propertiesFile != null) { + try(FileInputStream fileInputStream = new FileInputStream(propertiesFile)) { + properties = new Properties(); + properties.load(fileInputStream); + + if(properties.containsKey(DBLIB_PROPERTY_NAME)) { + String sensitive = properties.getProperty(DBLIB_PROPERTY_NAME); + if(sensitive != null && sensitive.startsWith("ENC:")) { + try { + sensitive = sensitive.substring(4); + String postsense = decrypt(sensitive); + properties.setProperty(DBLIB_PROPERTY_NAME, postsense); + } catch(Exception exc) { + LOG.error("Failed to translate property", exc); + } + } + } + + } catch (final IOException e) { + LOG.error("Failed to load properties for file: {}", propertiesFile.toString(), + new DblibConfigurationException("Failed to load properties for file: " + + propertiesFile.toString(), e)); + } + } + } + + /** + * + * @param value + * @return decrypted string if successful or the original value if unsuccessful + */ + private String decrypt(String value) { + return aaaEncryptionService.decrypt(value); + } + + /** + * Extract db config properties. + * + * @return the db config properties + */ + public Properties getProperties() { + return properties; + } + + /** + * Reports the method chosen for properties resolution to the Logger. + * + * @param message Some user friendly message + * @param fileOptional The file location of the chosen properties file + * @return the file location of the chosen properties file + */ + private static File reportSuccess(final String message, final Optional fileOptional) { + if(fileOptional.isPresent()) { + final File file = fileOptional.get(); + LOG.info("{} {}", message, file.getPath()); + return file; + } + return null; + } + + /** + * Reports fatal errors. This is the case in which no properties file could be found. + * + * @param message An appropriate fatal error message + * @param dblibConfigurationException An exception describing what went wrong during resolution + */ + private static void reportFailure(final String message, + final DblibConfigurationException dblibConfigurationException) { + + LOG.error("{}", message, dblibConfigurationException); + } + + /** + * Determines the dblib properties file to use based on the following priority: + *
    + *
  1. A directory identified by the system environment variable SDNC_CONFIG_DIR
  2. + *
  3. The default directory DEFAULT_DBLIB_PROP_DIR
  4. + *
  5. A directory identified by the JRE argument dblib.properties
  6. + *
  7. A dblib.properties file located in the karaf root directory
  8. + *
+ */ + File determinePropertiesFile(final DBLIBResourceProviderLighty dblibResourceProvider) { + + for (final PropertiesFileResolver dblibPropertiesFileResolver : dblibPropertiesFileResolvers) { + final Optional fileOptional = dblibPropertiesFileResolver.resolveFile(DBLIB_PROP_FILE_NAME); + if (fileOptional.isPresent()) { + return reportSuccess(dblibPropertiesFileResolver.getSuccessfulResolutionMessage(), fileOptional); + } + } + + reportFailure("Missing configuration properties resource(3)", + new DblibConfigurationException("Missing configuration properties resource(3): " + + DBLIB_PROP_FILE_NAME)); + return null; + } +} diff --git a/dblib/lighty/src/main/java/org/onap/ccsdk/sli/core/dblib/DBResourceManagerLighty.java b/dblib/lighty/src/main/java/org/onap/ccsdk/sli/core/dblib/DBResourceManagerLighty.java new file mode 100644 index 00000000..9b474a5e --- /dev/null +++ b/dblib/lighty/src/main/java/org/onap/ccsdk/sli/core/dblib/DBResourceManagerLighty.java @@ -0,0 +1,977 @@ +/*- + * ============LICENSE_START======================================================= + * onap + * ================================================================================ + * Copyright (C) 2016 - 2017 ONAP + * ================================================================================ + * Modifications Copyright (C) 2018 IBM. + * ================================================================================ + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END========================================================= + */ + +package org.onap.ccsdk.sli.core.dblib; + +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.Driver; +import java.sql.SQLDataException; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.SQLIntegrityConstraintViolationException; +import java.sql.SQLNonTransientConnectionException; +import java.sql.SQLSyntaxErrorException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Observable; +import java.util.Properties; +import java.util.Set; +import java.util.SortedSet; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentSkipListSet; +import java.util.stream.Collectors; +import javax.sql.DataSource; +import javax.sql.rowset.CachedRowSet; +import org.apache.tomcat.jdbc.pool.PoolExhaustedException; +import org.onap.ccsdk.sli.core.dblib.config.DbConfigPool; +import org.onap.ccsdk.sli.core.dblib.config.JDBCConfiguration; +import org.onap.ccsdk.sli.core.dblib.config.TerminatingConfiguration; +import org.onap.ccsdk.sli.core.dblib.factory.DBConfigFactory; +import org.onap.ccsdk.sli.core.dblib.pm.PollingWorker; +import org.onap.ccsdk.sli.core.dblib.pm.SQLExecutionMonitor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * THIS CLASS IS A COPY OF {@link DBResourceManager} WITH REMOVED OSGi DEPENDENCIES + */ +public class DBResourceManagerLighty implements DataSource, DataAccessor, DBResourceObserver, DbLibService { + private static final Logger LOGGER = LoggerFactory.getLogger(DBResourceManagerLighty.class); + private static final String DATABASE_URL = "org.onap.ccsdk.sli.jdbc.url"; + + transient boolean terminating = false; + transient protected long retryInterval = 10000L; + transient boolean recoveryMode = true; + + SortedSet dsQueue = new ConcurrentSkipListSet<>(new DataSourceComparator()); + protected final Set broken = Collections.synchronizedSet(new HashSet()); + protected final Object monitor = new Object(); + protected final Properties configProps; + protected final Thread worker; + + protected final long terminationTimeOut; + protected final boolean monitorDbResponse; + protected final long monitoringInterval; + protected final long monitoringInitialDelay; + protected final long expectedCompletionTime; + protected final long unprocessedFailoverThreshold; + private static final String LOGGER_ALARM_MSG="Generated alarm: DBResourceManager.getData - No active DB connection pools are available."; + private static final String EXCEPTION_MSG= "No active DB connection pools are available in RequestDataNoRecovery call."; + + public DBResourceManagerLighty(final DBLIBResourceProviderLighty configuration) { + this(configuration.getProperties()); + } + + public DBResourceManagerLighty(final Properties properties) { + this.configProps = processSystemVariables(properties); + + // TODO : hack to force classloader to cache mariadb driver. This shouldnt be necessary, + // but for some reason it is (without this, dblib throws ClassNotFound on mariadb driver + // and fails to load). + LOGGER.info("Creating dummy instance of org.mariadb.jdbc.Driver"); + Driver dvr = new org.mariadb.jdbc.Driver(); + dvr = null; + + // get retry interval value + retryInterval = getLongFromProperties(configProps, "org.onap.dblib.connection.retry", 10000L); + + // get recovery mode flag + recoveryMode = getBooleanFromProperties(configProps, "org.onap.dblib.connection.recovery", true); + if(!recoveryMode) + { + recoveryMode = false; + LOGGER.info("Recovery Mode disabled"); + } + // get time out value for thread cleanup + terminationTimeOut = getLongFromProperties(configProps, "org.onap.dblib.termination.timeout", 300000L); + // get properties for monitoring + monitorDbResponse = getBooleanFromProperties(configProps, "org.onap.dblib.connection.monitor", false); + monitoringInterval = getLongFromProperties(configProps, "org.onap.dblib.connection.monitor.interval", 1000L); + monitoringInitialDelay = getLongFromProperties(configProps, "org.onap.dblib.connection.monitor.startdelay", 5000L); + expectedCompletionTime = getLongFromProperties(configProps, "org.onap.dblib.connection.monitor.expectedcompletiontime", 5000L); + unprocessedFailoverThreshold = getLongFromProperties(configProps, "org.onap.dblib.connection.monitor.unprocessedfailoverthreshold", 3L); + + // initialize performance monitor + PollingWorker.createInistance(configProps); + + // initialize recovery thread + worker = new RecoveryMgr(); + worker.setName("DBResourcemanagerWatchThread"); + worker.setDaemon(true); + worker.start(); + + try { + this.config(configProps); + } catch (final Exception e) { + // TODO: config throws Exception which is poor practice. Eliminate this in a separate patch. + LOGGER.error("Fatal Exception encountered while configuring DBResourceManager", e); + } + } + + public static Properties processSystemVariables(Properties properties) { + Map hmap = new Properties(); + hmap.putAll(properties); + + Map result = hmap.entrySet().stream() + .filter(map -> map.getValue().toString().startsWith("${")) + .filter(map -> map.getValue().toString().endsWith("}")) + .collect(Collectors.toMap(map -> map.getKey(), map -> map.getValue())); + + result.forEach((name, propEntries) -> { + hmap.put(name, replace(propEntries.toString())); + }); + + if(hmap.containsKey(DATABASE_URL) && hmap.get(DATABASE_URL).toString().contains("${")) { + String url = hmap.get(DATABASE_URL).toString(); + String[] innerChunks = url.split("\\$\\{"); + for(String chunk : innerChunks) { + if(chunk.contains("}")) { + String subChunk = chunk.substring(0, chunk.indexOf("}")); + String varValue = System.getenv(subChunk); + url = url.replace("${"+subChunk+"}", varValue); + } + } + hmap.put(DATABASE_URL, url); + } + return Properties.class.cast(hmap); + } + + + private static String replace(String value) { + String globalVariable = value.substring(2, value.length() -1); + String varValue = System.getenv(globalVariable); + return (varValue != null) ? varValue : value; + } + + + private void config(Properties configProps) throws Exception { + final ConcurrentLinkedQueue semaphore = new ConcurrentLinkedQueue<>(); + final DbConfigPool dbConfig = DBConfigFactory.createConfig(configProps); + + long startTime = System.currentTimeMillis(); + + try { + JDBCConfiguration[] config = dbConfig.getJDBCbSourceArray(); + CachedDataSource[] cachedDS = new CachedDataSource[config.length]; + if (cachedDS == null || cachedDS.length == 0) { + LOGGER.error("Initialization of CachedDataSources failed. No instance was created."); + throw new Exception("Failed to initialize DB Library. No data source was created."); + } + + for(int i = 0; i < config.length; i++) { + cachedDS[i] = CachedDataSourceFactory.createDataSource(config[i]); + if(cachedDS[i] == null) + continue; + semaphore.add(cachedDS[i]); + cachedDS[i].setInterval(monitoringInterval); + cachedDS[i].setInitialDelay(monitoringInitialDelay); + cachedDS[i].setExpectedCompletionTime(expectedCompletionTime); + cachedDS[i].setUnprocessedFailoverThreshold(unprocessedFailoverThreshold); + cachedDS[i].addObserver(DBResourceManagerLighty.this); + } + + DataSourceTester[] tester = new DataSourceTester[config.length]; + + for(int i=0; i { + @Override + public int compare(CachedDataSource left, CachedDataSource right) { + if(LOGGER.isTraceEnabled()) + LOGGER.trace("----------SORTING-------- () : ()", left.getDbConnectionName(), right.getDbConnectionName()); + try { + if(left == right) { + return 0; + } + if(left == null){ + return 1; + } + if(right == null){ + return -1; + } + + boolean leftMaster = !left.isSlave(); + if(leftMaster) { + if(left.getIndex() <= right.getIndex()) + return -1; + else { + boolean rightMaster = !right.isSlave(); + if(rightMaster) { + if(left.getIndex() <= right.getIndex()) + return -1; +// if(left.getIndex() > right.getIndex()) + else { + return 1; + } + } else { + return -1; + } + } + } + if(!right.isSlave()) + return 1; + + if(left.getIndex() <= right.getIndex()) + return -1; + if(left.getIndex() > right.getIndex()) + return 1; + + + } catch (Throwable e) { + LOGGER.warn("", e); + } + return -1; + } + } + + class DataSourceTester extends Thread { + + private final CachedDataSource ds; + private final DBResourceManagerLighty manager; + private final ConcurrentLinkedQueue semaphoreQ; + + public DataSourceTester(CachedDataSource ds, DBResourceManagerLighty manager, ConcurrentLinkedQueue semaphore) { + this.ds = ds; + this.manager = manager; + this.semaphoreQ = semaphore; + } + + @Override + public void run() { + manager.setDataSource(ds); + boolean slave = true; + if(ds != null) { + try { + slave = ds.isSlave(); + } catch (Exception exc) { + LOGGER.warn("", exc); + } + } + if(!slave) { + LOGGER.info("Adding MASTER {} to active queue", ds.getDbConnectionName()); + try { + synchronized (semaphoreQ) { + semaphoreQ.notifyAll(); + } + } catch(Exception exc) { + LOGGER.warn("", exc); + } + } + try { + synchronized (semaphoreQ) { + semaphoreQ.remove(ds); + } + if(semaphoreQ.isEmpty()) { + synchronized (semaphoreQ) { + semaphoreQ.notifyAll(); + } + } + } catch(Exception exc) { + LOGGER.warn("", exc); + } + if(ds != null) + LOGGER.info("Thread DataSourceTester terminated {} for {}", this.getName(), ds.getDbConnectionName()); + } + + } + + + private long getLongFromProperties(Properties props, String property, long defaultValue) + { + String value = null; + long tmpLongValue = defaultValue; + try { + value = props.getProperty(property); + if(value != null) + tmpLongValue = Long.parseLong(value); + + } catch(NumberFormatException exc) { + if(LOGGER.isWarnEnabled()){ + LOGGER.warn("'"+property+"'=" + value+" is invalid. It should be a numeric value"); + } + } catch(Exception exc) { + } + return tmpLongValue; + + } + + private boolean getBooleanFromProperties(Properties props, String property, boolean defaultValue) + { + boolean tmpValue = defaultValue; + String value = null; + + try { + value = props.getProperty(property); + if(value != null) + tmpValue = Boolean.parseBoolean(value); + + } catch(NumberFormatException exc) { + if(LOGGER.isWarnEnabled()){ + LOGGER.warn("'"+property+"'=" + value+" is invalid. It should be a boolean value"); + } + } catch(Exception exc) { + } + return tmpValue; + + } + + + @Override + public void update(Observable observable, Object data) { + // if observable is active and there is a standby available, switch + if(observable instanceof SQLExecutionMonitor) + { + SQLExecutionMonitor monitor = (SQLExecutionMonitor)observable; + if(monitor.getParent() instanceof CachedDataSource) + { + CachedDataSource dataSource = (CachedDataSource)monitor.getParent(); + if(dataSource == dsQueue.first()) + { + if(recoveryMode && dsQueue.size() > 1){ + handleGetConnectionException(dataSource, new Exception(data.toString())); + } + } + } + } + } + + public void testForceRecovery() + { + CachedDataSource active = this.dsQueue.first(); + handleGetConnectionException(active, new Exception("test")); + } + + class RecoveryMgr extends Thread { + + @Override + public void run() { + while(!terminating) + { + try { + Thread.sleep(retryInterval); + } catch (InterruptedException e1) { } + CachedDataSource brokenSource = null; + try { + if (!broken.isEmpty()) { + CachedDataSource[] sourceArray = broken.toArray(new CachedDataSource[0]); + for (int i = 0; i < sourceArray.length; i++) + { + brokenSource = sourceArray[i]; + if (brokenSource instanceof TerminatingCachedDataSource) + break; + if (resetConnectionPool(brokenSource)) { + broken.remove(brokenSource); + brokenSource.blockImmediateOffLine(); + dsQueue.add(brokenSource); + LOGGER.info("DataSource <" + + brokenSource.getDbConnectionName() + + "> recovered."); + } + brokenSource = null; + } + } + } catch (Exception exc) { + LOGGER.warn(exc.getMessage()); + if(brokenSource != null){ + try { + if(!broken.contains(brokenSource)) + broken.add(brokenSource); + brokenSource = null; + } catch (Exception e1) { } + } + } + } + LOGGER.info("DBResourceManager.RecoveryMgr <"+this.toString() +"> terminated." ); + } + + private boolean resetConnectionPool(CachedDataSource dataSource){ + try { + return dataSource.testConnection(); + } catch (Exception exc) { + LOGGER.info("DataSource <" + dataSource.getDbConnectionName() + "> resetCache failed with error: "+ exc.getMessage()); + return false; + } + } + } + + /* (non-Javadoc) + * @see org.onap.ccsdk.sli.resource.dblib.DbLibService#getData(java.lang.String, java.util.ArrayList, java.lang.String) + */ + @Override + public CachedRowSet getData(String statement, ArrayList arguments, String preferredDS) throws SQLException { + ArrayList newList= new ArrayList<>(); + if(arguments != null && !arguments.isEmpty()) { + newList.addAll(arguments); + } + if(recoveryMode) + return requestDataWithRecovery(statement, newList, preferredDS); + else + return requestDataNoRecovery(statement, newList, preferredDS); + } + + private CachedRowSet requestDataWithRecovery(String statement, ArrayList arguments, String preferredDS) throws SQLException { + Throwable lastException = null; + + // test if there are any connection pools available + if(this.dsQueue.isEmpty()){ + LOGGER.error(LOGGER_ALARM_MSG); + throw new DBLibException("No active DB connection pools are available in RequestDataWithRecovery call."); + } + + // loop through available data sources to retrieve data. + for(int i=0; i< 2; i++) + { + CachedDataSource active = this.dsQueue.first(); + + long time = System.currentTimeMillis(); + try { + if(!active.isFabric()) { + if(this.dsQueue.size() > 1 && active.isSlave()) { + CachedDataSource master = findMaster(); + if(master != null) { + active = master; + } + } + } + + return active.getData(statement, arguments); + } catch(SQLDataException | SQLSyntaxErrorException | SQLIntegrityConstraintViolationException exc){ + throw exc; + } catch(Throwable exc){ + if(exc instanceof SQLException) { + SQLException sqlExc = (SQLException)exc; + int code = sqlExc.getErrorCode(); + String state = sqlExc.getSQLState(); + LOGGER.debug("SQLException code: {} state: {}", code, state); + if("07001".equals(sqlExc.getSQLState())) { + throw sqlExc; + } + } + lastException = exc; + LOGGER.error("Generated alarm: {}", active.getDbConnectionName(), exc); + handleGetConnectionException(active, exc); + } finally { + if(LOGGER.isDebugEnabled()){ + time = System.currentTimeMillis() - time; + LOGGER.debug("getData processing time : {} {} miliseconds.", active.getDbConnectionName(), time); + } + } + } + if(lastException instanceof SQLException){ + throw (SQLException)lastException; + } + // repackage the exception + // you are here because either you run out of available data sources + // or the last exception was not of SQLException type. + // repackage the exception + if(lastException == null) { + throw new DBLibException("The operation timed out while waiting to acquire a new connection." ); + } else { + SQLException exception = new DBLibException(lastException.getMessage()); + exception.setStackTrace(lastException.getStackTrace()); + if(lastException.getCause() instanceof SQLException) { + throw (SQLException)lastException.getCause(); + } + throw exception; + } + } + + private CachedRowSet requestDataNoRecovery(String statement, ArrayList arguments, String preferredDS) throws SQLException { + if(dsQueue.isEmpty()){ + LOGGER.error(LOGGER_ALARM_MSG); + throw new DBLibException(EXCEPTION_MSG); + } + CachedDataSource active = this.dsQueue.first(); + long time = System.currentTimeMillis(); + try { + if(!active.isFabric()) { + if(this.dsQueue.size() > 1 && active.isSlave()) { + CachedDataSource master = findMaster(); + if(master != null) { + active = master; + } + } + } + return active.getData(statement, arguments); + + } catch(Throwable exc){ + String message = exc.getMessage(); + if(message == null) + message = exc.getClass().getName(); + LOGGER.error("Generated alarm: {} - {}",active.getDbConnectionName(), message); + if(exc instanceof SQLException) + throw (SQLException)exc; + else { + DBLibException excptn = new DBLibException(exc.getMessage()); + excptn.setStackTrace(exc.getStackTrace()); + throw excptn; + } + } finally { + if(LOGGER.isDebugEnabled()){ + time = System.currentTimeMillis() - time; + LOGGER.debug(">> getData : {} {} miliseconds.", active.getDbConnectionName(), time); + } + } + } + + + /* (non-Javadoc) + * @see org.onap.ccsdk.sli.resource.dblib.DbLibService#writeData(java.lang.String, java.util.ArrayList, java.lang.String) + */ + @Override + public boolean writeData(String statement, ArrayList arguments, String preferredDS) throws SQLException + { + ArrayList newList= new ArrayList<>(); + if(arguments != null && !arguments.isEmpty()) { + newList.addAll(arguments); + } + + return writeDataNoRecovery(statement, newList, preferredDS); + } + + synchronized CachedDataSource findMaster() throws SQLException { + final CachedDataSource[] clone = this.dsQueue.toArray(new CachedDataSource[0]); + + for(final CachedDataSource dss : clone) { + if(!dss.isSlave()) { + final CachedDataSource first = this.dsQueue.first(); + if(first != dss) { + if(LOGGER.isDebugEnabled()) + LOGGER.debug("----------REODRERING--------"); + dsQueue.clear(); + if(!dsQueue.addAll(Arrays.asList(clone))) { + LOGGER.error("Failed adding datasources"); + } + } + return dss; + } + } + LOGGER.warn("MASTER not found."); + return null; + } + + + private boolean writeDataNoRecovery(String statement, ArrayList arguments, String preferredDS) throws SQLException { + if(dsQueue.isEmpty()){ + LOGGER.error(LOGGER_ALARM_MSG); + throw new DBLibException(EXCEPTION_MSG); + } + + boolean initialRequest = true; + boolean retryAllowed = true; + CachedDataSource active = this.dsQueue.first(); + long time = System.currentTimeMillis(); + while(initialRequest) { + initialRequest = false; + try { + if(!active.isFabric()) { + if(this.dsQueue.size() > 1 && active.isSlave()) { + CachedDataSource master = findMaster(); + if(master != null) { + active = master; + } + } + } + + return active.writeData(statement, arguments); + } catch(Throwable exc){ + String message = exc.getMessage(); + if(message == null) + message = exc.getClass().getName(); + LOGGER.error("Generated alarm: {} - {}", active.getDbConnectionName(), message); + if(exc instanceof SQLException) { + SQLException sqlExc = SQLException.class.cast(exc); + // handle read-only exception + if(sqlExc.getErrorCode() == 1290 && "HY000".equals(sqlExc.getSQLState())) { + LOGGER.warn("retrying due to: {}", sqlExc.getMessage()); + this.findMaster(); + if(retryAllowed){ + retryAllowed = false; + initialRequest = true; + continue; + } + } + throw (SQLException)exc; + } else { + DBLibException excptn = new DBLibException(exc.getMessage()); + excptn.setStackTrace(exc.getStackTrace()); + throw excptn; + } + } finally { + if(LOGGER.isDebugEnabled()){ + time = System.currentTimeMillis() - time; + LOGGER.debug("writeData processing time : {} {} miliseconds.", active.getDbConnectionName(), time); + } + } + } + return true; + } + + public void setDataSource(CachedDataSource dataSource) { + if(this.dsQueue.contains(dataSource)) + return; + if(this.broken.contains(dataSource)) + return; + + if(dataSource.testConnection(true)){ + this.dsQueue.add(dataSource); + } else { + this.broken.add(dataSource); + } + } + + @Override + public Connection getConnection() throws SQLException { + Throwable lastException = null; + CachedDataSource active = null; + + if(dsQueue.isEmpty()){ + throw new DBLibException("No active DB connection pools are available in GetConnection call."); + } + + try { + active = dsQueue.first(); + + if(!active.isFabric()) { + if(this.dsQueue.size() > 1 && active.isSlave()) { + LOGGER.debug("Forcing reorder on: {}", dsQueue.toString()); + CachedDataSource master = findMaster(); + if(master != null) { + active = master; + } + } + } + return new DBLibConnection(active.getConnection(), active); + } catch(javax.sql.rowset.spi.SyncFactoryException exc){ + LOGGER.debug("Free memory (bytes): " + Runtime.getRuntime().freeMemory()); + LOGGER.warn("CLASSPATH issue. Allowing retry", exc); + lastException = exc; + } catch(PoolExhaustedException exc) { + throw new NoAvailableConnectionsException(exc); + } catch(SQLNonTransientConnectionException exc){ + throw new NoAvailableConnectionsException(exc); + } catch(Exception exc){ + lastException = exc; + if(recoveryMode){ + handleGetConnectionException(active, exc); + } else { + if(exc instanceof SQLException) { + throw (SQLException)exc; + } else { + DBLibException excptn = new DBLibException(exc.getMessage()); + excptn.setStackTrace(exc.getStackTrace()); + throw excptn; + } + } + } catch (Throwable trwb) { + DBLibException excptn = new DBLibException(trwb.getMessage()); + excptn.setStackTrace(trwb.getStackTrace()); + throw excptn; + } finally { + if(LOGGER.isDebugEnabled()){ + displayState(); + } + } + + if(lastException instanceof SQLException){ + throw (SQLException)lastException; + } + // repackage the exception + if(lastException == null) { + throw new DBLibException("The operation timed out while waiting to acquire a new connection." ); + } else { + SQLException exception = new DBLibException(lastException.getMessage()); + exception.setStackTrace(lastException.getStackTrace()); + if(lastException.getCause() instanceof SQLException) { + + throw (SQLException)lastException.getCause(); + } + throw exception; + } + } + + @Override + public Connection getConnection(String username, String password) + throws SQLException { + CachedDataSource active = null; + + if(dsQueue.isEmpty()){ + throw new DBLibException("No active DB connection pools are available in GetConnection call."); + } + + + try { + active = dsQueue.first(); + if(!active.isFabric()) { + if(this.dsQueue.size() > 1 && active.isSlave()) { + CachedDataSource master = findMaster(); + if(master != null) { + active = master; + } + } + } + return active.getConnection(username, password); + } catch(Throwable exc){ + if(recoveryMode){ + handleGetConnectionException(active, exc); + } else { + if(exc instanceof SQLException) + throw (SQLException)exc; + else { + DBLibException excptn = new DBLibException(exc.getMessage()); + excptn.setStackTrace(exc.getStackTrace()); + throw excptn; + } + } + + } + + throw new DBLibException("No connections available in DBResourceManager in GetConnection call."); + } + + private void handleGetConnectionException(final CachedDataSource source, Throwable exc) { + try { + if(!source.canTakeOffLine()) + { + LOGGER.error("Could not switch due to blocking"); + return; + } + + boolean removed = dsQueue.remove(source); + if(!broken.contains(source)) + { + if(broken.add(source)) + { + LOGGER.warn("DB Recovery: DataSource <" + source.getDbConnectionName() + "> put in the recovery mode. Reason : " + exc.getMessage()); + } else { + LOGGER.warn("Error putting DataSource <" +source.getDbConnectionName()+ "> in recovery mode."); + } + } else { + LOGGER.info("DB Recovery: DataSource <" + source.getDbConnectionName() + "> already in recovery queue"); + } + if(removed) + { + if(!dsQueue.isEmpty()) + { + LOGGER.warn("DB DataSource <" + dsQueue.first().getDbConnectionName() + "> became active"); + } + } + } catch (Exception e) { + LOGGER.error("", e); + } + } + + public void cleanUp() { + for(Iterator it=dsQueue.iterator();it.hasNext();){ + CachedDataSource cds = it.next(); + it.remove(); + cds.cleanUp(); + } + + try { + this.terminating = true; + if(broken != null) + { + try { + broken.add( new TerminatingCachedDataSource(new TerminatingConfiguration())); + } catch(Exception exc){ + LOGGER.error("Waiting for Worker to stop", exc); + } + } + worker.join(terminationTimeOut); + LOGGER.info("DBResourceManager.RecoveryMgr <"+worker.toString() +"> termination was successful: " + worker.getState()); + } catch(Exception exc){ + LOGGER.error("Waiting for Worker thread to terminate ", exc); + } + } + + @Override + public PrintWriter getLogWriter() throws SQLException { + return this.dsQueue.first().getLogWriter(); + } + + @Override + public int getLoginTimeout() throws SQLException { + return this.dsQueue.first().getLoginTimeout(); + } + + @Override + public void setLogWriter(PrintWriter out) throws SQLException { + this.dsQueue.first().setLogWriter(out); + } + + @Override + public void setLoginTimeout(int seconds) throws SQLException { + this.dsQueue.first().setLoginTimeout(seconds); + } + + public void displayState(){ + if(LOGGER.isDebugEnabled()){ + LOGGER.debug("POOLS : Active = "+dsQueue.size() + ";\t Broken = "+broken.size()); + CachedDataSource current = dsQueue.first(); + if(current != null) { + LOGGER.debug("POOL : Active name = \'"+current.getDbConnectionName()+ "\'"); + } + } + } + + /* (non-Javadoc) + * @see org.onap.ccsdk.sli.resource.dblib.DbLibService#isActive() + */ + @Override + public boolean isActive() { + return this.dsQueue.size()>0; + } + + public String getActiveStatus(){ + return "Connected: " + dsQueue.size()+"\tIn-recovery: "+broken.size(); + } + + public String getDBStatus(boolean htmlFormat) { + StringBuilder buffer = new StringBuilder(); + + ArrayList list = new ArrayList<>(); + list.addAll(dsQueue); + list.addAll(broken); + if (htmlFormat) + { + buffer.append("") + .append("Name:").append(""); + for (int i = 0; i < list.size(); i++) { + buffer.append(""); + buffer.append(list.get(i).getDbConnectionName()).append(""); + } + buffer.append(""); + + buffer.append("State:"); + for (int i = 0; i < list.size(); i++) { + if (broken.contains(list.get(i))) { + buffer.append("in recovery"); + } + if (dsQueue.contains(list.get(i))) { + if (dsQueue.first() == list.get(i)) + buffer.append("active"); + else + buffer.append("standby"); + } + } + buffer.append(""); + + } else { + for (int i = 0; i < list.size(); i++) { + buffer.append("Name: ").append(list.get(i).getDbConnectionName()); + buffer.append("\tState: "); + if (broken.contains(list.get(i))) { + buffer.append("in recovery"); + } else + if (dsQueue.contains(list.get(i))) { + if (dsQueue.first() == list.get(i)) + buffer.append("active"); + else + buffer.append("standby"); + } + + buffer.append("\n"); + + } + } + return buffer.toString(); + } + + @Override + public boolean isWrapperFor(Class iface) throws SQLException { + return false; + } + + @Override + public T unwrap(Class iface) throws SQLException { + return null; + } + + /** + * @return the monitorDbResponse + */ + @Override + public final boolean isMonitorDbResponse() { + return recoveryMode && monitorDbResponse; + } + + public void test(){ + CachedDataSource obj = dsQueue.first(); + Exception ption = new Exception(); + try { + for(int i=0; i<5; i++) + { + handleGetConnectionException(obj, ption); + } + } catch(Throwable exc){ + LOGGER.warn("", exc); + } + } + + @Override + public java.util.logging.Logger getParentLogger() + throws SQLFeatureNotSupportedException { + return null; + } + + class RemindTask extends TimerTask { + @Override + public void run() { + CachedDataSource ds = dsQueue.first(); + if(ds != null) + ds.getPoolInfo(false); + } + } + + public int poolSize() { + return dsQueue.size(); + } +} diff --git a/dblib/lighty/src/main/java/org/onap/ccsdk/sli/core/dblib/lighty/DblibModule.java b/dblib/lighty/src/main/java/org/onap/ccsdk/sli/core/dblib/lighty/DblibModule.java new file mode 100644 index 00000000..bce166df --- /dev/null +++ b/dblib/lighty/src/main/java/org/onap/ccsdk/sli/core/dblib/lighty/DblibModule.java @@ -0,0 +1,56 @@ +/* + * ============LICENSE_START========================================== + * Copyright (c) 2019 PANTHEON.tech s.r.o. + * =================================================================== + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on + * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS + * OF ANY KIND, either express or implied. See the License for the specific language governing permissions and + * limitations under the License. + * ============LICENSE_END============================================ + * + */ + +package org.onap.ccsdk.sli.core.dblib.lighty; + +import io.lighty.core.controller.api.AbstractLightyModule; +import org.onap.ccsdk.sli.core.dblib.DBLIBResourceProviderLighty; +import org.onap.ccsdk.sli.core.dblib.DBResourceManagerLighty; +import org.onap.ccsdk.sli.core.dblib.DbLibService; +import org.opendaylight.aaa.encrypt.AAAEncryptionService; + +/** + * The implementation of the {@link io.lighty.core.controller.api.LightyModule} that manages and provides services from + * the dblib artifact. + */ +public class DblibModule extends AbstractLightyModule { + + private final AAAEncryptionService aaaEncryptionService; + + private DBLIBResourceProviderLighty dbLibResourceProvider; + private DBResourceManagerLighty dbResourceManager; + + public DblibModule(AAAEncryptionService aaaEncryptionService) { + this.aaaEncryptionService = aaaEncryptionService; + } + + @Override + protected boolean initProcedure() { + this.dbLibResourceProvider = new DBLIBResourceProviderLighty(aaaEncryptionService); + this.dbResourceManager = new DBResourceManagerLighty(this.dbLibResourceProvider); + return true; + } + + @Override + protected boolean stopProcedure() { + return true; + } + + public DbLibService getDbLibService() { + return dbResourceManager; + } +} -- cgit 1.2.3-korg