8c3a1e28cf22b89bb30df05669c432ef5a971a21
[netconf.git] / plugins / netconf-server-mdsal / src / main / java / org / opendaylight / netconf / server / mdsal / operations / CopyConfig.java
1 /*
2  * Copyright (c) 2018 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 package org.opendaylight.netconf.server.mdsal.operations;
9
10 import java.io.IOException;
11 import java.net.URI;
12 import java.net.URISyntaxException;
13 import java.nio.charset.StandardCharsets;
14 import java.nio.file.Files;
15 import java.nio.file.Path;
16 import java.nio.file.Paths;
17 import java.util.List;
18 import java.util.Optional;
19 import java.util.concurrent.ExecutionException;
20 import javax.xml.stream.XMLOutputFactory;
21 import javax.xml.stream.XMLStreamException;
22 import javax.xml.stream.XMLStreamWriter;
23 import javax.xml.transform.dom.DOMResult;
24 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
25 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
26 import org.opendaylight.netconf.api.DocumentedException;
27 import org.opendaylight.netconf.api.NamespaceURN;
28 import org.opendaylight.netconf.api.xml.XmlElement;
29 import org.opendaylight.netconf.api.xml.XmlNetconfConstants;
30 import org.opendaylight.netconf.api.xml.XmlUtil;
31 import org.opendaylight.netconf.server.mdsal.CurrentSchemaContext;
32 import org.opendaylight.netconf.server.mdsal.TransactionProvider;
33 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.netconf.base._1._0.rev110601.SessionIdType;
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.NormalizationResultHolder;
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;
51
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;
56
57     static {
58         XML_OUTPUT_FACTORY = XMLOutputFactory.newFactory();
59         XML_OUTPUT_FACTORY.setProperty(XMLOutputFactory.IS_REPAIRING_NAMESPACES, true);
60     }
61
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();
65
66     private final TransactionProvider transactionProvider;
67
68     public CopyConfig(final SessionIdType sessionId, final CurrentSchemaContext schemaContext,
69                       final TransactionProvider transactionProvider) {
70         super(sessionId, schemaContext);
71         this.transactionProvider = transactionProvider;
72     }
73
74     @Override
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);
86         } else {
87             throw new DocumentedException("Unsupported target: " + target,
88                 ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT, ErrorSeverity.ERROR);
89         }
90         return document.createElement(XmlNetconfConstants.OK);
91     }
92
93     private void copyToCandidate(final XmlElement operationElement)
94         throws DocumentedException {
95         final XmlElement source = getSourceElement(operationElement);
96         final List<XmlElement> configElements = getConfigElement(source).getChildElements();
97
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.of(), EMPTY_ROOT_NODE);
102
103         // Then create nodes present in the <config> element:
104         for (final XmlElement element : configElements) {
105             final NormalizationResultHolder resultHolder = new NormalizationResultHolder();
106             parseIntoNormalizedNode(getSchemaNodeFromNamespace(element.getNamespace(), element), element,
107                 ImmutableNormalizedNodeStreamWriter.from(resultHolder));
108             final NormalizedNode data = resultHolder.getResult().data();
109             final YangInstanceIdentifier path = YangInstanceIdentifier.of(data.name());
110             // Doing merge instead of put to support top-level list:
111             rwTx.merge(LogicalDatastoreType.CONFIGURATION, path, data);
112         }
113     }
114
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));
119     }
120
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);
126         }
127
128         // Read data from datastore:
129         final XmlElement source = getSourceElement(operationElement).getOnlyChildElement();
130         final ContainerNode data = readData(source);
131
132         // Transform NN to XML:
133         final Document document = operationElement.getDomElement().getOwnerDocument();
134         final Node node = transformNormalizedNode(document, data);
135
136         // Save XML to file:
137         final String xml = XmlUtil.toString((Element) node);
138         try {
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);
147         }
148     }
149
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.of();
154         try {
155             final Optional<NormalizedNode> normalizedNodeOptional = rwTx.read(
156                 LogicalDatastoreType.CONFIGURATION, dataRoot).get();
157             if (sourceDatastore == Datastore.running) {
158                 transactionProvider.abortRunningTransaction(rwTx);
159             }
160             return (ContainerNode) normalizedNodeOptional.orElseThrow();
161         } catch (InterruptedException | ExecutionException e) {
162             throw new IllegalStateException("Unable to read data " + dataRoot, e);
163         }
164     }
165
166     private static Datastore getDatastore(final XmlElement source) throws DocumentedException {
167         try {
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);
172         }
173     }
174
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();
180         }
181         throw new DocumentedException("Incorrect Datastore: ", ErrorType.PROTOCOL, ErrorTag.BAD_ELEMENT,
182             ErrorSeverity.ERROR);
183     }
184
185     private Node transformNormalizedNode(final Document document, final ContainerNode data) {
186         final Element configElement = document.createElementNS(NamespaceURN.BASE, CONFIG_KEY);
187         final DOMResult result = new DOMResult(configElement);
188         try {
189             final XMLStreamWriter xmlWriter = XML_OUTPUT_FACTORY.createXMLStreamWriter(result);
190             final NormalizedNodeStreamWriter nnStreamWriter = XMLStreamNormalizedNodeStreamWriter.create(xmlWriter,
191                 schemaContext.getCurrentContext());
192
193             final NormalizedNodeWriter nnWriter = NormalizedNodeWriter.forStreamWriter(nnStreamWriter, true);
194             for (DataContainerChild child : data.body()) {
195                 nnWriter.write(child);
196             }
197             nnWriter.flush();
198             xmlWriter.flush();
199         } catch (XMLStreamException | IOException e) {
200             // FIXME: throw DocumentedException
201             throw new IllegalStateException(e);
202         }
203         return result.getNode();
204     }
205
206     @Override
207     protected String getOperationName() {
208         return OPERATION_NAME;
209     }
210 }