Merge "MD-SAL Statistics Manager -Added group-id to group-statistics API"
[controller.git] / opendaylight / config / config-manager / src / main / java / org / opendaylight / controller / config / manager / impl / ConfigRegistryImpl.java
1 /*
2  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.controller.config.manager.impl;
9
10 import org.opendaylight.controller.config.api.ConflictingVersionException;
11 import org.opendaylight.controller.config.api.ModuleIdentifier;
12 import org.opendaylight.controller.config.api.RuntimeBeanRegistratorAwareModule;
13 import org.opendaylight.controller.config.api.ValidationException;
14 import org.opendaylight.controller.config.api.jmx.CommitStatus;
15 import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
16 import org.opendaylight.controller.config.manager.impl.dynamicmbean.DynamicReadableWrapper;
17 import org.opendaylight.controller.config.manager.impl.factoriesresolver.HierarchicalConfigMBeanFactoriesHolder;
18 import org.opendaylight.controller.config.manager.impl.factoriesresolver.ModuleFactoriesResolver;
19 import org.opendaylight.controller.config.manager.impl.jmx.BaseJMXRegistrator;
20 import org.opendaylight.controller.config.manager.impl.jmx.ModuleJMXRegistrator;
21 import org.opendaylight.controller.config.manager.impl.jmx.RootRuntimeBeanRegistratorImpl;
22 import org.opendaylight.controller.config.manager.impl.jmx.TransactionJMXRegistrator;
23 import org.opendaylight.controller.config.manager.impl.osgi.BeanToOsgiServiceManager;
24 import org.opendaylight.controller.config.manager.impl.osgi.BeanToOsgiServiceManager.OsgiRegistration;
25 import org.opendaylight.controller.config.manager.impl.util.LookupBeansUtil;
26 import org.opendaylight.controller.config.spi.Module;
27 import org.opendaylight.controller.config.spi.ModuleFactory;
28 import org.osgi.framework.BundleContext;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 import javax.annotation.concurrent.GuardedBy;
33 import javax.annotation.concurrent.NotThreadSafe;
34 import javax.annotation.concurrent.ThreadSafe;
35 import javax.management.InstanceAlreadyExistsException;
36 import javax.management.InstanceNotFoundException;
37 import javax.management.MBeanServer;
38 import javax.management.MBeanServerFactory;
39 import javax.management.ObjectName;
40 import java.util.ArrayList;
41 import java.util.Collection;
42 import java.util.Collections;
43 import java.util.HashMap;
44 import java.util.Iterator;
45 import java.util.LinkedList;
46 import java.util.List;
47 import java.util.Map;
48 import java.util.Map.Entry;
49 import java.util.Set;
50
51 /**
52  * Singleton that is responsible for creating and committing Config
53  * Transactions. It is registered in Platform MBean Server.
54  */
55 @ThreadSafe
56 public class ConfigRegistryImpl implements AutoCloseable, ConfigRegistryImplMXBean {
57     private static final Logger logger = LoggerFactory.getLogger(ConfigRegistryImpl.class);
58
59     private final ModuleFactoriesResolver resolver;
60     private final MBeanServer configMBeanServer;
61
62     @GuardedBy("this")
63     private final BundleContext bundleContext;
64
65     @GuardedBy("this")
66     private long version = 0;
67     @GuardedBy("this")
68     private long versionCounter = 0;
69
70     /**
71      * Contains current configuration in form of {moduleName:{instanceName,read
72      * only module}} for copying state to new transaction. Each running module
73      * is present just once, no matter how many interfaces it exposes.
74      */
75     @GuardedBy("this")
76     private final ConfigHolder currentConfig = new ConfigHolder();
77
78     /**
79      * Will return true unless there was a transaction that succeeded during
80      * validation but failed in second phase of commit. In this case the server
81      * is unstable and its state is undefined.
82      */
83     @GuardedBy("this")
84     private boolean isHealthy = true;
85
86     /**
87      * Holds Map<transactionName, transactionController> and purges it each time
88      * its content is requested.
89      */
90     @GuardedBy("this")
91     private final TransactionsHolder transactionsHolder = new TransactionsHolder();
92
93     private final BaseJMXRegistrator baseJMXRegistrator;
94
95     private final BeanToOsgiServiceManager beanToOsgiServiceManager;
96
97     // internal jmx server for read only beans
98     private final MBeanServer registryMBeanServer;
99     // internal jmx server shared by all transactions
100     private final MBeanServer transactionsMBeanServer;
101
102     @GuardedBy("this")
103     private List<ModuleFactory> lastListOfFactories = Collections.emptyList();
104
105     // constructor
106     public ConfigRegistryImpl(ModuleFactoriesResolver resolver,
107             BundleContext bundleContext, MBeanServer configMBeanServer) {
108         this(resolver, bundleContext, configMBeanServer,
109                 new BaseJMXRegistrator(configMBeanServer));
110     }
111
112     // constructor
113     public ConfigRegistryImpl(ModuleFactoriesResolver resolver,
114             BundleContext bundleContext, MBeanServer configMBeanServer,
115             BaseJMXRegistrator baseJMXRegistrator) {
116         this.resolver = resolver;
117         this.beanToOsgiServiceManager = new BeanToOsgiServiceManager(
118                 bundleContext);
119         this.bundleContext = bundleContext;
120         this.configMBeanServer = configMBeanServer;
121         this.baseJMXRegistrator = baseJMXRegistrator;
122         this.registryMBeanServer = MBeanServerFactory
123                 .createMBeanServer("ConfigRegistry" + configMBeanServer.getDefaultDomain());
124         this.transactionsMBeanServer = MBeanServerFactory
125                 .createMBeanServer("ConfigTransactions" + configMBeanServer.getDefaultDomain());
126     }
127
128     /**
129      * Create new {@link ConfigTransactionControllerImpl }
130      */
131     @Override
132     public synchronized ObjectName beginConfig() {
133         return beginConfigInternal().getControllerObjectName();
134     }
135
136     private synchronized ConfigTransactionControllerInternal beginConfigInternal() {
137         versionCounter++;
138         String transactionName = "ConfigTransaction-" + version + "-" + versionCounter;
139         TransactionJMXRegistrator transactionRegistrator = baseJMXRegistrator
140                 .createTransactionJMXRegistrator(transactionName);
141         List<ModuleFactory> allCurrentFactories = Collections.unmodifiableList(resolver.getAllFactories());
142         ConfigTransactionControllerInternal transactionController = new ConfigTransactionControllerImpl(
143                 transactionName, transactionRegistrator, version,
144                 versionCounter, allCurrentFactories, transactionsMBeanServer, configMBeanServer, bundleContext);
145         try {
146             transactionRegistrator.registerMBean(transactionController, transactionController.getControllerObjectName());
147         } catch (InstanceAlreadyExistsException e) {
148             throw new IllegalStateException(e);
149         }
150
151         transactionController.copyExistingModulesAndProcessFactoryDiff(currentConfig.getEntries(), lastListOfFactories);
152
153         transactionsHolder.add(transactionName, transactionController);
154         return transactionController;
155     }
156
157     /**
158      * {@inheritDoc}
159      */
160     @Override
161     public synchronized CommitStatus commitConfig(ObjectName transactionControllerON)
162             throws ConflictingVersionException, ValidationException {
163         final String transactionName = ObjectNameUtil
164                 .getTransactionName(transactionControllerON);
165         logger.info("About to commit {}. Current parentVersion: {}, versionCounter {}", transactionName, version, versionCounter);
166
167         // find ConfigTransactionController
168         Map<String, ConfigTransactionControllerInternal> transactions = transactionsHolder.getCurrentTransactions();
169         ConfigTransactionControllerInternal configTransactionController = transactions.get(transactionName);
170         if (configTransactionController == null) {
171             throw new IllegalArgumentException(String.format(
172                     "Transaction with name '%s' not found", transactionName));
173         }
174         // check optimistic lock
175         if (version != configTransactionController.getParentVersion()) {
176             throw new ConflictingVersionException(
177                     String.format(
178                             "Optimistic lock failed. Expected parent version %d, was %d",
179                             version,
180                             configTransactionController.getParentVersion()));
181         }
182         // optimistic lock ok
183
184         CommitInfo commitInfo = configTransactionController.validateBeforeCommitAndLockTransaction();
185         lastListOfFactories = Collections.unmodifiableList(configTransactionController.getCurrentlyRegisteredFactories());
186         // non recoverable from here:
187         try {
188             return secondPhaseCommit(
189                     configTransactionController, commitInfo);
190         } catch (Throwable t) { // some libs throw Errors: e.g.
191                                 // javax.xml.ws.spi.FactoryFinder$ConfigurationError
192             isHealthy = false;
193             logger.error("Configuration Transaction failed on 2PC, server is unhealthy", t);
194             if (t instanceof RuntimeException) {
195                 throw (RuntimeException) t;
196             } else if (t instanceof Error) {
197                 throw (Error) t;
198             } else {
199                 throw new RuntimeException(t);
200             }
201         }
202     }
203
204     private CommitStatus secondPhaseCommit(ConfigTransactionControllerInternal configTransactionController,
205                                            CommitInfo commitInfo) {
206
207         // close instances which were destroyed by the user, including
208         // (hopefully) runtime beans
209         for (DestroyedModule toBeDestroyed : commitInfo
210                 .getDestroyedFromPreviousTransactions()) {
211             toBeDestroyed.close(); // closes instance (which should close
212                                    // runtime jmx registrator),
213             // also closes osgi registration and ModuleJMXRegistrator
214             // registration
215             currentConfig.remove(toBeDestroyed.getIdentifier());
216         }
217
218         // set RuntimeBeanRegistrators on beans implementing
219         // RuntimeBeanRegistratorAwareModule, each module
220         // should have exactly one runtime jmx registrator.
221         Map<ModuleIdentifier, RootRuntimeBeanRegistratorImpl> runtimeRegistrators = new HashMap<>();
222         for (ModuleInternalTransactionalInfo entry : commitInfo.getCommitted()
223                 .values()) {
224             RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator;
225             if (entry.hasOldModule() == false) {
226                 runtimeBeanRegistrator = baseJMXRegistrator
227                         .createRuntimeBeanRegistrator(entry.getName());
228             } else {
229                 // reuse old JMX registrator
230                 runtimeBeanRegistrator = entry.getOldInternalInfo()
231                         .getRuntimeBeanRegistrator();
232             }
233             // set runtime jmx registrator if required
234             Module module = entry.getModule();
235             if (module instanceof RuntimeBeanRegistratorAwareModule) {
236                 ((RuntimeBeanRegistratorAwareModule) module)
237                         .setRuntimeBeanRegistrator(runtimeBeanRegistrator);
238             }
239             // save it to info so it is accessible afterwards
240             runtimeRegistrators.put(entry.getName(), runtimeBeanRegistrator);
241         }
242
243         // can register runtime beans
244         List<ModuleIdentifier> orderedModuleIdentifiers = configTransactionController
245                 .secondPhaseCommit();
246
247         // copy configuration to read only mode
248         List<ObjectName> newInstances = new LinkedList<>();
249         List<ObjectName> reusedInstances = new LinkedList<>();
250         List<ObjectName> recreatedInstances = new LinkedList<>();
251
252         Map<Module, ModuleInternalInfo> newConfigEntries = new HashMap<>();
253
254         int orderingIdx = 0;
255         for (ModuleIdentifier moduleIdentifier : orderedModuleIdentifiers) {
256             ModuleInternalTransactionalInfo entry = commitInfo.getCommitted()
257                     .get(moduleIdentifier);
258             if (entry == null)
259                 throw new NullPointerException("Module not found "
260                         + moduleIdentifier);
261             Module module = entry.getModule();
262             ObjectName primaryReadOnlyON = ObjectNameUtil
263                     .createReadOnlyModuleON(moduleIdentifier);
264
265             // determine if current instance was recreated or reused or is new
266
267             // rules for closing resources:
268             // osgi registration - will be (re)created every time, so it needs
269             // to be closed here
270             // module jmx registration - will be (re)created every time, needs
271             // to be closed here
272             // runtime jmx registration - should be taken care of by module
273             // itself
274             // instance - is closed only if it was destroyed
275             ModuleJMXRegistrator newModuleJMXRegistrator = baseJMXRegistrator
276                     .createModuleJMXRegistrator();
277
278             OsgiRegistration osgiRegistration = null;
279             if (entry.hasOldModule()) {
280                 ModuleInternalInfo oldInternalInfo = entry.getOldInternalInfo();
281                 DynamicReadableWrapper oldReadableConfigBean = oldInternalInfo
282                         .getReadableModule();
283                 currentConfig.remove(entry.getName());
284
285                 // test if old instance == new instance
286                 if (oldReadableConfigBean.getInstance().equals(module.getInstance())) {
287                     // reused old instance:
288                     // wrap in readable dynamic mbean
289                     reusedInstances.add(primaryReadOnlyON);
290                     osgiRegistration = oldInternalInfo.getOsgiRegistration();
291                 } else {
292                     // recreated instance:
293                     // it is responsibility of module to call the old instance -
294                     // we just need to unregister configbean
295                     recreatedInstances.add(primaryReadOnlyON);
296
297                     // close old osgi registration
298                     oldInternalInfo.getOsgiRegistration().close();
299                 }
300
301                 // close old module jmx registrator
302                 oldInternalInfo.getModuleJMXRegistrator().close();
303             } else {
304                 // new instance:
305                 // wrap in readable dynamic mbean
306                 newInstances.add(primaryReadOnlyON);
307             }
308
309             DynamicReadableWrapper newReadableConfigBean = new DynamicReadableWrapper(
310                     module, module.getInstance(), moduleIdentifier,
311                     registryMBeanServer, configMBeanServer);
312
313             // register to JMX
314             try {
315                 newModuleJMXRegistrator.registerMBean(newReadableConfigBean,
316                         primaryReadOnlyON);
317             } catch (InstanceAlreadyExistsException e) {
318                 throw new IllegalStateException(e);
319             }
320
321             // register to OSGi
322             if (osgiRegistration == null) {
323                 osgiRegistration = beanToOsgiServiceManager.registerToOsgi(module.getClass(),
324                         newReadableConfigBean.getInstance(), entry.getName());
325             }
326
327             RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator = runtimeRegistrators
328                     .get(entry.getName());
329             ModuleInternalInfo newInfo = new ModuleInternalInfo(
330                     entry.getName(), newReadableConfigBean, osgiRegistration,
331                     runtimeBeanRegistrator, newModuleJMXRegistrator,
332                     orderingIdx);
333
334             newConfigEntries.put(module, newInfo);
335             orderingIdx++;
336         }
337         currentConfig.addAll(newConfigEntries.values());
338
339         // update version
340         version = configTransactionController.getVersion();
341         return new CommitStatus(newInstances, reusedInstances,
342                 recreatedInstances);
343     }
344
345     /**
346      * {@inheritDoc}
347      */
348     @Override
349     public synchronized List<ObjectName> getOpenConfigs() {
350         Map<String, ConfigTransactionControllerInternal> transactions = transactionsHolder
351                 .getCurrentTransactions();
352         List<ObjectName> result = new ArrayList<>(transactions.size());
353         for (ConfigTransactionControllerInternal configTransactionController : transactions
354                 .values()) {
355             result.add(configTransactionController.getControllerObjectName());
356         }
357         return result;
358     }
359
360     /**
361      * Abort open transactions and unregister read only modules. Since this
362      * class is not responsible for registering itself under
363      * {@link ConfigRegistryMXBean#OBJECT_NAME}, it will not unregister itself
364      * here.
365      */
366     @Override
367     public synchronized void close() {
368         // abort transactions
369         Map<String, ConfigTransactionControllerInternal> transactions = transactionsHolder
370                 .getCurrentTransactions();
371         for (ConfigTransactionControllerInternal configTransactionController : transactions
372                 .values()) {
373             try {
374                 configTransactionController.abortConfig();
375             } catch (RuntimeException e) {
376                 logger.warn("Ignoring exception while aborting {}",
377                         configTransactionController, e);
378             }
379         }
380
381         // destroy all live objects one after another in order of the dependency
382         // hierarchy
383         List<DestroyedModule> destroyedModules = currentConfig
384                 .getModulesToBeDestroyed();
385         for (DestroyedModule destroyedModule : destroyedModules) {
386             destroyedModule.close();
387         }
388         // unregister MBeans that failed to unregister properly
389         baseJMXRegistrator.close();
390         // remove jmx servers
391         MBeanServerFactory.releaseMBeanServer(registryMBeanServer);
392         MBeanServerFactory.releaseMBeanServer(transactionsMBeanServer);
393
394     }
395
396     /**
397      * {@inheritDoc}
398      */
399     @Override
400     public long getVersion() {
401         return version;
402     }
403
404     /**
405      * {@inheritDoc}
406      */
407     @Override
408     public Set<String> getAvailableModuleNames() {
409         return new HierarchicalConfigMBeanFactoriesHolder(
410                 resolver.getAllFactories()).getModuleNames();
411     }
412
413     /**
414      * {@inheritDoc}
415      */
416     @Override
417     public boolean isHealthy() {
418         return isHealthy;
419     }
420
421     // filtering methods
422
423     /**
424      * {@inheritDoc}
425      */
426     @Override
427     public Set<ObjectName> lookupConfigBeans() {
428         return lookupConfigBeans("*", "*");
429     }
430
431     /**
432      * {@inheritDoc}
433      */
434     @Override
435     public Set<ObjectName> lookupConfigBeans(String moduleName) {
436         return lookupConfigBeans(moduleName, "*");
437     }
438
439     /**
440      * {@inheritDoc}
441      */
442     @Override
443     public ObjectName lookupConfigBean(String moduleName, String instanceName)
444             throws InstanceNotFoundException {
445         return LookupBeansUtil.lookupConfigBean(this, moduleName, instanceName);
446     }
447
448     /**
449      * {@inheritDoc}
450      */
451     @Override
452     public Set<ObjectName> lookupConfigBeans(String moduleName,
453             String instanceName) {
454         ObjectName namePattern = ObjectNameUtil.createModulePattern(moduleName,
455                 instanceName);
456         return baseJMXRegistrator.queryNames(namePattern, null);
457     }
458
459     /**
460      * {@inheritDoc}
461      */
462     @Override
463     public Set<ObjectName> lookupRuntimeBeans() {
464         return lookupRuntimeBeans("*", "*");
465     }
466
467     /**
468      * {@inheritDoc}
469      */
470     @Override
471     public Set<ObjectName> lookupRuntimeBeans(String moduleName,
472             String instanceName) {
473         if (moduleName == null)
474             moduleName = "*";
475         if (instanceName == null)
476             instanceName = "*";
477         ObjectName namePattern = ObjectNameUtil.createRuntimeBeanPattern(
478                 moduleName, instanceName);
479         return baseJMXRegistrator.queryNames(namePattern, null);
480     }
481
482 }
483
484 /**
485  * Holds currently running modules
486  */
487 @NotThreadSafe
488 class ConfigHolder {
489     private final Map<ModuleIdentifier, ModuleInternalInfo> currentConfig = new HashMap<>();
490
491     /**
492      * Add all modules to the internal map. Also add service instance to OSGi
493      * Service Registry.
494      */
495     public void addAll(Collection<ModuleInternalInfo> configInfos) {
496         if (currentConfig.size() > 0) {
497             throw new IllegalStateException(
498                     "Error - some config entries were not removed: "
499                             + currentConfig);
500         }
501         for (ModuleInternalInfo configInfo : configInfos) {
502             add(configInfo);
503         }
504     }
505
506     private void add(ModuleInternalInfo configInfo) {
507         ModuleInternalInfo oldValue = currentConfig.put(configInfo.getName(),
508                 configInfo);
509         if (oldValue != null) {
510             throw new IllegalStateException(
511                     "Cannot overwrite module with same name:"
512                             + configInfo.getName() + ":" + configInfo);
513         }
514     }
515
516     /**
517      * Remove entry from current config.
518      */
519     public void remove(ModuleIdentifier name) {
520         ModuleInternalInfo removed = currentConfig.remove(name);
521         if (removed == null) {
522             throw new IllegalStateException(
523                     "Cannot remove from ConfigHolder - name not found:" + name);
524         }
525     }
526
527     public Collection<ModuleInternalInfo> getEntries() {
528         return currentConfig.values();
529     }
530
531     public List<DestroyedModule> getModulesToBeDestroyed() {
532         List<DestroyedModule> result = new ArrayList<>();
533         for (ModuleInternalInfo moduleInternalInfo : getEntries()) {
534             result.add(moduleInternalInfo.toDestroyedModule());
535         }
536         Collections.sort(result);
537         return result;
538     }
539 }
540
541 /**
542  * Holds Map<transactionName, transactionController> and purges it each time its
543  * content is requested.
544  */
545 @NotThreadSafe
546 class TransactionsHolder {
547     /**
548      * This map keeps transaction names and
549      * {@link ConfigTransactionControllerInternal} instances, because platform
550      * MBeanServer transforms mbeans into another representation. Map is cleaned
551      * every time current transactions are requested.
552      *
553      */
554     @GuardedBy("ConfigRegistryImpl.this")
555     private final Map<String /* transactionName */, ConfigTransactionControllerInternal> transactions = new HashMap<>();
556
557     /**
558      * Can only be called from within synchronized method.
559      */
560     public void add(String transactionName,
561             ConfigTransactionControllerInternal transactionController) {
562         Object oldValue = transactions.put(transactionName,
563                 transactionController);
564         if (oldValue != null) {
565             throw new IllegalStateException(
566                     "Error: two transactions with same name");
567         }
568     }
569
570     /**
571      * Purges closed transactions from transactions map. Can only be called from
572      * within synchronized method. Calling this method more than once within the
573      * method can modify the resulting map that was obtained in previous calls.
574      *
575      * @return current view on transactions map.
576      */
577     public Map<String, ConfigTransactionControllerInternal> getCurrentTransactions() {
578         // first, remove closed transaction
579         for (Iterator<Entry<String, ConfigTransactionControllerInternal>> it = transactions
580                 .entrySet().iterator(); it.hasNext();) {
581             Entry<String, ConfigTransactionControllerInternal> entry = it
582                     .next();
583             if (entry.getValue().isClosed()) {
584                 it.remove();
585             }
586         }
587         return Collections.unmodifiableMap(transactions);
588     }
589 }