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