blob: 28fad43f2caa084168f7a79d0d7cb4600209240e [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 org.hamcrest.Matchers.hasItem;
19import static org.hamcrest.Matchers.hasSize;
20import static org.hamcrest.Matchers.is;
21import static org.hamcrest.Matchers.stringContainsInOrder;
22import static org.junit.Assert.*;
23import java.io.IOException;
24import java.io.InputStream;
25import java.net.URI;
26import java.util.Arrays;
27import java.util.List;
28import java.util.concurrent.CompletableFuture;
29import java.util.concurrent.TimeUnit;
30import java.util.function.BiFunction;
31import java.util.regex.Matcher;
32import java.util.regex.Pattern;
33
34import org.apache.commons.io.input.ReaderInputStream;
35import org.hamcrest.Matchers;
36import org.junit.Before;
37import org.junit.Test;
38import org.onlab.util.XmlString;
39import org.onosproject.d.config.sync.DeviceConfigSynchronizationProviderService;
40import org.onosproject.d.config.sync.impl.netconf.NetconfDeviceConfigSynchronizerComponent.NetconfContext;
41import org.onosproject.d.config.sync.operation.SetRequest;
42import org.onosproject.d.config.sync.operation.SetResponse;
43import org.onosproject.d.config.sync.operation.SetResponse.Code;
44import org.onosproject.net.DeviceId;
45import org.onosproject.net.provider.ProviderId;
46import org.onosproject.netconf.NetconfController;
47import org.onosproject.netconf.NetconfException;
48import org.onosproject.netconf.NetconfSession;
49import org.onosproject.netconf.NetconfSessionAdapter;
50import org.onosproject.yang.model.DataNode;
51import org.onosproject.yang.model.DataNode.Type;
52import org.onosproject.yang.model.InnerNode;
53import org.onosproject.yang.model.LeafNode;
54import org.onosproject.yang.model.ResourceData;
55import org.onosproject.yang.model.ResourceId;
56import org.onosproject.yang.model.SchemaContextProvider;
57import org.onosproject.yang.runtime.AnnotatedNodeInfo;
58import org.onosproject.yang.runtime.CompositeData;
59import org.onosproject.yang.runtime.CompositeStream;
60import org.onosproject.yang.runtime.DefaultAnnotatedNodeInfo;
61import org.onosproject.yang.runtime.DefaultAnnotation;
62import org.onosproject.yang.runtime.DefaultCompositeStream;
63import org.onosproject.yang.runtime.RuntimeContext;
64import org.onosproject.yang.runtime.YangRuntimeService;
65import com.google.common.io.CharSource;
66
67public class NetconfDeviceConfigSynchronizerProviderTest {
68
69 private static final ProviderId PID = new ProviderId("netconf", "test");
70 private static final DeviceId DID = DeviceId.deviceId("netconf:testDevice");
71
72 private static final String XMLNS_XC = "xmlns:xc";
73 private static final String NETCONF_1_0_BASE_NAMESPACE =
74 "urn:ietf:params:xml:ns:netconf:base:1.0";
75
76 private static final DefaultAnnotation XC_ANNOTATION =
77 new DefaultAnnotation(XMLNS_XC, NETCONF_1_0_BASE_NAMESPACE);
78
79 private static final DefaultAnnotation AN_XC_REPLACE_OPERATION =
80 new DefaultAnnotation("xc:operation", "replace");
81
82 private static final DefaultAnnotation AN_XC_REMOVE_OPERATION =
83 new DefaultAnnotation("xc:operation", "remove");
84
85 /**
86 * Yang namespace for test config data.
87 */
88 private static final String TEST_NS = "testNS";
89
90 private static final ResourceId RID_INTERFACES =
91 ResourceId.builder().addBranchPointSchema("interfaces", TEST_NS).build();
92
93 private NetconfDeviceConfigSynchronizerProvider sut;
94
95 private NetconfContext ncCtx;
96
97
98 // Set following accordingly to suite test scenario
99 NetconfSession testNcSession;
100 YangRuntimeService testYangRuntime;
101
102
103 @Before
104 public void setUp() throws Exception {
105
106 ncCtx = new TestNetconfContext();
107
108 sut = new NetconfDeviceConfigSynchronizerProvider(PID, ncCtx) {
109 // overriding to avoid mocking whole NetconController and all that.
110 @Override
111 protected NetconfSession getNetconfSession(DeviceId deviceId) {
112 assertEquals(DID, deviceId);
113 return testNcSession;
114 }
115 };
116 }
117
118 @Test
119 public void testReplaceOperation() throws Exception {
120 // plug drivers with assertions
121 testYangRuntime = onEncode((data, context) -> {
122 assertEquals("xml", context.getDataFormat());
123 assertThat(context.getProtocolAnnotations(), hasItem(XC_ANNOTATION));
124
125 // assert CompositeData
126 ResourceData rData = data.resourceData();
127 List<AnnotatedNodeInfo> infos = data.annotatedNodesInfo();
128
129 ResourceId interfacesRid = RID_INTERFACES;
130 AnnotatedNodeInfo intfsAnnot = DefaultAnnotatedNodeInfo.builder()
131 .resourceId(interfacesRid)
132 .addAnnotation(AN_XC_REPLACE_OPERATION)
133 .build();
134 assertThat("interfaces has replace operation", infos, hasItem(intfsAnnot));
135
136 // assertion for ResourceData.
137 assertEquals(RID_INTERFACES, rData.resourceId());
138 assertThat("has 1 child", rData.dataNodes(), hasSize(1));
139 assertThat("which is interface",
140 rData.dataNodes().get(0).key().schemaId().name(),
141 is("interface"));
142 // todo: assert the rest of the tree if it make sense.
143
144 // FIXME it's unclear what URI is expected here
145 String id = URI.create("netconf:testDevice").toString();
146
147 String inXml = deviceConfigAsXml("replace");
148
149 return toCompositeStream(id, inXml);
150 });
151 testNcSession = new TestEditNetconfSession();
152
153
154 // building test data
155 ResourceId interfacesId = RID_INTERFACES;
156 DataNode interfaces = deviceConfigNode();
157 SetRequest request = SetRequest.builder()
158 .replace(interfacesId, interfaces)
159 .build();
160
161 // test start
162 CompletableFuture<SetResponse> f = sut.setConfiguration(DID, request);
163 SetResponse response = f.get(5, TimeUnit.MINUTES);
164
165 assertEquals(Code.OK, response.code());
166 assertEquals(request.subjects(), response.subjects());
167 }
168
169
170 @Test
171 public void testDeleteOperation() throws Exception {
172 // plug drivers with assertions
173 testYangRuntime = onEncode((data, context) -> {
174 assertEquals("xml", context.getDataFormat());
175 assertThat(context.getProtocolAnnotations(), hasItem(XC_ANNOTATION));
176
177 // assert CompositeData
178 ResourceData rData = data.resourceData();
179 List<AnnotatedNodeInfo> infos = data.annotatedNodesInfo();
180
181 ResourceId interfacesRid = RID_INTERFACES;
182 AnnotatedNodeInfo intfsAnnot = DefaultAnnotatedNodeInfo.builder()
183 .resourceId(interfacesRid)
184 .addAnnotation(AN_XC_REMOVE_OPERATION)
185 .build();
186 assertThat("interfaces has replace operation", infos, hasItem(intfsAnnot));
187
188 // assertion for ResourceData.
189 assertEquals(RID_INTERFACES, rData.resourceId());
190 assertThat("has no child", rData.dataNodes(), hasSize(0));
191
192 // FIXME it's unclear what URI is expected here
193 String id = URI.create("netconf:testDevice").toString();
194
195 String inXml = deviceConfigAsXml("remove");
196
197 return toCompositeStream(id, inXml);
198 });
199 testNcSession = new TestEditNetconfSession();
200
201 // building test data
202 ResourceId interfacesId = RID_INTERFACES;
203 SetRequest request = SetRequest.builder()
204 .delete(interfacesId)
205 .build();
206
207 // test start
208 CompletableFuture<SetResponse> f = sut.setConfiguration(DID, request);
209
210 SetResponse response = f.get(5, TimeUnit.MINUTES);
211 assertEquals(Code.OK, response.code());
212 assertEquals(request.subjects(), response.subjects());
213 }
214
215 /**
216 * DataNode for testing.
217 *
218 * <pre>
219 * +-interfaces
220 * |
221 * +- interface{intf-name="en0"}
222 * |
223 * +- speed = "10G"
224 * +- state = "up"
225 *
226 * </pre>
227 * @return DataNode
228 */
229 private DataNode deviceConfigNode() {
230 InnerNode.Builder intfs = InnerNode.builder("interfaces", TEST_NS);
231 intfs.type(Type.SINGLE_INSTANCE_NODE);
232 InnerNode.Builder intf = intfs.createChildBuilder("interface", TEST_NS);
233 intf.type(Type.SINGLE_INSTANCE_LEAF_VALUE_NODE);
234 intf.addKeyLeaf("name", TEST_NS, "Ethernet0/0");
235 LeafNode.Builder speed = intf.createChildBuilder("mtu", TEST_NS, "1500");
236 speed.type(Type.SINGLE_INSTANCE_LEAF_VALUE_NODE);
237
238 intf.addNode(speed.build());
239 intfs.addNode(intf.build());
240 return intfs.build();
241 }
242
243 /**
244 * {@link #deviceConfigNode()} as XML.
245 *
246 * @param operation xc:operation value on {@code interfaces} node
247 * @return XML
248 */
249 private String deviceConfigAsXml(String operation) {
250 return "<interfaces xmlns=\"http://example.com/schema/1.2/config\""
251 + " xc:operation=\"" + operation + "\">\n" +
252 " <interface>\n" +
253 " <name>Ethernet0/0</name>\n" +
254 " <mtu>1500</mtu>\n" +
255 " </interface>\n" +
256 "</interfaces>";
257 }
258
259 private String rpcReplyOk(int messageid) {
260 return "<rpc-reply message-id=\"" + messageid + "\"\n" +
261 " xmlns=\"urn:ietf:params:xml:ns:netconf:base:1.0\">\n" +
262 " <ok/>\n" +
263 "</rpc-reply>";
264 }
265
266 private int fetchMessageId(String request) {
267 int messageid;
268 Pattern msgId = Pattern.compile("message-id=['\"]([0-9]+)['\"]");
269 Matcher matcher = msgId.matcher(request);
270 if (matcher.find()) {
271 messageid = Integer.parseInt(matcher.group(1));
272 } else {
273 messageid = -1;
274 }
275 return messageid;
276 }
277
278
279 protected CompositeStream toCompositeStream(String id, String inXml) {
280 try {
281 InputStream xml = new ReaderInputStream(
282 CharSource.wrap(inXml)
283 .openStream());
284
285 return new DefaultCompositeStream(id, xml);
286 } catch (IOException e) {
Ray Milkeydbd38212018-07-02 09:18:09 -0700287 throw new IllegalStateException(e);
Yuta HIGUCHI8810aa42017-08-02 15:05:37 -0700288 }
289 }
290
291 /**
292 * Asserts that it received edit-config message and reply Ok.
293 */
294 private class TestEditNetconfSession extends NetconfSessionAdapter {
295 @Override
Yuta HIGUCHI4f55c672018-06-14 18:10:43 -0700296 public CompletableFuture<String> rpc(String request)
Yuta HIGUCHI8810aa42017-08-02 15:05:37 -0700297 throws NetconfException {
298 System.out.println("TestEditNetconfSession received:");
299 System.out.println(XmlString.prettifyXml(request));
300
301 // Extremely naive request rpc message check
302 assertThat(request, stringContainsInOrder(Arrays.asList(
303 "<rpc",
304 "<edit-config",
305 "<target",
306 "<config",
307
308 "</config>",
309 "</edit-config>",
310 "</rpc>")));
311
312 assertThat("XML namespace decl exists",
313 request, Matchers.containsString("xmlns:xc"));
314
315 assertThat("netconf operation exists",
316 request, Matchers.containsString("xc:operation"));
317
318 return CompletableFuture.completedFuture(rpcReplyOk(fetchMessageId(request)));
319 }
320 }
321
322 /**
323 * Creates mock YangRuntimeService.
324 *
325 * @param body to execute when {@link YangRuntimeService#encode(CompositeData, RuntimeContext)} was called.
326 * @return YangRuntimeService instance
327 */
328 TestYangRuntimeService onEncode(BiFunction<CompositeData, RuntimeContext, CompositeStream> body) {
329 return new TestYangRuntimeService() {
330 @Override
331 public CompositeStream encode(CompositeData internal,
332 RuntimeContext context) {
333 return body.apply(internal, context);
334 }
335 };
336 }
337
338 private abstract class TestYangRuntimeService implements YangRuntimeService {
339
340 @Override
341 public CompositeStream encode(CompositeData internal,
342 RuntimeContext context) {
343 fail("stub not implemented");
344 return null;
345 }
346 @Override
347 public CompositeData decode(CompositeStream external,
348 RuntimeContext context) {
349 fail("stub not implemented");
350 return null;
351 }
352 }
353
354 private final class TestNetconfContext implements NetconfContext {
355 @Override
356 public DeviceConfigSynchronizationProviderService providerService() {
357 fail("Add stub driver as necessary");
358 return null;
359 }
360
361 @Override
362 public SchemaContextProvider schemaContextProvider() {
363 fail("Add stub driver as necessary");
364 return null;
365 }
366
367 @Override
368 public YangRuntimeService yangRuntime() {
369 return testYangRuntime;
370 }
371
372 @Override
373 public NetconfController netconfController() {
374 fail("Add stub driver as necessary");
375 return null;
376 }
377 }
378
379}