[VOL-4246] Feature parity with the previous implementation
Change-Id: I3741edb3c1b88b1cf8b5e6d4ff0900132e2e5e6a
diff --git a/impl/src/test/java/org/opencord/olt/impl/OltDeviceListenerTest.java b/impl/src/test/java/org/opencord/olt/impl/OltDeviceListenerTest.java
new file mode 100644
index 0000000..f8229da
--- /dev/null
+++ b/impl/src/test/java/org/opencord/olt/impl/OltDeviceListenerTest.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright 2021-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.opencord.olt.impl;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+import org.onlab.packet.ChassisId;
+import org.onosproject.cluster.ClusterService;
+import org.onosproject.cluster.LeadershipService;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.AnnotationKeys;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceEvent;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.provider.ProviderId;
+import org.opencord.sadis.BaseInformationService;
+import org.opencord.sadis.SubscriberAndDeviceInformation;
+import org.opencord.sadis.UniTagInformation;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import static org.mockito.Matchers.any;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+public class OltDeviceListenerTest extends OltTestHelpers {
+ private Olt olt;
+ private Olt.OltDeviceListener oltDeviceListener;
+
+ private final DeviceId deviceId = DeviceId.deviceId("test-device");
+ private final Device testDevice = new DefaultDevice(ProviderId.NONE, deviceId, Device.Type.OLT,
+ "testManufacturer", "1.0", "1.0", "SN", new ChassisId(1));
+
+ @Before
+ public void setUp() {
+ olt = new Olt();
+ olt.eventsQueues = new HashMap<>();
+ olt.mastershipService = Mockito.mock(MastershipService.class);
+ olt.oltDeviceService = Mockito.mock(OltDeviceService.class);
+ olt.oltFlowService = Mockito.mock(OltFlowService.class);
+ olt.oltMeterService = Mockito.mock(OltMeterService.class);
+ olt.deviceService = Mockito.mock(DeviceService.class);
+ olt.leadershipService = Mockito.mock(LeadershipService.class);
+ olt.clusterService = Mockito.mock(ClusterService.class);
+ olt.subsService = Mockito.mock(BaseInformationService.class);
+
+ Olt.OltDeviceListener baseClass = olt.deviceListener;
+ baseClass.eventExecutor = Mockito.mock(ExecutorService.class);
+ oltDeviceListener = Mockito.spy(baseClass);
+
+ // mock the executor so it immediately invokes the method
+ doAnswer(new Answer<Object>() {
+ public Object answer(InvocationOnMock invocation) throws Exception {
+ ((Runnable) invocation.getArguments()[0]).run();
+ return null;
+ }
+ }).when(baseClass.eventExecutor).execute(any(Runnable.class));
+
+ olt.eventsQueues.forEach((cp, q) -> q.clear());
+ }
+
+ @Test
+ public void testDeviceDisconnection() {
+ doReturn(true).when(olt.oltDeviceService).isOlt(testDevice);
+ doReturn(false).when(olt.deviceService).isAvailable(any());
+ doReturn(new LinkedList<Port>()).when(olt.deviceService).getPorts(any());
+ doReturn(true).when(olt.oltDeviceService).isLocalLeader(any());
+
+ DeviceEvent disconnect = new DeviceEvent(DeviceEvent.Type.DEVICE_AVAILABILITY_CHANGED, testDevice, null);
+ oltDeviceListener.event(disconnect);
+
+ verify(olt.oltFlowService, times(1)).purgeDeviceFlows(testDevice.id());
+ verify(olt.oltMeterService, times(1)).purgeDeviceMeters(testDevice.id());
+ }
+
+ @Test
+ public void testPortEventOwnership() {
+ // make sure that we ignore events for devices that are not local to this node
+
+ // make sure the device is recognized as an OLT and the port is not an NNI
+ doReturn(true).when(olt.oltDeviceService).isOlt(testDevice);
+ doReturn(false).when(olt.oltDeviceService).isNniPort(eq(testDevice), any());
+
+ // make sure we're not leaders of the device
+ doReturn(false).when(olt.oltDeviceService).isLocalLeader(any());
+
+ // this is a new port, should not create an entry in the queue
+ // we're not owners of the device
+ Port uniUpdateEnabled = new OltPort(testDevice, true, PortNumber.portNumber(16),
+ DefaultAnnotations.builder().set(AnnotationKeys.PORT_NAME, "uni-1").build());
+ DeviceEvent uniUpdateEnabledEvent =
+ new DeviceEvent(DeviceEvent.Type.PORT_UPDATED, testDevice, uniUpdateEnabled);
+ oltDeviceListener.event(uniUpdateEnabledEvent);
+
+ // the queue won't even be created
+ assert olt.eventsQueues.isEmpty();
+ }
+
+ @Test
+ public void testNniEvent() throws InterruptedException {
+ // make sure the device is recognized as an OLT and the port is recognized as an NNI,
+ // and we're local leaders
+ doReturn(true).when(olt.oltDeviceService).isOlt(testDevice);
+ doReturn(true).when(olt.oltDeviceService).isNniPort(eq(testDevice), any());
+ doReturn(true).when(olt.oltDeviceService).isLocalLeader(any());
+
+ Port enabledNniPort = new OltPort(testDevice, true, PortNumber.portNumber(1048576),
+ DefaultAnnotations.builder().set(AnnotationKeys.PORT_NAME, "nni-1").build());
+ DeviceEvent nniEnabledEvent = new DeviceEvent(DeviceEvent.Type.PORT_ADDED, testDevice, enabledNniPort);
+ oltDeviceListener.event(nniEnabledEvent);
+
+ // NNI events are straight forward, we can provision the flows directly
+ assert olt.eventsQueues.isEmpty();
+ verify(olt.oltFlowService, times(1))
+ .handleNniFlows(testDevice, enabledNniPort, OltFlowService.FlowOperation.ADD);
+
+ Port disabledNniPort = new OltPort(testDevice, false, PortNumber.portNumber(1048576),
+ DefaultAnnotations.builder().set(AnnotationKeys.PORT_NAME, "nni-1").build());
+ DeviceEvent nniDisabledEvent = new DeviceEvent(DeviceEvent.Type.PORT_UPDATED, testDevice, disabledNniPort);
+ oltDeviceListener.event(nniDisabledEvent);
+
+ assert olt.eventsQueues.isEmpty();
+ verify(olt.oltFlowService, times(1))
+ .handleNniFlows(testDevice, disabledNniPort, OltFlowService.FlowOperation.REMOVE);
+
+ // when we disable the device we receive a PORT_REMOVED event with status ENABLED
+ // make sure we're removing the flows correctly
+ DeviceEvent nniRemoveEvent = new DeviceEvent(DeviceEvent.Type.PORT_REMOVED, testDevice, enabledNniPort);
+ oltDeviceListener.event(nniRemoveEvent);
+
+ assert olt.eventsQueues.isEmpty();
+ verify(olt.oltFlowService, times(1))
+ .handleNniFlows(testDevice, enabledNniPort, OltFlowService.FlowOperation.REMOVE);
+ }
+
+ @Test
+ public void testUniEvents() {
+ DiscoveredSubscriber sub;
+ // there are few cases we need to test in the UNI port case:
+ // - [X] UNI port added in disabled state
+ // - [X] UNI port added in disabled state (with default EAPOL installed)
+ // - UNI port added in enabled state
+ // - [X] UNI port updated to enabled state
+ // - UNI port updated to disabled state
+ // - UNI port removed (assumes it's disabled state)
+
+ // make sure the device is recognized as an OLT, the port is not an NNI,
+ // and we're local masters
+ doReturn(true).when(olt.oltDeviceService).isOlt(testDevice);
+ doReturn(false).when(olt.oltDeviceService).isNniPort(eq(testDevice), any());
+ doReturn(true).when(olt.oltDeviceService).isLocalLeader(any());
+
+ PortNumber uniPortNumber = PortNumber.portNumber(16);
+ Port uniAddedDisabled = new OltPort(testDevice, false, uniPortNumber,
+ DefaultAnnotations.builder().set(AnnotationKeys.PORT_NAME, "uni-1").build());
+ DeviceEvent uniAddedDisabledEvent = new DeviceEvent(DeviceEvent.Type.PORT_ADDED, testDevice, uniAddedDisabled);
+ ConnectPoint cp = new ConnectPoint(testDevice.id(), uniPortNumber);
+
+ // if the port does not have default EAPOL we should not generate an event
+ oltDeviceListener.event(uniAddedDisabledEvent);
+
+ // no event == no queue is created
+ assert olt.eventsQueues.isEmpty();
+
+ // if the port has default EAPOL then create an entry in the queue to remove it
+ doReturn(true).when(olt.oltFlowService)
+ .hasDefaultEapol(uniAddedDisabled);
+ // create empty service for testing
+ List<UniTagInformation> uniTagInformationList = new LinkedList<>();
+ UniTagInformation empty = new UniTagInformation.Builder().build();
+ uniTagInformationList.add(empty);
+
+ SubscriberAndDeviceInformation si = new SubscriberAndDeviceInformation();
+ si.setUniTagList(uniTagInformationList);
+ doReturn(si).when(olt.subsService).get("uni-1");
+
+ oltDeviceListener.event(uniAddedDisabledEvent);
+ LinkedBlockingQueue<DiscoveredSubscriber> q = olt.eventsQueues.get(cp);
+ assert !q.isEmpty();
+ sub = q.poll();
+ assert !sub.hasSubscriber; // this is not a provision subscriber call
+ assert sub.device.equals(testDevice);
+ assert sub.port.equals(uniAddedDisabled);
+ assert sub.status.equals(DiscoveredSubscriber.Status.REMOVED); // we need to remove flows for this port (if any)
+ assert q.isEmpty(); // the queue is now empty
+
+ Port uniUpdateEnabled = new OltPort(testDevice, true, PortNumber.portNumber(16),
+ DefaultAnnotations.builder().set(AnnotationKeys.PORT_NAME, "uni-1").build());
+ DeviceEvent uniUpdateEnabledEvent =
+ new DeviceEvent(DeviceEvent.Type.PORT_UPDATED, testDevice, uniUpdateEnabled);
+ oltDeviceListener.event(uniUpdateEnabledEvent);
+
+ assert !q.isEmpty();
+ sub = q.poll();
+ assert !sub.hasSubscriber; // this is not a provision subscriber call
+ assert sub.device.equals(testDevice);
+ assert sub.port.equals(uniUpdateEnabled);
+ assert sub.status.equals(DiscoveredSubscriber.Status.ADDED); // we need to remove flows for this port (if any)
+ assert q.isEmpty(); // the queue is now empty
+ }
+}