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