creating a default subnet
[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         // ensure default module to be registered to jmx even if its module factory does not use dependencyResolverFactory
228         dependencyResolverManager.getOrCreate(moduleIdentifier);
229         return writableON;
230     }
231
232     @Override
233     public void destroyModule(ObjectName objectName)
234             throws InstanceNotFoundException {
235         String foundTransactionName = ObjectNameUtil
236                 .getTransactionName(objectName);
237         if (transactionIdentifier.getName().equals(foundTransactionName) == false) {
238             throw new IllegalArgumentException("Wrong transaction name "
239                     + objectName);
240         }
241         ObjectNameUtil.checkDomain(objectName);
242         ModuleIdentifier moduleIdentifier = ObjectNameUtil.fromON(objectName,
243                 ObjectNameUtil.TYPE_MODULE);
244         destroyModule(moduleIdentifier);
245     }
246
247     private void destroyModule(ModuleIdentifier moduleIdentifier) {
248         logger.debug("Destroying module {} in transaction {}", moduleIdentifier, this);
249         transactionStatus.checkNotAborted();
250         ModuleInternalTransactionalInfo removedTInfo = dependencyResolverManager.destroyModule(moduleIdentifier);
251         // remove from jmx
252         removedTInfo.getTransactionModuleJMXRegistration().close();
253     }
254
255     @Override
256     public long getParentVersion() {
257         return parentVersion;
258     }
259
260     @Override
261     public long getVersion() {
262         return currentVersion;
263     }
264
265     @Override
266     public synchronized void validateConfig() throws ValidationException {
267         if (configBeanModificationDisabled.get())
268             throw new IllegalStateException("Cannot start validation");
269         configBeanModificationDisabled.set(true);
270         try {
271             validate_noLocks();
272         } finally {
273             configBeanModificationDisabled.set(false);
274         }
275     }
276
277     private void validate_noLocks() throws ValidationException {
278         transactionStatus.checkNotAborted();
279         logger.info("Validating transaction {}", transactionIdentifier);
280         // call validate()
281         List<ValidationException> collectedExceptions = new ArrayList<>();
282         for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
283                 .getAllModules().entrySet()) {
284             ModuleIdentifier name = entry.getKey();
285             Module module = entry.getValue();
286             try {
287                 module.validate();
288             } catch (Exception e) {
289                 logger.warn("Validation exception in {}", getTransactionName(),
290                         e);
291                 collectedExceptions.add(ValidationException
292                         .createForSingleException(name, e));
293             }
294         }
295         if (collectedExceptions.size() > 0) {
296             throw ValidationException
297                     .createFromCollectedValidationExceptions(collectedExceptions);
298         }
299         logger.info("Validated transaction {}", transactionIdentifier);
300     }
301
302     /**
303      * If this method passes validation, it will grab
304      * {@link TransactionStatus#secondPhaseCommitStarted} lock. This lock will
305      * prevent calling @{link #validateBeforeCommitAndLockTransaction},
306      * effectively only allowing to call {@link #secondPhaseCommit} after
307      * successful return of this method.
308      */
309     @Override
310     public synchronized CommitInfo validateBeforeCommitAndLockTransaction()
311             throws ValidationException {
312         transactionStatus.checkNotAborted();
313         transactionStatus.checkNotCommitStarted();
314         configBeanModificationDisabled.set(true);
315         try {
316             validate_noLocks();
317         } catch (ValidationException e) {
318             logger.info("Commit failed on validation");
319             configBeanModificationDisabled.set(false); // recoverable error
320             throw e;
321         }
322         // errors in this state are not recoverable. modules are not mutable
323         // anymore.
324         transactionStatus.setSecondPhaseCommitStarted();
325         return dependencyResolverManager.toCommitInfo();
326     }
327
328     /**
329      * {@inheritDoc}
330      */
331     @Override
332     public synchronized List<ModuleIdentifier> secondPhaseCommit() {
333         transactionStatus.checkNotAborted();
334         transactionStatus.checkCommitStarted();
335         if (configBeanModificationDisabled.get() == false) {
336             throw new IllegalStateException(
337                     "Internal error - validateBeforeCommitAndLockTransaction should be called "
338                             + "to obtain a lock");
339         }
340
341         logger.info("Committing transaction {}", transactionIdentifier);
342
343         // call getInstance()
344         for (Entry<ModuleIdentifier, Module> entry : dependencyResolverManager
345                 .getAllModules().entrySet()) {
346             Module module = entry.getValue();
347             ModuleIdentifier name = entry.getKey();
348             try {
349                 logger.debug("About to commit {} in transaction {}",
350                         name, transactionIdentifier);
351                 module.getInstance();
352             } catch (Exception e) {
353                 logger.error("Commit failed on {} in transaction {}", name,
354                         transactionIdentifier, e);
355                 internalAbort();
356                 throw new RuntimeException(
357                         format("Error - getInstance() failed for %s in transaction %s",
358                                 name, transactionIdentifier), e);
359             }
360         }
361
362         // count dependency order
363
364         logger.info("Committed configuration {}", transactionIdentifier);
365         transactionStatus.setCommitted();
366         // unregister this and all modules from jmx
367         close();
368
369         return dependencyResolverManager.getSortedModuleIdentifiers();
370     }
371
372     @Override
373     public synchronized void abortConfig() {
374         transactionStatus.checkNotCommitStarted();
375         transactionStatus.checkNotAborted();
376         internalAbort();
377     }
378
379     private void internalAbort() {
380         transactionStatus.setAborted();
381         close();
382     }
383
384     private void close() {
385         transactionRegistrator.close();
386     }
387
388     @Override
389     public ObjectName getControllerObjectName() {
390         return controllerON;
391     }
392
393     @Override
394     public String getTransactionName() {
395         return transactionIdentifier.getName();
396     }
397
398     /**
399      * {@inheritDoc}
400      */
401     @Override
402     public Set<ObjectName> lookupConfigBeans() {
403         return lookupConfigBeans("*", "*");
404     }
405
406     /**
407      * {@inheritDoc}
408      */
409     @Override
410     public Set<ObjectName> lookupConfigBeans(String moduleName) {
411         return lookupConfigBeans(moduleName, "*");
412     }
413
414     /**
415      * {@inheritDoc}
416      */
417     @Override
418     public ObjectName lookupConfigBean(String moduleName, String instanceName)
419             throws InstanceNotFoundException {
420         return LookupBeansUtil.lookupConfigBean(this, moduleName, instanceName);
421     }
422
423     /**
424      * {@inheritDoc}
425      */
426     @Override
427     public Set<ObjectName> lookupConfigBeans(String moduleName,
428             String instanceName) {
429         ObjectName namePattern = ObjectNameUtil.createModulePattern(moduleName,
430                 instanceName, transactionIdentifier.getName());
431         return txModuleJMXRegistrator.queryNames(namePattern, null);
432     }
433
434     @Override
435     public Set<String> getAvailableModuleNames() {
436         return factoriesHolder.getModuleNames();
437     }
438
439     @Override
440     public boolean isClosed() {
441         return transactionStatus.isAbortedOrCommitted();
442     }
443
444     @Override
445     public String toString() {
446         StringBuilder sb = new StringBuilder();
447         sb.append("transactionName=");
448         sb.append(getTransactionName());
449         return sb.toString();
450     }
451
452     // @VisibleForTesting
453
454     TransactionModuleJMXRegistrator getTxModuleJMXRegistrator() {
455         return txModuleJMXRegistrator;
456     }
457
458     public TransactionIdentifier getName() {
459         return transactionIdentifier;
460     }
461
462     @Override
463     public List<ModuleFactory> getCurrentlyRegisteredFactories() {
464         return currentlyRegisteredFactories;
465     }
466
467     @Override
468     public TransactionIdentifier getIdentifier() {
469         return transactionIdentifier;
470     }
471 }