Fix config transaction handling in netconf.
[controller.git] / opendaylight / netconf / config-netconf-connector / src / main / java / org / opendaylight / controller / netconf / confignetconfconnector / transactions / TransactionProvider.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
9 package org.opendaylight.controller.netconf.confignetconfconnector.transactions;
10
11 import com.google.common.base.Optional;
12 import com.google.common.base.Preconditions;
13 import org.opendaylight.controller.config.api.ValidationException;
14 import org.opendaylight.controller.config.api.jmx.CommitStatus;
15 import org.opendaylight.controller.config.util.ConfigRegistryClient;
16 import org.opendaylight.controller.config.util.ConfigTransactionClient;
17 import org.opendaylight.controller.netconf.api.NetconfDocumentedException;
18 import org.slf4j.Logger;
19 import org.slf4j.LoggerFactory;
20
21 import javax.management.InstanceNotFoundException;
22 import javax.management.ObjectName;
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.Set;
26
27 public class TransactionProvider implements AutoCloseable {
28     private static final Logger logger = LoggerFactory.getLogger(TransactionProvider.class);
29
30     private final ConfigRegistryClient configRegistryClient;
31
32     private final String netconfSessionIdForReporting;
33     private ObjectName transaction;
34     private final List<ObjectName> allOpenedTransactions = new ArrayList<>();
35
36     public TransactionProvider(ConfigRegistryClient configRegistryClient, String netconfSessionIdForReporting) {
37         this.configRegistryClient = configRegistryClient;
38         this.netconfSessionIdForReporting = netconfSessionIdForReporting;
39     }
40
41     @Override
42     public synchronized void close() {
43         for (ObjectName tx : allOpenedTransactions) {
44             try {
45                 if (isStillOpenTransaction(tx)) {
46                     configRegistryClient.getConfigTransactionClient(tx).abortConfig();
47                 }
48             } catch (Exception e) {
49                 logger.debug("Ignoring exception while closing transaction {}", tx, e);
50             }
51         }
52         allOpenedTransactions.clear();
53     }
54
55     public Optional<ObjectName> getTransaction() {
56
57         if (transaction == null)
58             return Optional.absent();
59
60         // Transaction was already closed somehow
61         if (isStillOpenTransaction(transaction) == false) {
62             logger.warn("Fixing illegal state: transaction {} was closed in {}", transaction,
63                     netconfSessionIdForReporting);
64             transaction = null;
65             return Optional.absent();
66         }
67         return Optional.of(transaction);
68     }
69
70     private boolean isStillOpenTransaction(ObjectName transaction) {
71         boolean isStillOpenTransaction = configRegistryClient.getOpenConfigs().contains(transaction);
72         return isStillOpenTransaction;
73     }
74
75     public synchronized ObjectName getOrCreateTransaction() {
76         Optional<ObjectName> ta = getTransaction();
77
78         if (ta.isPresent())
79             return ta.get();
80         transaction = configRegistryClient.beginConfig();
81         allOpenedTransactions.add(transaction);
82         return transaction;
83     }
84
85     /**
86      * Used for editConfig test option
87      */
88     public synchronized ObjectName getTestTransaction() {
89         ObjectName testTx = configRegistryClient.beginConfig();
90         allOpenedTransactions.add(testTx);
91         return testTx;
92     }
93
94     /**
95      * Commit and notification send must be atomic
96      */
97     public synchronized CommitStatus commitTransaction() throws NetconfDocumentedException {
98         final Optional<ObjectName> maybeTaON = getTransaction();
99         Preconditions.checkState(maybeTaON.isPresent(), "No transaction found for session " + netconfSessionIdForReporting);
100         ObjectName taON = maybeTaON.get();
101         try {
102             CommitStatus status = configRegistryClient.commitConfig(taON);
103             // clean up
104             allOpenedTransactions.remove(transaction);
105             transaction = null;
106             return status;
107         } catch (ValidationException validationException) {
108             // no clean up: user can reconfigure and recover this transaction
109             logger.warn("Transaction {} failed on {}", taON, validationException.toString());
110             throw validationException;
111         } catch (Exception e) {
112             logger.error("Exception while commit of {}, aborting transaction", taON, e);
113             // clean up
114             abortTransaction();
115             throw e;
116         }
117     }
118
119     public synchronized void abortTransaction() {
120         logger.debug("Aborting current transaction");
121         Optional<ObjectName> taON = getTransaction();
122         Preconditions.checkState(taON.isPresent(), "No transaction found for session " + netconfSessionIdForReporting);
123
124         ConfigTransactionClient transactionClient = configRegistryClient.getConfigTransactionClient(taON.get());
125         transactionClient.abortConfig();
126         allOpenedTransactions.remove(transaction);
127         transaction = null;
128     }
129
130     public synchronized void abortTestTransaction(ObjectName testTx) {
131         logger.debug("Aborting transaction {}", testTx);
132         ConfigTransactionClient transactionClient = configRegistryClient.getConfigTransactionClient(testTx);
133         allOpenedTransactions.remove(testTx);
134         transactionClient.abortConfig();
135     }
136
137     public void validateTransaction() throws ValidationException {
138         Optional<ObjectName> taON = getTransaction();
139         Preconditions.checkState(taON.isPresent(), "No transaction found for session " + netconfSessionIdForReporting);
140
141         ConfigTransactionClient transactionClient = configRegistryClient.getConfigTransactionClient(taON.get());
142         transactionClient.validateConfig();
143     }
144
145     public void validateTestTransaction(ObjectName taON) {
146         ConfigTransactionClient transactionClient = configRegistryClient.getConfigTransactionClient(taON);
147         transactionClient.validateConfig();
148     }
149
150     public void wipeTestTransaction(ObjectName taON) {
151         wipeInternal(taON, true, null);
152     }
153
154     /**
155      * Wiping means removing all module instances keeping the transaction open + service references.
156      */
157     synchronized void wipeInternal(ObjectName taON, boolean isTest, String moduleName) {
158         ConfigTransactionClient transactionClient = configRegistryClient.getConfigTransactionClient(taON);
159
160         Set<ObjectName> lookupConfigBeans = moduleName == null ? transactionClient.lookupConfigBeans()
161                 : transactionClient.lookupConfigBeans(moduleName);
162         int i = lookupConfigBeans.size();
163         for (ObjectName instance : lookupConfigBeans) {
164             try {
165                 transactionClient.destroyModule(instance);
166             } catch (InstanceNotFoundException e) {
167                 if (isTest)
168                     logger.debug("Unable to clean configuration in transactiom {}", taON, e);
169                 else
170                     logger.warn("Unable to clean configuration in transactiom {}", taON, e);
171
172                 throw new IllegalStateException("Unable to clean configuration in transactiom " + taON, e);
173             }
174         }
175         logger.debug("Transaction {} wiped clean of {} config beans", taON, i);
176
177         transactionClient.removeAllServiceReferences();
178         logger.debug("Transaction {} wiped clean of all service references", taON);
179     }
180
181     public void wipeTransaction() {
182         Optional<ObjectName> taON = getTransaction();
183         Preconditions.checkState(taON.isPresent(), "No transaction found for session " + netconfSessionIdForReporting);
184         wipeInternal(taON.get(), false, null);
185     }
186
187 }