X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=blobdiff_plain;ds=sidebyside;f=opendaylight%2Fconfig%2Fconfig-manager%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fcontroller%2Fconfig%2Fmanager%2Fimpl%2FConfigRegistryImpl.java;h=763ed66cb10d947f4ef5aa95d4a6178acf8d009e;hb=db89afdefb3cfa4454dfdc6008e1d54d3ede66fb;hp=0fd6c5279409e3412de86a33735c3dc4daddf058;hpb=b2d4575c4425e3b3d5aeaf1190e01e5d5a5286aa;p=controller.git diff --git a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigRegistryImpl.java b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigRegistryImpl.java index 0fd6c52794..763ed66cb1 100644 --- a/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigRegistryImpl.java +++ b/opendaylight/config/config-manager/src/main/java/org/opendaylight/controller/config/manager/impl/ConfigRegistryImpl.java @@ -7,12 +7,40 @@ */ package org.opendaylight.controller.config.manager.impl; +import com.google.common.base.Throwables; +import com.google.common.collect.Maps; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import javax.annotation.concurrent.GuardedBy; +import javax.annotation.concurrent.ThreadSafe; +import javax.management.InstanceAlreadyExistsException; +import javax.management.InstanceNotFoundException; +import javax.management.MBeanServer; +import javax.management.MBeanServerFactory; +import javax.management.ObjectName; import org.opendaylight.controller.config.api.ConflictingVersionException; import org.opendaylight.controller.config.api.ModuleIdentifier; import org.opendaylight.controller.config.api.RuntimeBeanRegistratorAwareModule; import org.opendaylight.controller.config.api.ValidationException; +import org.opendaylight.controller.config.api.annotations.ServiceInterfaceAnnotation; import org.opendaylight.controller.config.api.jmx.CommitStatus; import org.opendaylight.controller.config.api.jmx.ObjectNameUtil; +import org.opendaylight.controller.config.manager.impl.dependencyresolver.DestroyedModule; +import org.opendaylight.controller.config.manager.impl.dependencyresolver.ModuleInternalTransactionalInfo; import org.opendaylight.controller.config.manager.impl.dynamicmbean.DynamicReadableWrapper; import org.opendaylight.controller.config.manager.impl.factoriesresolver.HierarchicalConfigMBeanFactoriesHolder; import org.opendaylight.controller.config.manager.impl.factoriesresolver.ModuleFactoriesResolver; @@ -22,57 +50,36 @@ import org.opendaylight.controller.config.manager.impl.jmx.RootRuntimeBeanRegist import org.opendaylight.controller.config.manager.impl.jmx.TransactionJMXRegistrator; import org.opendaylight.controller.config.manager.impl.osgi.BeanToOsgiServiceManager; import org.opendaylight.controller.config.manager.impl.osgi.BeanToOsgiServiceManager.OsgiRegistration; +import org.opendaylight.controller.config.manager.impl.osgi.mapping.BindingContextProvider; import org.opendaylight.controller.config.manager.impl.util.LookupBeansUtil; +import org.opendaylight.controller.config.manager.impl.util.ModuleQNameUtil; import org.opendaylight.controller.config.spi.Module; import org.opendaylight.controller.config.spi.ModuleFactory; import org.osgi.framework.BundleContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.concurrent.GuardedBy; -import javax.annotation.concurrent.NotThreadSafe; -import javax.annotation.concurrent.ThreadSafe; -import javax.management.InstanceAlreadyExistsException; -import javax.management.InstanceNotFoundException; -import javax.management.MBeanServer; -import javax.management.MBeanServerFactory; -import javax.management.ObjectName; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; - /** * Singleton that is responsible for creating and committing Config * Transactions. It is registered in Platform MBean Server. */ @ThreadSafe public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBean { - private static final Logger logger = LoggerFactory.getLogger(ConfigRegistryImpl.class); + private static final Logger LOG = LoggerFactory.getLogger(ConfigRegistryImpl.class); + private static final ObjectName NOOP_TX_NAME = ObjectNameUtil.createTransactionControllerON("noop"); private final ModuleFactoriesResolver resolver; private final MBeanServer configMBeanServer; + private final BindingContextProvider bindingContextProvider; - @GuardedBy("this") - private final BundleContext bundleContext; - - @GuardedBy("this") - private long version = 0; - @GuardedBy("this") - private long versionCounter = 0; + private volatile long version = 0; + private volatile long versionCounter = 0; /** * Contains current configuration in form of {moduleName:{instanceName,read * only module}} for copying state to new transaction. Each running module * is present just once, no matter how many interfaces it exposes. */ - @GuardedBy("this") private final ConfigHolder currentConfig = new ConfigHolder(); /** @@ -80,14 +87,12 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe * validation but failed in second phase of commit. In this case the server * is unstable and its state is undefined. */ - @GuardedBy("this") - private boolean isHealthy = true; + private volatile boolean isHealthy = true; /** * Holds Map and purges it each time * its content is requested. */ - @GuardedBy("this") private final TransactionsHolder transactionsHolder = new TransactionsHolder(); private final BaseJMXRegistrator baseJMXRegistrator; @@ -99,25 +104,37 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe // internal jmx server shared by all transactions private final MBeanServer transactionsMBeanServer; - @GuardedBy("this") + private final AtomicBoolean closed = new AtomicBoolean(); + + private final Object readableSRRegistryLock = new Object(); + + private final Lock configTransactionLock = new ReentrantLock(); + + // Used for finding new factory instances for default module functionality + @GuardedBy("configTransactionLock") private List lastListOfFactories = Collections.emptyList(); + // switched in every 2ndPC + @GuardedBy("readableSRRegistryLock") + private CloseableServiceReferenceReadableRegistry readableSRRegistry = + ServiceReferenceRegistryImpl.createInitialSRLookupRegistry(); + // constructor public ConfigRegistryImpl(ModuleFactoriesResolver resolver, - BundleContext bundleContext, MBeanServer configMBeanServer) { - this(resolver, bundleContext, configMBeanServer, - new BaseJMXRegistrator(configMBeanServer)); + MBeanServer configMBeanServer, BindingContextProvider bindingContextProvider) { + this(resolver, configMBeanServer, + new BaseJMXRegistrator(configMBeanServer), bindingContextProvider); } // constructor public ConfigRegistryImpl(ModuleFactoriesResolver resolver, - BundleContext bundleContext, MBeanServer configMBeanServer, - BaseJMXRegistrator baseJMXRegistrator) { + MBeanServer configMBeanServer, + BaseJMXRegistrator baseJMXRegistrator, BindingContextProvider bindingContextProvider) { this.resolver = resolver; this.beanToOsgiServiceManager = new BeanToOsgiServiceManager(); - this.bundleContext = bundleContext; this.configMBeanServer = configMBeanServer; this.baseJMXRegistrator = baseJMXRegistrator; + this.bindingContextProvider = bindingContextProvider; this.registryMBeanServer = MBeanServerFactory .createMBeanServer("ConfigRegistry" + configMBeanServer.getDefaultDomain()); this.transactionsMBeanServer = MBeanServerFactory @@ -128,48 +145,132 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe * Create new {@link ConfigTransactionControllerImpl } */ @Override - public synchronized ObjectName beginConfig() { - return beginConfigInternal().getControllerObjectName(); + public ObjectName beginConfig() { + return beginConfig(false); + } + + /** + * @param blankTransaction true if this transaction is created automatically by + * org.opendaylight.controller.config.manager.impl.osgi.BlankTransactionServiceTracker + */ + public ObjectName beginConfig(boolean blankTransaction) { + // If we're closed or in the process of closing then all modules are destroyed or being destroyed + // so there's no point in trying to acquire the lock and beginning an actual transaction. Also we want + // to avoid trying to lock as it may block the shutdown process if there is an outstanding transaction + // attempting to be committed. + // + // We could throw an exception here to indicate this but that's not part of the original API contract + // and callers may not be expecting an unchecked exception. Therefore we return a special transaction + // handle that signifies a "no-op". + if(closed.get()) { + return NOOP_TX_NAME; + } + + if(blankTransaction) { + try { + // For a "blank" transaction, we'll try to obtain the config lock but "blank" transactions + // are initiated via OSGi events so we don't want to block indefinitely or for a long period + // of time. + if(!configTransactionLock.tryLock(5, TimeUnit.SECONDS)) { + LOG.debug("Timed out trying to obtain configTransactionLock"); + return NOOP_TX_NAME; + } + } catch(InterruptedException e) { + LOG.debug("Interrupted trying to obtain configTransactionLock", e); + Thread.currentThread().interrupt(); + return NOOP_TX_NAME; + } + } else { + configTransactionLock.lock(); + } + + try { + return beginConfigSafe(blankTransaction).getControllerObjectName(); + } finally { + configTransactionLock.unlock(); + } } - private synchronized ConfigTransactionControllerInternal beginConfigInternal() { + @GuardedBy("configTransactionLock") + private ConfigTransactionControllerInternal beginConfigSafe(boolean blankTransaction) { versionCounter++; - String transactionName = "ConfigTransaction-" + version + "-" + versionCounter; - TransactionJMXRegistrator transactionRegistrator = baseJMXRegistrator - .createTransactionJMXRegistrator(transactionName); - Map> allCurrentFactories = Collections.unmodifiableMap(resolver.getAllFactories()); + final String transactionName = "ConfigTransaction-" + version + "-" + versionCounter; + + TransactionJMXRegistratorFactory factory = new TransactionJMXRegistratorFactory() { + @Override + public TransactionJMXRegistrator create() { + return baseJMXRegistrator.createTransactionJMXRegistrator(transactionName); + } + }; + + Map> allCurrentFactories = new HashMap<>( + resolver.getAllFactories()); + + // add all factories that disappeared from SR but are still committed + for (ModuleInternalInfo moduleInternalInfo : currentConfig.getEntries()) { + String name = moduleInternalInfo.getModuleFactory().getImplementationName(); + if (!allCurrentFactories.containsKey(name)) { + LOG.trace("Factory {} not found in SR, using reference from previous commit", name); + allCurrentFactories.put(name, + Maps.immutableEntry(moduleInternalInfo.getModuleFactory(), moduleInternalInfo.getBundleContext())); + } + } + allCurrentFactories = Collections.unmodifiableMap(allCurrentFactories); + + // closed by transaction controller + ConfigTransactionLookupRegistry txLookupRegistry = new ConfigTransactionLookupRegistry(new TransactionIdentifier( + transactionName), factory, allCurrentFactories); + SearchableServiceReferenceWritableRegistry writableRegistry = ServiceReferenceRegistryImpl.createSRWritableRegistry( + readableSRRegistry, txLookupRegistry, allCurrentFactories); + ConfigTransactionControllerInternal transactionController = new ConfigTransactionControllerImpl( - transactionName, transactionRegistrator, version, - versionCounter, allCurrentFactories, transactionsMBeanServer, configMBeanServer, bundleContext); + txLookupRegistry, version, bindingContextProvider, + versionCounter, allCurrentFactories, transactionsMBeanServer, + configMBeanServer, blankTransaction, writableRegistry); try { - transactionRegistrator.registerMBean(transactionController, transactionController.getControllerObjectName()); + txLookupRegistry.registerMBean(transactionController, transactionController.getControllerObjectName()); } catch (InstanceAlreadyExistsException e) { throw new IllegalStateException(e); } - transactionController.copyExistingModulesAndProcessFactoryDiff(currentConfig.getEntries(), lastListOfFactories); - - transactionsHolder.add(transactionName, transactionController); + transactionsHolder.add(transactionName, transactionController, txLookupRegistry); return transactionController; } /** * {@inheritDoc} + * @throws ConflictingVersionException */ @Override - public synchronized CommitStatus commitConfig(ObjectName transactionControllerON) + public CommitStatus commitConfig(ObjectName transactionControllerON) + throws ValidationException, ConflictingVersionException { + if(NOOP_TX_NAME.equals(transactionControllerON) || closed.get()) { + return new CommitStatus(Collections.emptyList(), Collections.emptyList(), Collections.emptyList()); + } + + configTransactionLock.lock(); + try { + return commitConfigSafe(transactionControllerON); + } finally { + configTransactionLock.unlock(); + } + } + + @GuardedBy("configTransactionLock") + private CommitStatus commitConfigSafe(ObjectName transactionControllerON) throws ConflictingVersionException, ValidationException { final String transactionName = ObjectNameUtil .getTransactionName(transactionControllerON); - logger.info("About to commit {}. Current parentVersion: {}, versionCounter {}", transactionName, version, versionCounter); + LOG.trace("About to commit {}. Current parentVersion: {}, versionCounter {}", transactionName, version, versionCounter); // find ConfigTransactionController - Map transactions = transactionsHolder.getCurrentTransactions(); - ConfigTransactionControllerInternal configTransactionController = transactions.get(transactionName); - if (configTransactionController == null) { + Map> transactions = transactionsHolder.getCurrentTransactions(); + Entry configTransactionControllerEntry = transactions.get(transactionName); + if (configTransactionControllerEntry == null) { throw new IllegalArgumentException(String.format( "Transaction with name '%s' not found", transactionName)); } + ConfigTransactionControllerInternal configTransactionController = configTransactionControllerEntry.getKey(); // check optimistic lock if (version != configTransactionController.getParentVersion()) { throw new ConflictingVersionException( @@ -184,64 +285,68 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe lastListOfFactories = Collections.unmodifiableList(configTransactionController.getCurrentlyRegisteredFactories()); // non recoverable from here: try { - return secondPhaseCommit( - configTransactionController, commitInfo); - } catch (Throwable t) { // some libs throw Errors: e.g. - // javax.xml.ws.spi.FactoryFinder$ConfigurationError + return secondPhaseCommit(configTransactionController, commitInfo, configTransactionControllerEntry.getValue()); + // some libs throw Errors: e.g. + // javax.xml.ws.spi.FactoryFinder$ConfigurationError + } catch (Throwable t) { isHealthy = false; - logger.error("Configuration Transaction failed on 2PC, server is unhealthy", t); - if (t instanceof RuntimeException) { - throw (RuntimeException) t; - } else if (t instanceof Error) { - throw (Error) t; - } else { - throw new RuntimeException(t); - } + LOG.error("Configuration Transaction failed on 2PC, server is unhealthy", t); + throw Throwables.propagate(t); } } + @GuardedBy("configTransactionLock") private CommitStatus secondPhaseCommit(ConfigTransactionControllerInternal configTransactionController, - CommitInfo commitInfo) { + CommitInfo commitInfo, ConfigTransactionLookupRegistry txLookupRegistry) { // close instances which were destroyed by the user, including // (hopefully) runtime beans for (DestroyedModule toBeDestroyed : commitInfo .getDestroyedFromPreviousTransactions()) { - toBeDestroyed.close(); // closes instance (which should close - // runtime jmx registrator), + // closes instance (which should close + // runtime jmx registrator), // also closes osgi registration and ModuleJMXRegistrator // registration + toBeDestroyed.close(); currentConfig.remove(toBeDestroyed.getIdentifier()); } // set RuntimeBeanRegistrators on beans implementing - // RuntimeBeanRegistratorAwareModule, each module - // should have exactly one runtime jmx registrator. + // RuntimeBeanRegistratorAwareModule Map runtimeRegistrators = new HashMap<>(); for (ModuleInternalTransactionalInfo entry : commitInfo.getCommitted() .values()) { - RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator; - if (entry.hasOldModule() == false) { - runtimeBeanRegistrator = baseJMXRegistrator - .createRuntimeBeanRegistrator(entry.getName()); - } else { - // reuse old JMX registrator - runtimeBeanRegistrator = entry.getOldInternalInfo() - .getRuntimeBeanRegistrator(); - } // set runtime jmx registrator if required - Module module = entry.getModule(); + Module module = entry.getProxiedModule(); + RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator = null; + if (module instanceof RuntimeBeanRegistratorAwareModule) { - ((RuntimeBeanRegistratorAwareModule) module) - .setRuntimeBeanRegistrator(runtimeBeanRegistrator); + + if(entry.hasOldModule()) { + + if(module.canReuse(entry.getOldInternalInfo().getReadableModule().getModule())) { + runtimeBeanRegistrator = entry.getOldInternalInfo().getRuntimeBeanRegistrator(); + ((RuntimeBeanRegistratorAwareModule) module).setRuntimeBeanRegistrator(runtimeBeanRegistrator); + } else { + runtimeBeanRegistrator = baseJMXRegistrator.createRuntimeBeanRegistrator(entry.getIdentifier()); + entry.getOldInternalInfo().getRuntimeBeanRegistrator().close(); + ((RuntimeBeanRegistratorAwareModule) module).setRuntimeBeanRegistrator(runtimeBeanRegistrator); + } + } else { + runtimeBeanRegistrator = baseJMXRegistrator.createRuntimeBeanRegistrator(entry.getIdentifier()); + ((RuntimeBeanRegistratorAwareModule) module).setRuntimeBeanRegistrator(runtimeBeanRegistrator); + } } // save it to info so it is accessible afterwards - runtimeRegistrators.put(entry.getName(), runtimeBeanRegistrator); + if(runtimeBeanRegistrator != null) { + runtimeRegistrators.put(entry.getIdentifier(), runtimeBeanRegistrator); + } } // can register runtime beans - List orderedModuleIdentifiers = configTransactionController - .secondPhaseCommit(); + List orderedModuleIdentifiers = configTransactionController.secondPhaseCommit(); + txLookupRegistry.close(); + configTransactionController.close(); // copy configuration to read only mode List newInstances = new LinkedList<>(); @@ -252,20 +357,21 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe int orderingIdx = 0; for (ModuleIdentifier moduleIdentifier : orderedModuleIdentifiers) { + LOG.trace("Registering {}", moduleIdentifier); ModuleInternalTransactionalInfo entry = commitInfo.getCommitted() .get(moduleIdentifier); - if (entry == null) + if (entry == null) { throw new NullPointerException("Module not found " + moduleIdentifier); - Module module = entry.getModule(); + } + ObjectName primaryReadOnlyON = ObjectNameUtil .createReadOnlyModuleON(moduleIdentifier); // determine if current instance was recreated or reused or is new // rules for closing resources: - // osgi registration - will be (re)created every time, so it needs - // to be closed here + // osgi registration - will be reused if possible. // module jmx registration - will be (re)created every time, needs // to be closed here // runtime jmx registration - should be taken care of by module @@ -275,14 +381,14 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe .createModuleJMXRegistrator(); OsgiRegistration osgiRegistration = null; + AutoCloseable instance = entry.getProxiedModule().getInstance(); if (entry.hasOldModule()) { ModuleInternalInfo oldInternalInfo = entry.getOldInternalInfo(); - DynamicReadableWrapper oldReadableConfigBean = oldInternalInfo - .getReadableModule(); - currentConfig.remove(entry.getName()); + DynamicReadableWrapper oldReadableConfigBean = oldInternalInfo.getReadableModule(); + currentConfig.remove(entry.getIdentifier()); // test if old instance == new instance - if (oldReadableConfigBean.getInstance().equals(module.getInstance())) { + if (oldReadableConfigBean.getInstance().equals(instance)) { // reused old instance: // wrap in readable dynamic mbean reusedInstances.add(primaryReadOnlyON); @@ -299,52 +405,62 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe // close old module jmx registrator oldInternalInfo.getModuleJMXRegistrator().close(); + + // We no longer need old internal info. Clear it out, so we do not create a serial leak evidenced + // by BUG-4514. The reason is that modules retain their resolver, which retains modules. If we retain + // the old module, we would have the complete reconfiguration history held in heap for no good reason. + entry.clearOldInternalInfo(); } else { // new instance: // wrap in readable dynamic mbean newInstances.add(primaryReadOnlyON); } + Module realModule = entry.getRealModule(); DynamicReadableWrapper newReadableConfigBean = new DynamicReadableWrapper( - module, module.getInstance(), moduleIdentifier, + realModule, instance, moduleIdentifier, registryMBeanServer, configMBeanServer); // register to JMX try { - newModuleJMXRegistrator.registerMBean(newReadableConfigBean, - primaryReadOnlyON); + newModuleJMXRegistrator.registerMBean(newReadableConfigBean, primaryReadOnlyON); } catch (InstanceAlreadyExistsException e) { - throw new IllegalStateException(e); + throw new IllegalStateException("Possible code error, already registered:" + primaryReadOnlyON,e); } - // register to OSGi + // register services to OSGi + Map annotationMapping = configTransactionController.getWritableRegistry().findServiceInterfaces(moduleIdentifier); + BundleContext bc = configTransactionController.getModuleFactoryBundleContext( + entry.getModuleFactory().getImplementationName()); if (osgiRegistration == null) { - ModuleFactory moduleFactory = entry.getModuleFactory(); - if(moduleFactory != null) { - BundleContext bc = configTransactionController. - getModuleFactoryBundleContext(moduleFactory.getImplementationName()); - osgiRegistration = beanToOsgiServiceManager.registerToOsgi(module.getClass(), - newReadableConfigBean.getInstance(), entry.getName(), bc); - } else { - throw new NullPointerException(entry.getIdentifier().getFactoryName() + " ModuleFactory not found."); - } - + osgiRegistration = beanToOsgiServiceManager.registerToOsgi( + newReadableConfigBean.getInstance(), moduleIdentifier, bc, annotationMapping); + } else { + osgiRegistration.updateRegistrations(annotationMapping, bc, instance); } RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator = runtimeRegistrators - .get(entry.getName()); + .get(entry.getIdentifier()); ModuleInternalInfo newInfo = new ModuleInternalInfo( - entry.getName(), newReadableConfigBean, osgiRegistration, + entry.getIdentifier(), newReadableConfigBean, osgiRegistration, runtimeBeanRegistrator, newModuleJMXRegistrator, - orderingIdx); + orderingIdx, entry.isDefaultBean(), entry.getModuleFactory(), entry.getBundleContext()); - newConfigEntries.put(module, newInfo); + newConfigEntries.put(realModule, newInfo); orderingIdx++; } currentConfig.addAll(newConfigEntries.values()); // update version version = configTransactionController.getVersion(); + + // switch readable Service Reference Registry + synchronized(readableSRRegistryLock) { + readableSRRegistry.close(); + readableSRRegistry = ServiceReferenceRegistryImpl.createSRReadableRegistry( + configTransactionController.getWritableRegistry(), this, baseJMXRegistrator); + } + return new CommitStatus(newInstances, reusedInstances, recreatedInstances); } @@ -353,13 +469,13 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe * {@inheritDoc} */ @Override - public synchronized List getOpenConfigs() { - Map transactions = transactionsHolder + public List getOpenConfigs() { + Map> transactions = transactionsHolder .getCurrentTransactions(); List result = new ArrayList<>(transactions.size()); - for (ConfigTransactionControllerInternal configTransactionController : transactions + for (Entry configTransactionControllerEntry : transactions .values()) { - result.add(configTransactionController.getControllerObjectName()); + result.add(configTransactionControllerEntry.getKey().getControllerObjectName()); } return result; } @@ -367,28 +483,35 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe /** * Abort open transactions and unregister read only modules. Since this * class is not responsible for registering itself under - * {@link ConfigRegistryMXBean#OBJECT_NAME}, it will not unregister itself + * {@link org.opendaylight.controller.config.api.ConfigRegistry#OBJECT_NAME}, it will not unregister itself * here. */ @Override - public synchronized void close() { + public void close() { + if(!closed.compareAndSet(false, true)) { + return; + } + // abort transactions - Map transactions = transactionsHolder + Map> transactions = transactionsHolder .getCurrentTransactions(); - for (ConfigTransactionControllerInternal configTransactionController : transactions + for (Entry configTransactionControllerEntry : transactions .values()) { + + ConfigTransactionControllerInternal configTransactionController = configTransactionControllerEntry.getKey(); try { + configTransactionControllerEntry.getValue().close(); configTransactionController.abortConfig(); } catch (RuntimeException e) { - logger.warn("Ignoring exception while aborting {}", - configTransactionController, e); + LOG.debug("Ignoring exception while aborting {}", configTransactionController, e); } } - // destroy all live objects one after another in order of the dependency - // hierarchy - List destroyedModules = currentConfig - .getModulesToBeDestroyed(); + // destroy all live objects one after another in order of the dependency hierarchy, from top to bottom + List destroyedModules = currentConfig.getModulesToBeDestroyed(); + + LOG.info("ConfigRegistry closing - destroying {} modules", destroyedModules.size()); + for (DestroyedModule destroyedModule : destroyedModules) { destroyedModule.close(); } @@ -398,6 +521,7 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe MBeanServerFactory.releaseMBeanServer(registryMBeanServer); MBeanServerFactory.releaseMBeanServer(transactionsMBeanServer); + LOG.info("ConfigRegistry closed"); } /** @@ -457,7 +581,7 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe */ @Override public Set lookupConfigBeans(String moduleName, - String instanceName) { + String instanceName) { ObjectName namePattern = ObjectNameUtil.createModulePattern(moduleName, instanceName); return baseJMXRegistrator.queryNames(namePattern, null); @@ -476,31 +600,102 @@ public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBe */ @Override public Set lookupRuntimeBeans(String moduleName, - String instanceName) { - if (moduleName == null) - moduleName = "*"; - if (instanceName == null) - instanceName = "*"; + String instanceName) { + String finalModuleName = moduleName == null ? "*" : moduleName; + String finalInstanceName = instanceName == null ? "*" : instanceName; ObjectName namePattern = ObjectNameUtil.createRuntimeBeanPattern( - moduleName, instanceName); + finalModuleName, finalInstanceName); return baseJMXRegistrator.queryNames(namePattern, null); } + @Override + public void checkConfigBeanExists(ObjectName objectName) throws InstanceNotFoundException { + ObjectNameUtil.checkDomain(objectName); + ObjectNameUtil.checkType(objectName, ObjectNameUtil.TYPE_MODULE); + String transactionName = ObjectNameUtil.getTransactionName(objectName); + if (transactionName != null) { + throw new IllegalArgumentException("Transaction attribute not supported in registry, wrong ObjectName: " + objectName); + } + // make sure exactly one match is found: + LookupBeansUtil.lookupConfigBean(this, ObjectNameUtil.getFactoryName(objectName), ObjectNameUtil.getInstanceName(objectName)); + } + + // service reference functionality: + @Override + public ObjectName lookupConfigBeanByServiceInterfaceName(String serviceInterfaceQName, String refName) { + synchronized(readableSRRegistryLock) { + return readableSRRegistry.lookupConfigBeanByServiceInterfaceName(serviceInterfaceQName, refName); + } + } + + @Override + public Map> getServiceMapping() { + synchronized(readableSRRegistryLock) { + return readableSRRegistry.getServiceMapping(); + } + } + + @Override + public Map lookupServiceReferencesByServiceInterfaceName(String serviceInterfaceQName) { + synchronized(readableSRRegistryLock) { + return readableSRRegistry.lookupServiceReferencesByServiceInterfaceName(serviceInterfaceQName); + } + } + + @Override + public Set lookupServiceInterfaceNames(ObjectName objectName) throws InstanceNotFoundException { + synchronized(readableSRRegistryLock) { + return readableSRRegistry.lookupServiceInterfaceNames(objectName); + } + } + + @Override + public String getServiceInterfaceName(String namespace, String localName) { + synchronized(readableSRRegistryLock) { + return readableSRRegistry.getServiceInterfaceName(namespace, localName); + } + } + + @Override + public void checkServiceReferenceExists(ObjectName objectName) throws InstanceNotFoundException { + synchronized(readableSRRegistryLock) { + readableSRRegistry.checkServiceReferenceExists(objectName); + } + } + + @Override + public ObjectName getServiceReference(String serviceInterfaceQName, String refName) throws InstanceNotFoundException { + synchronized(readableSRRegistryLock) { + return readableSRRegistry.getServiceReference(serviceInterfaceQName, refName); + } + } + + @Override + public Set getAvailableModuleFactoryQNames() { + return ModuleQNameUtil.getQNames(resolver.getAllFactories()); + } + + @Override + public String toString() { + return "ConfigRegistryImpl{" + + "versionCounter=" + versionCounter + + ", version=" + version + + '}'; + } } /** * Holds currently running modules */ -@NotThreadSafe class ConfigHolder { - private final Map currentConfig = new HashMap<>(); + private final ConcurrentMap currentConfig = new ConcurrentHashMap<>(); /** * Add all modules to the internal map. Also add service instance to OSGi * Service Registry. */ public void addAll(Collection configInfos) { - if (currentConfig.size() > 0) { + if (!currentConfig.isEmpty()) { throw new IllegalStateException( "Error - some config entries were not removed: " + currentConfig); @@ -511,12 +706,11 @@ class ConfigHolder { } private void add(ModuleInternalInfo configInfo) { - ModuleInternalInfo oldValue = currentConfig.put(configInfo.getName(), - configInfo); + ModuleInternalInfo oldValue = currentConfig.putIfAbsent(configInfo.getIdentifier(), configInfo); if (oldValue != null) { throw new IllegalStateException( "Cannot overwrite module with same name:" - + configInfo.getName() + ":" + configInfo); + + configInfo.getIdentifier() + ":" + configInfo); } } @@ -543,31 +737,28 @@ class ConfigHolder { Collections.sort(result); return result; } + + } /** * Holds Map and purges it each time its * content is requested. */ -@NotThreadSafe class TransactionsHolder { /** * This map keeps transaction names and * {@link ConfigTransactionControllerInternal} instances, because platform * MBeanServer transforms mbeans into another representation. Map is cleaned * every time current transactions are requested. - * */ - @GuardedBy("ConfigRegistryImpl.this") - private final Map transactions = new HashMap<>(); + private final ConcurrentMap> transactions = new ConcurrentHashMap<>(); - /** - * Can only be called from within synchronized method. - */ public void add(String transactionName, - ConfigTransactionControllerInternal transactionController) { - Object oldValue = transactions.put(transactionName, - transactionController); + ConfigTransactionControllerInternal transactionController, ConfigTransactionLookupRegistry txLookupRegistry) { + Object oldValue = transactions.putIfAbsent(transactionName, + Maps.immutableEntry(transactionController, txLookupRegistry)); if (oldValue != null) { throw new IllegalStateException( "Error: two transactions with same name"); @@ -575,19 +766,18 @@ class TransactionsHolder { } /** - * Purges closed transactions from transactions map. Can only be called from - * within synchronized method. Calling this method more than once within the + * Purges closed transactions from transactions map. Calling this method more than once within the * method can modify the resulting map that was obtained in previous calls. * * @return current view on transactions map. */ - public Map getCurrentTransactions() { + public Map> getCurrentTransactions() { // first, remove closed transaction - for (Iterator> it = transactions - .entrySet().iterator(); it.hasNext();) { - Entry entry = it + for (Iterator>> it = transactions + .entrySet().iterator(); it.hasNext(); ) { + Entry> entry = it .next(); - if (entry.getValue().isClosed()) { + if (entry.getValue().getKey().isClosed()) { it.remove(); } }