2 * Copyright (c) 2018 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
8 package org.opendaylight.netconf.mdsal.connector.ops;
10 import static org.opendaylight.netconf.api.xml.XmlNetconfConstants.URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0;
12 import java.io.IOException;
14 import java.net.URISyntaxException;
15 import java.nio.charset.StandardCharsets;
16 import java.nio.file.Files;
17 import java.nio.file.Path;
18 import java.nio.file.Paths;
19 import java.util.List;
20 import java.util.Optional;
21 import java.util.concurrent.ExecutionException;
22 import javax.xml.stream.XMLOutputFactory;
23 import javax.xml.stream.XMLStreamException;
24 import javax.xml.stream.XMLStreamWriter;
25 import javax.xml.transform.dom.DOMResult;
26 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
27 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
28 import org.opendaylight.netconf.api.DocumentedException;
29 import org.opendaylight.netconf.api.xml.XmlElement;
30 import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
31 import org.opendaylight.netconf.api.xml.XmlUtil;
32 import org.opendaylight.netconf.mdsal.connector.CurrentSchemaContext;
33 import org.opendaylight.netconf.mdsal.connector.TransactionProvider;
34 import org.opendaylight.yangtools.yang.common.ErrorSeverity;
35 import org.opendaylight.yangtools.yang.common.ErrorTag;
36 import org.opendaylight.yangtools.yang.common.ErrorType;
37 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
38 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
39 import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
40 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
41 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeStreamWriter;
42 import org.opendaylight.yangtools.yang.data.api.schema.stream.NormalizedNodeWriter;
43 import org.opendaylight.yangtools.yang.data.codec.xml.XMLStreamNormalizedNodeStreamWriter;
44 import org.opendaylight.yangtools.yang.data.impl.schema.Builders;
45 import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNormalizedNodeStreamWriter;
46 import org.opendaylight.yangtools.yang.data.impl.schema.NormalizedNodeResult;
47 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
48 import org.w3c.dom.Document;
49 import org.w3c.dom.Element;
50 import org.w3c.dom.Node;
52 public final class CopyConfig extends AbstractEdit {
53 private static final String OPERATION_NAME = "copy-config";
54 private static final String SOURCE_KEY = "source";
55 private static final XMLOutputFactory XML_OUTPUT_FACTORY;
58 XML_OUTPUT_FACTORY = XMLOutputFactory.newFactory();
59 XML_OUTPUT_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
62 // Top-level "data" node without child nodes
63 private static final ContainerNode EMPTY_ROOT_NODE = Builders.containerBuilder()
64 .withNodeIdentifier(new YangInstanceIdentifier.NodeIdentifier(SchemaContext.NAME)).build();
66 private final TransactionProvider transactionProvider;
68 public CopyConfig(final String netconfSessionIdForReporting, final CurrentSchemaContext schemaContext,
69 final TransactionProvider transactionProvider) {
70 super(netconfSessionIdForReporting, schemaContext);
71 this.transactionProvider = transactionProvider;
75 protected Element handleWithNoSubsequentOperations(final Document document, final XmlElement operationElement)
76 throws DocumentedException {
77 final XmlElement targetElement = extractTargetElement(operationElement, OPERATION_NAME);
78 final String target = targetElement.getName();
79 if (Datastore.running.toString().equals(target)) {
80 throw new DocumentedException("edit-config on running datastore is not supported",
81 ErrorType.PROTOCOL, ErrorTag.OPERATION_NOT_SUPPORTED, ErrorSeverity.ERROR);
82 } else if (Datastore.candidate.toString().equals(target)) {
83 copyToCandidate(operationElement);
84 } else if (URL_KEY.equals(target)) {
85 copyToUrl(targetElement, operationElement);
87 throw new DocumentedException("Unsupported target: " + target,
88 ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT, ErrorSeverity.ERROR);
90 return document.createElement(XmlNetconfConstants.OK);
93 private void copyToCandidate(final XmlElement operationElement)
94 throws DocumentedException {
95 final XmlElement source = getSourceElement(operationElement);
96 final List<XmlElement> configElements = getConfigElement(source).getChildElements();
98 // <copy-config>, unlike <edit-config>, always replaces entire configuration,
99 // so remove old configuration first:
100 final DOMDataTreeReadWriteTransaction rwTx = transactionProvider.getOrCreateTransaction();
101 rwTx.put(LogicalDatastoreType.CONFIGURATION, YangInstanceIdentifier.empty(), EMPTY_ROOT_NODE);
103 // Then create nodes present in the <config> element:
104 for (final XmlElement element : configElements) {
105 final NormalizedNodeResult resultHolder = new NormalizedNodeResult();
106 parseIntoNormalizedNode(getSchemaNodeFromNamespace(element.getNamespace(), element), element,
107 ImmutableNormalizedNodeStreamWriter.from(resultHolder));
108 final NormalizedNode data = resultHolder.getResult();
109 final YangInstanceIdentifier path = YangInstanceIdentifier.create(data.getIdentifier());
110 // Doing merge instead of put to support top-level list:
111 rwTx.merge(LogicalDatastoreType.CONFIGURATION, path, data);
115 private static XmlElement getSourceElement(final XmlElement parent) throws DocumentedException {
116 return parent.getOnlyChildElementOptionally(SOURCE_KEY)
117 .orElseThrow(() -> new DocumentedException("<source> element is missing",
118 ErrorType.PROTOCOL, ErrorTag.MISSING_ELEMENT, ErrorSeverity.ERROR));
121 private void copyToUrl(final XmlElement urlElement, final XmlElement operationElement) throws DocumentedException {
122 final String url = urlElement.getTextContent();
123 if (!url.startsWith("file:")) {
124 throw new DocumentedException("Unsupported <url> protocol: " + url,
125 ErrorType.PROTOCOL, ErrorTag.OPERATION_NOT_SUPPORTED, ErrorSeverity.ERROR);
128 // Read data from datastore:
129 final XmlElement source = getSourceElement(operationElement).getOnlyChildElement();
130 final ContainerNode data = readData(source);
132 // Transform NN to XML:
133 final Document document = operationElement.getDomElement().getOwnerDocument();
134 final Node node = transformNormalizedNode(document, data);
137 final String xml = XmlUtil.toString((Element) node);
139 final Path file = Paths.get(new URI(url));
140 Files.write(file, xml.getBytes(StandardCharsets.UTF_8));
141 } catch (URISyntaxException | IllegalArgumentException e) {
142 throw new DocumentedException("Invalid URI: " + url, e,
143 ErrorType.RPC, ErrorTag.INVALID_VALUE, ErrorSeverity.ERROR);
144 } catch (IOException e) {
145 throw new DocumentedException("Failed to write : " + url, e,
146 ErrorType.APPLICATION, ErrorTag.OPERATION_FAILED, ErrorSeverity.ERROR);
150 private ContainerNode readData(final XmlElement source) throws DocumentedException {
151 final Datastore sourceDatastore = getDatastore(source);
152 final DOMDataTreeReadWriteTransaction rwTx = getTransaction(sourceDatastore);
153 final YangInstanceIdentifier dataRoot = YangInstanceIdentifier.empty();
155 final Optional<NormalizedNode> normalizedNodeOptional = rwTx.read(
156 LogicalDatastoreType.CONFIGURATION, dataRoot).get();
157 if (sourceDatastore == Datastore.running) {
158 transactionProvider.abortRunningTransaction(rwTx);
160 return (ContainerNode) normalizedNodeOptional.orElseThrow();
161 } catch (InterruptedException | ExecutionException e) {
162 throw new IllegalStateException("Unable to read data " + dataRoot, e);
166 private static Datastore getDatastore(final XmlElement source) throws DocumentedException {
168 return Datastore.valueOf(source.getName());
169 } catch (IllegalArgumentException e) {
170 throw new DocumentedException("Unsupported source for <url> target", e,
171 ErrorType.PROTOCOL, ErrorTag.OPERATION_NOT_SUPPORTED, ErrorSeverity.ERROR);
175 private DOMDataTreeReadWriteTransaction getTransaction(final Datastore datastore) throws DocumentedException {
176 if (datastore == Datastore.candidate) {
177 return transactionProvider.getOrCreateTransaction();
178 } else if (datastore == Datastore.running) {
179 return transactionProvider.createRunningTransaction();
181 throw new DocumentedException("Incorrect Datastore: ", ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT,
182 ErrorSeverity.ERROR);
185 private Node transformNormalizedNode(final Document document, final ContainerNode data) {
186 final Element configElement = document.createElementNS(URN_IETF_PARAMS_XML_NS_NETCONF_BASE_1_0, CONFIG_KEY);
187 final DOMResult result = new DOMResult(configElement);
189 final XMLStreamWriter xmlWriter = XML_OUTPUT_FACTORY.createXMLStreamWriter(result);
190 final NormalizedNodeStreamWriter nnStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter,
191 schemaContext.getCurrentContext());
193 final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(nnStreamWriter, true);
194 for (DataContainerChild child : data.body()) {
195 nnWriter.write(child);
199 } catch (XMLStreamException | IOException e) {
200 // FIXME: throw DocumentedException
201 throw new IllegalStateException(e);
203 return result.getNode();
207 protected String getOperationName() {
208 return OPERATION_NAME;