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