eadc45d6f44314952692be314d5f9b9fccef8887
[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.base.Optional;
12 import com.google.common.collect.ImmutableMap;
13 import java.net.URI;
14 import java.net.URISyntaxException;
15 import java.util.Collections;
16 import java.util.List;
17 import java.util.ListIterator;
18 import java.util.Map;
19 import org.opendaylight.controller.config.util.xml.DocumentedException;
20 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorSeverity;
21 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorTag;
22 import org.opendaylight.controller.config.util.xml.DocumentedException.ErrorType;
23 import org.opendaylight.controller.config.util.xml.XmlElement;
24 import org.opendaylight.controller.config.util.xml.XmlUtil;
25 import org.opendaylight.controller.md.sal.common.api.data.LogicalDatastoreType;
26 import org.opendaylight.controller.md.sal.common.api.data.ReadFailedException;
27 import org.opendaylight.controller.md.sal.dom.api.DOMDataReadWriteTransaction;
28 import org.opendaylight.netconf.api.NetconfDocumentedException;
29 import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
30 import org.opendaylight.netconf.mdsal.connector.CurrentSchemaContext;
31 import org.opendaylight.netconf.mdsal.connector.TransactionProvider;
32 import org.opendaylight.netconf.mdsal.connector.ops.DataTreeChangeTracker.DataTreeChange;
33 import org.opendaylight.netconf.util.mapping.AbstractSingletonNetconfOperation;
34 import org.opendaylight.yangtools.yang.common.QName;
35 import org.opendaylight.yangtools.yang.data.api.ModifyAction;
36 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
37 import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
38 import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
39 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
40 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
41 import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.DomUtils;
42 import org.opendaylight.yangtools.yang.data.impl.schema.transform.dom.parser.DomToNormalizedNodeParserFactory;
43 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
44 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
45 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
46 import org.opendaylight.yangtools.yang.model.api.Module;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49 import org.w3c.dom.Document;
50 import org.w3c.dom.Element;
51 import org.w3c.dom.NodeList;
52
53 public class EditConfig extends AbstractSingletonNetconfOperation {
54
55     private static final Logger LOG = LoggerFactory.getLogger(EditConfig.class);
56
57     private static final String OPERATION_NAME = "edit-config";
58     private static final String CONFIG_KEY = "config";
59     private static final String TARGET_KEY = "target";
60     private static final String DEFAULT_OPERATION_KEY = "default-operation";
61     private final CurrentSchemaContext schemaContext;
62     private final TransactionProvider transactionProvider;
63
64     public EditConfig(final String netconfSessionIdForReporting, final CurrentSchemaContext schemaContext, final TransactionProvider transactionProvider) {
65         super(netconfSessionIdForReporting);
66         this.schemaContext = schemaContext;
67         this.transactionProvider = transactionProvider;
68     }
69
70     @Override
71     protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement) throws DocumentedException {
72         final Datastore targetDatastore = extractTargetParameter(operationElement);
73         if (targetDatastore == Datastore.running) {
74             throw new DocumentedException("edit-config on running datastore is not supported",
75                     ErrorType.PROTOCOL,
76                     ErrorTag.OPERATION_NOT_SUPPORTED,
77                     ErrorSeverity.ERROR);
78         }
79
80         final ModifyAction defaultAction = getDefaultOperation(operationElement);
81
82         final XmlElement configElement = getElement(operationElement, CONFIG_KEY);
83
84         for (final XmlElement element : configElement.getChildElements()) {
85             final String ns = element.getNamespace();
86             final DataSchemaNode schemaNode = getSchemaNodeFromNamespace(ns, element).get();
87
88             final DataTreeChangeTracker changeTracker = new DataTreeChangeTracker(defaultAction);
89             final DomToNormalizedNodeParserFactory.BuildingStrategyProvider editOperationStrategyProvider = new EditOperationStrategyProvider(changeTracker);
90
91             parseIntoNormalizedNode(schemaNode, element, editOperationStrategyProvider);
92             executeOperations(changeTracker);
93         }
94
95         return XmlUtil.createElement(document, XmlNetconfConstants.OK, Optional.absent());
96     }
97
98     private void executeOperations(final DataTreeChangeTracker changeTracker) throws DocumentedException {
99         final DOMDataReadWriteTransaction rwTx = transactionProvider.getOrCreateTransaction();
100         final List<DataTreeChange> aa = changeTracker.getDataTreeChanges();
101         final ListIterator<DataTreeChange> iterator = aa.listIterator(aa.size());
102
103         while (iterator.hasPrevious()) {
104             final DataTreeChange dtc = iterator.previous();
105             executeChange(rwTx, dtc);
106         }
107     }
108
109     private void executeChange(final DOMDataReadWriteTransaction rwtx, final DataTreeChange change) throws DocumentedException {
110         final YangInstanceIdentifier path = YangInstanceIdentifier.create(change.getPath());
111         final NormalizedNode<?, ?> changeData = change.getChangeRoot();
112         switch (change.getAction()) {
113         case NONE:
114             return;
115         case MERGE:
116             mergeParentMap(rwtx, path, changeData);
117             rwtx.merge(LogicalDatastoreType.CONFIGURATION, path, changeData);
118             break;
119         case CREATE:
120             try {
121                 final Optional<NormalizedNode<?, ?>> readResult = rwtx.read(LogicalDatastoreType.CONFIGURATION, path).checkedGet();
122                 if (readResult.isPresent()) {
123                     throw new DocumentedException("Data already exists, cannot execute CREATE operation", ErrorType.PROTOCOL, ErrorTag.DATA_EXISTS, ErrorSeverity.ERROR);
124                 }
125                 mergeParentMap(rwtx, path, changeData);
126                 rwtx.put(LogicalDatastoreType.CONFIGURATION, path, changeData);
127             } catch (final ReadFailedException e) {
128                 LOG.warn("Read from datastore failed when trying to read data for create operation", change, e);
129             }
130             break;
131         case REPLACE:
132             mergeParentMap(rwtx, path, changeData);
133             rwtx.put(LogicalDatastoreType.CONFIGURATION, path, changeData);
134             break;
135         case DELETE:
136             try {
137                 final Optional<NormalizedNode<?, ?>> readResult = rwtx.read(LogicalDatastoreType.CONFIGURATION, path).checkedGet();
138                 if (!readResult.isPresent()) {
139                     throw new DocumentedException("Data is missing, cannot execute DELETE operation", ErrorType.PROTOCOL, ErrorTag.DATA_MISSING, ErrorSeverity.ERROR);
140                 }
141                 rwtx.delete(LogicalDatastoreType.CONFIGURATION, path);
142             } catch (final ReadFailedException e) {
143                 LOG.warn("Read from datastore failed when trying to read data for delete operation", change, e);
144             }
145             break;
146         case REMOVE:
147             rwtx.delete(LogicalDatastoreType.CONFIGURATION, path);
148             break;
149         default:
150             LOG.warn("Unknown/not implemented operation, not executing");
151         }
152     }
153
154     private void mergeParentMap(final DOMDataReadWriteTransaction rwtx, final YangInstanceIdentifier path,
155                                 final NormalizedNode change) {
156         if (change instanceof MapEntryNode) {
157             final YangInstanceIdentifier mapNodeYid = path.getParent();
158             //merge empty map
159             final MapNode mixinNode = Builders.mapBuilder()
160                     .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(mapNodeYid.getLastPathArgument().getNodeType()))
161                     .build();
162             rwtx.merge(LogicalDatastoreType.CONFIGURATION, mapNodeYid, mixinNode);
163         }
164     }
165
166     private NormalizedNode parseIntoNormalizedNode(final DataSchemaNode schemaNode, final XmlElement element,
167                                                    final DomToNormalizedNodeParserFactory.BuildingStrategyProvider editOperationStrategyProvider) {
168
169
170         if (schemaNode instanceof ContainerSchemaNode) {
171             return DomToNormalizedNodeParserFactory
172                     .getInstance(DomUtils.defaultValueCodecProvider(), schemaContext.getCurrentContext(), editOperationStrategyProvider)
173                     .getContainerNodeParser()
174                     .parse(Collections.singletonList(element.getDomElement()), (ContainerSchemaNode) schemaNode);
175         } else if (schemaNode instanceof ListSchemaNode) {
176             return DomToNormalizedNodeParserFactory
177                     .getInstance(DomUtils.defaultValueCodecProvider(), schemaContext.getCurrentContext(), editOperationStrategyProvider)
178                     .getMapNodeParser()
179                     .parse(Collections.singletonList(element.getDomElement()), (ListSchemaNode) schemaNode);
180         } else {
181             //this should never happen since edit-config on any other node type should not be possible nor makes sense
182             LOG.debug("DataNode from module is not ContainerSchemaNode nor ListSchemaNode, aborting..");
183         }
184         throw new UnsupportedOperationException("implement exception if parse fails");
185     }
186
187     private Optional<DataSchemaNode> getSchemaNodeFromNamespace(final String namespace, final XmlElement element) throws DocumentedException{
188         Optional<DataSchemaNode> dataSchemaNode = Optional.absent();
189         try {
190             //returns module with newest revision since findModuleByNamespace returns a set of modules and we only need the newest one
191             final Module module = schemaContext.getCurrentContext().findModuleByNamespaceAndRevision(new URI(namespace), null);
192             if (module == null) {
193                 // no module is present with this namespace
194                 throw new NetconfDocumentedException("Unable to find module by namespace: " + namespace,
195                         ErrorType.APPLICATION, ErrorTag.UNKNOWN_NAMESPACE, ErrorSeverity.ERROR);
196             }
197             final DataSchemaNode schemaNode =
198                     module.getDataChildByName(QName.create(module.getQNameModule(), element.getName()));
199             if (schemaNode != null) {
200                 dataSchemaNode = Optional.of(schemaNode);
201             } else {
202                 throw new DocumentedException("Unable to find node with namespace: " + namespace + "in module: " + module.toString(),
203                         ErrorType.APPLICATION,
204                         ErrorTag.UNKNOWN_NAMESPACE,
205                         ErrorSeverity.ERROR);
206             }
207         } catch (final URISyntaxException e) {
208             LOG.debug("Unable to create URI for namespace : {}", namespace);
209         }
210
211         return dataSchemaNode;
212     }
213
214     private Datastore extractTargetParameter(final XmlElement operationElement) throws DocumentedException {
215         final NodeList elementsByTagName = operationElement.getDomElement().getElementsByTagName(TARGET_KEY);
216         // Direct lookup instead of using XmlElement class due to performance
217         if (elementsByTagName.getLength() == 0) {
218             final Map<String, String> errorInfo = ImmutableMap.of("bad-attribute", TARGET_KEY, "bad-element", OPERATION_NAME);
219             throw new DocumentedException("Missing target element",
220                     ErrorType.PROTOCOL, ErrorTag.MISSING_ATTRIBUTE, ErrorSeverity.ERROR, errorInfo);
221         } else if (elementsByTagName.getLength() > 1) {
222             throw new DocumentedException("Multiple target elements", ErrorType.RPC, ErrorTag.UNKNOWN_ATTRIBUTE, ErrorSeverity.ERROR);
223         } else {
224             final XmlElement targetChildNode = XmlElement.fromDomElement((Element) elementsByTagName.item(0)).getOnlyChildElement();
225             return Datastore.valueOf(targetChildNode.getName());
226         }
227     }
228
229     private ModifyAction getDefaultOperation(final XmlElement operationElement) throws DocumentedException {
230         final NodeList elementsByTagName = operationElement.getDomElement().getElementsByTagName(DEFAULT_OPERATION_KEY);
231         if(elementsByTagName.getLength() == 0) {
232             return ModifyAction.MERGE;
233         } else if(elementsByTagName.getLength() > 1) {
234             throw new DocumentedException("Multiple " + DEFAULT_OPERATION_KEY + " elements",
235                     ErrorType.RPC, ErrorTag.UNKNOWN_ATTRIBUTE, ErrorSeverity.ERROR);
236         } else {
237             return ModifyAction.fromXmlValue(elementsByTagName.item(0).getTextContent());
238         }
239
240     }
241
242     private XmlElement getElement(final XmlElement operationElement, final String elementName) throws DocumentedException {
243         final Optional<XmlElement> childNode = operationElement.getOnlyChildElementOptionally(elementName);
244         if (!childNode.isPresent()) {
245             throw new DocumentedException(elementName + " element is missing",
246                     ErrorType.PROTOCOL,
247                     ErrorTag.MISSING_ELEMENT,
248                     ErrorSeverity.ERROR);
249         }
250
251         return childNode.get();
252     }
253
254     @Override
255     protected String getOperationName() {
256         return OPERATION_NAME;
257     }
258
259 }