blob: 2ab2bbc736549bfe5854128432da4030b7f4874c [file] [log] [blame]
Yuta HIGUCHI8810aa42017-08-02 15:05:37 -07001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.d.config.sync.impl.netconf;
17
18import static com.google.common.base.Preconditions.checkNotNull;
19import static java.nio.charset.StandardCharsets.UTF_8;
20import static java.util.concurrent.CompletableFuture.completedFuture;
21import static org.slf4j.LoggerFactory.getLogger;
22
23import java.io.IOException;
24import java.io.InputStreamReader;
25import java.util.concurrent.CompletableFuture;
Yuta HIGUCHI8810aa42017-08-02 15:05:37 -070026import org.onlab.util.XmlString;
27import org.onosproject.d.config.ResourceIds;
28import org.onosproject.d.config.sync.DeviceConfigSynchronizationProvider;
29import org.onosproject.d.config.sync.impl.netconf.NetconfDeviceConfigSynchronizerComponent.NetconfContext;
30import org.onosproject.d.config.sync.operation.SetRequest;
31import org.onosproject.d.config.sync.operation.SetRequest.Change;
32import org.onosproject.d.config.sync.operation.SetRequest.Change.Operation;
33import org.onosproject.d.config.sync.operation.SetResponse;
34import org.onosproject.d.config.sync.operation.SetResponse.Code;
35import org.onosproject.net.DeviceId;
36import org.onosproject.net.provider.AbstractProvider;
37import org.onosproject.net.provider.ProviderId;
38import org.onosproject.netconf.NetconfDevice;
39import org.onosproject.netconf.NetconfException;
40import org.onosproject.netconf.NetconfSession;
41import org.onosproject.yang.model.DataNode;
42import org.onosproject.yang.model.DefaultResourceData;
43import org.onosproject.yang.model.InnerNode;
44import org.onosproject.yang.model.ResourceData;
45import org.onosproject.yang.model.ResourceId;
46import org.onosproject.yang.runtime.AnnotatedNodeInfo;
47import org.onosproject.yang.runtime.Annotation;
Yuta HIGUCHI9285d972017-10-16 16:15:00 -070048import org.onosproject.yang.runtime.CompositeData;
Yuta HIGUCHI8810aa42017-08-02 15:05:37 -070049import org.onosproject.yang.runtime.CompositeStream;
50import org.onosproject.yang.runtime.DefaultAnnotatedNodeInfo;
51import org.onosproject.yang.runtime.DefaultAnnotation;
52import org.onosproject.yang.runtime.DefaultCompositeData;
53import org.onosproject.yang.runtime.DefaultRuntimeContext;
54import org.onosproject.yang.runtime.RuntimeContext;
55import org.slf4j.Logger;
56import com.google.common.io.CharStreams;
57
58/**
59 * Dynamic config synchronizer provider for NETCONF.
Yuta HIGUCHIab350802018-05-07 14:56:32 -070060 *
Yuta HIGUCHI8810aa42017-08-02 15:05:37 -070061 * <ul>
62 * <li> Converts POJO YANG into XML.
63 * <li> Adds NETCONF envelope around it.
64 * <li> Send request down to the device over NETCONF
65 * </ul>
66 */
67public class NetconfDeviceConfigSynchronizerProvider
68 extends AbstractProvider
69 implements DeviceConfigSynchronizationProvider {
70
71 private static final Logger log = getLogger(NetconfDeviceConfigSynchronizerProvider.class);
72
73 // TODO this should probably be defined on YRT Serializer side
74 /**
75 * {@link RuntimeContext} parameter Dataformat specifying XML.
76 */
77 private static final String DATAFORMAT_XML = "xml";
78
79 private static final String XMLNS_XC = "xmlns:xc";
80 private static final String NETCONF_1_0_BASE_NAMESPACE =
81 "urn:ietf:params:xml:ns:netconf:base:1.0";
82
83 /**
84 * Annotation to add xc namespace declaration.
85 * {@value #XMLNS_XC}={@value #NETCONF_1_0_BASE_NAMESPACE}
86 */
87 private static final DefaultAnnotation XMLNS_XC_ANNOTATION =
88 new DefaultAnnotation(XMLNS_XC, NETCONF_1_0_BASE_NAMESPACE);
89
90 private static final String XC_OPERATION = "xc:operation";
91
92
93 private NetconfContext context;
94
Yuta HIGUCHI8810aa42017-08-02 15:05:37 -070095 protected NetconfDeviceConfigSynchronizerProvider(ProviderId id,
96 NetconfContext context) {
97 super(id);
98 this.context = checkNotNull(context);
99 }
100
101 @Override
102 public CompletableFuture<SetResponse> setConfiguration(DeviceId deviceId,
103 SetRequest request) {
104 // sanity check and handle empty change?
105
106 // TODOs:
107 // - Construct convert request object into XML
108 // -- [FutureWork] may need to introduce behaviour for Device specific
109 // workaround insertion
110
111 StringBuilder rpc = new StringBuilder();
112
113 // - Add NETCONF envelope
Yuta HIGUCHI9285d972017-10-16 16:15:00 -0700114 rpc.append("<rpc xmlns=\"").append(NETCONF_1_0_BASE_NAMESPACE).append('"')
115 .append(">");
Yuta HIGUCHI8810aa42017-08-02 15:05:37 -0700116
117 rpc.append("<edit-config>");
118 rpc.append("<target>");
119 // TODO directly writing to running for now
120 rpc.append("<running/>");
121 rpc.append("</target>\n");
122 rpc.append("<config ")
123 .append(XMLNS_XC).append("=\"").append(NETCONF_1_0_BASE_NAMESPACE).append("\">");
124 // TODO netconf SBI should probably be adding these envelopes once
125 // netconf SBI is in better shape
126 // TODO In such case netconf sbi need to define namespace externally visible.
127 // ("xc" in above instance)
128 // to be used to add operations on config tree nodes
129
130
131 // Convert change(s) into a DataNode tree
132 for (Change change : request.changes()) {
Yuta HIGUCHI9285d972017-10-16 16:15:00 -0700133 log.trace("change={}", change);
Yuta HIGUCHI8810aa42017-08-02 15:05:37 -0700134
135 // TODO switch statement can probably be removed
136 switch (change.op()) {
137 case REPLACE:
138 case UPDATE:
139 case DELETE:
140 // convert DataNode -> ResourceData
141 ResourceData data = toResourceData(change);
142
143 // build CompositeData
144 DefaultCompositeData.Builder compositeData =
145 DefaultCompositeData.builder();
146
147 // add ResourceData
148 compositeData.resourceData(data);
149
150 // add AnnotatedNodeInfo operation
151 compositeData.addAnnotatedNodeInfo(toAnnotatedNodeInfo(change.op(), change.path()));
152
153 RuntimeContext yrtContext = new DefaultRuntimeContext.Builder()
154 .setDataFormat(DATAFORMAT_XML)
155 .addAnnotation(XMLNS_XC_ANNOTATION)
156 .build();
Yuta HIGUCHI9285d972017-10-16 16:15:00 -0700157 CompositeData cdata = compositeData.build();
158 log.trace("CompositeData:{}", cdata);
159 CompositeStream xml = context.yangRuntime().encode(cdata,
Yuta HIGUCHI8810aa42017-08-02 15:05:37 -0700160 yrtContext);
161 try {
162 CharStreams.copy(new InputStreamReader(xml.resourceData(), UTF_8), rpc);
163 } catch (IOException e) {
164 log.error("IOException thrown", e);
165 // FIXME handle error
166 }
167 break;
168
169 default:
170 log.error("Should never reach here. {}", change);
171 break;
172 }
173 }
174
175 // - close NETCONF envelope
176 // TODO eventually these should be handled by NETCONF SBI side
177 rpc.append('\n');
178 rpc.append("</config>");
179 rpc.append("</edit-config>");
180 rpc.append("</rpc>");
181
182 // - send requests down to the device
183 NetconfSession session = getNetconfSession(deviceId);
184 if (session == null) {
185 log.error("No session available for {}", deviceId);
186 return completedFuture(SetResponse.response(request,
187 Code.FAILED_PRECONDITION,
188 "No session for " + deviceId));
189 }
190 try {
191 // FIXME Netconf async API is currently screwed up, need to fix
192 // NetconfSession, etc.
Yuta HIGUCHI9285d972017-10-16 16:15:00 -0700193 CompletableFuture<String> response = session.rpc(rpc.toString());
194 log.trace("raw request:\n{}", rpc);
195 log.trace("prettified request:\n{}", XmlString.prettifyXml(rpc));
Yuta HIGUCHI8810aa42017-08-02 15:05:37 -0700196 return response.handle((resp, err) -> {
197 if (err == null) {
Yuta HIGUCHI9285d972017-10-16 16:15:00 -0700198 log.trace("reply:\n{}", XmlString.prettifyXml(resp));
Yuta HIGUCHI8810aa42017-08-02 15:05:37 -0700199 // FIXME check response properly
200 return SetResponse.ok(request);
201 } else {
202 return SetResponse.response(request, Code.UNKNOWN, err.getMessage());
203 }
204 });
205 } catch (NetconfException e) {
206 // TODO Handle error
207 log.error("NetconfException thrown", e);
208 return completedFuture(SetResponse.response(request, Code.UNKNOWN, e.getMessage()));
209
210 }
211 }
212
213 // overridable for ease of testing
214 /**
215 * Returns a session for the specified deviceId.
216 *
217 * @param deviceId for which we wish to retrieve a session
218 * @return a NetconfSession with the specified node
219 * or null if this node does not have the session to the specified Device.
220 */
221 protected NetconfSession getNetconfSession(DeviceId deviceId) {
222 NetconfDevice device = context.netconfController().getNetconfDevice(deviceId);
223 checkNotNull(device, "The specified deviceId could not be found by the NETCONF controller.");
224 NetconfSession session = device.getSession();
225 checkNotNull(session, "A session could not be retrieved for the specified deviceId.");
226 return session;
227 }
228
229 /**
230 * Creates AnnotatedNodeInfo for {@code node}.
231 *
232 * @param op operation
233 * @param parent resourceId
234 * @param node the node
235 * @return AnnotatedNodeInfo
236 */
237 static AnnotatedNodeInfo annotatedNodeInfo(Operation op,
238 ResourceId parent,
239 DataNode node) {
240 return DefaultAnnotatedNodeInfo.builder()
241 .resourceId(ResourceIds.resourceId(parent, node))
242 .addAnnotation(toAnnotation(op))
243 .build();
244 }
245
246 /**
247 * Creates AnnotatedNodeInfo for specified resource path.
248 *
249 * @param op operation
250 * @param path resourceId
251 * @return AnnotatedNodeInfo
252 */
253 static AnnotatedNodeInfo toAnnotatedNodeInfo(Operation op,
254 ResourceId path) {
255 return DefaultAnnotatedNodeInfo.builder()
256 .resourceId(path)
257 .addAnnotation(toAnnotation(op))
258 .build();
259 }
260
261 /**
262 * Transform DataNode into a ResourceData.
263 *
264 * @param change object
265 * @return ResourceData
266 */
267 static ResourceData toResourceData(Change change) {
268 DefaultResourceData.Builder builder = DefaultResourceData.builder();
269 builder.resourceId(change.path());
270 if (change.op() != Change.Operation.DELETE) {
271 DataNode dataNode = change.val();
272 if (dataNode instanceof InnerNode) {
273 ((InnerNode) dataNode).childNodes().values().forEach(builder::addDataNode);
274 } else {
275 log.error("Unexpected DataNode encountered", change);
276 }
277 }
278
279 return builder.build();
280 }
281
282 static Annotation toAnnotation(Operation op) {
283 switch (op) {
284 case DELETE:
285 return new DefaultAnnotation(XC_OPERATION, "remove");
286 case REPLACE:
287 return new DefaultAnnotation(XC_OPERATION, "replace");
288 case UPDATE:
289 return new DefaultAnnotation(XC_OPERATION, "merge");
290 default:
291 throw new IllegalArgumentException("Unknown operation " + op);
292 }
293 }
294
295}