Fix star import and enable checkstyle rule to prevent it.
[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             if (entry.hasOldModule()) {
279                 ModuleInternalInfo oldInternalInfo = entry.getOldInternalInfo();
280                 DynamicReadableWrapper oldReadableConfigBean = oldInternalInfo
281                         .getReadableModule();
282                 currentConfig.remove(entry.getName());
283
284                 // test if old instance == new instance
285                 if (oldReadableConfigBean.getInstance().equals(
286                         module.getInstance())) {
287                     // reused old instance:
288                     // wrap in readable dynamic mbean
289                     reusedInstances.add(primaryReadOnlyON);
290                 } else {
291                     // recreated instance:
292                     // it is responsibility of module to call the old instance -
293                     // we just need to unregister configbean
294                     recreatedInstances.add(primaryReadOnlyON);
295                 }
296                 // close old osgi registration in any case
297                 oldInternalInfo.getOsgiRegistration().close();
298                 // close old module jmx registrator
299                 oldInternalInfo.getModuleJMXRegistrator().close();
300             } else {
301                 // new instance:
302                 // wrap in readable dynamic mbean
303                 newInstances.add(primaryReadOnlyON);
304             }
305
306             DynamicReadableWrapper newReadableConfigBean = new DynamicReadableWrapper(
307                     module, module.getInstance(), moduleIdentifier,
308                     registryMBeanServer, configMBeanServer);
309
310             // register to JMX
311             try {
312                 newModuleJMXRegistrator.registerMBean(newReadableConfigBean,
313                         primaryReadOnlyON);
314             } catch (InstanceAlreadyExistsException e) {
315                 throw new IllegalStateException(e);
316             }
317
318             // register to OSGi
319             OsgiRegistration osgiRegistration = beanToOsgiServiceManager
320                     .registerToOsgi(module.getClass(),
321                             newReadableConfigBean.getInstance(),
322                             entry.getName());
323
324             RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator = runtimeRegistrators
325                     .get(entry.getName());
326             ModuleInternalInfo newInfo = new ModuleInternalInfo(
327                     entry.getName(), newReadableConfigBean, osgiRegistration,
328                     runtimeBeanRegistrator, newModuleJMXRegistrator,
329                     orderingIdx);
330
331             newConfigEntries.put(module, newInfo);
332             orderingIdx++;
333         }
334         currentConfig.addAll(newConfigEntries.values());
335
336         // update version
337         version = configTransactionController.getVersion();
338         return new CommitStatus(newInstances, reusedInstances,
339                 recreatedInstances);
340     }
341
342     /**
343      * {@inheritDoc}
344      */
345     @Override
346     public synchronized List<ObjectName> getOpenConfigs() {
347         Map<String, ConfigTransactionControllerInternal> transactions = transactionsHolder
348                 .getCurrentTransactions();
349         List<ObjectName> result = new ArrayList<>(transactions.size());
350         for (ConfigTransactionControllerInternal configTransactionController : transactions
351                 .values()) {
352             result.add(configTransactionController.getControllerObjectName());
353         }
354         return result;
355     }
356
357     /**
358      * Abort open transactions and unregister read only modules. Since this
359      * class is not responsible for registering itself under
360      * {@link ConfigRegistryMXBean#OBJECT_NAME}, it will not unregister itself
361      * here.
362      */
363     @Override
364     public synchronized void close() {
365         // abort transactions
366         Map<String, ConfigTransactionControllerInternal> transactions = transactionsHolder
367                 .getCurrentTransactions();
368         for (ConfigTransactionControllerInternal configTransactionController : transactions
369                 .values()) {
370             try {
371                 configTransactionController.abortConfig();
372             } catch (RuntimeException e) {
373                 logger.warn("Ignoring exception while aborting {}",
374                         configTransactionController, e);
375             }
376         }
377
378         // destroy all live objects one after another in order of the dependency
379         // hierarchy
380         List<DestroyedModule> destroyedModules = currentConfig
381                 .getModulesToBeDestroyed();
382         for (DestroyedModule destroyedModule : destroyedModules) {
383             destroyedModule.close();
384         }
385         // unregister MBeans that failed to unregister properly
386         baseJMXRegistrator.close();
387         // remove jmx servers
388         MBeanServerFactory.releaseMBeanServer(registryMBeanServer);
389         MBeanServerFactory.releaseMBeanServer(transactionsMBeanServer);
390
391     }
392
393     /**
394      * {@inheritDoc}
395      */
396     @Override
397     public long getVersion() {
398         return version;
399     }
400
401     /**
402      * {@inheritDoc}
403      */
404     @Override
405     public Set<String> getAvailableModuleNames() {
406         return new HierarchicalConfigMBeanFactoriesHolder(
407                 resolver.getAllFactories()).getModuleNames();
408     }
409
410     /**
411      * {@inheritDoc}
412      */
413     @Override
414     public boolean isHealthy() {
415         return isHealthy;
416     }
417
418     // filtering methods
419
420     /**
421      * {@inheritDoc}
422      */
423     @Override
424     public Set<ObjectName> lookupConfigBeans() {
425         return lookupConfigBeans("*", "*");
426     }
427
428     /**
429      * {@inheritDoc}
430      */
431     @Override
432     public Set<ObjectName> lookupConfigBeans(String moduleName) {
433         return lookupConfigBeans(moduleName, "*");
434     }
435
436     /**
437      * {@inheritDoc}
438      */
439     @Override
440     public ObjectName lookupConfigBean(String moduleName, String instanceName)
441             throws InstanceNotFoundException {
442         return LookupBeansUtil.lookupConfigBean(this, moduleName, instanceName);
443     }
444
445     /**
446      * {@inheritDoc}
447      */
448     @Override
449     public Set<ObjectName> lookupConfigBeans(String moduleName,
450             String instanceName) {
451         ObjectName namePattern = ObjectNameUtil.createModulePattern(moduleName,
452                 instanceName);
453         return baseJMXRegistrator.queryNames(namePattern, null);
454     }
455
456     /**
457      * {@inheritDoc}
458      */
459     @Override
460     public Set<ObjectName> lookupRuntimeBeans() {
461         return lookupRuntimeBeans("*", "*");
462     }
463
464     /**
465      * {@inheritDoc}
466      */
467     @Override
468     public Set<ObjectName> lookupRuntimeBeans(String moduleName,
469             String instanceName) {
470         if (moduleName == null)
471             moduleName = "*";
472         if (instanceName == null)
473             instanceName = "*";
474         ObjectName namePattern = ObjectNameUtil.createRuntimeBeanPattern(
475                 moduleName, instanceName);
476         return baseJMXRegistrator.queryNames(namePattern, null);
477     }
478
479 }
480
481 /**
482  * Holds currently running modules
483  */
484 @NotThreadSafe
485 class ConfigHolder {
486     private final Map<ModuleIdentifier, ModuleInternalInfo> currentConfig = new HashMap<>();
487
488     /**
489      * Add all modules to the internal map. Also add service instance to OSGi
490      * Service Registry.
491      */
492     public void addAll(Collection<ModuleInternalInfo> configInfos) {
493         if (currentConfig.size() > 0) {
494             throw new IllegalStateException(
495                     "Error - some config entries were not removed: "
496                             + currentConfig);
497         }
498         for (ModuleInternalInfo configInfo : configInfos) {
499             add(configInfo);
500         }
501     }
502
503     private void add(ModuleInternalInfo configInfo) {
504         ModuleInternalInfo oldValue = currentConfig.put(configInfo.getName(),
505                 configInfo);
506         if (oldValue != null) {
507             throw new IllegalStateException(
508                     "Cannot overwrite module with same name:"
509                             + configInfo.getName() + ":" + configInfo);
510         }
511     }
512
513     /**
514      * Remove entry from current config.
515      */
516     public void remove(ModuleIdentifier name) {
517         ModuleInternalInfo removed = currentConfig.remove(name);
518         if (removed == null) {
519             throw new IllegalStateException(
520                     "Cannot remove from ConfigHolder - name not found:" + name);
521         }
522     }
523
524     public Collection<ModuleInternalInfo> getEntries() {
525         return currentConfig.values();
526     }
527
528     public List<DestroyedModule> getModulesToBeDestroyed() {
529         List<DestroyedModule> result = new ArrayList<>();
530         for (ModuleInternalInfo moduleInternalInfo : getEntries()) {
531             result.add(moduleInternalInfo.toDestroyedModule());
532         }
533         Collections.sort(result);
534         return result;
535     }
536 }
537
538 /**
539  * Holds Map<transactionName, transactionController> and purges it each time its
540  * content is requested.
541  */
542 @NotThreadSafe
543 class TransactionsHolder {
544     /**
545      * This map keeps transaction names and
546      * {@link ConfigTransactionControllerInternal} instances, because platform
547      * MBeanServer transforms mbeans into another representation. Map is cleaned
548      * every time current transactions are requested.
549      *
550      */
551     @GuardedBy("ConfigRegistryImpl.this")
552     private final Map<String /* transactionName */, ConfigTransactionControllerInternal> transactions = new HashMap<>();
553
554     /**
555      * Can only be called from within synchronized method.
556      */
557     public void add(String transactionName,
558             ConfigTransactionControllerInternal transactionController) {
559         Object oldValue = transactions.put(transactionName,
560                 transactionController);
561         if (oldValue != null) {
562             throw new IllegalStateException(
563                     "Error: two transactions with same name");
564         }
565     }
566
567     /**
568      * Purges closed transactions from transactions map. Can only be called from
569      * within synchronized method. Calling this method more than once within the
570      * method can modify the resulting map that was obtained in previous calls.
571      *
572      * @return current view on transactions map.
573      */
574     public Map<String, ConfigTransactionControllerInternal> getCurrentTransactions() {
575         // first, remove closed transaction
576         for (Iterator<Entry<String, ConfigTransactionControllerInternal>> it = transactions
577                 .entrySet().iterator(); it.hasNext();) {
578             Entry<String, ConfigTransactionControllerInternal> entry = it
579                     .next();
580             if (entry.getValue().isClosed()) {
581                 it.remove();
582             }
583         }
584         return Collections.unmodifiableMap(transactions);
585     }
586 }