2 * Copyright (c) 2015 Cisco Systems, Inc. and others. All rights reserved.
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
9 package org.opendaylight.netconf.mdsal.connector.ops;
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;
17 import java.net.URISyntaxException;
18 import java.util.Iterator;
19 import java.util.List;
20 import java.util.ListIterator;
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;
62 public class EditConfig extends AbstractSingletonNetconfOperation {
64 private static final Logger LOG = LoggerFactory.getLogger(EditConfig.class);
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;
73 public EditConfig(final String netconfSessionIdForReporting, final CurrentSchemaContext schemaContext,
74 final TransactionProvider transactionProvider) {
75 super(netconfSessionIdForReporting);
76 this.schemaContext = schemaContext;
77 this.transactionProvider = transactionProvider;
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",
87 ErrorTag.OPERATION_NOT_SUPPORTED,
91 final ModifyAction defaultAction = getDefaultOperation(operationElement);
93 final XmlElement configElement = getElement(operationElement, CONFIG_KEY);
95 for (final XmlElement element : configElement.getChildElements()) {
96 final String ns = element.getNamespace();
97 final DataSchemaNode schemaNode = getSchemaNodeFromNamespace(ns, element).get();
99 final DataTreeChangeTracker changeTracker = new DataTreeChangeTracker(defaultAction);
101 parseIntoNormalizedNode(schemaNode, element, changeTracker);
102 executeOperations(changeTracker);
105 return XmlUtil.createElement(document, XmlNetconfConstants.OK, Optional.absent());
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());
113 while (iterator.hasPrevious()) {
114 final DataTreeChange dtc = iterator.previous();
115 executeChange(rwTx, dtc);
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()) {
127 mergeParentMixin(rwtx, path, changeData);
128 rwtx.merge(LogicalDatastoreType.CONFIGURATION, path, changeData);
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);
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);
145 mergeParentMixin(rwtx, path, changeData);
146 rwtx.put(LogicalDatastoreType.CONFIGURATION, path, changeData);
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);
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);
162 rwtx.delete(LogicalDatastoreType.CONFIGURATION, path);
165 LOG.warn("Unknown/not implemented operation, not executing");
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()));
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.");
184 //merge empty ordered or unordered map
185 if (((ListSchemaNode) schemaNode).isUserOrdered()) {
186 final MapNode mixinNode = Builders.orderedMapBuilder()
188 new YangInstanceIdentifier.NodeIdentifier(
189 parentNodeYid.getLastPathArgument().getNodeType()))
191 rwtx.merge(LogicalDatastoreType.CONFIGURATION, parentNodeYid, mixinNode);
195 final MapNode mixinNode = Builders.mapBuilder()
197 new YangInstanceIdentifier.NodeIdentifier(
198 parentNodeYid.getLastPathArgument().getNodeType()))
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);
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");
220 final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
221 final NormalizedNodeStreamWriter writer = new EditOperationNormalizedNodeStreamWriter(resultHolder,
223 final XmlParserStream xmlParser = XmlParserStream.create(writer, schemaContext.getCurrentContext(), schemaNode);
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);
231 return resultHolder.getResult();
234 private Optional<DataSchemaNode> getSchemaNodeFromNamespace(final String namespace, final XmlElement element)
235 throws DocumentedException {
236 final Iterator<Module> it;
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();
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);
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);
265 return Optional.fromJavaUtil(schemaNode);
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",
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);
280 final XmlElement targetChildNode =
281 XmlElement.fromDomElement((Element) elementsByTagName.item(0)).getOnlyChildElement();
282 return Datastore.valueOf(targetChildNode.getName());
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);
294 return ModifyAction.fromXmlValue(elementsByTagName.item(0).getTextContent());
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",
305 ErrorTag.MISSING_ELEMENT,
306 ErrorSeverity.ERROR);
309 return childNode.get();
313 static NodeList getElementsByTagName(final XmlElement operationElement, final String key) throws
314 DocumentedException {
315 final Element element = operationElement.getDomElement();
316 final NodeList elementsByTagName;
318 if (Strings.isNullOrEmpty(element.getPrefix())) {
319 elementsByTagName = element.getElementsByTagName(key);
321 elementsByTagName = element.getElementsByTagNameNS(operationElement.getNamespace(), key);
324 return elementsByTagName;
328 protected String getOperationName() {
329 return OPERATION_NAME;