Merge "Fixed netconf monitoring."
[controller.git] / opendaylight / config / config-manager / src / main / java / org / opendaylight / controller / config / manager / impl / ConfigTransactionControllerImpl.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.DependencyResolver;
11 import org.opendaylight.controller.config.api.ModuleIdentifier;
12 import org.opendaylight.controller.config.api.ServiceReferenceWritableRegistry;
13 import org.opendaylight.controller.config.api.ValidationException;
14 import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
15 import org.opendaylight.controller.config.manager.impl.dependencyresolver.DependencyResolverManager;
16 import org.opendaylight.controller.config.manager.impl.dynamicmbean.DynamicWritableWrapper;
17 import org.opendaylight.controller.config.manager.impl.dynamicmbean.ReadOnlyAtomicBoolean;
18 import org.opendaylight.controller.config.manager.impl.dynamicmbean.ReadOnlyAtomicBoolean.ReadOnlyAtomicBooleanImpl;
19 import org.opendaylight.controller.config.manager.impl.factoriesresolver.HierarchicalConfigMBeanFactoriesHolder;
20 import org.opendaylight.controller.config.manager.impl.jmx.TransactionModuleJMXRegistrator;
21 import org.opendaylight.controller.config.manager.impl.jmx.TransactionModuleJMXRegistrator.TransactionModuleJMXRegistration;
22 import org.opendaylight.controller.config.spi.Module;
23 import org.opendaylight.controller.config.spi.ModuleFactory;
24 import org.opendaylight.yangtools.concepts.Identifiable;
25 import org.osgi.framework.BundleContext;
26 import org.slf4j.Logger;
27 import org.slf4j.LoggerFactory;
28
29 import javax.annotation.Nullable;
30 import javax.annotation.concurrent.GuardedBy;
31 import javax.management.DynamicMBean;
32 import javax.management.InstanceAlreadyExistsException;
33 import javax.management.InstanceNotFoundException;
34 import javax.management.MBeanServer;
35 import javax.management.ObjectName;
36 import java.util.ArrayList;
37 import java.util.Collection;
38 import java.util.HashSet;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Map.Entry;
42 import java.util.Set;
43 import java.util.concurrent.atomic.AtomicBoolean;
44
45 import static java.lang.String.format;
46
47 /**
48  * This is a JMX bean representing current transaction. It contains
49  * transaction identifier, unique version and parent version for
50  * optimistic locking.
51  */
52 class ConfigTransactionControllerImpl implements
53         ConfigTransactionControllerInternal,
54         ConfigTransactionControllerImplMXBean,
55         Identifiable<TransactionIdentifier>{
56     private static final Logger logger = LoggerFactory.getLogger(ConfigTransactionControllerImpl.class);
57
58     private final ConfigTransactionLookupRegistry txLookupRegistry;
59     private final ObjectName controllerON;
60
61     private final long parentVersion, currentVersion;
62     private final HierarchicalConfigMBeanFactoriesHolder factoriesHolder;
63     private final DependencyResolverManager dependencyResolverManager;
64     private final TransactionStatus transactionStatus;
65     private final MBeanServer transactionsMBeanServer;
66     private final Map<String, Map.Entry<ModuleFactory, BundleContext>> currentlyRegisteredFactories;
67
68     /**
69      * Disables ability of {@link DynamicWritableWrapper} to change attributes
70      * during validation.
71      */
72     @GuardedBy("this")
73     private final AtomicBoolean configBeanModificationDisabled = new AtomicBoolean(
74             false);
75     private final ReadOnlyAtomicBoolean readOnlyAtomicBoolean = new ReadOnlyAtomicBooleanImpl(
76             configBeanModificationDisabled);
77     private final MBeanServer configMBeanServer;
78
79     private final boolean blankTransaction;
80
81     @GuardedBy("this")
82     private final ServiceReferenceWritableRegistry writableSRRegistry;
83
84     public ConfigTransactionControllerImpl(ConfigTransactionLookupRegistry txLookupRegistry,
85                                            long parentVersion, long currentVersion,
86                                            Map<String, Map.Entry<ModuleFactory, BundleContext>> currentlyRegisteredFactories,
87                                            MBeanServer transactionsMBeanServer, MBeanServer configMBeanServer,
88                                            boolean blankTransaction, ServiceReferenceWritableRegistry writableSRRegistry) {
89
90         this.txLookupRegistry = txLookupRegistry;
91         String transactionName = txLookupRegistry.getTransactionIdentifier().getName();
92         this.controllerON = ObjectNameUtil.createTransactionControllerON(transactionName);
93         this.parentVersion = parentVersion;
94         this.currentVersion = currentVersion;
95         this.currentlyRegisteredFactories = currentlyRegisteredFactories;
96         this.factoriesHolder = new HierarchicalConfigMBeanFactoriesHolder(currentlyRegisteredFactories);
97         this.transactionStatus = new TransactionStatus();
98         this.dependencyResolverManager = new DependencyResolverManager(transactionName, transactionStatus);
99         this.transactionsMBeanServer = transactionsMBeanServer;
100         this.configMBeanServer = configMBeanServer;
101         this.blankTransaction = blankTransaction;
102         this.writableSRRegistry = writableSRRegistry;
103     }
104
105     @Override
106     public void copyExistingModulesAndProcessFactoryDiff(Collection<ModuleInternalInfo> existingModules, List<ModuleFactory> lastListOfFactories) {
107         // copy old configuration to this server
108         for (ModuleInternalInfo oldConfigInfo : existingModules) {
109             try {
110                 copyExistingModule(oldConfigInfo);
111             } catch (InstanceAlreadyExistsException e) {
112                 throw new IllegalStateException("Error while copying " + oldConfigInfo, e);
113             }
114         }
115         processDefaultBeans(lastListOfFactories);
116     }
117
118     private synchronized void processDefaultBeans(List<ModuleFactory> lastListOfFactories) {
119         transactionStatus.checkNotCommitStarted();
120         transactionStatus.checkNotAborted();
121
122         Set<ModuleFactory> oldSet = new HashSet<>(lastListOfFactories);
123         Set<ModuleFactory> newSet = new HashSet<>(factoriesHolder.getModuleFactories());
124
125         List<ModuleFactory> toBeAdded = new ArrayList<>();
126         List<ModuleFactory> toBeRemoved = new ArrayList<>();
127         for(ModuleFactory moduleFactory: factoriesHolder.getModuleFactories()) {
128             if (oldSet.contains(moduleFactory) == false){
129                 toBeAdded.add(moduleFactory);
130             }
131         }
132         for(ModuleFactory moduleFactory: lastListOfFactories){
133             if (newSet.contains(moduleFactory) == false) {
134                 toBeRemoved.add(moduleFactory);
135             }
136         }
137         // add default modules
138         for (ModuleFactory moduleFactory : toBeAdded) {
139             Set<? extends Module> defaultModules = moduleFactory.getDefaultModules(dependencyResolverManager,
140                     getModuleFactoryBundleContext(moduleFactory.getImplementationName()));
141             for (Module module : defaultModules) {
142                 // ensure default module to be registered to jmx even if its module factory does not use dependencyResolverFactory
143                 DependencyResolver dependencyResolver = dependencyResolverManager.getOrCreate(module.getIdentifier());
144                 try {
145                     boolean defaultBean = true;
146                     putConfigBeanToJMXAndInternalMaps(module.getIdentifier(), module, moduleFactory, null, dependencyResolver, defaultBean);
147                 } catch (InstanceAlreadyExistsException e) {
148                     throw new IllegalStateException(e);
149                 }
150             }
151         }
152
153         // remove modules belonging to removed factories
154         for(ModuleFactory removedFactory: toBeRemoved){
155             List<ModuleIdentifier> modulesOfRemovedFactory = dependencyResolverManager.findAllByFactory(removedFactory);
156             for (ModuleIdentifier name : modulesOfRemovedFactory) {
157                 destroyModule(name);
158             }
159         }
160     }
161
162
163     private synchronized void copyExistingModule(
164             ModuleInternalInfo oldConfigBeanInfo)
165             throws InstanceAlreadyExistsException {
166         transactionStatus.checkNotCommitStarted();
167         transactionStatus.checkNotAborted();
168         ModuleIdentifier moduleIdentifier = oldConfigBeanInfo.getName();
169         dependencyResolverManager.assertNotExists(moduleIdentifier);
170
171         ModuleFactory moduleFactory = factoriesHolder
172                 .findByModuleName(moduleIdentifier.getFactoryName());
173
174         Module module;
175         DependencyResolver dependencyResolver = dependencyResolverManager
176                 .getOrCreate(moduleIdentifier);
177         try {
178             BundleContext bc = getModuleFactoryBundleContext(moduleFactory.getImplementationName());
179             module = moduleFactory.createModule(
180                     moduleIdentifier.getInstanceName(), dependencyResolver,
181                     oldConfigBeanInfo.getReadableModule(), bc);
182         } catch (Exception e) {
183             throw new IllegalStateException(format(
184                     "Error while copying old configuration from %s to %s",
185                     oldConfigBeanInfo, moduleFactory), e);
186         }
187         putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module, moduleFactory, oldConfigBeanInfo, dependencyResolver,
188                 oldConfigBeanInfo.isDefaultBean());
189     }
190
191     @Override
192     public synchronized ObjectName createModule(String factoryName,
193             String instanceName) throws InstanceAlreadyExistsException {
194
195         transactionStatus.checkNotCommitStarted();
196         transactionStatus.checkNotAborted();
197         ModuleIdentifier moduleIdentifier = new ModuleIdentifier(factoryName, instanceName);
198         dependencyResolverManager.assertNotExists(moduleIdentifier);
199
200         // find factory
201         ModuleFactory moduleFactory = factoriesHolder.findByModuleName(factoryName);
202         DependencyResolver dependencyResolver = dependencyResolverManager.getOrCreate(moduleIdentifier);
203         Module module = moduleFactory.createModule(instanceName, dependencyResolver,
204                 getModuleFactoryBundleContext(moduleFactory.getImplementationName()));
205         boolean defaultBean = false;
206         return putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module,
207                 moduleFactory, null, dependencyResolver, defaultBean);
208     }
209
210     private synchronized ObjectName putConfigBeanToJMXAndInternalMaps(
211             ModuleIdentifier moduleIdentifier, Module module,
212             ModuleFactory moduleFactory,
213             @Nullable ModuleInternalInfo maybeOldConfigBeanInfo, DependencyResolver dependencyResolver, boolean isDefaultBean)
214             throws InstanceAlreadyExistsException {
215
216         logger.debug("Adding module {} to transaction {}", moduleIdentifier, this);
217         if (moduleIdentifier.equals(module.getIdentifier())==false) {
218             throw new IllegalStateException("Incorrect name reported by module. Expected "
219              + moduleIdentifier + ", got " + module.getIdentifier());
220         }
221         if (dependencyResolver.getIdentifier().equals(moduleIdentifier) == false ) {
222             throw new IllegalStateException("Incorrect name reported by dependency resolver. Expected "
223                     + moduleIdentifier + ", got " + dependencyResolver.getIdentifier());
224         }
225         DynamicMBean writableDynamicWrapper = new DynamicWritableWrapper(
226                 module, moduleIdentifier, getTransactionIdentifier(),
227                 readOnlyAtomicBoolean, transactionsMBeanServer,
228                 configMBeanServer);
229
230         ObjectName writableON = ObjectNameUtil.createTransactionModuleON(
231                 getTransactionIdentifier().getName(), moduleIdentifier);
232         // put wrapper to jmx
233         TransactionModuleJMXRegistration transactionModuleJMXRegistration = getTxModuleJMXRegistrator()
234                 .registerMBean(writableDynamicWrapper, writableON);
235         ModuleInternalTransactionalInfo moduleInternalTransactionalInfo = new ModuleInternalTransactionalInfo(
236                 moduleIdentifier, module, moduleFactory,
237                 maybeOldConfigBeanInfo, transactionModuleJMXRegistration, isDefaultBean);
238
239         dependencyResolverManager.put(moduleInternalTransactionalInfo);
240         return writableON;
241     }
242
243     @Override
244     public synchronized void destroyModule(ObjectName objectName) throws InstanceNotFoundException {
245         checkTransactionName(objectName);
246         ObjectNameUtil.checkDomain(objectName);
247         ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(objectName,
248                 ObjectNameUtil.TYPE_MODULE);
249         destroyModule(moduleIdentifier);
250     }
251
252     private void checkTransactionName(ObjectName objectName) {
253         String foundTransactionName = ObjectNameUtil
254                 .getTransactionName(objectName);
255         if (getTransactionIdentifier().getName().equals(foundTransactionName) == false) {
256             throw new IllegalArgumentException("Wrong transaction name "
257                     + objectName);
258         }
259     }
260
261     private synchronized void destroyModule(ModuleIdentifier moduleIdentifier) {
262         logger.debug("Destroying module {} in transaction {}", moduleIdentifier, this);
263         transactionStatus.checkNotAborted();
264
265         if (blankTransaction == false) {
266             ModuleInternalTransactionalInfo found =
267                     dependencyResolverManager.findModuleInternalTransactionalInfo(moduleIdentifier);
268             if (found.isDefaultBean()) {
269                 logger.warn("Warning: removing default bean. This will be forbidden in next version of config-subsystem");
270             }
271         }
272         // first remove refNames, it checks for objectname existence
273         try {
274             writableSRRegistry.removeServiceReferences(
275                     ObjectNameUtil.createTransactionModuleON(getTransactionName(),moduleIdentifier));
276         } catch (InstanceNotFoundException e) {
277             logger.error("Possible code error: cannot find {} in {}", moduleIdentifier, writableSRRegistry);
278             throw new IllegalStateException("Possible code error: cannot find " + moduleIdentifier, e);
279         }
280
281         ModuleInternalTransactionalInfo removedTInfo = dependencyResolverManager.destroyModule(moduleIdentifier);
282         // remove from jmx
283         removedTInfo.getTransactionModuleJMXRegistration().close();
284     }
285
286     @Override
287     public long getParentVersion() {
288         return parentVersion;
289     }
290
291     @Override
292     public long getVersion() {
293         return currentVersion;
294     }
295
296     @Override
297     public synchronized void validateConfig() throws ValidationException {
298         if (configBeanModificationDisabled.get())
299             throw new IllegalStateException("Cannot start validation");
300         configBeanModificationDisabled.set(true);
301         try {
302             validate_noLocks();
303         } finally {
304             configBeanModificationDisabled.set(false);
305         }
306     }
307
308     private void validate_noLocks() throws ValidationException {
309         transactionStatus.checkNotAborted();
310         logger.info("Validating transaction {}", getTransactionIdentifier());
311         // call validate()
312         List<ValidationException> collectedExceptions = new ArrayList<>();
313         for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
314                 .getAllModules().entrySet()) {
315             ModuleIdentifier name = entry.getKey();
316             Module module = entry.getValue();
317             try {
318                 module.validate();
319             } catch (Exception e) {
320                 logger.warn("Validation exception in {}", getTransactionName(),
321                         e);
322                 collectedExceptions.add(ValidationException
323                         .createForSingleException(name, e));
324             }
325         }
326         if (collectedExceptions.size() > 0) {
327             throw ValidationException
328                     .createFromCollectedValidationExceptions(collectedExceptions);
329         }
330         logger.info("Validated transaction {}", getTransactionIdentifier());
331     }
332
333     /**
334      * If this method passes validation, it will grab
335      * {@link TransactionStatus#secondPhaseCommitStarted} lock. This lock will
336      * prevent calling @{link #validateBeforeCommitAndLockTransaction},
337      * effectively only allowing to call {@link #secondPhaseCommit} after
338      * successful return of this method.
339      */
340     @Override
341     public synchronized CommitInfo validateBeforeCommitAndLockTransaction()
342             throws ValidationException {
343         transactionStatus.checkNotAborted();
344         transactionStatus.checkNotCommitStarted();
345         configBeanModificationDisabled.set(true);
346         try {
347             validate_noLocks();
348         } catch (ValidationException e) {
349             logger.info("Commit failed on validation");
350             configBeanModificationDisabled.set(false); // recoverable error
351             throw e;
352         }
353         // errors in this state are not recoverable. modules are not mutable
354         // anymore.
355         transactionStatus.setSecondPhaseCommitStarted();
356         return dependencyResolverManager.toCommitInfo();
357     }
358
359     /**
360      * {@inheritDoc}
361      */
362     @Override
363     public synchronized List<ModuleIdentifier> secondPhaseCommit() {
364         transactionStatus.checkNotAborted();
365         transactionStatus.checkCommitStarted();
366         if (configBeanModificationDisabled.get() == false) {
367             throw new IllegalStateException(
368                     "Internal error - validateBeforeCommitAndLockTransaction should be called "
369                             + "to obtain a lock");
370         }
371
372         logger.info("Committing transaction {}", getTransactionIdentifier());
373
374         // call getInstance()
375         for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
376                 .getAllModules().entrySet()) {
377             Module module = entry.getValue();
378             ModuleIdentifier name = entry.getKey();
379             try {
380                 logger.debug("About to commit {} in transaction {}",
381                         name, getTransactionIdentifier());
382                 module.getInstance();
383             } catch (Exception e) {
384                 logger.error("Commit failed on {} in transaction {}", name,
385                         getTransactionIdentifier(), e);
386                 internalAbort();
387                 throw new RuntimeException(
388                         format("Error - getInstance() failed for %s in transaction %s",
389                                 name, getTransactionIdentifier()), e);
390             }
391         }
392
393         // count dependency order
394
395         logger.info("Committed configuration {}", getTransactionIdentifier());
396         transactionStatus.setCommitted();
397         // unregister this and all modules from jmx
398         close();
399
400         return dependencyResolverManager.getSortedModuleIdentifiers();
401     }
402
403     @Override
404     public synchronized void abortConfig() {
405         transactionStatus.checkNotCommitStarted();
406         transactionStatus.checkNotAborted();
407         internalAbort();
408     }
409
410     private void internalAbort() {
411         transactionStatus.setAborted();
412         close();
413     }
414
415     private void close() {
416         //FIXME: should not close object that was retrieved in constructor, a wrapper object should do that perhaps
417         txLookupRegistry.close();
418     }
419
420     @Override
421     public ObjectName getControllerObjectName() {
422         return controllerON;
423     }
424
425     @Override
426     public String getTransactionName() {
427         return getTransactionIdentifier().getName();
428     }
429
430     /**
431      * {@inheritDoc}
432      */
433     @Override
434     public Set<ObjectName> lookupConfigBeans() {
435         return txLookupRegistry.lookupConfigBeans();
436     }
437
438     /**
439      * {@inheritDoc}
440      */
441     @Override
442     public Set<ObjectName> lookupConfigBeans(String moduleName) {
443         return txLookupRegistry.lookupConfigBeans(moduleName);
444     }
445
446     /**
447      * {@inheritDoc}
448      */
449     @Override
450     public ObjectName lookupConfigBean(String moduleName, String instanceName)
451             throws InstanceNotFoundException {
452         return txLookupRegistry.lookupConfigBean(moduleName, instanceName);
453     }
454
455     /**
456      * {@inheritDoc}
457      */
458     @Override
459     public Set<ObjectName> lookupConfigBeans(String moduleName, String instanceName) {
460         return txLookupRegistry.lookupConfigBeans(moduleName, instanceName);
461     }
462
463     /**
464      * {@inheritDoc}
465      */
466     @Override
467     public void checkConfigBeanExists(ObjectName objectName) throws InstanceNotFoundException {
468         txLookupRegistry.checkConfigBeanExists(objectName);
469     }
470     // --
471
472     /**
473      * {@inheritDoc}
474      */
475     @Override
476     public Set<String> getAvailableModuleNames() {
477         return factoriesHolder.getModuleNames();
478     }
479
480     @Override
481     public boolean isClosed() {
482         return transactionStatus.isAbortedOrCommitted();
483     }
484
485     @Override
486     public String toString() {
487         StringBuilder sb = new StringBuilder();
488         sb.append("transactionName=");
489         sb.append(getTransactionName());
490         return sb.toString();
491     }
492
493     // @VisibleForTesting
494
495     TransactionModuleJMXRegistrator getTxModuleJMXRegistrator() {
496         return txLookupRegistry.getTxModuleJMXRegistrator();
497     }
498
499     public TransactionIdentifier getName() {
500         return getTransactionIdentifier();
501     }
502
503     @Override
504     public List<ModuleFactory> getCurrentlyRegisteredFactories() {
505         return new ArrayList<>(factoriesHolder.getModuleFactories());
506     }
507
508     @Override
509     public TransactionIdentifier getIdentifier() {
510         return getTransactionIdentifier();
511     }
512
513     @Override
514     public BundleContext getModuleFactoryBundleContext(String factoryName) {
515         Map.Entry<ModuleFactory, BundleContext> factoryBundleContextEntry = this.currentlyRegisteredFactories.get(factoryName);
516         if (factoryBundleContextEntry == null || factoryBundleContextEntry.getValue() == null) {
517             throw new NullPointerException("Bundle context of " + factoryName + " ModuleFactory not found.");
518         }
519         return factoryBundleContextEntry.getValue();
520     }
521
522     // service reference functionality:
523
524
525     @Override
526     public synchronized ObjectName lookupConfigBeanByServiceInterfaceName(String serviceInterfaceName, String refName) {
527         return writableSRRegistry.lookupConfigBeanByServiceInterfaceName(serviceInterfaceName, refName);
528     }
529
530     @Override
531     public synchronized Map<String, Map<String, ObjectName>> getServiceMapping() {
532         return writableSRRegistry.getServiceMapping();
533     }
534
535     @Override
536     public synchronized Map<String, ObjectName> lookupServiceReferencesByServiceInterfaceName(String serviceInterfaceName) {
537         return writableSRRegistry.lookupServiceReferencesByServiceInterfaceName(serviceInterfaceName);
538     }
539
540     @Override
541     public synchronized Set<String> lookupServiceInterfaceNames(ObjectName objectName) throws InstanceNotFoundException {
542         return writableSRRegistry.lookupServiceInterfaceNames(objectName);
543     }
544
545     @Override
546     public synchronized String getServiceInterfaceName(String namespace, String localName) {
547         return writableSRRegistry.getServiceInterfaceName(namespace, localName);
548     }
549
550     @Override
551     public synchronized void saveServiceReference(String serviceInterfaceName, String refName, ObjectName objectName) throws InstanceNotFoundException {
552         writableSRRegistry.saveServiceReference(serviceInterfaceName, refName, objectName);
553     }
554
555     @Override
556     public synchronized boolean removeServiceReference(String serviceInterfaceName, String refName) {
557         return writableSRRegistry.removeServiceReference(serviceInterfaceName, refName);
558     }
559
560     @Override
561     public synchronized void removeAllServiceReferences() {
562         writableSRRegistry.removeAllServiceReferences();
563     }
564
565     @Override
566     public boolean removeServiceReferences(ObjectName objectName) throws InstanceNotFoundException {
567         return writableSRRegistry.removeServiceReferences(objectName);
568     }
569
570     @Override
571     public ServiceReferenceWritableRegistry getWritableRegistry() {
572         return writableSRRegistry;
573     }
574
575     public TransactionIdentifier getTransactionIdentifier() {
576         return txLookupRegistry.getTransactionIdentifier();
577     }
578
579     @Override
580     public Set<String> getAvailableModuleFactoryQNames() {
581         return txLookupRegistry.getAvailableModuleFactoryQNames();
582     }
583
584 }