Fix star import and enable checkstyle rule to prevent it.
[controller.git] / opendaylight / config / config-manager / src / main / java / org / opendaylight / controller / config / manager / impl / 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.ValidationException;
13 import org.opendaylight.controller.config.api.jmx.ObjectNameUtil;
14 import org.opendaylight.controller.config.manager.impl.dependencyresolver.DependencyResolverManager;
15 import org.opendaylight.controller.config.manager.impl.dynamicmbean.DynamicWritableWrapper;
16 import org.opendaylight.controller.config.manager.impl.dynamicmbean.ReadOnlyAtomicBoolean;
17 import org.opendaylight.controller.config.manager.impl.dynamicmbean.ReadOnlyAtomicBoolean.ReadOnlyAtomicBooleanImpl;
18 import org.opendaylight.controller.config.manager.impl.factoriesresolver.HierarchicalConfigMBeanFactoriesHolder;
19 import org.opendaylight.controller.config.manager.impl.jmx.TransactionJMXRegistrator;
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.manager.impl.util.LookupBeansUtil;
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.osgi.framework.BundleContext;
27 import org.slf4j.Logger;
28 import org.slf4j.LoggerFactory;
29
30 import javax.annotation.Nullable;
31 import javax.annotation.concurrent.GuardedBy;
32 import javax.management.DynamicMBean;
33 import javax.management.InstanceAlreadyExistsException;
34 import javax.management.InstanceNotFoundException;
35 import javax.management.MBeanServer;
36 import javax.management.ObjectName;
37 import java.util.ArrayList;
38 import java.util.Collection;
39 import java.util.HashSet;
40 import java.util.List;
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  * {@link #transactionIdentifier}, 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 TransactionIdentifier transactionIdentifier;
59     private final ObjectName controllerON;
60     private final TransactionJMXRegistrator transactionRegistrator;
61     private final TransactionModuleJMXRegistrator txModuleJMXRegistrator;
62     private final long parentVersion, currentVersion;
63     private final HierarchicalConfigMBeanFactoriesHolder factoriesHolder;
64     private final DependencyResolverManager dependencyResolverManager;
65     private final TransactionStatus transactionStatus;
66     private final MBeanServer transactionsMBeanServer;
67     private final List<ModuleFactory> currentlyRegisteredFactories;
68
69     /**
70      * Disables ability of {@link DynamicWritableWrapper} to change attributes
71      * during validation.
72      */
73     @GuardedBy("this")
74     private final AtomicBoolean configBeanModificationDisabled = new AtomicBoolean(
75             false);
76     private final ReadOnlyAtomicBoolean readOnlyAtomicBoolean = new ReadOnlyAtomicBooleanImpl(
77             configBeanModificationDisabled);
78     private final MBeanServer configMBeanServer;
79
80     private final BundleContext bundleContext;
81
82     public ConfigTransactionControllerImpl(String transactionName,
83                                            TransactionJMXRegistrator transactionRegistrator,
84                                            long parentVersion, long currentVersion,
85                                            List<ModuleFactory> currentlyRegisteredFactories,
86                                            MBeanServer transactionsMBeanServer, MBeanServer configMBeanServer, BundleContext bundleContext) {
87
88         this.transactionIdentifier = new TransactionIdentifier(transactionName);
89         this.controllerON = ObjectNameUtil
90                 .createTransactionControllerON(transactionName);
91         this.transactionRegistrator = transactionRegistrator;
92         txModuleJMXRegistrator = transactionRegistrator
93                 .createTransactionModuleJMXRegistrator();
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(transactionName, transactionStatus);
100         this.transactionsMBeanServer = transactionsMBeanServer;
101         this.configMBeanServer = configMBeanServer;
102         this.bundleContext = bundleContext;
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<>(currentlyRegisteredFactories);
124
125         List<ModuleFactory> toBeAdded = new ArrayList<>();
126         List<ModuleFactory> toBeRemoved = new ArrayList<>();
127         for(ModuleFactory moduleFactory: currentlyRegisteredFactories) {
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, bundleContext);
140             for (Module module : defaultModules) {
141                 try {
142                     putConfigBeanToJMXAndInternalMaps(module.getIdentifier(), module, moduleFactory, null);
143                 } catch (InstanceAlreadyExistsException e) {
144                     throw new IllegalStateException(e);
145                 }
146             }
147         }
148
149         // remove modules belonging to removed factories
150         for(ModuleFactory removedFactory: toBeRemoved){
151             List<ModuleIdentifier> modulesOfRemovedFactory = dependencyResolverManager.findAllByFactory(removedFactory);
152             for (ModuleIdentifier name : modulesOfRemovedFactory) {
153                 destroyModule(name);
154             }
155         }
156     }
157
158
159     private synchronized void copyExistingModule(
160             ModuleInternalInfo oldConfigBeanInfo)
161             throws InstanceAlreadyExistsException {
162         transactionStatus.checkNotCommitStarted();
163         transactionStatus.checkNotAborted();
164         ModuleIdentifier moduleIdentifier = oldConfigBeanInfo.getName();
165         dependencyResolverManager.assertNotExists(moduleIdentifier);
166
167         ModuleFactory moduleFactory = factoriesHolder
168                 .findByModuleName(moduleIdentifier.getFactoryName());
169
170         Module module;
171         DependencyResolver dependencyResolver = dependencyResolverManager
172                 .getOrCreate(moduleIdentifier);
173         try {
174             module = moduleFactory.createModule(
175                     moduleIdentifier.getInstanceName(), dependencyResolver,
176                     oldConfigBeanInfo.getReadableModule(), bundleContext);
177         } catch (Exception e) {
178             throw new IllegalStateException(format(
179                     "Error while copying old configuration from %s to %s",
180                     oldConfigBeanInfo, moduleFactory), e);
181         }
182         putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module, moduleFactory, oldConfigBeanInfo);
183     }
184
185     @Override
186     public synchronized ObjectName createModule(String factoryName,
187             String instanceName) throws InstanceAlreadyExistsException {
188
189         transactionStatus.checkNotCommitStarted();
190         transactionStatus.checkNotAborted();
191         ModuleIdentifier moduleIdentifier = new ModuleIdentifier(factoryName, instanceName);
192         dependencyResolverManager.assertNotExists(moduleIdentifier);
193
194         // find factory
195         ModuleFactory moduleFactory = factoriesHolder.findByModuleName(factoryName);
196         DependencyResolver dependencyResolver = dependencyResolverManager.getOrCreate(moduleIdentifier);
197         Module module = moduleFactory.createModule(instanceName, dependencyResolver, bundleContext);
198         return putConfigBeanToJMXAndInternalMaps(moduleIdentifier, module,
199                 moduleFactory, null);
200     }
201
202     private synchronized ObjectName putConfigBeanToJMXAndInternalMaps(
203             ModuleIdentifier moduleIdentifier, Module module,
204             ModuleFactory moduleFactory,
205             @Nullable ModuleInternalInfo maybeOldConfigBeanInfo)
206             throws InstanceAlreadyExistsException {
207         logger.debug("Adding module {} to transaction {}", moduleIdentifier, this);
208         if (moduleIdentifier.equals(module.getIdentifier())==false) {
209             throw new IllegalStateException("Incorrect name reported by module. Expected "
210              + moduleIdentifier + ", got " + module.getIdentifier());
211         }
212         DynamicMBean writableDynamicWrapper = new DynamicWritableWrapper(
213                 module, moduleIdentifier, transactionIdentifier,
214                 readOnlyAtomicBoolean, transactionsMBeanServer,
215                 configMBeanServer);
216
217         ObjectName writableON = ObjectNameUtil.createTransactionModuleON(
218                 transactionIdentifier.getName(), moduleIdentifier);
219         // put wrapper to jmx
220         TransactionModuleJMXRegistration transactionModuleJMXRegistration = txModuleJMXRegistrator
221                 .registerMBean(writableDynamicWrapper, writableON);
222         ModuleInternalTransactionalInfo moduleInternalTransactionalInfo = new ModuleInternalTransactionalInfo(
223                 moduleIdentifier, module, moduleFactory,
224                 maybeOldConfigBeanInfo, transactionModuleJMXRegistration);
225
226         dependencyResolverManager.put(moduleInternalTransactionalInfo);
227         return writableON;
228     }
229
230     @Override
231     public void destroyModule(ObjectName objectName)
232             throws InstanceNotFoundException {
233         String foundTransactionName = ObjectNameUtil
234                 .getTransactionName(objectName);
235         if (transactionIdentifier.getName().equals(foundTransactionName) == false) {
236             throw new IllegalArgumentException("Wrong transaction name "
237                     + objectName);
238         }
239         ObjectNameUtil.checkDomain(objectName);
240         ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(objectName,
241                 ObjectNameUtil.TYPE_MODULE);
242         destroyModule(moduleIdentifier);
243     }
244
245     private void destroyModule(ModuleIdentifier moduleIdentifier) {
246         logger.debug("Destroying module {} in transaction {}", moduleIdentifier, this);
247         transactionStatus.checkNotAborted();
248         ModuleInternalTransactionalInfo removedTInfo = dependencyResolverManager.destroyModule(moduleIdentifier);
249         // remove from jmx
250         removedTInfo.getTransactionModuleJMXRegistration().close();
251     }
252
253     @Override
254     public long getParentVersion() {
255         return parentVersion;
256     }
257
258     @Override
259     public long getVersion() {
260         return currentVersion;
261     }
262
263     @Override
264     public synchronized void validateConfig() throws ValidationException {
265         if (configBeanModificationDisabled.get())
266             throw new IllegalStateException("Cannot start validation");
267         configBeanModificationDisabled.set(true);
268         try {
269             validate_noLocks();
270         } finally {
271             configBeanModificationDisabled.set(false);
272         }
273     }
274
275     private void validate_noLocks() throws ValidationException {
276         transactionStatus.checkNotAborted();
277         logger.info("Validating transaction {}", transactionIdentifier);
278         // call validate()
279         List<ValidationException> collectedExceptions = new ArrayList<>();
280         for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
281                 .getAllModules().entrySet()) {
282             ModuleIdentifier name = entry.getKey();
283             Module module = entry.getValue();
284             try {
285                 module.validate();
286             } catch (Exception e) {
287                 logger.warn("Validation exception in {}", getTransactionName(),
288                         e);
289                 collectedExceptions.add(ValidationException
290                         .createForSingleException(name, e));
291             }
292         }
293         if (collectedExceptions.size() > 0) {
294             throw ValidationException
295                     .createFromCollectedValidationExceptions(collectedExceptions);
296         }
297         logger.info("Validated transaction {}", transactionIdentifier);
298     }
299
300     /**
301      * If this method passes validation, it will grab
302      * {@link TransactionStatus#secondPhaseCommitStarted} lock. This lock will
303      * prevent calling @{link #validateBeforeCommitAndLockTransaction},
304      * effectively only allowing to call {@link #secondPhaseCommit} after
305      * successful return of this method.
306      */
307     @Override
308     public synchronized CommitInfo validateBeforeCommitAndLockTransaction()
309             throws ValidationException {
310         transactionStatus.checkNotAborted();
311         transactionStatus.checkNotCommitStarted();
312         configBeanModificationDisabled.set(true);
313         try {
314             validate_noLocks();
315         } catch (ValidationException e) {
316             logger.info("Commit failed on validation");
317             configBeanModificationDisabled.set(false); // recoverable error
318             throw e;
319         }
320         // errors in this state are not recoverable. modules are not mutable
321         // anymore.
322         transactionStatus.setSecondPhaseCommitStarted();
323         return dependencyResolverManager.toCommitInfo();
324     }
325
326     /**
327      * {@inheritDoc}
328      */
329     @Override
330     public synchronized List<ModuleIdentifier> secondPhaseCommit() {
331         transactionStatus.checkNotAborted();
332         transactionStatus.checkCommitStarted();
333         if (configBeanModificationDisabled.get() == false) {
334             throw new IllegalStateException(
335                     "Internal error - validateBeforeCommitAndLockTransaction should be called "
336                             + "to obtain a lock");
337         }
338
339         logger.info("Committing transaction {}", transactionIdentifier);
340
341         // call getInstance()
342         for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
343                 .getAllModules().entrySet()) {
344             Module module = entry.getValue();
345             ModuleIdentifier name = entry.getKey();
346             try {
347                 logger.debug("About to commit {} in transaction {}",
348                         transactionIdentifier, name);
349                 module.getInstance();
350             } catch (Exception e) {
351                 logger.error("Commit failed on {} in transaction {}", name,
352                         transactionIdentifier, e);
353                 internalAbort();
354                 throw new RuntimeException(
355                         format("Error - getInstance() failed for %s in transaction %s",
356                                 name, transactionIdentifier), e);
357             }
358         }
359
360         // count dependency order
361
362         logger.info("Committed configuration {}", transactionIdentifier);
363         transactionStatus.setCommitted();
364         // unregister this and all modules from jmx
365         close();
366
367         return dependencyResolverManager.getSortedModuleIdentifiers();
368     }
369
370     @Override
371     public synchronized void abortConfig() {
372         transactionStatus.checkNotCommitStarted();
373         transactionStatus.checkNotAborted();
374         internalAbort();
375     }
376
377     private void internalAbort() {
378         transactionStatus.setAborted();
379         close();
380     }
381
382     private void close() {
383         transactionRegistrator.close();
384     }
385
386     @Override
387     public ObjectName getControllerObjectName() {
388         return controllerON;
389     }
390
391     @Override
392     public String getTransactionName() {
393         return transactionIdentifier.getName();
394     }
395
396     /**
397      * {@inheritDoc}
398      */
399     @Override
400     public Set<ObjectName> lookupConfigBeans() {
401         return lookupConfigBeans("*", "*");
402     }
403
404     /**
405      * {@inheritDoc}
406      */
407     @Override
408     public Set<ObjectName> lookupConfigBeans(String moduleName) {
409         return lookupConfigBeans(moduleName, "*");
410     }
411
412     /**
413      * {@inheritDoc}
414      */
415     @Override
416     public ObjectName lookupConfigBean(String moduleName, String instanceName)
417             throws InstanceNotFoundException {
418         return LookupBeansUtil.lookupConfigBean(this, moduleName, instanceName);
419     }
420
421     /**
422      * {@inheritDoc}
423      */
424     @Override
425     public Set<ObjectName> lookupConfigBeans(String moduleName,
426             String instanceName) {
427         ObjectName namePattern = ObjectNameUtil.createModulePattern(moduleName,
428                 instanceName, transactionIdentifier.getName());
429         return txModuleJMXRegistrator.queryNames(namePattern, null);
430     }
431
432     @Override
433     public Set<String> getAvailableModuleNames() {
434         return factoriesHolder.getModuleNames();
435     }
436
437     @Override
438     public boolean isClosed() {
439         return transactionStatus.isAbortedOrCommitted();
440     }
441
442     @Override
443     public String toString() {
444         StringBuilder sb = new StringBuilder();
445         sb.append("transactionName=");
446         sb.append(getTransactionName());
447         return sb.toString();
448     }
449
450     // @VisibleForTesting
451
452     TransactionModuleJMXRegistrator getTxModuleJMXRegistrator() {
453         return txModuleJMXRegistrator;
454     }
455
456     public TransactionIdentifier getName() {
457         return transactionIdentifier;
458     }
459
460     @Override
461     public List<ModuleFactory> getCurrentlyRegisteredFactories() {
462         return currentlyRegisteredFactories;
463     }
464
465     @Override
466     public TransactionIdentifier getIdentifier() {
467         return transactionIdentifier;
468     }
469 }