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