blob: 038d2dae540b121dc447bb6362d95988333312ef [file] [log] [blame]
Hyunsun Moon2c3f0ee2017-04-06 16:47:21 +09001/*
2 * Copyright 2017-present Open Networking Laboratory
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.opencord.cordvtn.impl;
17
18import com.google.common.collect.ImmutableSet;
19import com.google.common.collect.Lists;
20import com.google.common.collect.Maps;
21import com.google.common.collect.Sets;
22import com.jcraft.jsch.Session;
23import org.junit.After;
24import org.junit.Before;
25import org.junit.Test;
26import org.junit.runner.RunWith;
27import org.onlab.junit.TestTools;
28import org.onlab.packet.ChassisId;
29import org.onlab.packet.IpAddress;
30import org.onosproject.cluster.ClusterServiceAdapter;
31import org.onosproject.cluster.ControllerNode;
32import org.onosproject.cluster.DefaultControllerNode;
33import org.onosproject.cluster.LeadershipServiceAdapter;
34import org.onosproject.cluster.NodeId;
35import org.onosproject.core.ApplicationId;
36import org.onosproject.core.CoreServiceAdapter;
37import org.onosproject.core.DefaultApplicationId;
38import org.onosproject.event.DefaultEventSinkRegistry;
39import org.onosproject.event.Event;
40import org.onosproject.event.EventDeliveryService;
41import org.onosproject.event.EventSink;
42import org.onosproject.net.Annotations;
43import org.onosproject.net.ConnectPoint;
44import org.onosproject.net.DefaultDevice;
45import org.onosproject.net.Device;
46import org.onosproject.net.DeviceId;
47import org.onosproject.net.Host;
48import org.onosproject.net.HostId;
49import org.onosproject.net.Port;
50import org.onosproject.net.behaviour.BridgeConfig;
51import org.onosproject.net.behaviour.BridgeDescription;
52import org.onosproject.net.behaviour.InterfaceConfig;
53import org.onosproject.net.config.NetworkConfigServiceAdapter;
54import org.onosproject.net.device.DeviceAdminService;
55import org.onosproject.net.device.DeviceEvent;
56import org.onosproject.net.device.DeviceListener;
57import org.onosproject.net.device.DeviceServiceAdapter;
58import org.onosproject.net.driver.Behaviour;
59import org.onosproject.net.flow.FlowRule;
60import org.onosproject.net.host.HostDescription;
61import org.onosproject.net.host.HostServiceAdapter;
62import org.onosproject.net.provider.ProviderId;
63import org.onosproject.ovsdb.controller.OvsdbClientService;
64import org.onosproject.ovsdb.controller.OvsdbController;
65import org.opencord.cordvtn.api.core.CordVtnPipeline;
66import org.opencord.cordvtn.api.core.InstanceService;
67import org.opencord.cordvtn.api.node.CordVtnNode;
68import org.opencord.cordvtn.api.node.CordVtnNodeAdminService;
69import org.opencord.cordvtn.api.node.CordVtnNodeEvent;
70import org.opencord.cordvtn.api.node.CordVtnNodeListener;
71import org.opencord.cordvtn.api.node.CordVtnNodeService;
72import org.powermock.api.easymock.PowerMock;
73import org.powermock.core.classloader.annotations.PrepareForTest;
74import org.powermock.modules.junit4.PowerMockRunner;
75
76import java.util.List;
77import java.util.Map;
78import java.util.Set;
79import java.util.stream.Collectors;
80
81import static com.google.common.base.Preconditions.checkState;
82import static org.easymock.EasyMock.*;
83import static org.junit.Assert.assertEquals;
84import static org.onosproject.net.Device.Type.CONTROLLER;
85import static org.onosproject.net.NetTestTools.injectEventDispatcher;
86import static org.onosproject.net.device.DeviceEvent.Type.*;
87import static org.opencord.cordvtn.api.Constants.INTEGRATION_BRIDGE;
88import static org.opencord.cordvtn.api.node.CordVtnNodeEvent.Type.NODE_CREATED;
89import static org.opencord.cordvtn.api.node.CordVtnNodeEvent.Type.NODE_UPDATED;
90import static org.opencord.cordvtn.api.node.CordVtnNodeState.*;
91import static org.opencord.cordvtn.impl.RemoteIpCommandUtil.*;
92import static org.powermock.api.easymock.PowerMock.mockStatic;
93
94/**
95 * Unit test for CordVtnNodeHandler which provides cordvtn node bootstrap state machine.
96 */
97@RunWith(PowerMockRunner.class)
98public class DefaultCordVtnNodeHandlerTest extends CordVtnNodeTest {
99
100 private static final String ERR_STATE = "Node state did not match";
101
102 private static final ApplicationId TEST_APP_ID = new DefaultApplicationId(1, "test");
103 private static final NodeId LOCAL_NODE_ID = new NodeId("local");
104 private static final ControllerNode LOCAL_CTRL =
105 new DefaultControllerNode(LOCAL_NODE_ID, IpAddress.valueOf("127.0.0.1"));
106
107 private static final Device OVSDB_DEVICE = new TestDevice(
108 new ProviderId("of", "foo"),
109 DeviceId.deviceId("ovsdb:" + TEST_CIDR_ADDR.ip().toString()),
110 CONTROLLER,
111 "manufacturer",
112 "hwVersion",
113 "swVersion",
114 "serialNumber",
115 new ChassisId(1));
116
117 private static final Device OF_DEVICE_1 = createDevice(1);
118
119 private static final Device OF_DEVICE_2 = createDevice(2);
120 private static final Port OF_DEVICE_2_PORT_1 = createPort(OF_DEVICE_2, 1, TEST_DATA_IFACE);
121 private static final Port OF_DEVICE_2_PORT_2 = createPort(OF_DEVICE_2, 2, TEST_VXLAN_IFACE);
122
123 private static final Device OF_DEVICE_3 = createDevice(3);
124 private static final Port OF_DEVICE_3_PORT_1 = createPort(OF_DEVICE_3, 1, TEST_DATA_IFACE);
125 private static final Port OF_DEVICE_3_PORT_2 = createPort(OF_DEVICE_3, 2, TEST_VXLAN_IFACE);
126
127 private static final Device OF_DEVICE_4 = createDevice(4);
128 private static final Port OF_DEVICE_4_PORT_1 = createPort(OF_DEVICE_4, 1, TEST_DATA_IFACE);
129 private static final Port OF_DEVICE_4_PORT_2 = createPort(OF_DEVICE_4, 2, TEST_VXLAN_IFACE);
130
131 private static final CordVtnNode NODE_1 = createNode("node-01", OF_DEVICE_1, INIT);
132 private static final CordVtnNode NODE_2 = createNode("node-02", OF_DEVICE_2, DEVICE_CREATED);
133 private static final CordVtnNode NODE_3 = createNode("node-03", OF_DEVICE_3, PORT_CREATED);
134 private static final CordVtnNode NODE_4 = createNode("node-04", OF_DEVICE_4, COMPLETE);
135
136 private TestDeviceService deviceService;
137 private TestNodeManager nodeManager;
138 private DefaultCordVtnNodeHandler target;
139
140 @Before
141 public void setUp() throws Exception {
142 this.deviceService = new TestDeviceService();
143 this.nodeManager = new TestNodeManager();
144
145 // add fake ovsdb device
146 this.deviceService.devMap.put(OVSDB_DEVICE.id(), OVSDB_DEVICE);
147
148 // add fake OF devices
149 this.deviceService.devMap.put(OF_DEVICE_1.id(), OF_DEVICE_1);
150 this.deviceService.devMap.put(OF_DEVICE_2.id(), OF_DEVICE_2);
151 this.deviceService.devMap.put(OF_DEVICE_3.id(), OF_DEVICE_3);
152 this.deviceService.devMap.put(OF_DEVICE_4.id(), OF_DEVICE_4);
153
154 // add fake OF ports
155 this.deviceService.portList.add(OF_DEVICE_2_PORT_1);
156 this.deviceService.portList.add(OF_DEVICE_2_PORT_2);
157 this.deviceService.portList.add(OF_DEVICE_3_PORT_1);
158 this.deviceService.portList.add(OF_DEVICE_3_PORT_2);
159 this.deviceService.portList.add(OF_DEVICE_4_PORT_1);
160 this.deviceService.portList.add(OF_DEVICE_4_PORT_2);
161
162 // add fake nodes
163 this.nodeManager.nodeMap.put(OF_DEVICE_1.id(), NODE_1);
164 this.nodeManager.nodeMap.put(OF_DEVICE_2.id(), NODE_2);
165 this.nodeManager.nodeMap.put(OF_DEVICE_3.id(), NODE_3);
166 this.nodeManager.nodeMap.put(OF_DEVICE_4.id(), NODE_4);
167
168 OvsdbClientService mockOvsdbClient = createMock(OvsdbClientService.class);
169 expect(mockOvsdbClient.isConnected())
170 .andReturn(true)
171 .anyTimes();
172 replay(mockOvsdbClient);
173
174 OvsdbController mockOvsdbController = createMock(OvsdbController.class);
175 expect(mockOvsdbController.getOvsdbClient(anyObject()))
176 .andReturn(mockOvsdbClient)
177 .anyTimes();
178 replay(mockOvsdbController);
179
180 DeviceAdminService mockDeviceAdminService = createMock(DeviceAdminService.class);
181 mockDeviceAdminService.removeDevice(anyObject());
182 replay(mockDeviceAdminService);
183
184 target = new DefaultCordVtnNodeHandler();
185 target.coreService = new TestCoreService();
186 target.leadershipService = new TestLeadershipService();
187 target.clusterService = new TestClusterService();
188 target.configService = new TestConfigService();
189 target.deviceService = this.deviceService;
190 target.deviceAdminService = mockDeviceAdminService;
191 target.hostService = new TestHostService();
192 target.ovsdbController = mockOvsdbController;
193 target.nodeService = this.nodeManager;
194 target.nodeAdminService = this.nodeManager;
195 target.instanceService = new TestInstanceService();
196 target.pipelineService = new TestCordVtnPipeline();
197 injectEventDispatcher(target, new TestEventDispatcher());
198 target.activate();
199 }
200
201 @After
202 public void tearDown() {
203 target.deactivate();
204 deviceService = null;
205 nodeManager = null;
206 target = null;
207 }
208
209 /**
210 * Checks if the node state changes from INIT to DEVICE_CREATED when
211 * the integration bridge created.
212 */
213 @Test
214 public void testProcessInitState() {
215 deviceService.addDevice(OF_DEVICE_1);
216 TestTools.assertAfter(100, () -> {
217 CordVtnNode current = nodeManager.node(NODE_1.integrationBridgeId());
218 assertEquals(ERR_STATE, DEVICE_CREATED, current.state());
219 });
220 }
221
222 /**
223 * Checks if the node state changes from DEVICE_CREATED to PORT_CREATED
224 * when the data interface and vxlan interface are added.
225 */
226 @Test
227 public void testProcessDeviceCreatedState() {
228 // Add the data port and check if the state is still in DEVICE_CREAGED
229 deviceService.addPort(OF_DEVICE_2, OF_DEVICE_2_PORT_1);
230 TestTools.assertAfter(100, () -> {
231 CordVtnNode current = nodeManager.node(NODE_2.integrationBridgeId());
232 assertEquals(ERR_STATE, DEVICE_CREATED, current.state());
233 });
234
235 // Add the vxlan port and check if the state changes to PORT_CREATED
236 deviceService.addPort(OF_DEVICE_2, OF_DEVICE_2_PORT_2);
237 TestTools.assertAfter(100, () -> {
238 CordVtnNode current = nodeManager.node(NODE_2.integrationBridgeId());
239 assertEquals(ERR_STATE, PORT_CREATED, current.state());
240 });
241 }
242
243 /**
244 * Checks if the node state changes to COMPLETE when the network interface
245 * configuration is done.
246 */
247 @PrepareForTest(RemoteIpCommandUtil.class)
248 @Test
249 public void testProcessPortCreatedState() {
250 Session mockSession = createMock(Session.class);
251 mockStatic(RemoteIpCommandUtil.class);
252 expect(connect(anyObject())).andReturn(mockSession);
253 expect(getCurrentIps(mockSession, INTEGRATION_BRIDGE)).andReturn(Sets.newHashSet(TEST_CIDR_ADDR.ip()));
254 expect(getCurrentIps(mockSession, TEST_DATA_IFACE)).andReturn(Sets.newHashSet());
255 expect(isInterfaceUp(anyObject(), anyObject())).andReturn(true).anyTimes();
256 RemoteIpCommandUtil.disconnect(anyObject());
257 PowerMock.replay(RemoteIpCommandUtil.class);
258
259 // There's no events for IP address changes on the interfaces, so just
260 // Set node state updated to trigger node bootstrap
261 nodeManager.updateNode(NODE_3);
262 TestTools.assertAfter(100, () -> {
263 CordVtnNode current = nodeManager.node(NODE_3.integrationBridgeId());
264 assertEquals(ERR_STATE, COMPLETE, current.state());
265 });
266 }
267
268 /**
269 * Checks if the node state falls back to INIT when the integration bridge
270 * is removed.
271 */
272 @Test
273 public void testBackToInitStateWhenDeviceRemoved() {
274 // Remove the device from DEVICE_CREATED state node
275 deviceService.removeDevice(OF_DEVICE_2);
276 TestTools.assertAfter(100, () -> {
277 CordVtnNode current = nodeManager.node(NODE_2.integrationBridgeId());
278 assertEquals(ERR_STATE, INIT, current.state());
279 });
280
281 // Remove the device from PORT_CREATED state node
282 deviceService.removeDevice(OF_DEVICE_3);
283 TestTools.assertAfter(100, () -> {
284 CordVtnNode current = nodeManager.node(NODE_3.integrationBridgeId());
285 assertEquals(ERR_STATE, INIT, current.state());
286 });
287
288 // Remove the device from COMPLETE state node
289 deviceService.removeDevice(OF_DEVICE_4);
290 TestTools.assertAfter(100, () -> {
291 CordVtnNode current = nodeManager.node(NODE_4.integrationBridgeId());
292 assertEquals(ERR_STATE, INIT, current.state());
293 });
294 }
295
296 /**
297 * Checks if the node state falls back to DEVICE_CREATED when the ports
298 * are removed.
299 */
300 @Test
301 public void testBackToDeviceCreatedStateWhenPortRemoved() {
302 // Remove the device from PORT_CREATED state node
303 deviceService.removePort(OF_DEVICE_3, OF_DEVICE_3_PORT_1);
304 TestTools.assertAfter(100, () -> {
305 CordVtnNode current = nodeManager.node(NODE_3.integrationBridgeId());
306 assertEquals(ERR_STATE, DEVICE_CREATED, current.state());
307 });
308
309 // Remove the device from COMPLETE state node
310 deviceService.removePort(OF_DEVICE_4, OF_DEVICE_4_PORT_1);
311 TestTools.assertAfter(100, () -> {
312 CordVtnNode current = nodeManager.node(NODE_4.integrationBridgeId());
313 assertEquals(ERR_STATE, DEVICE_CREATED, current.state());
314 });
315 }
316
317 /**
318 * Checks if the node state falls back to PORT_CREATED when the interface
319 * configurations are incomplete.
320 */
321 @PrepareForTest(RemoteIpCommandUtil.class)
322 @Test
323 public void testBackToPortCreatedStateWhenIpRemoved() {
324 // Mocks SSH connection failure
325 mockStatic(RemoteIpCommandUtil.class);
326 expect(connect(anyObject())).andReturn(null);
327 PowerMock.replay(RemoteIpCommandUtil.class);
328
329 // ONOS is not able to detect IP is removed from the interface
330 // Just triggers node update event for the PORT_CREATED node and
331 // check if it stays in PORT_CREATED state
332 nodeManager.updateNode(NODE_3);
333 TestTools.assertAfter(100, () -> {
334 CordVtnNode current = nodeManager.node(NODE_3.integrationBridgeId());
335 assertEquals(ERR_STATE, PORT_CREATED, current.state());
336 });
337 }
338
339 private static final class TestDevice extends DefaultDevice {
340 InterfaceConfig mockInterfaceConfig = createMock(InterfaceConfig.class);
341 BridgeConfig mockBridgeConfig = createMock(BridgeConfig.class);
342
343 private TestDevice(ProviderId providerId,
344 DeviceId id,
345 Type type,
346 String manufacturer,
347 String hwVersion,
348 String swVersion,
349 String serialNumber,
350 ChassisId chassisId,
351 Annotations... annotations) {
352 super(providerId,
353 id,
354 type,
355 manufacturer,
356 hwVersion,
357 swVersion,
358 serialNumber,
359 chassisId,
360 annotations);
361 }
362
363 @Override
364 @SuppressWarnings("unchecked")
365 public <B extends Behaviour> B as(Class<B> projectionClass) {
366 if (projectionClass.equals(BridgeConfig.class)) {
367 expect(this.mockBridgeConfig.addBridge((BridgeDescription) anyObject()))
368 .andReturn(true)
369 .anyTimes();
370 this.mockBridgeConfig.addPort(anyObject(), anyString());
371 replay(mockBridgeConfig);
372 return (B) mockBridgeConfig;
373 } else if (projectionClass.equals(InterfaceConfig.class)) {
374 expect(this.mockInterfaceConfig.addTunnelMode(anyString(), anyObject()))
375 .andReturn(true)
376 .anyTimes();
377 replay(mockInterfaceConfig);
378 return (B) mockInterfaceConfig;
379 } else {
380 return null;
381 }
382 }
383
384 @Override
385 public <B extends Behaviour> boolean is(Class<B> projectionClass) {
386 return true;
387 }
388 }
389
390
391 private static class TestCoreService extends CoreServiceAdapter {
392
393 @Override
394 public ApplicationId registerApplication(String name) {
395 return TEST_APP_ID;
396 }
397 }
398
399 private static class TestLeadershipService extends LeadershipServiceAdapter {
400
401 @Override
402 public NodeId getLeader(String path) {
403 return LOCAL_NODE_ID;
404 }
405 }
406
407 private static class TestClusterService extends ClusterServiceAdapter {
408
409 @Override
410 public ControllerNode getLocalNode() {
411 return LOCAL_CTRL;
412 }
413 }
414
415 private static class TestConfigService extends NetworkConfigServiceAdapter {
416
417 }
418
419 private static class TestNodeManager implements CordVtnNodeService, CordVtnNodeAdminService {
420 Map<DeviceId, CordVtnNode> nodeMap = Maps.newHashMap();
421 List<CordVtnNodeListener> listeners = Lists.newArrayList();
422
423 @Override
424 public Set<CordVtnNode> nodes() {
425 return ImmutableSet.copyOf(nodeMap.values());
426 }
427
428 @Override
429 public Set<CordVtnNode> completeNodes() {
430 return null;
431 }
432
433 @Override
434 public CordVtnNode node(String hostname) {
435 return null;
436 }
437
438 @Override
439 public CordVtnNode node(DeviceId deviceId) {
440 return nodeMap.get(deviceId);
441 }
442
443 @Override
444 public void addListener(CordVtnNodeListener listener) {
445 listeners.add(listener);
446 }
447
448 @Override
449 public void removeListener(CordVtnNodeListener listener) {
450 listeners.remove(listener);
451 }
452
453 @Override
454 public void createNode(CordVtnNode node) {
455 nodeMap.put(node.integrationBridgeId(), node);
456 CordVtnNodeEvent event = new CordVtnNodeEvent(NODE_CREATED, node);
457 listeners.forEach(l -> l.event(event));
458 }
459
460 @Override
461 public void updateNode(CordVtnNode node) {
462 nodeMap.put(node.integrationBridgeId(), node);
463 CordVtnNodeEvent event = new CordVtnNodeEvent(NODE_UPDATED, node);
464 listeners.forEach(l -> l.event(event));
465 }
466
467 @Override
468 public CordVtnNode removeNode(String hostname) {
469 return null;
470 }
471 }
472
473 private static class TestDeviceService extends DeviceServiceAdapter {
474 Map<DeviceId, Device> devMap = Maps.newHashMap();
475 List<Port> portList = Lists.newArrayList();
476 List<DeviceListener> listeners = Lists.newArrayList();
477
478 @Override
479 public void addListener(DeviceListener listener) {
480 listeners.add(listener);
481 }
482
483 @Override
484 public void removeListener(DeviceListener listener) {
485 listeners.remove(listener);
486 }
487
488 @Override
489 public Device getDevice(DeviceId deviceId) {
490 return devMap.get(deviceId);
491 }
492
493 @Override
494 public List<Port> getPorts(DeviceId deviceId) {
495 return this.portList.stream()
496 .filter(p -> p.element().id().equals(deviceId))
497 .collect(Collectors.toList());
498 }
499
500 @Override
501 public boolean isAvailable(DeviceId deviceId) {
502 return devMap.containsKey(deviceId);
503 }
504
505 void addDevice(Device device) {
506 devMap.put(device.id(), device);
507 DeviceEvent event = new DeviceEvent(DEVICE_ADDED, device);
508 listeners.forEach(l -> l.event(event));
509 }
510
511 void removeDevice(Device device) {
512 devMap.remove(device.id());
513 DeviceEvent event = new DeviceEvent(DEVICE_AVAILABILITY_CHANGED, device);
514 listeners.forEach(l -> l.event(event));
515 }
516
517 void addPort(Device device, Port port) {
518 portList.add(port);
519 DeviceEvent event = new DeviceEvent(PORT_ADDED, device, port);
520 listeners.forEach(l -> l.event(event));
521 }
522
523 void removePort(Device device, Port port) {
524 portList.remove(port);
525 DeviceEvent event = new DeviceEvent(PORT_REMOVED, device, port);
526 listeners.forEach(l -> l.event(event));
527 }
528 }
529
530 private static class TestHostService extends HostServiceAdapter {
531
532 @Override
533 public Iterable<Host> getHosts() {
534 return Lists.newArrayList();
535 }
536 }
537
538 private static class TestInstanceService implements InstanceService {
539
540 @Override
541 public void addInstance(ConnectPoint connectPoint) {
542
543 }
544
545 @Override
546 public void addInstance(HostId hostId, HostDescription description) {
547
548 }
549
550 @Override
551 public void removeInstance(ConnectPoint connectPoint) {
552
553 }
554
555 @Override
556 public void removeInstance(HostId hostId) {
557
558 }
559 }
560
561 private static class TestCordVtnPipeline implements CordVtnPipeline {
562
563 @Override
564 public void initPipeline(CordVtnNode node) {
565
566 }
567
568 @Override
569 public void cleanupPipeline() {
570
571 }
572
573 @Override
574 public void processFlowRule(boolean install, FlowRule rule) {
575
576 }
577 }
578
579 public class TestEventDispatcher extends DefaultEventSinkRegistry
580 implements EventDeliveryService {
581
582 @Override
583 @SuppressWarnings("unchecked")
584 public synchronized void post(Event event) {
585 EventSink sink = getSink(event.getClass());
586 checkState(sink != null, "No sink for event %s", event);
587 sink.process(event);
588 }
589
590 @Override
591 public void setDispatchTimeLimit(long millis) {
592 }
593
594 @Override
595 public long getDispatchTimeLimit() {
596 return 0;
597 }
598 }
599}