blob: 8519ed5e31ba114e4a751ca04a7c9602a2ba2b7c [file] [log] [blame]
/*
* Copyright 2020-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.onosproject.inbandtelemetry.impl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import org.easymock.Capture;
import org.easymock.EasyMock;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.onlab.junit.TestUtils;
import org.onlab.packet.IpAddress;
import org.onlab.packet.IpPrefix;
import org.onlab.packet.TpPort;
import org.onlab.packet.VlanId;
import org.onosproject.TestApplicationId;
import org.onosproject.core.ApplicationId;
import org.onosproject.core.CoreService;
import org.onosproject.inbandtelemetry.api.IntIntent;
import org.onosproject.inbandtelemetry.api.IntIntentId;
import org.onosproject.mastership.MastershipService;
import org.onosproject.net.ConnectPoint;
import org.onosproject.net.DefaultDevice;
import org.onosproject.net.DefaultHost;
import org.onosproject.net.DefaultPort;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Host;
import org.onosproject.net.HostId;
import org.onosproject.net.Port;
import org.onosproject.net.PortNumber;
import org.onosproject.net.behaviour.inbandtelemetry.IntObjective;
import org.onosproject.net.behaviour.inbandtelemetry.IntProgrammable;
import org.onosproject.net.behaviour.inbandtelemetry.IntReportConfig;
import org.onosproject.net.behaviour.inbandtelemetry.IntDeviceConfig;
import org.onosproject.net.config.NetworkConfigEvent;
import org.onosproject.net.config.NetworkConfigListener;
import org.onosproject.net.config.NetworkConfigRegistry;
import org.onosproject.net.config.NetworkConfigService;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.flow.DefaultTrafficSelector;
import org.onosproject.net.flow.TrafficSelector;
import org.onosproject.net.host.HostService;
import org.onosproject.store.service.ConsistentMap;
import org.onosproject.store.service.StorageService;
import org.onosproject.store.service.TestStorageService;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static org.easymock.EasyMock.anyObject;
import static org.easymock.EasyMock.createNiceMock;
import static org.easymock.EasyMock.eq;
import static org.easymock.EasyMock.expect;
import static org.easymock.EasyMock.expectLastCall;
import static org.easymock.EasyMock.newCapture;
import static org.easymock.EasyMock.replay;
import static org.easymock.EasyMock.reset;
import static org.easymock.EasyMock.verify;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.onosproject.net.behaviour.inbandtelemetry.IntProgrammable.IntFunctionality.POSTCARD;
import static org.onosproject.net.behaviour.inbandtelemetry.IntProgrammable.IntFunctionality.SINK;
import static org.onosproject.net.behaviour.inbandtelemetry.IntProgrammable.IntFunctionality.SOURCE;
import static org.onosproject.net.behaviour.inbandtelemetry.IntProgrammable.IntFunctionality.TRANSIT;
public class SimpleIntManagerTest {
private static final String APP_NAME = "org.onosproject.inbandtelemetry";
private static final ApplicationId APP_ID = new TestApplicationId(APP_NAME);
private static final IpAddress COLLECTOR_IP = IpAddress.valueOf("10.0.0.1");
private static final TpPort COLLECTOR_PORT = TpPort.tpPort(32766);
private static final int MIN_FLOW_HOP_LATENCY_CHANGE_NS = 32;
private static final String INT_REPORT_CONFIG_KEY = "report";
private static final DeviceId DEVICE_ID = DeviceId.deviceId("device:leaf1");
private static final String WATCHED_SUBNET_1 = "192.168.10.0/24";
private static final String WATCHED_SUBNET_2 = "192.168.20.0/24";
private static final TrafficSelector FLOW_SELECTOR1 = DefaultTrafficSelector.builder()
.matchIPDst(IpPrefix.valueOf(WATCHED_SUBNET_1))
.matchVlanId(VlanId.vlanId((short) 10))
.build();
private static final TrafficSelector FLOW_SELECTOR2 = DefaultTrafficSelector.builder()
.matchIPDst(IpPrefix.valueOf(WATCHED_SUBNET_2))
.matchVlanId(VlanId.vlanId((short) 20))
.build();
private static final Device DEFAULT_DEVICE =
new DefaultDevice(null, DEVICE_ID, Device.Type.SWITCH, "", "", "", "", null);
private static final List<Port> DEVICE_PORTS = ImmutableList.of(
new DefaultPort(DEFAULT_DEVICE, PortNumber.portNumber(1), true),
new DefaultPort(DEFAULT_DEVICE, PortNumber.portNumber(2), true)
);
private static final Host HOST1 =
new DefaultHost(null, HostId.hostId("00:00:00:00:00:01/None"), null,
VlanId.NONE, ImmutableSet.of(), ImmutableSet.of(), true);
private static final Host HOST2 =
new DefaultHost(null, HostId.hostId("00:00:00:00:00:02/None"), null,
VlanId.NONE, ImmutableSet.of(), ImmutableSet.of(), true);
private static final Map<ConnectPoint, Host> HOSTS = ImmutableMap.of(
ConnectPoint.fromString("device:leaf1/1"), HOST1,
ConnectPoint.fromString("device:leaf1/2"), HOST2
);
private SimpleIntManager manager;
private StorageService storageService;
private MastershipService mastershipService;
private CoreService coreService;
private HostService hostService;
private DeviceService deviceService;
private NetworkConfigRegistry networkConfigRegistry;
private NetworkConfigService networkConfigService;
private NetworkConfigListener networkConfigListener;
@Before
public void setup() throws IOException {
storageService = new TestStorageService();
mastershipService = createNiceMock(MastershipService.class);
coreService = createNiceMock(CoreService.class);
hostService = createNiceMock(HostService.class);
deviceService = createNiceMock(DeviceService.class);
expect(deviceService.getDevices()).andReturn(ImmutableList.of()).anyTimes();
networkConfigRegistry = createNiceMock(NetworkConfigRegistry.class);
networkConfigService = createNiceMock(NetworkConfigService.class);
manager = new SimpleIntManager();
manager.coreService = coreService;
manager.deviceService = deviceService;
manager.storageService = storageService;
manager.mastershipService = mastershipService;
manager.hostService = hostService;
manager.netcfgService = networkConfigService;
manager.netcfgRegistry = networkConfigRegistry;
expect(coreService.registerApplication(APP_NAME))
.andReturn(APP_ID).anyTimes();
networkConfigRegistry.registerConfigFactory(anyObject());
expectLastCall().once();
Capture<NetworkConfigListener> capture = newCapture();
networkConfigService.addListener(EasyMock.capture(capture));
expectLastCall().once();
IntReportConfig config = getIntReportConfig("/report-config.json");
expect(networkConfigService.getConfig(APP_ID, IntReportConfig.class))
.andReturn(config)
.anyTimes();
replay(mastershipService, deviceService, coreService,
hostService, networkConfigRegistry, networkConfigService);
manager.activate();
networkConfigListener = capture.getValue();
}
@After
public void teardown() {
manager.deactivate();
}
@Test
public void testPushIntAppConfig() throws IOException {
IntReportConfig config = getIntReportConfig("/report-config.json");
NetworkConfigEvent event =
new NetworkConfigEvent(NetworkConfigEvent.Type.CONFIG_ADDED, APP_ID,
config, null, IntReportConfig.class);
networkConfigListener.event(event);
// We expected that the manager will store the device config which
// converted from the app config.
IntDeviceConfig expectedConfig = createIntDeviceConfig();
IntDeviceConfig actualConfig = manager.getConfig();
assertEquals(expectedConfig, actualConfig);
// Install watch subnets via netcfg
// In the report-config.json, there are 3 subnets we want to watch
// For subnet 0.0.0.0/0, the IntManager will create only one IntIntent with an empty selector.
Set<IntIntent> expectedIntIntents = Sets.newHashSet();
ConsistentMap<IntIntentId, IntIntent> intentMap = TestUtils.getField(manager, "intentMap");
IntIntent.Builder baseIntentBuilder = IntIntent.builder()
.withReportType(IntIntent.IntReportType.TRACKED_FLOW)
.withReportType(IntIntent.IntReportType.DROPPED_PACKET)
.withReportType(IntIntent.IntReportType.CONGESTED_QUEUE)
.withTelemetryMode(IntIntent.TelemetryMode.POSTCARD);
// Watch IP Src == subnet 1
TrafficSelector expectedSelector = DefaultTrafficSelector.builder()
.matchIPSrc(IpPrefix.valueOf(WATCHED_SUBNET_1))
.build();
expectedIntIntents.add(baseIntentBuilder.withSelector(expectedSelector).build());
// Watch IP Dst == subnet 1
expectedSelector = DefaultTrafficSelector.builder()
.matchIPDst(IpPrefix.valueOf(WATCHED_SUBNET_1))
.build();
expectedIntIntents.add(baseIntentBuilder.withSelector(expectedSelector).build());
// Watch IP Src == subnet 2
expectedSelector = DefaultTrafficSelector.builder()
.matchIPSrc(IpPrefix.valueOf(WATCHED_SUBNET_2))
.build();
expectedIntIntents.add(baseIntentBuilder.withSelector(expectedSelector).build());
// Watch IP Dst == subnet 2
expectedSelector = DefaultTrafficSelector.builder()
.matchIPDst(IpPrefix.valueOf(WATCHED_SUBNET_2))
.build();
expectedIntIntents.add(baseIntentBuilder.withSelector(expectedSelector).build());
// Any packets
expectedSelector = DefaultTrafficSelector.emptySelector();
expectedIntIntents.add(baseIntentBuilder.withSelector(expectedSelector).build());
// The INT intent installation order can be random, so we need to collect
// all expected INT intents and check if actual intent exists.
assertEquals(5, intentMap.size());
intentMap.entrySet().forEach(entry -> {
IntIntent actualIntIntent = entry.getValue().value();
assertTrue(expectedIntIntents.contains(actualIntIntent));
});
}
@Test
public void testConfigNonIntDevice() {
reset(deviceService);
Device device = getMockDevice(false, DEVICE_ID);
expect(deviceService.getDevice(DEVICE_ID))
.andReturn(device)
.anyTimes();
expect(deviceService.getDevices())
.andReturn(ImmutableSet.of(device))
.anyTimes();
replay(deviceService, device);
assertTrue(manager.configDevice(DEVICE_ID));
verify();
}
@Test
public void testConfigSourceDevice() {
reset(deviceService, hostService);
Device device = getMockDevice(true, DEVICE_ID);
IntProgrammable intProg = getMockIntProgrammable(true, false, false, false);
setUpDeviceTest(device, intProg, true, false);
IntObjective intObj = IntObjective.builder()
.withSelector(FLOW_SELECTOR2)
.build();
expect(intProg.addIntObjective(eq(intObj)))
.andReturn(true)
.once();
expect(intProg.setSourcePort(PortNumber.portNumber(1))).andReturn(true).once();
expect(intProg.setSourcePort(PortNumber.portNumber(2))).andReturn(true).once();
replay(deviceService, hostService, device, intProg);
installTestIntents();
assertTrue(manager.configDevice(DEVICE_ID));
verify(intProg);
}
@Test
public void testConfigTransitDevice() {
reset(deviceService, hostService);
Device device = getMockDevice(true, DEVICE_ID);
IntProgrammable intProg = getMockIntProgrammable(false, true, false, false);
setUpDeviceTest(device, intProg, false, false);
replay(deviceService, hostService, device, intProg);
installTestIntents();
assertTrue(manager.configDevice(DEVICE_ID));
verify(intProg);
}
@Test
public void testConfigSinkDevice() {
reset(deviceService, hostService);
Device device = getMockDevice(true, DEVICE_ID);
IntProgrammable intProg = getMockIntProgrammable(false, false, true, false);
setUpDeviceTest(device, intProg, true, true);
expect(intProg.setSinkPort(PortNumber.portNumber(1))).andReturn(true).once();
expect(intProg.setSinkPort(PortNumber.portNumber(2))).andReturn(true).once();
replay(deviceService, hostService, device, intProg);
installTestIntents();
assertTrue(manager.configDevice(DEVICE_ID));
verify(intProg);
}
@Test
public void testConfigPostcardOnlyDevice() {
reset(deviceService, hostService);
Device device = getMockDevice(true, DEVICE_ID);
IntProgrammable intProg = getMockIntProgrammable(false, false, false, true);
setUpDeviceTest(device, intProg, true, true);
IntObjective intObj = IntObjective.builder()
.withSelector(FLOW_SELECTOR1)
.build();
expect(intProg.addIntObjective(eq(intObj)))
.andReturn(true)
.once();
replay(deviceService, hostService, device, intProg);
installTestIntents();
assertTrue(manager.configDevice(DEVICE_ID));
verify(intProg);
}
/*
* Utilities
*/
private void installTestIntents() {
// Pre-install an INT intent to the manager.
IntIntent.Builder intentBuilder = IntIntent.builder()
.withHeaderType(IntIntent.IntHeaderType.HOP_BY_HOP)
.withReportType(IntIntent.IntReportType.TRACKED_FLOW);
IntIntent postcardIntent = intentBuilder
.withTelemetryMode(IntIntent.TelemetryMode.POSTCARD)
.withSelector(FLOW_SELECTOR1)
.build();
IntIntent nonPoscardIntent = intentBuilder
.withTelemetryMode(IntIntent.TelemetryMode.INBAND_TELEMETRY)
.withSelector(FLOW_SELECTOR2)
.build();
manager.installIntIntent(nonPoscardIntent);
manager.installIntIntent(postcardIntent);
}
private void setUpDeviceTest(Device device, IntProgrammable intProg,
boolean hostConnected, boolean setupIntConfig) {
expect(device.as(IntProgrammable.class))
.andReturn(intProg)
.anyTimes();
expect(deviceService.getDevice(eq(DEVICE_ID)))
.andReturn(device)
.anyTimes();
expect(deviceService.getDevices())
.andReturn(ImmutableList.of(device))
.anyTimes();
if (setupIntConfig) {
IntDeviceConfig expectedConfig = createIntDeviceConfig();
expect(intProg.setupIntConfig(eq(expectedConfig)))
.andReturn(true)
.atLeastOnce();
}
expect(deviceService.getPorts(DEVICE_ID))
.andReturn(DEVICE_PORTS)
.anyTimes();
if (hostConnected) {
HOSTS.forEach((cp, host) -> {
expect(hostService.getConnectedHosts(eq(cp)))
.andReturn(ImmutableSet.of(host))
.anyTimes();
});
expect(hostService.getConnectedHosts(eq(DEVICE_ID)))
.andReturn(Sets.newHashSet(HOSTS.values()));
} else {
expect(hostService.getConnectedHosts(eq(DEVICE_ID)))
.andReturn(ImmutableSet.of())
.anyTimes();
}
}
private IntReportConfig getIntReportConfig(String fileName) throws IOException {
IntReportConfig config = new IntReportConfig();
InputStream jsonStream = getClass().getResourceAsStream(fileName);
ObjectMapper mapper = new ObjectMapper();
JsonNode jsonNode = mapper.readTree(jsonStream);
config.init(APP_ID, INT_REPORT_CONFIG_KEY, jsonNode, mapper, c -> {
});
return config;
}
private Device getMockDevice(boolean supportInt, DeviceId deviceId) {
Device device = createNiceMock(Device.class);
expect(device.is(IntProgrammable.class))
.andReturn(supportInt)
.anyTimes();
expect(device.id())
.andReturn(deviceId)
.anyTimes();
return device;
}
private IntProgrammable getMockIntProgrammable(boolean supportSource, boolean supportTransit, boolean supportSink,
boolean supportPostcard) {
IntProgrammable intProg = createNiceMock(IntProgrammable.class);
if (supportSource) {
expect(intProg.supportsFunctionality(SOURCE))
.andReturn(true).anyTimes();
}
if (supportTransit) {
expect(intProg.supportsFunctionality(TRANSIT))
.andReturn(true).anyTimes();
}
if (supportSink) {
expect(intProg.supportsFunctionality(SINK))
.andReturn(true).anyTimes();
}
if (supportPostcard) {
expect(intProg.supportsFunctionality(POSTCARD))
.andReturn(true).anyTimes();
}
expect(intProg.init())
.andReturn(true)
.anyTimes();
return intProg;
}
private IntDeviceConfig createIntDeviceConfig() {
return IntDeviceConfig.builder()
.withMinFlowHopLatencyChangeNs(MIN_FLOW_HOP_LATENCY_CHANGE_NS)
.withCollectorPort(COLLECTOR_PORT)
.withCollectorIp(COLLECTOR_IP)
.enabled(true)
.build();
}
}