Merge "Table features : modified yang model. Patch set 2: Modified match types as...
[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         this.bundleContext = bundleContext;
119         this.configMBeanServer = configMBeanServer;
120         this.baseJMXRegistrator = baseJMXRegistrator;
121         this.registryMBeanServer = MBeanServerFactory
122                 .createMBeanServer("ConfigRegistry" + configMBeanServer.getDefaultDomain());
123         this.transactionsMBeanServer = MBeanServerFactory
124                 .createMBeanServer("ConfigTransactions" + configMBeanServer.getDefaultDomain());
125     }
126
127     /**
128      * Create new {@link ConfigTransactionControllerImpl }
129      */
130     @Override
131     public synchronized ObjectName beginConfig() {
132         return beginConfigInternal().getControllerObjectName();
133     }
134
135     private synchronized ConfigTransactionControllerInternal beginConfigInternal() {
136         versionCounter++;
137         String transactionName = "ConfigTransaction-" + version + "-" + versionCounter;
138         TransactionJMXRegistrator transactionRegistrator = baseJMXRegistrator
139                 .createTransactionJMXRegistrator(transactionName);
140         Map<String, Map.Entry<ModuleFactory, BundleContext>> allCurrentFactories = Collections.unmodifiableMap(resolver.getAllFactories());
141         ConfigTransactionControllerInternal transactionController = new ConfigTransactionControllerImpl(
142                 transactionName, transactionRegistrator, version,
143                 versionCounter, allCurrentFactories, transactionsMBeanServer, configMBeanServer, bundleContext);
144         try {
145             transactionRegistrator.registerMBean(transactionController, transactionController.getControllerObjectName());
146         } catch (InstanceAlreadyExistsException e) {
147             throw new IllegalStateException(e);
148         }
149
150         transactionController.copyExistingModulesAndProcessFactoryDiff(currentConfig.getEntries(), lastListOfFactories);
151
152         transactionsHolder.add(transactionName, transactionController);
153         return transactionController;
154     }
155
156     /**
157      * {@inheritDoc}
158      */
159     @Override
160     public synchronized CommitStatus commitConfig(ObjectName transactionControllerON)
161             throws ConflictingVersionException, ValidationException {
162         final String transactionName = ObjectNameUtil
163                 .getTransactionName(transactionControllerON);
164         logger.info("About to commit {}. Current parentVersion: {}, versionCounter {}", transactionName, version, versionCounter);
165
166         // find ConfigTransactionController
167         Map<String, ConfigTransactionControllerInternal> transactions = transactionsHolder.getCurrentTransactions();
168         ConfigTransactionControllerInternal configTransactionController = transactions.get(transactionName);
169         if (configTransactionController == null) {
170             throw new IllegalArgumentException(String.format(
171                     "Transaction with name '%s' not found", transactionName));
172         }
173         // check optimistic lock
174         if (version != configTransactionController.getParentVersion()) {
175             throw new ConflictingVersionException(
176                     String.format(
177                             "Optimistic lock failed. Expected parent version %d, was %d",
178                             version,
179                             configTransactionController.getParentVersion()));
180         }
181         // optimistic lock ok
182
183         CommitInfo commitInfo = configTransactionController.validateBeforeCommitAndLockTransaction();
184         lastListOfFactories = Collections.unmodifiableList(configTransactionController.getCurrentlyRegisteredFactories());
185         // non recoverable from here:
186         try {
187             return secondPhaseCommit(
188                     configTransactionController, commitInfo);
189         } catch (Throwable t) { // some libs throw Errors: e.g.
190                                 // javax.xml.ws.spi.FactoryFinder$ConfigurationError
191             isHealthy = false;
192             logger.error("Configuration Transaction failed on 2PC, server is unhealthy", t);
193             if (t instanceof RuntimeException) {
194                 throw (RuntimeException) t;
195             } else if (t instanceof Error) {
196                 throw (Error) t;
197             } else {
198                 throw new RuntimeException(t);
199             }
200         }
201     }
202
203     private CommitStatus secondPhaseCommit(ConfigTransactionControllerInternal configTransactionController,
204                                            CommitInfo commitInfo) {
205
206         // close instances which were destroyed by the user, including
207         // (hopefully) runtime beans
208         for (DestroyedModule toBeDestroyed : commitInfo
209                 .getDestroyedFromPreviousTransactions()) {
210             toBeDestroyed.close(); // closes instance (which should close
211                                    // runtime jmx registrator),
212             // also closes osgi registration and ModuleJMXRegistrator
213             // registration
214             currentConfig.remove(toBeDestroyed.getIdentifier());
215         }
216
217         // set RuntimeBeanRegistrators on beans implementing
218         // RuntimeBeanRegistratorAwareModule, each module
219         // should have exactly one runtime jmx registrator.
220         Map<ModuleIdentifier, RootRuntimeBeanRegistratorImpl> runtimeRegistrators = new HashMap<>();
221         for (ModuleInternalTransactionalInfo entry : commitInfo.getCommitted()
222                 .values()) {
223             RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator;
224             if (entry.hasOldModule() == false) {
225                 runtimeBeanRegistrator = baseJMXRegistrator
226                         .createRuntimeBeanRegistrator(entry.getName());
227             } else {
228                 // reuse old JMX registrator
229                 runtimeBeanRegistrator = entry.getOldInternalInfo()
230                         .getRuntimeBeanRegistrator();
231             }
232             // set runtime jmx registrator if required
233             Module module = entry.getModule();
234             if (module instanceof RuntimeBeanRegistratorAwareModule) {
235                 ((RuntimeBeanRegistratorAwareModule) module)
236                         .setRuntimeBeanRegistrator(runtimeBeanRegistrator);
237             }
238             // save it to info so it is accessible afterwards
239             runtimeRegistrators.put(entry.getName(), runtimeBeanRegistrator);
240         }
241
242         // can register runtime beans
243         List<ModuleIdentifier> orderedModuleIdentifiers = configTransactionController
244                 .secondPhaseCommit();
245
246         // copy configuration to read only mode
247         List<ObjectName> newInstances = new LinkedList<>();
248         List<ObjectName> reusedInstances = new LinkedList<>();
249         List<ObjectName> recreatedInstances = new LinkedList<>();
250
251         Map<Module, ModuleInternalInfo> newConfigEntries = new HashMap<>();
252
253         int orderingIdx = 0;
254         for (ModuleIdentifier moduleIdentifier : orderedModuleIdentifiers) {
255             ModuleInternalTransactionalInfo entry = commitInfo.getCommitted()
256                     .get(moduleIdentifier);
257             if (entry == null)
258                 throw new NullPointerException("Module not found "
259                         + moduleIdentifier);
260             Module module = entry.getModule();
261             ObjectName primaryReadOnlyON = ObjectNameUtil
262                     .createReadOnlyModuleON(moduleIdentifier);
263
264             // determine if current instance was recreated or reused or is new
265
266             // rules for closing resources:
267             // osgi registration - will be (re)created every time, so it needs
268             // to be closed here
269             // module jmx registration - will be (re)created every time, needs
270             // to be closed here
271             // runtime jmx registration - should be taken care of by module
272             // itself
273             // instance - is closed only if it was destroyed
274             ModuleJMXRegistrator newModuleJMXRegistrator = baseJMXRegistrator
275                     .createModuleJMXRegistrator();
276
277             OsgiRegistration osgiRegistration = null;
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(module.getInstance())) {
286                     // reused old instance:
287                     // wrap in readable dynamic mbean
288                     reusedInstances.add(primaryReadOnlyON);
289                     osgiRegistration = oldInternalInfo.getOsgiRegistration();
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
297                     oldInternalInfo.getOsgiRegistration().close();
298                 }
299
300                 // close old module jmx registrator
301                 oldInternalInfo.getModuleJMXRegistrator().close();
302             } else {
303                 // new instance:
304                 // wrap in readable dynamic mbean
305                 newInstances.add(primaryReadOnlyON);
306             }
307
308             DynamicReadableWrapper newReadableConfigBean = new DynamicReadableWrapper(
309                     module, module.getInstance(), moduleIdentifier,
310                     registryMBeanServer, configMBeanServer);
311
312             // register to JMX
313             try {
314                 newModuleJMXRegistrator.registerMBean(newReadableConfigBean,
315                         primaryReadOnlyON);
316             } catch (InstanceAlreadyExistsException e) {
317                 throw new IllegalStateException(e);
318             }
319
320             // register to OSGi
321             if (osgiRegistration == null) {
322                 ModuleFactory moduleFactory = entry.getModuleFactory();
323                 if(moduleFactory != null) {
324                     BundleContext bc = configTransactionController.
325                             getModuleFactoryBundleContext(moduleFactory.getImplementationName());
326                     osgiRegistration = beanToOsgiServiceManager.registerToOsgi(module.getClass(),
327                             newReadableConfigBean.getInstance(), entry.getName(), bc);
328                 } else {
329                     throw new NullPointerException(entry.getIdentifier().getFactoryName() + " ModuleFactory not found.");
330                 }
331
332             }
333
334             RootRuntimeBeanRegistratorImpl runtimeBeanRegistrator = runtimeRegistrators
335                     .get(entry.getName());
336             ModuleInternalInfo newInfo = new ModuleInternalInfo(
337                     entry.getName(), newReadableConfigBean, osgiRegistration,
338                     runtimeBeanRegistrator, newModuleJMXRegistrator,
339                     orderingIdx);
340
341             newConfigEntries.put(module, newInfo);
342             orderingIdx++;
343         }
344         currentConfig.addAll(newConfigEntries.values());
345
346         // update version
347         version = configTransactionController.getVersion();
348         return new CommitStatus(newInstances, reusedInstances,
349                 recreatedInstances);
350     }
351
352     /**
353      * {@inheritDoc}
354      */
355     @Override
356     public synchronized List<ObjectName> getOpenConfigs() {
357         Map<String, ConfigTransactionControllerInternal> transactions = transactionsHolder
358                 .getCurrentTransactions();
359         List<ObjectName> result = new ArrayList<>(transactions.size());
360         for (ConfigTransactionControllerInternal configTransactionController : transactions
361                 .values()) {
362             result.add(configTransactionController.getControllerObjectName());
363         }
364         return result;
365     }
366
367     /**
368      * Abort open transactions and unregister read only modules. Since this
369      * class is not responsible for registering itself under
370      * {@link ConfigRegistryMXBean#OBJECT_NAME}, it will not unregister itself
371      * here.
372      */
373     @Override
374     public synchronized void close() {
375         // abort transactions
376         Map<String, ConfigTransactionControllerInternal> transactions = transactionsHolder
377                 .getCurrentTransactions();
378         for (ConfigTransactionControllerInternal configTransactionController : transactions
379                 .values()) {
380             try {
381                 configTransactionController.abortConfig();
382             } catch (RuntimeException e) {
383                 logger.warn("Ignoring exception while aborting {}",
384                         configTransactionController, e);
385             }
386         }
387
388         // destroy all live objects one after another in order of the dependency
389         // hierarchy
390         List<DestroyedModule> destroyedModules = currentConfig
391                 .getModulesToBeDestroyed();
392         for (DestroyedModule destroyedModule : destroyedModules) {
393             destroyedModule.close();
394         }
395         // unregister MBeans that failed to unregister properly
396         baseJMXRegistrator.close();
397         // remove jmx servers
398         MBeanServerFactory.releaseMBeanServer(registryMBeanServer);
399         MBeanServerFactory.releaseMBeanServer(transactionsMBeanServer);
400
401     }
402
403     /**
404      * {@inheritDoc}
405      */
406     @Override
407     public long getVersion() {
408         return version;
409     }
410
411     /**
412      * {@inheritDoc}
413      */
414     @Override
415     public Set<String> getAvailableModuleNames() {
416         return new HierarchicalConfigMBeanFactoriesHolder(
417                 resolver.getAllFactories()).getModuleNames();
418     }
419
420     /**
421      * {@inheritDoc}
422      */
423     @Override
424     public boolean isHealthy() {
425         return isHealthy;
426     }
427
428     // filtering methods
429
430     /**
431      * {@inheritDoc}
432      */
433     @Override
434     public Set<ObjectName> lookupConfigBeans() {
435         return lookupConfigBeans("*", "*");
436     }
437
438     /**
439      * {@inheritDoc}
440      */
441     @Override
442     public Set<ObjectName> lookupConfigBeans(String moduleName) {
443         return lookupConfigBeans(moduleName, "*");
444     }
445
446     /**
447      * {@inheritDoc}
448      */
449     @Override
450     public ObjectName lookupConfigBean(String moduleName, String instanceName)
451             throws InstanceNotFoundException {
452         return LookupBeansUtil.lookupConfigBean(this, moduleName, instanceName);
453     }
454
455     /**
456      * {@inheritDoc}
457      */
458     @Override
459     public Set<ObjectName> lookupConfigBeans(String moduleName,
460             String instanceName) {
461         ObjectName namePattern = ObjectNameUtil.createModulePattern(moduleName,
462                 instanceName);
463         return baseJMXRegistrator.queryNames(namePattern, null);
464     }
465
466     /**
467      * {@inheritDoc}
468      */
469     @Override
470     public Set<ObjectName> lookupRuntimeBeans() {
471         return lookupRuntimeBeans("*", "*");
472     }
473
474     /**
475      * {@inheritDoc}
476      */
477     @Override
478     public Set<ObjectName> lookupRuntimeBeans(String moduleName,
479             String instanceName) {
480         if (moduleName == null)
481             moduleName = "*";
482         if (instanceName == null)
483             instanceName = "*";
484         ObjectName namePattern = ObjectNameUtil.createRuntimeBeanPattern(
485                 moduleName, instanceName);
486         return baseJMXRegistrator.queryNames(namePattern, null);
487     }
488
489 }
490
491 /**
492  * Holds currently running modules
493  */
494 @NotThreadSafe
495 class ConfigHolder {
496     private final Map<ModuleIdentifier, ModuleInternalInfo> currentConfig = new HashMap<>();
497
498     /**
499      * Add all modules to the internal map. Also add service instance to OSGi
500      * Service Registry.
501      */
502     public void addAll(Collection<ModuleInternalInfo> configInfos) {
503         if (currentConfig.size() > 0) {
504             throw new IllegalStateException(
505                     "Error - some config entries were not removed: "
506                             + currentConfig);
507         }
508         for (ModuleInternalInfo configInfo : configInfos) {
509             add(configInfo);
510         }
511     }
512
513     private void add(ModuleInternalInfo configInfo) {
514         ModuleInternalInfo oldValue = currentConfig.put(configInfo.getName(),
515                 configInfo);
516         if (oldValue != null) {
517             throw new IllegalStateException(
518                     "Cannot overwrite module with same name:"
519                             + configInfo.getName() + ":" + configInfo);
520         }
521     }
522
523     /**
524      * Remove entry from current config.
525      */
526     public void remove(ModuleIdentifier name) {
527         ModuleInternalInfo removed = currentConfig.remove(name);
528         if (removed == null) {
529             throw new IllegalStateException(
530                     "Cannot remove from ConfigHolder - name not found:" + name);
531         }
532     }
533
534     public Collection<ModuleInternalInfo> getEntries() {
535         return currentConfig.values();
536     }
537
538     public List<DestroyedModule> getModulesToBeDestroyed() {
539         List<DestroyedModule> result = new ArrayList<>();
540         for (ModuleInternalInfo moduleInternalInfo : getEntries()) {
541             result.add(moduleInternalInfo.toDestroyedModule());
542         }
543         Collections.sort(result);
544         return result;
545     }
546 }
547
548 /**
549  * Holds Map<transactionName, transactionController> and purges it each time its
550  * content is requested.
551  */
552 @NotThreadSafe
553 class TransactionsHolder {
554     /**
555      * This map keeps transaction names and
556      * {@link ConfigTransactionControllerInternal} instances, because platform
557      * MBeanServer transforms mbeans into another representation. Map is cleaned
558      * every time current transactions are requested.
559      *
560      */
561     @GuardedBy("ConfigRegistryImpl.this")
562     private final Map<String /* transactionName */, ConfigTransactionControllerInternal> transactions = new HashMap<>();
563
564     /**
565      * Can only be called from within synchronized method.
566      */
567     public void add(String transactionName,
568             ConfigTransactionControllerInternal transactionController) {
569         Object oldValue = transactions.put(transactionName,
570                 transactionController);
571         if (oldValue != null) {
572             throw new IllegalStateException(
573                     "Error: two transactions with same name");
574         }
575     }
576
577     /**
578      * Purges closed transactions from transactions map. Can only be called from
579      * within synchronized method. Calling this method more than once within the
580      * method can modify the resulting map that was obtained in previous calls.
581      *
582      * @return current view on transactions map.
583      */
584     public Map<String, ConfigTransactionControllerInternal> getCurrentTransactions() {
585         // first, remove closed transaction
586         for (Iterator<Entry<String, ConfigTransactionControllerInternal>> it = transactions
587                 .entrySet().iterator(); it.hasNext();) {
588             Entry<String, ConfigTransactionControllerInternal> entry = it
589                     .next();
590             if (entry.getValue().isClosed()) {
591                 it.remove();
592             }
593         }
594         return Collections.unmodifiableMap(transactions);
595     }
596 }