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.List;
19 import java.util.ListIterator;
21 import java.util.stream.Collectors;
22 import javax.xml.transform.dom.DOMSource;
23 import org.opendaylight.controller.config.util.xml.DocumentedException;
24 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorSeverity;
25 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorTag;
26 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorType;
27 import org.opendaylight.controller.config.util.xml.XmlElement;
28 import org.opendaylight.controller.config.util.xml.XmlUtil;
29 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
30 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
31 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
32 import org.opendaylight.netconf.api.NetconfDocumentedException;
33 import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
34 import org.opendaylight.netconf.mdsal.connector.CurrentSchemaContext;
35 import org.opendaylight.netconf.mdsal.connector.TransactionProvider;
36 import org.opendaylight.netconf.mdsal.connector.ops.DataTreeChangeTracker.DataTreeChange;
37 import org.opendaylight.netconf.util.mapping.AbstractSingletonNetconfOperation;
38 import org.opendaylight.yangtools.yang.common.QName;
39 import org.opendaylight.yangtools.yang.data.api.ModifyAction;
40 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
41 import org.opendaylight.yangtools.yang.data.api.schema.AugmentationNode;
42 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
43 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
44 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
45 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
46 import org.opendaylight.yangtools.yang.data.codec.xml.XmlParserStream;
47 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
48 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
49 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
50 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
51 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
52 import org.opendaylight.yangtools.yang.model.api.Module;
53 import org.opendaylight.yangtools.yang.model.api.SchemaNode;
54 import org.opendaylight.yangtools.yang.model.util.SchemaContextUtil;
55 import org.slf4j.Logger;
56 import org.slf4j.LoggerFactory;
57 import org.w3c.dom.Document;
58 import org.w3c.dom.Element;
59 import org.w3c.dom.NodeList;
61 public class EditConfig extends AbstractSingletonNetconfOperation {
63 private static final Logger LOG = LoggerFactory.getLogger(EditConfig.class);
65 private static final String OPERATION_NAME = "edit-config";
66 private static final String CONFIG_KEY = "config";
67 private static final String TARGET_KEY = "target";
68 private static final String DEFAULT_OPERATION_KEY = "default-operation";
69 private final CurrentSchemaContext schemaContext;
70 private final TransactionProvider transactionProvider;
72 public EditConfig(final String netconfSessionIdForReporting, final CurrentSchemaContext schemaContext,
73 final TransactionProvider transactionProvider) {
74 super(netconfSessionIdForReporting);
75 this.schemaContext = schemaContext;
76 this.transactionProvider = transactionProvider;
80 protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement)
81 throws DocumentedException {
82 final Datastore targetDatastore = extractTargetParameter(operationElement);
83 if (targetDatastore == Datastore.running) {
84 throw new DocumentedException("edit-config on running datastore is not supported",
86 ErrorTag.OPERATION_NOT_SUPPORTED,
90 final ModifyAction defaultAction = getDefaultOperation(operationElement);
92 final XmlElement configElement = getElement(operationElement, CONFIG_KEY);
94 for (final XmlElement element : configElement.getChildElements()) {
95 final String ns = element.getNamespace();
96 final DataSchemaNode schemaNode = getSchemaNodeFromNamespace(ns, element).get();
98 final DataTreeChangeTracker changeTracker = new DataTreeChangeTracker(defaultAction);
100 parseIntoNormalizedNode(schemaNode, element, changeTracker);
101 executeOperations(changeTracker);
104 return XmlUtil.createElement(document, XmlNetconfConstants.OK, Optional.absent());
107 private void executeOperations(final DataTreeChangeTracker changeTracker) throws DocumentedException {
108 final DOMDataReadWriteTransaction rwTx = transactionProvider.getOrCreateTransaction();
109 final List<DataTreeChange> aa = changeTracker.getDataTreeChanges();
110 final ListIterator<DataTreeChange> iterator = aa.listIterator(aa.size());
112 while (iterator.hasPrevious()) {
113 final DataTreeChange dtc = iterator.previous();
114 executeChange(rwTx, dtc);
118 private void executeChange(final DOMDataReadWriteTransaction rwtx, final DataTreeChange change)
119 throws DocumentedException {
120 final YangInstanceIdentifier path = YangInstanceIdentifier.create(change.getPath());
121 final NormalizedNode<?, ?> changeData = change.getChangeRoot();
122 switch (change.getAction()) {
126 mergeParentMixin(rwtx, path, changeData);
127 rwtx.merge(LogicalDatastoreType.CONFIGURATION, path, changeData);
131 final Optional<NormalizedNode<?, ?>> readResult =
132 rwtx.read(LogicalDatastoreType.CONFIGURATION, path).checkedGet();
133 if (readResult.isPresent()) {
134 throw new DocumentedException("Data already exists, cannot execute CREATE operation",
135 ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, ErrorSeverity.ERROR);
137 mergeParentMixin(rwtx, path, changeData);
138 rwtx.put(LogicalDatastoreType.CONFIGURATION, path, changeData);
139 } catch (final ReadFailedException e) {
140 LOG.warn("Read from datastore failed when trying to read data for create operation", change, e);
144 mergeParentMixin(rwtx, path, changeData);
145 rwtx.put(LogicalDatastoreType.CONFIGURATION, path, changeData);
149 final Optional<NormalizedNode<?, ?>> readResult =
150 rwtx.read(LogicalDatastoreType.CONFIGURATION, path).checkedGet();
151 if (!readResult.isPresent()) {
152 throw new DocumentedException("Data is missing, cannot execute DELETE operation",
153 ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, ErrorSeverity.ERROR);
155 rwtx.delete(LogicalDatastoreType.CONFIGURATION, path);
156 } catch (final ReadFailedException e) {
157 LOG.warn("Read from datastore failed when trying to read data for delete operation", change, e);
161 rwtx.delete(LogicalDatastoreType.CONFIGURATION, path);
164 LOG.warn("Unknown/not implemented operation, not executing");
168 private void mergeParentMixin(final DOMDataReadWriteTransaction rwtx, final YangInstanceIdentifier path,
169 final NormalizedNode change) {
170 final YangInstanceIdentifier parentNodeYid = path.getParent();
171 if (change instanceof MapEntryNode) {
172 final SchemaNode schemaNode = SchemaContextUtil.findNodeInSchemaContext(
173 schemaContext.getCurrentContext(),
174 parentNodeYid.getPathArguments().stream()
175 // filter out identifiers not present in the schema tree
176 .filter(arg -> !(arg instanceof YangInstanceIdentifier.NodeIdentifierWithPredicates))
177 .filter(arg -> !(arg instanceof YangInstanceIdentifier.AugmentationIdentifier))
178 .map(YangInstanceIdentifier.PathArgument::getNodeType).collect(Collectors.toList()));
180 // we should have the schema node that points to the parent list now, enforce it
181 Preconditions.checkState(schemaNode instanceof ListSchemaNode, "Schema node is not pointing to a list.");
183 //merge empty ordered or unordered map
184 if (((ListSchemaNode) schemaNode).isUserOrdered()) {
185 final MapNode mixinNode = Builders.orderedMapBuilder()
187 new YangInstanceIdentifier.NodeIdentifier(
188 parentNodeYid.getLastPathArgument().getNodeType()))
190 rwtx.merge(LogicalDatastoreType.CONFIGURATION, parentNodeYid, mixinNode);
194 final MapNode mixinNode = Builders.mapBuilder()
196 new YangInstanceIdentifier.NodeIdentifier(
197 parentNodeYid.getLastPathArgument().getNodeType()))
199 rwtx.merge(LogicalDatastoreType.CONFIGURATION, parentNodeYid, mixinNode);
200 } else if (parentNodeYid.getLastPathArgument() instanceof YangInstanceIdentifier.AugmentationIdentifier) {
201 // merge empty augmentation node
202 final YangInstanceIdentifier.AugmentationIdentifier augmentationYid =
203 (YangInstanceIdentifier.AugmentationIdentifier) parentNodeYid.getLastPathArgument();
204 final AugmentationNode augmentationNode = Builders.augmentationBuilder()
205 .withNodeIdentifier(augmentationYid).build();
206 rwtx.merge(LogicalDatastoreType.CONFIGURATION, parentNodeYid, augmentationNode);
210 @SuppressWarnings("checkstyle:IllegalCatch")
211 private NormalizedNode<?, ?> parseIntoNormalizedNode(final DataSchemaNode schemaNode, final XmlElement element,
212 final DataTreeChangeTracker changeTracker) throws DocumentedException {
213 if (!(schemaNode instanceof ContainerSchemaNode) && !(schemaNode instanceof ListSchemaNode)) {
214 //this should never happen since edit-config on any other node type should not be possible nor makes sense
215 LOG.debug("DataNode from module is not ContainerSchemaNode nor ListSchemaNode, aborting..");
216 throw new UnsupportedOperationException("implement exception if parse fails");
219 final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
220 final NormalizedNodeStreamWriter writer = new EditOperationNormalizedNodeStreamWriter(resultHolder,
222 final XmlParserStream xmlParser = XmlParserStream.create(writer, schemaContext.getCurrentContext(), schemaNode);
224 xmlParser.traverse(new DOMSource(element.getDomElement()));
225 } catch (final Exception ex) {
226 throw new NetconfDocumentedException("Error parsing input: " + ex.getMessage(), ex, ErrorType.PROTOCOL,
227 ErrorTag.MALFORMED_MESSAGE, ErrorSeverity.ERROR);
230 return resultHolder.getResult();
233 private Optional<DataSchemaNode> getSchemaNodeFromNamespace(final String namespace, final XmlElement element)
234 throws DocumentedException {
235 Optional<DataSchemaNode> dataSchemaNode = Optional.absent();
237 // returns module with newest revision since findModuleByNamespace returns a set of modules and we only
238 // need the newest one
239 final Module module =
240 schemaContext.getCurrentContext().findModuleByNamespaceAndRevision(new URI(namespace), null);
241 if (module == null) {
242 // no module is present with this namespace
243 throw new NetconfDocumentedException("Unable to find module by namespace: " + namespace,
244 ErrorType.APPLICATION, ErrorTag.UNKNOWN_NAMESPACE, ErrorSeverity.ERROR);
246 final DataSchemaNode schemaNode =
247 module.getDataChildByName(QName.create(module.getQNameModule(), element.getName()));
248 if (schemaNode != null) {
249 dataSchemaNode = Optional.of(schemaNode);
251 throw new DocumentedException(
252 "Unable to find node with namespace: " + namespace + "in module: " + module.toString(),
253 ErrorType.APPLICATION,
254 ErrorTag.UNKNOWN_NAMESPACE,
255 ErrorSeverity.ERROR);
257 } catch (final URISyntaxException e) {
258 LOG.debug("Unable to create URI for namespace : {}", namespace);
261 return dataSchemaNode;
264 private static Datastore extractTargetParameter(final XmlElement operationElement) throws DocumentedException {
265 final NodeList elementsByTagName = getElementsByTagName(operationElement, TARGET_KEY);
266 // Direct lookup instead of using XmlElement class due to performance
267 if (elementsByTagName.getLength() == 0) {
268 final Map<String, String> errorInfo = ImmutableMap.of("bad-attribute", TARGET_KEY, "bad-element",
270 throw new DocumentedException("Missing target element", ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE,
271 ErrorSeverity.ERROR, errorInfo);
272 } else if (elementsByTagName.getLength() > 1) {
273 throw new DocumentedException("Multiple target elements", ErrorType.RPC, ErrorTag.UNKNOWN_ATTRIBUTE,
274 ErrorSeverity.ERROR);
276 final XmlElement targetChildNode =
277 XmlElement.fromDomElement((Element) elementsByTagName.item(0)).getOnlyChildElement();
278 return Datastore.valueOf(targetChildNode.getName());
282 private static ModifyAction getDefaultOperation(final XmlElement operationElement) throws DocumentedException {
283 final NodeList elementsByTagName = getElementsByTagName(operationElement, DEFAULT_OPERATION_KEY);
284 if (elementsByTagName.getLength() == 0) {
285 return ModifyAction.MERGE;
286 } else if (elementsByTagName.getLength() > 1) {
287 throw new DocumentedException("Multiple " + DEFAULT_OPERATION_KEY + " elements", ErrorType.RPC,
288 ErrorTag.UNKNOWN_ATTRIBUTE, ErrorSeverity.ERROR);
290 return ModifyAction.fromXmlValue(elementsByTagName.item(0).getTextContent());
295 private static XmlElement getElement(final XmlElement operationElement, final String elementName)
296 throws DocumentedException {
297 final Optional<XmlElement> childNode = operationElement.getOnlyChildElementOptionally(elementName);
298 if (!childNode.isPresent()) {
299 throw new DocumentedException(elementName + " element is missing",
301 ErrorTag.MISSING_ELEMENT,
302 ErrorSeverity.ERROR);
305 return childNode.get();
309 static NodeList getElementsByTagName(final XmlElement operationElement, final String key) throws
310 DocumentedException {
311 final Element element = operationElement.getDomElement();
312 final NodeList elementsByTagName;
314 if (Strings.isNullOrEmpty(element.getPrefix())) {
315 elementsByTagName = element.getElementsByTagName(key);
317 elementsByTagName = element.getElementsByTagNameNS(operationElement.getNamespace(), key);
320 return elementsByTagName;
324 protected String getOperationName() {
325 return OPERATION_NAME;