Bug 4455 - Inconsistent COMMIT operation handling when no transactions are present
[controller.git] / opendaylight / netconf / config-netconf-connector / src / main / java / org / opendaylight / controller / netconf / confignetconfconnector / transactions / TransactionProvider.java
1 /*
2  * Copyright (c) 2015 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 java.util.ArrayList;
14 import java.util.Collections;
15 import java.util.List;
16 import java.util.Set;
17 import javax.management.InstanceNotFoundException;
18 import javax.management.ObjectName;
19 import org.opendaylight.controller.config.api.ConflictingVersionException;
20 import org.opendaylight.controller.config.api.ValidationException;
21 import org.opendaylight.controller.config.api.jmx.CommitStatus;
22 import org.opendaylight.controller.config.util.ConfigRegistryClient;
23 import org.opendaylight.controller.config.util.ConfigTransactionClient;
24 import org.slf4j.Logger;
25 import org.slf4j.LoggerFactory;
26
27 public class TransactionProvider implements AutoCloseable {
28     private static final Logger LOG = LoggerFactory.getLogger(TransactionProvider.class);
29
30     private final ConfigRegistryClient configRegistryClient;
31
32     private final String netconfSessionIdForReporting;
33     private ObjectName candidateTx;
34     private ObjectName readTx;
35     private final List<ObjectName> allOpenedTransactions = new ArrayList<>();
36     private static final String  NO_TRANSACTION_FOUND_FOR_SESSION = "No transaction found for session ";
37
38     public TransactionProvider(ConfigRegistryClient configRegistryClient, String netconfSessionIdForReporting) {
39         this.configRegistryClient = configRegistryClient;
40         this.netconfSessionIdForReporting = netconfSessionIdForReporting;
41     }
42
43     @Override
44     public synchronized void close() {
45         for (ObjectName tx : allOpenedTransactions) {
46             try {
47                 if (isStillOpenTransaction(tx)) {
48                     configRegistryClient.getConfigTransactionClient(tx).abortConfig();
49                 }
50             } catch (Exception e) {
51                 LOG.debug("Ignoring exception while closing transaction {}", tx, e);
52             }
53         }
54         allOpenedTransactions.clear();
55     }
56
57     public synchronized Optional<ObjectName> getTransaction() {
58
59         if (candidateTx == null){
60             return Optional.absent();
61         }
62
63         // Transaction was already closed somehow
64         if (!isStillOpenTransaction(candidateTx)) {
65             LOG.warn("Fixing illegal state: transaction {} was closed in {}", candidateTx,
66                     netconfSessionIdForReporting);
67             candidateTx = null;
68             return Optional.absent();
69         }
70         return Optional.of(candidateTx);
71     }
72
73     public synchronized Optional<ObjectName> getReadTransaction() {
74
75         if (readTx == null){
76             return Optional.absent();
77         }
78
79         // Transaction was already closed somehow
80         if (!isStillOpenTransaction(readTx)) {
81             LOG.warn("Fixing illegal state: transaction {} was closed in {}", readTx,
82                     netconfSessionIdForReporting);
83             readTx = null;
84             return Optional.absent();
85         }
86         return Optional.of(readTx);
87     }
88
89     private boolean isStillOpenTransaction(ObjectName transaction) {
90         return configRegistryClient.getOpenConfigs().contains(transaction);
91     }
92
93     public synchronized ObjectName getOrCreateTransaction() {
94         Optional<ObjectName> ta = getTransaction();
95
96         if (ta.isPresent()) {
97             return ta.get();
98         }
99         candidateTx = configRegistryClient.beginConfig();
100         allOpenedTransactions.add(candidateTx);
101         return candidateTx;
102     }
103
104     public synchronized ObjectName getOrCreateReadTransaction() {
105         Optional<ObjectName> ta = getReadTransaction();
106
107         if (ta.isPresent()) {
108             return ta.get();
109         }
110         readTx = configRegistryClient.beginConfig();
111         allOpenedTransactions.add(readTx);
112         return readTx;
113     }
114
115     /**
116      * Used for editConfig test option
117      */
118     public synchronized ObjectName getTestTransaction() {
119         ObjectName testTx = configRegistryClient.beginConfig();
120         allOpenedTransactions.add(testTx);
121         return testTx;
122     }
123
124     /**
125      * Commit and notification send must be atomic
126      */
127     public CommitStatus commitTransaction() throws ValidationException, ConflictingVersionException {
128         return commitTransaction(configRegistryClient);
129     }
130
131     /**
132      * Commit and notification send must be atomic
133      * @param configRegistryClient
134      */
135     public synchronized CommitStatus commitTransaction(final ConfigRegistryClient configRegistryClient) throws ValidationException, ConflictingVersionException {
136         if (!getTransaction().isPresent()){
137             //making empty commit without prior opened transaction, just return commit status with empty lists
138             LOG.debug("Making commit without open candidate transaction for session {}", netconfSessionIdForReporting);
139             return new CommitStatus(Collections.EMPTY_LIST, Collections.EMPTY_LIST, Collections.EMPTY_LIST);
140         }
141         final Optional<ObjectName> maybeTaON = getTransaction();
142         ObjectName taON = maybeTaON.get();
143         try {
144             CommitStatus status = configRegistryClient.commitConfig(taON);
145             // clean up
146             allOpenedTransactions.remove(candidateTx);
147             candidateTx = null;
148             return status;
149         } catch (ValidationException validationException) {
150             // no clean up: user can reconfigure and recover this transaction
151             LOG.warn("Transaction {} failed on {}", taON, validationException.toString());
152             throw validationException;
153         } catch (ConflictingVersionException e) {
154             LOG.error("Exception while commit of {}, aborting transaction", taON, e);
155             // clean up
156             abortTransaction();
157             throw e;
158         }
159     }
160
161     public synchronized void abortTransaction() {
162         LOG.debug("Aborting current transaction");
163         Optional<ObjectName> taON = getTransaction();
164         Preconditions.checkState(taON.isPresent(), NO_TRANSACTION_FOUND_FOR_SESSION + netconfSessionIdForReporting);
165
166         ConfigTransactionClient transactionClient = configRegistryClient.getConfigTransactionClient(taON.get());
167         transactionClient.abortConfig();
168         allOpenedTransactions.remove(candidateTx);
169         candidateTx = null;
170     }
171
172     public synchronized void closeReadTransaction() {
173         LOG.debug("Closing read transaction");
174         Optional<ObjectName> taON = getReadTransaction();
175         Preconditions.checkState(taON.isPresent(), NO_TRANSACTION_FOUND_FOR_SESSION + netconfSessionIdForReporting);
176
177         ConfigTransactionClient transactionClient = configRegistryClient.getConfigTransactionClient(taON.get());
178         transactionClient.abortConfig();
179         allOpenedTransactions.remove(readTx);
180         readTx = null;
181     }
182
183     public synchronized void abortTestTransaction(ObjectName testTx) {
184         LOG.debug("Aborting transaction {}", testTx);
185         ConfigTransactionClient transactionClient = configRegistryClient.getConfigTransactionClient(testTx);
186         allOpenedTransactions.remove(testTx);
187         transactionClient.abortConfig();
188     }
189
190     public void validateTransaction() throws ValidationException {
191         Optional<ObjectName> taON = getTransaction();
192         Preconditions.checkState(taON.isPresent(), NO_TRANSACTION_FOUND_FOR_SESSION + netconfSessionIdForReporting);
193
194         ConfigTransactionClient transactionClient = configRegistryClient.getConfigTransactionClient(taON.get());
195         transactionClient.validateConfig();
196     }
197
198     public void validateTestTransaction(ObjectName taON) throws ValidationException {
199         ConfigTransactionClient transactionClient = configRegistryClient.getConfigTransactionClient(taON);
200         transactionClient.validateConfig();
201     }
202
203     public void wipeTestTransaction(ObjectName taON) {
204         wipeInternal(taON, true);
205     }
206
207     /**
208      * Wiping means removing all module instances keeping the transaction open + service references.
209      */
210     synchronized void wipeInternal(ObjectName taON, boolean isTest) {
211         ConfigTransactionClient transactionClient = configRegistryClient.getConfigTransactionClient(taON);
212
213         Set<ObjectName> lookupConfigBeans = transactionClient.lookupConfigBeans();
214         int i = lookupConfigBeans.size();
215         for (ObjectName instance : lookupConfigBeans) {
216             try {
217                 transactionClient.destroyModule(instance);
218             } catch (InstanceNotFoundException e) {
219                 if (isTest){
220                     LOG.debug("Unable to clean configuration in transactiom {}", taON, e);
221                 } else {
222                     LOG.warn("Unable to clean configuration in transactiom {}", taON, e);
223                 }
224
225                 throw new IllegalStateException("Unable to clean configuration in transactiom " + taON, e);
226             }
227         }
228         LOG.debug("Transaction {} wiped clean of {} config beans", taON, i);
229
230         transactionClient.removeAllServiceReferences();
231         LOG.debug("Transaction {} wiped clean of all service references", taON);
232     }
233
234     public void wipeTransaction() {
235         Optional<ObjectName> taON = getTransaction();
236         Preconditions.checkState(taON.isPresent(), NO_TRANSACTION_FOUND_FOR_SESSION + netconfSessionIdForReporting);
237         wipeInternal(taON.get(), false);
238     }
239
240 }