Merge "BUG 2221 : Add metering to ShardTransaction actor"
[controller.git] / opendaylight / md-sal / sal-rest-connector / src / main / java / org / opendaylight / controller / sal / restconf / impl / BrokerFacade.java
1 /**
2  * Copyright (c) 2014 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.sal.restconf.impl;
9
10 import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.CONFIGURATION;
11 import static org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType.OPERATIONAL;
12
13 import com.google.common.base.Optional;
14 import com.google.common.util.concurrent.CheckedFuture;
15 import com.google.common.util.concurrent.ListenableFuture;
16 import java.util.ArrayList;
17 import java.util.Iterator;
18 import java.util.List;
19 import java.util.concurrent.ExecutionException;
20 import java.util.concurrent.Future;
21 import javax.ws.rs.core.Response.Status;
22 import org.opendaylight.controller.md.sal.common.api.data.AsyncDataBroker.DataChangeScope;
23 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
24 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
25 import org.opendaylight.controller.md.sal.common.api.data.TransactionCommitFailedException;
26 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationException;
27 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizationOperation;
28 import org.opendaylight.controller.md.sal.common.impl.util.compat.DataNormalizer;
29 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
30 import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
31 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadTransaction;
32 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
33 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
34 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
35 import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession;
36 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorTag;
37 import org.opendaylight.controller.sal.restconf.impl.RestconfError.ErrorType;
38 import org.opendaylight.controller.sal.streams.listeners.ListenerAdapter;
39 import org.opendaylight.yangtools.concepts.ListenerRegistration;
40 import org.opendaylight.yangtools.yang.common.QName;
41 import org.opendaylight.yangtools.yang.common.RpcResult;
42 import org.opendaylight.yangtools.yang.data.api.CompositeNode;
43 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
44 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
45 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 public class BrokerFacade {
50     private final static Logger LOG = LoggerFactory.getLogger(BrokerFacade.class);
51
52     private final static BrokerFacade INSTANCE = new BrokerFacade();
53     private volatile ConsumerSession context;
54     private DOMDataBroker domDataBroker;
55
56     private BrokerFacade() {
57     }
58
59     public void setContext(final ConsumerSession context) {
60         this.context = context;
61     }
62
63     public static BrokerFacade getInstance() {
64         return BrokerFacade.INSTANCE;
65     }
66
67     private void checkPreconditions() {
68         if (context == null || domDataBroker == null) {
69             throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
70         }
71     }
72
73     // READ configuration
74     public NormalizedNode<?, ?> readConfigurationData(final YangInstanceIdentifier path) {
75         checkPreconditions();
76         return readDataViaTransaction(domDataBroker.newReadOnlyTransaction(), CONFIGURATION, path);
77     }
78
79     public NormalizedNode<?, ?> readConfigurationData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
80         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
81         if (domDataBrokerService.isPresent()) {
82             return readDataViaTransaction(domDataBrokerService.get().newReadOnlyTransaction(), CONFIGURATION, path);
83         }
84         throw new RestconfDocumentedException("DOM data broker service isn't available for mount point.");
85     }
86
87     // READ operational
88     public NormalizedNode<?, ?> readOperationalData(final YangInstanceIdentifier path) {
89         checkPreconditions();
90         return readDataViaTransaction(domDataBroker.newReadOnlyTransaction(), OPERATIONAL, path);
91     }
92
93     public NormalizedNode<?, ?> readOperationalData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
94         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
95         if (domDataBrokerService.isPresent()) {
96             return readDataViaTransaction(domDataBrokerService.get().newReadOnlyTransaction(), OPERATIONAL, path);
97         }
98         throw new RestconfDocumentedException("DOM data broker service isn't available for mount point.");
99     }
100
101     // PUT configuration
102     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPut(
103             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
104         checkPreconditions();
105         DataNormalizationOperation<?> rootOp = ControllerContext.getInstance().getRootOperation();
106         return putDataViaTransaction(domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, rootOp);
107     }
108
109     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPut(
110             final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
111         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
112         if (domDataBrokerService.isPresent()) {
113             DataNormalizationOperation<?> rootOp = new DataNormalizer(mountPoint.getSchemaContext()).getRootOperation();
114             return putDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path,
115                     payload, rootOp);
116         }
117         throw new RestconfDocumentedException("DOM data broker service isn't available for mount point.");
118     }
119
120     // POST configuration
121     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
122             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
123         checkPreconditions();
124         DataNormalizationOperation<?> rootOp = ControllerContext.getInstance().getRootOperation();
125         return postDataViaTransaction(domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, rootOp);
126     }
127
128     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
129             final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
130         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
131         if (domDataBrokerService.isPresent()) {
132             DataNormalizationOperation<?> rootOp = new DataNormalizer(mountPoint.getSchemaContext()).getRootOperation();
133             return postDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path,
134                     payload, rootOp);
135         }
136         throw new RestconfDocumentedException("DOM data broker service isn't available for mount point.");
137     }
138
139     // DELETE configuration
140     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
141             final YangInstanceIdentifier path) {
142         checkPreconditions();
143         return deleteDataViaTransaction(domDataBroker.newWriteOnlyTransaction(), CONFIGURATION, path);
144     }
145
146     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
147             final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
148         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
149         if (domDataBrokerService.isPresent()) {
150             return deleteDataViaTransaction(domDataBrokerService.get().newWriteOnlyTransaction(), CONFIGURATION, path);
151         }
152         throw new RestconfDocumentedException("DOM data broker service isn't available for mount point.");
153     }
154
155     // RPC
156     public Future<RpcResult<CompositeNode>> invokeRpc(final QName type, final CompositeNode payload) {
157         this.checkPreconditions();
158
159         return context.rpc(type, payload);
160     }
161
162     public void registerToListenDataChanges(final LogicalDatastoreType datastore, final DataChangeScope scope,
163             final ListenerAdapter listener) {
164         this.checkPreconditions();
165
166         if (listener.isListening()) {
167             return;
168         }
169
170         YangInstanceIdentifier path = listener.getPath();
171         final ListenerRegistration<DOMDataChangeListener> registration = domDataBroker.registerDataChangeListener(
172                 datastore, path, listener, scope);
173
174         listener.setRegistration(registration);
175     }
176
177     private NormalizedNode<?, ?> readDataViaTransaction(final DOMDataReadTransaction transaction,
178             LogicalDatastoreType datastore, YangInstanceIdentifier path) {
179         LOG.trace("Read " + datastore.name() + " via Restconf: {}", path);
180         final ListenableFuture<Optional<NormalizedNode<?, ?>>> listenableFuture = transaction.read(datastore, path);
181         if (listenableFuture != null) {
182             Optional<NormalizedNode<?, ?>> optional;
183             try {
184                 LOG.debug("Reading result data from transaction.");
185                 optional = listenableFuture.get();
186             } catch (InterruptedException | ExecutionException e) {
187                 throw new RestconfDocumentedException("Problem to get data from transaction.", e.getCause());
188
189             }
190             if (optional != null) {
191                 if (optional.isPresent()) {
192                     return optional.get();
193                 }
194             }
195         }
196         return null;
197     }
198
199     private CheckedFuture<Void, TransactionCommitFailedException> postDataViaTransaction(
200             final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
201             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, DataNormalizationOperation<?> root) {
202         ListenableFuture<Optional<NormalizedNode<?, ?>>> futureDatastoreData = rWTransaction.read(datastore, path);
203         try {
204             final Optional<NormalizedNode<?, ?>> optionalDatastoreData = futureDatastoreData.get();
205             if (optionalDatastoreData.isPresent() && payload.equals(optionalDatastoreData.get())) {
206                 String errMsg = "Post Configuration via Restconf was not executed because data already exists";
207                 LOG.trace(errMsg + ":{}", path);
208                 throw new RestconfDocumentedException("Data already exists for path: " + path, ErrorType.PROTOCOL,
209                         ErrorTag.DATA_EXISTS);
210             }
211         } catch (InterruptedException | ExecutionException e) {
212             LOG.trace("It wasn't possible to get data loaded from datastore at path " + path);
213         }
214
215         ensureParentsByMerge(datastore, path, rWTransaction, root);
216         rWTransaction.merge(datastore, path, payload);
217         LOG.trace("Post " + datastore.name() + " via Restconf: {}", path);
218         return rWTransaction.submit();
219     }
220
221     private CheckedFuture<Void, TransactionCommitFailedException> putDataViaTransaction(
222             final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
223             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, DataNormalizationOperation<?> root) {
224         LOG.trace("Put " + datastore.name() + " via Restconf: {}", path);
225         ensureParentsByMerge(datastore, path, writeTransaction, root);
226         writeTransaction.put(datastore, path, payload);
227         return writeTransaction.submit();
228     }
229
230     private CheckedFuture<Void, TransactionCommitFailedException> deleteDataViaTransaction(
231             final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
232             YangInstanceIdentifier path) {
233         LOG.trace("Delete " + datastore.name() + " via Restconf: {}", path);
234         writeTransaction.delete(datastore, path);
235         return writeTransaction.submit();
236     }
237
238     public void setDomDataBroker(DOMDataBroker domDataBroker) {
239         this.domDataBroker = domDataBroker;
240     }
241
242     private final void ensureParentsByMerge(final LogicalDatastoreType store,
243             final YangInstanceIdentifier normalizedPath, final DOMDataReadWriteTransaction rwTx,
244             final DataNormalizationOperation<?> root) {
245         List<PathArgument> currentArguments = new ArrayList<>();
246         Iterator<PathArgument> iterator = normalizedPath.getPathArguments().iterator();
247         DataNormalizationOperation<?> currentOp = root;
248         while (iterator.hasNext()) {
249             PathArgument currentArg = iterator.next();
250             try {
251                 currentOp = currentOp.getChild(currentArg);
252             } catch (DataNormalizationException e) {
253                 throw new IllegalArgumentException(
254                         String.format("Invalid child encountered in path %s", normalizedPath), e);
255             }
256             currentArguments.add(currentArg);
257             YangInstanceIdentifier currentPath = YangInstanceIdentifier.create(currentArguments);
258
259             final Boolean exists;
260
261             try {
262
263                 CheckedFuture<Boolean, ReadFailedException> future = rwTx.exists(store, currentPath);
264                 exists = future.checkedGet();
265             } catch (ReadFailedException e) {
266                 LOG.error("Failed to read pre-existing data from store {} path {}", store, currentPath, e);
267                 throw new IllegalStateException("Failed to read pre-existing data", e);
268             }
269
270             if (!exists && iterator.hasNext()) {
271                 rwTx.merge(store, currentPath, currentOp.createDefault(currentArg));
272             }
273         }
274     }
275 }