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