Adjust to yangtools-2.0.0/odlparent-3.0.0 changes
[netconf.git] / netconf / mdsal-netconf-connector / src / main / java / org / opendaylight / netconf / mdsal / connector / ops / EditConfig.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.netconf.mdsal.connector.ops;
10
11 import com.google.common.annotations.VisibleForTesting;
12 import com.google.common.base.Optional;
13 import com.google.common.base.Preconditions;
14 import com.google.common.base.Strings;
15 import com.google.common.collect.ImmutableMap;
16 import java.net.URI;
17 import java.net.URISyntaxException;
18 import java.util.Iterator;
19 import java.util.List;
20 import java.util.ListIterator;
21 import java.util.Map;
22 import java.util.concurrent.ExecutionException;
23 import java.util.stream.Collectors;
24 import javax.xml.transform.dom.DOMSource;
25 import org.opendaylight.controller.config.util.xml.DocumentedException;
26 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorSeverity;
27 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorTag;
28 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorType;
29 import org.opendaylight.controller.config.util.xml.XmlElement;
30 import org.opendaylight.controller.config.util.xml.XmlUtil;
31 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
32 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
33 import org.opendaylight.netconf.api.NetconfDocumentedException;
34 import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
35 import org.opendaylight.netconf.mdsal.connector.CurrentSchemaContext;
36 import org.opendaylight.netconf.mdsal.connector.TransactionProvider;
37 import org.opendaylight.netconf.mdsal.connector.ops.DataTreeChangeTracker.DataTreeChange;
38 import org.opendaylight.netconf.util.mapping.AbstractSingletonNetconfOperation;
39 import org.opendaylight.yangtools.yang.common.QName;
40 import org.opendaylight.yangtools.yang.data.api.ModifyAction;
41 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
42 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
43 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
44 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
45 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
46 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
47 import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
48 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
49 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
50 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
51 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
52 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
53 import org.opendaylight.yangtools.yang.model.api.Module;
54 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
55 import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58 import org.w3c.dom.Document;
59 import org.w3c.dom.Element;
60 import org.w3c.dom.NodeList;
61
62 public class EditConfig extends AbstractSingletonNetconfOperation {
63
64     private static final Logger LOG = LoggerFactory.getLogger(EditConfig.class);
65
66     private static final String OPERATION_NAME = "edit-config";
67     private static final String CONFIG_KEY = "config";
68     private static final String TARGET_KEY = "target";
69     private static final String DEFAULT_OPERATION_KEY = "default-operation";
70     private final CurrentSchemaContext schemaContext;
71     private final TransactionProvider transactionProvider;
72
73     public EditConfig(final String netconfSessionIdForReporting, final CurrentSchemaContext schemaContext,
74             final TransactionProvider transactionProvider) {
75         super(netconfSessionIdForReporting);
76         this.schemaContext = schemaContext;
77         this.transactionProvider = transactionProvider;
78     }
79
80     @Override
81     protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement)
82             throws DocumentedException {
83         final Datastore targetDatastore = extractTargetParameter(operationElement);
84         if (targetDatastore == Datastore.running) {
85             throw new DocumentedException("edit-config on running datastore is not supported",
86                     ErrorType.PROTOCOL,
87                     ErrorTag.OPERATION_NOT_SUPPORTED,
88                     ErrorSeverity.ERROR);
89         }
90
91         final ModifyAction defaultAction = getDefaultOperation(operationElement);
92
93         final XmlElement configElement = getElement(operationElement, CONFIG_KEY);
94
95         for (final XmlElement element : configElement.getChildElements()) {
96             final String ns = element.getNamespace();
97             final DataSchemaNode schemaNode = getSchemaNodeFromNamespace(ns, element).get();
98
99             final DataTreeChangeTracker changeTracker = new DataTreeChangeTracker(defaultAction);
100
101             parseIntoNormalizedNode(schemaNode, element, changeTracker);
102             executeOperations(changeTracker);
103         }
104
105         return XmlUtil.createElement(document, XmlNetconfConstants.OK, Optional.absent());
106     }
107
108     private void executeOperations(final DataTreeChangeTracker changeTracker) throws DocumentedException {
109         final DOMDataReadWriteTransaction rwTx = transactionProvider.getOrCreateTransaction();
110         final List<DataTreeChange> aa = changeTracker.getDataTreeChanges();
111         final ListIterator<DataTreeChange> iterator = aa.listIterator(aa.size());
112
113         while (iterator.hasPrevious()) {
114             final DataTreeChange dtc = iterator.previous();
115             executeChange(rwTx, dtc);
116         }
117     }
118
119     private void executeChange(final DOMDataReadWriteTransaction rwtx, final DataTreeChange change)
120             throws DocumentedException {
121         final YangInstanceIdentifier path = YangInstanceIdentifier.create(change.getPath());
122         final NormalizedNode<?, ?> changeData = change.getChangeRoot();
123         switch (change.getAction()) {
124             case NONE:
125                 return;
126             case MERGE:
127                 mergeParentMixin(rwtx, path, changeData);
128                 rwtx.merge(LogicalDatastoreType.CONFIGURATION, path, changeData);
129                 break;
130             case CREATE:
131                 try {
132                     final Optional<NormalizedNode<?, ?>> readResult =
133                             rwtx.read(LogicalDatastoreType.CONFIGURATION, path).get();
134                     if (readResult.isPresent()) {
135                         throw new DocumentedException("Data already exists, cannot execute CREATE operation",
136                             ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, ErrorSeverity.ERROR);
137                     }
138                     mergeParentMixin(rwtx, path, changeData);
139                     rwtx.put(LogicalDatastoreType.CONFIGURATION, path, changeData);
140                 } catch (final InterruptedException | ExecutionException e) {
141                     LOG.warn("Read from datastore failed when trying to read data for create operation", change, e);
142                 }
143                 break;
144             case REPLACE:
145                 mergeParentMixin(rwtx, path, changeData);
146                 rwtx.put(LogicalDatastoreType.CONFIGURATION, path, changeData);
147                 break;
148             case DELETE:
149                 try {
150                     final Optional<NormalizedNode<?, ?>> readResult =
151                             rwtx.read(LogicalDatastoreType.CONFIGURATION, path).get();
152                     if (!readResult.isPresent()) {
153                         throw new DocumentedException("Data is missing, cannot execute DELETE operation",
154                             ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, ErrorSeverity.ERROR);
155                     }
156                     rwtx.delete(LogicalDatastoreType.CONFIGURATION, path);
157                 } catch (final InterruptedException | ExecutionException e) {
158                     LOG.warn("Read from datastore failed when trying to read data for delete operation", change, e);
159                 }
160                 break;
161             case REMOVE:
162                 rwtx.delete(LogicalDatastoreType.CONFIGURATION, path);
163                 break;
164             default:
165                 LOG.warn("Unknown/not implemented operation, not executing");
166         }
167     }
168
169     private void mergeParentMixin(final DOMDataReadWriteTransaction rwtx, final YangInstanceIdentifier path,
170                                 final NormalizedNode<?, ?> change) {
171         final YangInstanceIdentifier parentNodeYid = path.getParent();
172         if (change instanceof MapEntryNode) {
173             final SchemaNode schemaNode = SchemaContextUtil.findNodeInSchemaContext(
174                     schemaContext.getCurrentContext(),
175                     parentNodeYid.getPathArguments().stream()
176                             // filter out identifiers not present in the schema tree
177                             .filter(arg -> !(arg instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates))
178                             .filter(arg -> !(arg instanceof YangInstanceIdentifier.AugmentationIdentifier))
179                             .map(YangInstanceIdentifier.PathArgument::getNodeType).collect(Collectors.toList()));
180
181             // we should have the schema node that points to the parent list now, enforce it
182             Preconditions.checkState(schemaNode instanceof ListSchemaNode, "Schema node is not pointing to a list.");
183
184             //merge empty ordered or unordered map
185             if (((ListSchemaNode) schemaNode).isUserOrdered()) {
186                 final MapNode mixinNode = Builders.orderedMapBuilder()
187                         .withNodeIdentifier(
188                                 new YangInstanceIdentifier.NodeIdentifier(
189                                         parentNodeYid.getLastPathArgument().getNodeType()))
190                         .build();
191                 rwtx.merge(LogicalDatastoreType.CONFIGURATION, parentNodeYid, mixinNode);
192                 return;
193             }
194
195             final MapNode mixinNode = Builders.mapBuilder()
196                     .withNodeIdentifier(
197                             new YangInstanceIdentifier.NodeIdentifier(
198                                         parentNodeYid.getLastPathArgument().getNodeType()))
199                     .build();
200             rwtx.merge(LogicalDatastoreType.CONFIGURATION, parentNodeYid, mixinNode);
201         } else if (parentNodeYid.getLastPathArgument() instanceof YangInstanceIdentifier.AugmentationIdentifier) {
202             // merge empty augmentation node
203             final YangInstanceIdentifier.AugmentationIdentifier augmentationYid =
204                 (YangInstanceIdentifier.AugmentationIdentifier) parentNodeYid.getLastPathArgument();
205             final AugmentationNode augmentationNode = Builders.augmentationBuilder()
206                 .withNodeIdentifier(augmentationYid).build();
207             rwtx.merge(LogicalDatastoreType.CONFIGURATION, parentNodeYid, augmentationNode);
208         }
209     }
210
211     @SuppressWarnings("checkstyle:IllegalCatch")
212     private NormalizedNode<?, ?> parseIntoNormalizedNode(final DataSchemaNode schemaNode, final XmlElement element,
213             final DataTreeChangeTracker changeTracker) throws DocumentedException {
214         if (!(schemaNode instanceof ContainerSchemaNode) && !(schemaNode instanceof ListSchemaNode)) {
215             //this should never happen since edit-config on any other node type should not be possible nor makes sense
216             LOG.debug("DataNode from module is not ContainerSchemaNode nor ListSchemaNode, aborting..");
217             throw new UnsupportedOperationException("implement exception if parse fails");
218         }
219
220         final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
221         final NormalizedNodeStreamWriter writer = new EditOperationNormalizedNodeStreamWriter(resultHolder,
222                 changeTracker);
223         final XmlParserStream xmlParser = XmlParserStream.create(writer, schemaContext.getCurrentContext(), schemaNode);
224         try {
225             xmlParser.traverse(new DOMSource(element.getDomElement()));
226         } catch (final Exception ex) {
227             throw new NetconfDocumentedException("Error parsing input: " + ex.getMessage(), ex, ErrorType.PROTOCOL,
228                     ErrorTag.MALFORMED_MESSAGE, ErrorSeverity.ERROR);
229         }
230
231         return resultHolder.getResult();
232     }
233
234     private Optional<DataSchemaNode> getSchemaNodeFromNamespace(final String namespace, final XmlElement element)
235             throws DocumentedException {
236         final Iterator<Module> it;
237         try {
238             // returns module with newest revision since findModuleByNamespace returns a set of modules and we only
239             // need the newest one
240             it = schemaContext.getCurrentContext().findModules(new URI(namespace)).iterator();
241         } catch (final URISyntaxException e) {
242             LOG.debug("Unable to create URI for namespace : {}", namespace);
243             return Optional.absent();
244         }
245
246         if (!it.hasNext()) {
247             // no module is present with this namespace
248             throw new NetconfDocumentedException("Unable to find module by namespace: " + namespace,
249                 ErrorType.APPLICATION, ErrorTag.UNKNOWN_NAMESPACE, ErrorSeverity.ERROR);
250         }
251
252         final Module module = it.next();
253         final java.util.Optional<DataSchemaNode> schemaNode =
254                 module.findDataChildByName(QName.create(module.getQNameModule(), element.getName()));
255         if (!schemaNode.isPresent()) {
256             if (schemaNode != null) {
257                 throw new DocumentedException(
258                     "Unable to find node with namespace: " + namespace + "in module: " + module.toString(),
259                     ErrorType.APPLICATION,
260                     ErrorTag.UNKNOWN_NAMESPACE,
261                     ErrorSeverity.ERROR);
262             }
263         }
264
265         return Optional.fromJavaUtil(schemaNode);
266     }
267
268     private static Datastore extractTargetParameter(final XmlElement operationElement) throws DocumentedException {
269         final NodeList elementsByTagName = getElementsByTagName(operationElement, TARGET_KEY);
270         // Direct lookup instead of using XmlElement class due to performance
271         if (elementsByTagName.getLength() == 0) {
272             final Map<String, String> errorInfo = ImmutableMap.of("bad-attribute", TARGET_KEY, "bad-element",
273                 OPERATION_NAME);
274             throw new DocumentedException("Missing target element", ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE,
275                 ErrorSeverity.ERROR, errorInfo);
276         } else if (elementsByTagName.getLength() > 1) {
277             throw new DocumentedException("Multiple target elements", ErrorType.RPC, ErrorTag.UNKNOWN_ATTRIBUTE,
278                 ErrorSeverity.ERROR);
279         } else {
280             final XmlElement targetChildNode =
281                     XmlElement.fromDomElement((Element) elementsByTagName.item(0)).getOnlyChildElement();
282             return Datastore.valueOf(targetChildNode.getName());
283         }
284     }
285
286     private static ModifyAction getDefaultOperation(final XmlElement operationElement) throws DocumentedException {
287         final NodeList elementsByTagName = getElementsByTagName(operationElement, DEFAULT_OPERATION_KEY);
288         if (elementsByTagName.getLength() == 0) {
289             return ModifyAction.MERGE;
290         } else if (elementsByTagName.getLength() > 1) {
291             throw new DocumentedException("Multiple " + DEFAULT_OPERATION_KEY + " elements", ErrorType.RPC,
292                 ErrorTag.UNKNOWN_ATTRIBUTE, ErrorSeverity.ERROR);
293         } else {
294             return ModifyAction.fromXmlValue(elementsByTagName.item(0).getTextContent());
295         }
296
297     }
298
299     private static XmlElement getElement(final XmlElement operationElement, final String elementName)
300             throws DocumentedException {
301         final Optional<XmlElement> childNode = operationElement.getOnlyChildElementOptionally(elementName);
302         if (!childNode.isPresent()) {
303             throw new DocumentedException(elementName + " element is missing",
304                     ErrorType.PROTOCOL,
305                     ErrorTag.MISSING_ELEMENT,
306                     ErrorSeverity.ERROR);
307         }
308
309         return childNode.get();
310     }
311
312     @VisibleForTesting
313     static NodeList getElementsByTagName(final XmlElement operationElement, final String key) throws
314             DocumentedException {
315         final Element element = operationElement.getDomElement();
316         final NodeList elementsByTagName;
317
318         if (Strings.isNullOrEmpty(element.getPrefix())) {
319             elementsByTagName = element.getElementsByTagName(key);
320         } else {
321             elementsByTagName = element.getElementsByTagNameNS(operationElement.getNamespace(), key);
322         }
323
324         return elementsByTagName;
325     }
326
327     @Override
328     protected String getOperationName() {
329         return OPERATION_NAME;
330     }
331
332 }