Merge "Remove blocking checkedGet call"
[netconf.git] / restconf / sal-rest-connector / src / main / java / org / opendaylight / netconf / 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.netconf.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 import com.google.common.base.Optional;
13 import com.google.common.base.Preconditions;
14 import com.google.common.collect.ImmutableList;
15 import com.google.common.util.concurrent.CheckedFuture;
16 import com.google.common.util.concurrent.ListenableFuture;
17 import java.util.ArrayList;
18 import java.util.Iterator;
19 import java.util.List;
20 import java.util.concurrent.ExecutionException;
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.TransactionCommitFailedException;
25 import org.opendaylight.controller.md.sal.dom.api.DOMDataBroker;
26 import org.opendaylight.controller.md.sal.dom.api.DOMDataChangeListener;
27 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadTransaction;
28 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
29 import org.opendaylight.controller.md.sal.dom.api.DOMDataWriteTransaction;
30 import org.opendaylight.controller.md.sal.dom.api.DOMMountPoint;
31 import org.opendaylight.controller.md.sal.dom.api.DOMRpcException;
32 import org.opendaylight.controller.md.sal.dom.api.DOMRpcResult;
33 import org.opendaylight.controller.md.sal.dom.api.DOMRpcService;
34 import org.opendaylight.controller.sal.core.api.Broker.ConsumerSession;
35 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorTag;
36 import org.opendaylight.netconf.sal.restconf.impl.RestconfError.ErrorType;
37 import org.opendaylight.netconf.sal.streams.listeners.ListenerAdapter;
38 import org.opendaylight.yangtools.concepts.ListenerRegistration;
39 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
40 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
41 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
42 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
43 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
44 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
45 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
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         final String errMsg = "DOM data broker service isn't available for mount point " + path;
91         LOG.warn(errMsg);
92         throw new RestconfDocumentedException(errMsg);
93     }
94
95     // READ operational
96     public NormalizedNode<?, ?> readOperationalData(final YangInstanceIdentifier path) {
97         checkPreconditions();
98         return readDataViaTransaction(domDataBroker.newReadOnlyTransaction(), OPERATIONAL, path);
99     }
100
101     public NormalizedNode<?, ?> readOperationalData(final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
102         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
103         if (domDataBrokerService.isPresent()) {
104             return readDataViaTransaction(domDataBrokerService.get().newReadOnlyTransaction(), OPERATIONAL, path);
105         }
106         final String errMsg = "DOM data broker service isn't available for mount point " + path;
107         LOG.warn(errMsg);
108         throw new RestconfDocumentedException(errMsg);
109     }
110
111     // PUT configuration
112     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPut(
113             final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
114         checkPreconditions();
115         return putDataViaTransaction(domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, globalSchema);
116     }
117
118     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPut(
119             final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
120         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
121         if (domDataBrokerService.isPresent()) {
122             return putDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path,
123                     payload, mountPoint.getSchemaContext());
124         }
125         final String errMsg = "DOM data broker service isn't available for mount point " + path;
126         LOG.warn(errMsg);
127         throw new RestconfDocumentedException(errMsg);
128     }
129
130     public PATCHStatusContext patchConfigurationDataWithinTransaction(final PATCHContext context,
131                                                                       final SchemaContext globalSchema) {
132         final DOMDataReadWriteTransaction patchTransaction = domDataBroker.newReadWriteTransaction();
133         List<PATCHStatusEntity> editCollection = new ArrayList<>();
134         List<RestconfError> editErrors;
135         List<RestconfError> globalErrors = null;
136         int errorCounter = 0;
137
138         for (PATCHEntity patchEntity : context.getData()) {
139             final PATCHEditOperation operation = PATCHEditOperation.valueOf(patchEntity.getOperation().toUpperCase());
140
141             switch (operation) {
142                 case CREATE:
143                     if (errorCounter == 0) {
144                         try {
145                             postDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity.getTargetNode(),
146                                     patchEntity.getNode(), globalSchema);
147                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
148                         } catch (RestconfDocumentedException e) {
149                             editErrors = new ArrayList<>();
150                             editErrors.addAll(e.getErrors());
151                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
152                             errorCounter++;
153                         }
154                     }
155                     break;
156                 case REPLACE:
157                     if (errorCounter == 0) {
158                         try {
159                             putDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
160                                     .getTargetNode(), patchEntity.getNode(), globalSchema);
161                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
162                         } catch (RestconfDocumentedException e) {
163                             editErrors = new ArrayList<>();
164                             editErrors.addAll(e.getErrors());
165                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
166                             errorCounter++;
167                         }
168                     }
169                     break;
170                 case DELETE:
171                     if (errorCounter == 0) {
172                         try {
173                             deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
174                                     .getTargetNode());
175                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
176                         } catch (RestconfDocumentedException e) {
177                             editErrors = new ArrayList<>();
178                             editErrors.addAll(e.getErrors());
179                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), false, editErrors));
180                             errorCounter++;
181                         }
182                     }
183                     break;
184                 case REMOVE:
185                     if (errorCounter == 0) {
186                         try {
187                             deleteDataWithinTransaction(patchTransaction, CONFIGURATION, patchEntity
188                                     .getTargetNode());
189                             editCollection.add(new PATCHStatusEntity(patchEntity.getEditId(), true, null));
190                         } catch (RestconfDocumentedException e) {
191                             LOG.error("Error removing {} by {} operation", patchEntity.getTargetNode().toString(),
192                                     patchEntity.getEditId(), e);
193                         }
194                     }
195                     break;
196             }
197         }
198
199         //TODO: make sure possible global errors are filled up correctly and decide transaction submission based on that
200         //globalErrors = new ArrayList<>();
201         if (errorCounter == 0) {
202             final CheckedFuture<Void, TransactionCommitFailedException> submit = patchTransaction.submit();
203             return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), true,
204                     globalErrors);
205         } else {
206             patchTransaction.cancel();
207             return new PATCHStatusContext(context.getPatchId(), ImmutableList.copyOf(editCollection), false,
208                     globalErrors);
209         }
210     }
211
212     // POST configuration
213     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
214             final SchemaContext globalSchema, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
215         checkPreconditions();
216         return postDataViaTransaction(domDataBroker.newReadWriteTransaction(), CONFIGURATION, path, payload, globalSchema);
217     }
218
219     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataPost(
220             final DOMMountPoint mountPoint, final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload) {
221         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
222         if (domDataBrokerService.isPresent()) {
223             return postDataViaTransaction(domDataBrokerService.get().newReadWriteTransaction(), CONFIGURATION, path,
224                     payload, mountPoint.getSchemaContext());
225         }
226         final String errMsg = "DOM data broker service isn't available for mount point " + path;
227         LOG.warn(errMsg);
228         throw new RestconfDocumentedException(errMsg);
229     }
230
231     // DELETE configuration
232     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
233             final YangInstanceIdentifier path) {
234         checkPreconditions();
235         return deleteDataViaTransaction(domDataBroker.newWriteOnlyTransaction(), CONFIGURATION, path);
236     }
237
238     public CheckedFuture<Void, TransactionCommitFailedException> commitConfigurationDataDelete(
239             final DOMMountPoint mountPoint, final YangInstanceIdentifier path) {
240         final Optional<DOMDataBroker> domDataBrokerService = mountPoint.getService(DOMDataBroker.class);
241         if (domDataBrokerService.isPresent()) {
242             return deleteDataViaTransaction(domDataBrokerService.get().newWriteOnlyTransaction(), CONFIGURATION, path);
243         }
244         final String errMsg = "DOM data broker service isn't available for mount point " + path;
245         LOG.warn(errMsg);
246         throw new RestconfDocumentedException(errMsg);
247     }
248
249     // RPC
250     public CheckedFuture<DOMRpcResult, DOMRpcException> invokeRpc(final SchemaPath type, final NormalizedNode<?, ?> input) {
251         checkPreconditions();
252         if (rpcService == null) {
253             throw new RestconfDocumentedException(Status.SERVICE_UNAVAILABLE);
254         }
255         LOG.trace("Invoke RPC {} with input: {}", type, input);
256         return rpcService.invokeRpc(type, input);
257     }
258
259     public void registerToListenDataChanges(final LogicalDatastoreType datastore, final DataChangeScope scope,
260             final ListenerAdapter listener) {
261         checkPreconditions();
262
263         if (listener.isListening()) {
264             return;
265         }
266
267         final YangInstanceIdentifier path = listener.getPath();
268         final ListenerRegistration<DOMDataChangeListener> registration = domDataBroker.registerDataChangeListener(
269                 datastore, path, listener, scope);
270
271         listener.setRegistration(registration);
272     }
273
274     private NormalizedNode<?, ?> readDataViaTransaction(final DOMDataReadTransaction transaction,
275             final LogicalDatastoreType datastore, final YangInstanceIdentifier path) {
276         LOG.trace("Read {} via Restconf: {}", datastore.name(), path);
277         final ListenableFuture<Optional<NormalizedNode<?, ?>>> listenableFuture = transaction.read(datastore, path);
278         if (listenableFuture != null) {
279             Optional<NormalizedNode<?, ?>> optional;
280             try {
281                 LOG.debug("Reading result data from transaction.");
282                 optional = listenableFuture.get();
283             } catch (InterruptedException | ExecutionException e) {
284                 LOG.warn("Exception by reading {} via Restconf: {}", datastore.name(), path, e);
285                 throw new RestconfDocumentedException("Problem to get data from transaction.", e.getCause());
286
287             }
288             if (optional != null) {
289                 if (optional.isPresent()) {
290                     return optional.get();
291                 }
292             }
293         }
294         return null;
295     }
296
297     private CheckedFuture<Void, TransactionCommitFailedException> postDataViaTransaction(
298             final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
299             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
300         // FIXME: This is doing correct post for container and list children
301         //        not sure if this will work for choice case
302         if(payload instanceof MapNode) {
303             LOG.trace("POST {} via Restconf: {} with payload {}", datastore.name(), path, payload);
304             final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
305             rWTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
306             ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
307             for(final MapEntryNode child : ((MapNode) payload).getValue()) {
308                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
309                 checkItemDoesNotExists(rWTransaction, datastore, childPath);
310                 rWTransaction.put(datastore, childPath, child);
311             }
312         } else {
313             checkItemDoesNotExists(rWTransaction,datastore, path);
314             ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
315             rWTransaction.put(datastore, path, payload);
316         }
317         return rWTransaction.submit();
318     }
319
320     private void postDataWithinTransaction(
321             final DOMDataReadWriteTransaction rWTransaction, final LogicalDatastoreType datastore,
322             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
323         // FIXME: This is doing correct post for container and list children
324         //        not sure if this will work for choice case
325         if(payload instanceof MapNode) {
326             LOG.trace("POST {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
327             final NormalizedNode<?, ?> emptySubtree = ImmutableNodes.fromInstanceId(schemaContext, path);
328             rWTransaction.merge(datastore, YangInstanceIdentifier.create(emptySubtree.getIdentifier()), emptySubtree);
329             ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
330             for(final MapEntryNode child : ((MapNode) payload).getValue()) {
331                 final YangInstanceIdentifier childPath = path.node(child.getIdentifier());
332                 checkItemDoesNotExists(rWTransaction, datastore, childPath);
333                 rWTransaction.put(datastore, childPath, child);
334             }
335         } else {
336             checkItemDoesNotExists(rWTransaction,datastore, path);
337             ensureParentsByMerge(datastore, path, rWTransaction, schemaContext);
338             rWTransaction.put(datastore, path, payload);
339         }
340     }
341
342     private void checkItemDoesNotExists(final DOMDataReadWriteTransaction rWTransaction,final LogicalDatastoreType store, final YangInstanceIdentifier path) {
343         final ListenableFuture<Boolean> futureDatastoreData = rWTransaction.exists(store, path);
344         try {
345             if (futureDatastoreData.get()) {
346                 final String errMsg = "Post Configuration via Restconf was not executed because data already exists";
347                 LOG.trace("{}:{}", errMsg, path);
348                 rWTransaction.cancel();
349                 throw new RestconfDocumentedException("Data already exists for path: " + path, ErrorType.PROTOCOL,
350                         ErrorTag.DATA_EXISTS);
351             }
352         } catch (InterruptedException | ExecutionException e) {
353             LOG.warn("It wasn't possible to get data loaded from datastore at path {}", path, e);
354         }
355
356     }
357
358     private CheckedFuture<Void, TransactionCommitFailedException> putDataViaTransaction(
359             final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
360             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
361         LOG.trace("Put {} via Restconf: {} with payload {}", datastore.name(), path, payload);
362         ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
363         writeTransaction.put(datastore, path, payload);
364         return writeTransaction.submit();
365     }
366
367     private void putDataWithinTransaction(
368             final DOMDataReadWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
369             final YangInstanceIdentifier path, final NormalizedNode<?, ?> payload, final SchemaContext schemaContext) {
370         LOG.trace("Put {} within Restconf PATCH: {} with payload {}", datastore.name(), path, payload);
371         ensureParentsByMerge(datastore, path, writeTransaction, schemaContext);
372         writeTransaction.put(datastore, path, payload);
373     }
374
375     private CheckedFuture<Void, TransactionCommitFailedException> deleteDataViaTransaction(
376             final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
377             final YangInstanceIdentifier path) {
378         LOG.trace("Delete {} via Restconf: {}", datastore.name(), path);
379         writeTransaction.delete(datastore, path);
380         return writeTransaction.submit();
381     }
382
383     private void deleteDataWithinTransaction(
384             final DOMDataWriteTransaction writeTransaction, final LogicalDatastoreType datastore,
385             final YangInstanceIdentifier path) {
386         LOG.trace("Delete {} within Restconf PATCH: {}", datastore.name(), path);
387         writeTransaction.delete(datastore, path);
388     }
389
390     public void setDomDataBroker(final DOMDataBroker domDataBroker) {
391         this.domDataBroker = domDataBroker;
392     }
393
394     private void ensureParentsByMerge(final LogicalDatastoreType store,
395                                       final YangInstanceIdentifier normalizedPath, final DOMDataReadWriteTransaction rwTx, final SchemaContext schemaContext) {
396         final List<PathArgument> normalizedPathWithoutChildArgs = new ArrayList<>();
397         YangInstanceIdentifier rootNormalizedPath = null;
398
399         final Iterator<PathArgument> it = normalizedPath.getPathArguments().iterator();
400
401         while(it.hasNext()) {
402             final PathArgument pathArgument = it.next();
403             if(rootNormalizedPath == null) {
404                 rootNormalizedPath = YangInstanceIdentifier.create(pathArgument);
405             }
406
407             // Skip last element, its not a parent
408             if(it.hasNext()) {
409                 normalizedPathWithoutChildArgs.add(pathArgument);
410             }
411         }
412
413         // No parent structure involved, no need to ensure parents
414         if(normalizedPathWithoutChildArgs.isEmpty()) {
415             return;
416         }
417
418         Preconditions.checkArgument(rootNormalizedPath != null, "Empty path received");
419
420         final NormalizedNode<?, ?> parentStructure =
421                 ImmutableNodes.fromInstanceId(schemaContext, YangInstanceIdentifier.create(normalizedPathWithoutChildArgs));
422         rwTx.merge(store, rootNormalizedPath, parentStructure);
423     }
424 }